Imported markdown library from dartdoc
diff --git a/pkgs/markdown/.gitignore b/pkgs/markdown/.gitignore new file mode 100644 index 0000000..aeb4e13 --- /dev/null +++ b/pkgs/markdown/.gitignore
@@ -0,0 +1,5 @@ +packages +pubspec.lock +.project +.children +out \ No newline at end of file
diff --git a/pkgs/markdown/LICENSE b/pkgs/markdown/LICENSE new file mode 100644 index 0000000..81764fd --- /dev/null +++ b/pkgs/markdown/LICENSE
@@ -0,0 +1,24 @@ +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
diff --git a/pkgs/markdown/lib/classify.dart b/pkgs/markdown/lib/classify.dart new file mode 100644 index 0000000..2838fbd --- /dev/null +++ b/pkgs/markdown/lib/classify.dart
@@ -0,0 +1,209 @@ +// 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. + +library classify; + +import 'src/compiler/implementation/scanner/scannerlib.dart'; +// TODO(rnystrom): Use "package:" URL (#4968). +import 'markdown.dart' as md; + +/** + * Kinds of tokens that we care to highlight differently. The values of the + * fields here will be used as CSS class names for the generated spans. + */ +class Classification { + static const NONE = null; + static const ERROR = "e"; + static const COMMENT = "c"; + static const IDENTIFIER = "i"; + static const KEYWORD = "k"; + static const OPERATOR = "o"; + static const STRING = "s"; + static const NUMBER = "n"; + static const PUNCTUATION = "p"; + + // A few things that are nice to make different: + static const TYPE_IDENTIFIER = "t"; + + // Between a keyword and an identifier + static const SPECIAL_IDENTIFIER = "r"; + + static const ARROW_OPERATOR = "a"; + + static const STRING_INTERPOLATION = 'si'; +} + +/// Returns a marked up HTML string. If the code does not appear to be valid +/// Dart code, returns the original [text]. +String classifySource(String text) { + try { + var html = new StringBuffer(); + var tokenizer = new StringScanner(text, includeComments: true); + + var whitespaceOffset = 0; + var token = tokenizer.tokenize(); + var inString = false; + while (token.kind != EOF_TOKEN) { + html.add(text.substring(whitespaceOffset, token.charOffset)); + whitespaceOffset = token.charOffset + token.slowCharCount; + + // Track whether or not we're in a string. + switch (token.kind) { + case STRING_TOKEN: + case STRING_INTERPOLATION_TOKEN: + inString = true; + break; + } + + final kind = classify(token); + final escapedText = md.escapeHtml(token.slowToString()); + if (kind != null) { + // Add a secondary class to tokens appearing within a string so that + // we can highlight tokens in an interpolation specially. + var stringClass = inString ? Classification.STRING_INTERPOLATION : ''; + html.add('<span class="$kind $stringClass">$escapedText</span>'); + } else { + html.add(escapedText); + } + + // Track whether or not we're in a string. + if (token.kind == STRING_TOKEN) { + inString = false; + } + token = token.next; + } + return html.toString(); + } catch (e) { + return text; + } +} + +bool _looksLikeType(String name) { + // If the name looks like an UppercaseName, assume it's a type. + return _looksLikePublicType(name) || _looksLikePrivateType(name); +} + +bool _looksLikePublicType(String name) { + // If the name looks like an UppercaseName, assume it's a type. + return name.length >= 2 && isUpper(name[0]) && isLower(name[1]); +} + +bool _looksLikePrivateType(String name) { + // If the name looks like an _UppercaseName, assume it's a type. + return (name.length >= 3 && name[0] == '_' && isUpper(name[1]) + && isLower(name[2])); +} + +// These ensure that they don't return "true" if the string only has symbols. +bool isUpper(String s) => s.toLowerCase() != s; +bool isLower(String s) => s.toUpperCase() != s; + +String classify(Token token) { + switch (token.kind) { + case UNKNOWN_TOKEN: + return Classification.ERROR; + + case IDENTIFIER_TOKEN: + // Special case for names that look like types. + final text = token.slowToString(); + if (_looksLikeType(text) + || text == 'num' + || text == 'bool' + || text == 'int' + || text == 'double') { + return Classification.TYPE_IDENTIFIER; + } + return Classification.IDENTIFIER; + + case STRING_TOKEN: + case STRING_INTERPOLATION_TOKEN: + return Classification.STRING; + + case INT_TOKEN: + case HEXADECIMAL_TOKEN: + case DOUBLE_TOKEN: + return Classification.NUMBER; + + case COMMENT_TOKEN: + return Classification.COMMENT; + + // => is so awesome it is in a class of its own. + case FUNCTION_TOKEN: + return Classification.ARROW_OPERATOR; + + case OPEN_PAREN_TOKEN: + case CLOSE_PAREN_TOKEN: + case OPEN_SQUARE_BRACKET_TOKEN: + case CLOSE_SQUARE_BRACKET_TOKEN: + case OPEN_CURLY_BRACKET_TOKEN: + case CLOSE_CURLY_BRACKET_TOKEN: + case COLON_TOKEN: + case SEMICOLON_TOKEN: + case COMMA_TOKEN: + case PERIOD_TOKEN: + case PERIOD_PERIOD_TOKEN: + return Classification.PUNCTUATION; + + case PLUS_PLUS_TOKEN: + case MINUS_MINUS_TOKEN: + case TILDE_TOKEN: + case BANG_TOKEN: + case EQ_TOKEN: + case BAR_EQ_TOKEN: + case CARET_EQ_TOKEN: + case AMPERSAND_EQ_TOKEN: + case LT_LT_EQ_TOKEN: + case GT_GT_EQ_TOKEN: + case PLUS_EQ_TOKEN: + case MINUS_EQ_TOKEN: + case STAR_EQ_TOKEN: + case SLASH_EQ_TOKEN: + case TILDE_SLASH_EQ_TOKEN: + case PERCENT_EQ_TOKEN: + case QUESTION_TOKEN: + case BAR_BAR_TOKEN: + case AMPERSAND_AMPERSAND_TOKEN: + case BAR_TOKEN: + case CARET_TOKEN: + case AMPERSAND_TOKEN: + case LT_LT_TOKEN: + case GT_GT_TOKEN: + case PLUS_TOKEN: + case MINUS_TOKEN: + case STAR_TOKEN: + case SLASH_TOKEN: + case TILDE_SLASH_TOKEN: + case PERCENT_TOKEN: + case EQ_EQ_TOKEN: + case BANG_EQ_TOKEN: + case EQ_EQ_EQ_TOKEN: + case BANG_EQ_EQ_TOKEN: + case LT_TOKEN: + case GT_TOKEN: + case LT_EQ_TOKEN: + case GT_EQ_TOKEN: + case INDEX_TOKEN: + case INDEX_EQ_TOKEN: + return Classification.OPERATOR; + + // Color keyword token. Most are colored as keywords. + case HASH_TOKEN: + case KEYWORD_TOKEN: + if (token.stringValue == 'void') { + // Color "void" as a type. + return Classification.TYPE_IDENTIFIER; + } + if (token.stringValue == 'this' || token.stringValue == 'super') { + // Color "this" and "super" as identifiers. + return Classification.SPECIAL_IDENTIFIER; + } + return Classification.KEYWORD; + + case EOF_TOKEN: + return Classification.NONE; + + default: + return Classification.NONE; + } +}
diff --git a/pkgs/markdown/lib/markdown.dart b/pkgs/markdown/lib/markdown.dart new file mode 100644 index 0000000..ef111fb --- /dev/null +++ b/pkgs/markdown/lib/markdown.dart
@@ -0,0 +1,118 @@ +// 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. + +/// Parses text in a markdown-like format and renders to HTML. +library markdown; + +import 'classify.dart'; + +// TODO(rnystrom): Use "package:" URL (#4968). +part 'src/markdown/ast.dart'; +part 'src/markdown/block_parser.dart'; +part 'src/markdown/html_renderer.dart'; +part 'src/markdown/inline_parser.dart'; + +/// Converts the given string of markdown to HTML. +String markdownToHtml(String markdown) { + final document = new Document(); + + // Replace windows line endings with unix line endings, and split. + final lines = markdown.replaceAll('\r\n','\n').split('\n'); + document.parseRefLinks(lines); + final blocks = document.parseLines(lines); + return renderToHtml(blocks); +} + +/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents. +String escapeHtml(String html) { + return html.replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); +} + +var _implicitLinkResolver; + +Node setImplicitLinkResolver(Node resolver(String text)) { + _implicitLinkResolver = resolver; +} + +/// Maintains the context needed to parse a markdown document. +class Document { + final Map<String, Link> refLinks; + + Document() + : refLinks = <String, Link>{}; + + parseRefLinks(List<String> lines) { + // This is a hideous regex. It matches: + // [id]: http:foo.com "some title" + // Where there may whitespace in there, and where the title may be in + // single quotes, double quotes, or parentheses. + final indent = r'^[ ]{0,3}'; // Leading indentation. + final id = r'\[([^\]]+)\]'; // Reference id in [brackets]. + final quote = r'"[^"]+"'; // Title in "double quotes". + final apos = r"'[^']+'"; // Title in 'single quotes'. + final paren = r"\([^)]+\)"; // Title in (parentheses). + final pattern = new RegExp( + '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$'); + + for (int i = 0; i < lines.length; i++) { + final match = pattern.firstMatch(lines[i]); + if (match != null) { + // Parse the link. + var id = match[1]; + var url = match[2]; + var title = match[3]; + + if (title == '') { + // No title. + title = null; + } else { + // Remove "", '', or (). + title = title.substring(1, title.length - 1); + } + + // References are case-insensitive. + id = id.toLowerCase(); + + refLinks[id] = new Link(id, url, title); + + // Remove it from the output. We replace it with a blank line which will + // get consumed by later processing. + lines[i] = ''; + } + } + } + + /// Parse the given [lines] of markdown to a series of AST nodes. + List<Node> parseLines(List<String> lines) { + final parser = new BlockParser(lines, this); + + final blocks = []; + while (!parser.isDone) { + for (final syntax in BlockSyntax.syntaxes) { + if (syntax.canParse(parser)) { + final block = syntax.parse(parser); + if (block != null) blocks.add(block); + break; + } + } + } + + return blocks; + } + + /// Takes a string of raw text and processes all inline markdown tags, + /// returning a list of AST nodes. For example, given ``"*this **is** a* + /// `markdown`"``, returns: + /// `<em>this <strong>is</strong> a</em> <code>markdown</code>`. + List<Node> parseInline(String text) => new InlineParser(text, this).parse(); +} + +class Link { + final String id; + final String url; + final String title; + Link(this.id, this.url, this.title); +}
diff --git a/pkgs/markdown/lib/src/compiler/compiler.dart b/pkgs/markdown/lib/src/compiler/compiler.dart new file mode 100644 index 0000000..bbee705 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/compiler.dart
@@ -0,0 +1,177 @@ +// 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. + +library compiler; + +import 'dart:async'; +import 'dart:uri'; +import 'implementation/apiimpl.dart'; + +// Unless explicitly allowed, passing [:null:] for any argument to the +// methods of library will result in an Error being thrown. + +/** + * Returns a future that completes to the source corresponding to + * [uri]. If an exception occurs, the future completes with this + * exception. + */ +typedef Future<String> CompilerInputProvider(Uri uri); + +/// Deprecated, please use [CompilerInputProvider] instead. +typedef Future<String> ReadStringFromUri(Uri uri); + +/** + * Returns a [StreamSink] that will serve as compiler output for the given + * component. + * + * Components are identified by [name] and [extension]. By convention, + * the empty string [:"":] will represent the main script + * (corresponding to the script parameter of [compile]) even if the + * main script is a library. For libraries that are compiled + * separately, the library name is used. + * + * At least the following extensions can be expected: + * + * * "js" for JavaScript output. + * * "js.map" for source maps. + * * "dart" for Dart output. + * * "dart.map" for source maps. + * + * As more features are added to the compiler, new names and + * extensions may be introduced. + */ +typedef StreamSink<String> CompilerOutputProvider(String name, + String extension); + +/** + * Invoked by the compiler to report diagnostics. If [uri] is + * [:null:], so are [begin] and [end]. No other arguments may be + * [:null:]. If [uri] is not [:null:], neither are [begin] and + * [end]. [uri] indicates the compilation unit from where the + * diagnostic originates. [begin] and [end] are zero-based character + * offsets from the beginning of the compilaton unit. [message] is the + * diagnostic message, and [kind] indicates indicates what kind of + * diagnostic it is. + */ +typedef void DiagnosticHandler(Uri uri, int begin, int end, + String message, Diagnostic kind); + +/** + * Returns a future that completes to a non-null String when [script] + * has been successfully compiled. + * + * The compiler output is obtained by providing an [outputProvider]. + * + * If the compilation fails, the future's value will be [:null:] and + * [handler] will have been invoked at least once with [:kind == + * Diagnostic.ERROR:] or [:kind == Diagnostic.CRASH:]. + * + * Deprecated: if no [outputProvider] is given, the future completes + * to the compiled script. This behavior will be removed in the future + * as the compiler may create multiple files to support lazy loading + * of libraries. + */ +Future<String> compile(Uri script, + Uri libraryRoot, + Uri packageRoot, + CompilerInputProvider inputProvider, + DiagnosticHandler handler, + [List<String> options = const [], + CompilerOutputProvider outputProvider]) { + if (!libraryRoot.path.endsWith("/")) { + throw new ArgumentError("libraryRoot must end with a /"); + } + if (packageRoot != null && !packageRoot.path.endsWith("/")) { + throw new ArgumentError("packageRoot must end with a /"); + } + // TODO(ahe): Consider completing the future with an exception if + // code is null. + Compiler compiler = new Compiler(inputProvider, + outputProvider, + handler, + libraryRoot, + packageRoot, + options); + compiler.run(script); + String code = compiler.assembledCode; + if (code != null && outputProvider != null) { + String outputType = 'js'; + if (options.contains('--output-type=dart')) { + outputType = 'dart'; + } + outputProvider('', outputType) + ..add(code) + ..close(); + code = ''; // Non-null signals success. + } + return new Future.immediate(code); +} + +/** + * Kind of diagnostics that the compiler can report. + */ +class Diagnostic { + /** + * An error as identified by the "Dart Programming Language + * Specification" [http://www.dartlang.org/docs/spec/]. + * + * Note: the compiler may still produce an executable result after + * reporting a compilation error. The specification says: + * + * "A compile-time error must be reported by a Dart compiler before + * the erroneous code is executed." and "If a compile-time error + * occurs within the code of a running isolate A, A is immediately + * suspended." + * + * This means that the compiler can generate code that when executed + * terminates execution. + */ + static const Diagnostic ERROR = const Diagnostic(1, 'error'); + + /** + * A warning as identified by the "Dart Programming Language + * Specification" [http://www.dartlang.org/docs/spec/]. + */ + static const Diagnostic WARNING = const Diagnostic(2, 'warning'); + + /** + * Any other warning that is not covered by [WARNING]. + */ + static const Diagnostic LINT = const Diagnostic(4, 'lint'); + + /** + * Informational messages. + */ + static const Diagnostic INFO = const Diagnostic(8, 'info'); + + /** + * Informational messages that shouldn't be printed unless + * explicitly requested by the user of a compiler. + */ + static const Diagnostic VERBOSE_INFO = const Diagnostic(16, 'verbose info'); + + /** + * An internal error in the compiler. + */ + static const Diagnostic CRASH = const Diagnostic(32, 'crash'); + + /** + * An [int] representation of this kind. The ordinals are designed + * to be used as bitsets. + */ + final int ordinal; + + /** + * The name of this kind. + */ + final String name; + + /** + * This constructor is not private to support user-defined + * diagnostic kinds. + */ + const Diagnostic(this.ordinal, this.name); + + String toString() => name; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/README.txt b/pkgs/markdown/lib/src/compiler/implementation/README.txt new file mode 100644 index 0000000..8fe0a29 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/README.txt
@@ -0,0 +1,12 @@ +Dart2JS is the Dart-to-JavaScript compiler for Dart. This compiler +will provide high-quality translation from Dart to JavaScript. + +Some areas that will explored in this project are: + + * high-performance extensible scanner and parser + * concrete type inferencing + * fancy language tool support + * programming environment integration + * SSA-based intermediate representation + * adaptive compilation on the client +
diff --git a/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart b/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart new file mode 100644 index 0000000..663e003 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart
@@ -0,0 +1,266 @@ +// 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. + +library leg_apiimpl; + +import 'dart:uri'; +import 'dart:async'; + +import '../compiler.dart' as api; +import 'dart2jslib.dart' as leg; +import 'tree/tree.dart' as tree; +import 'elements/elements.dart' as elements; +import 'ssa/tracer.dart' as ssa; +import '../../libraries.dart'; +import 'source_file.dart'; + +class Compiler extends leg.Compiler { + api.ReadStringFromUri provider; + api.DiagnosticHandler handler; + final Uri libraryRoot; + final Uri packageRoot; + List<String> options; + bool mockableLibraryUsed = false; + final Set<String> allowedLibraryCategories; + + Compiler(this.provider, + api.CompilerOutputProvider outputProvider, + this.handler, + this.libraryRoot, + this.packageRoot, + List<String> options) + : this.options = options, + this.allowedLibraryCategories = getAllowedLibraryCategories(options), + super( + tracer: new ssa.HTracer(), + outputProvider: outputProvider, + enableTypeAssertions: hasOption(options, '--enable-checked-mode'), + enableUserAssertions: hasOption(options, '--enable-checked-mode'), + enableMinification: hasOption(options, '--minify'), + enableNativeLiveTypeAnalysis: + !hasOption(options, '--disable-native-live-type-analysis'), + emitJavaScript: !hasOption(options, '--output-type=dart'), + disallowUnsafeEval: hasOption(options, '--disallow-unsafe-eval'), + analyzeAll: hasOption(options, '--analyze-all'), + analyzeOnly: hasOption(options, '--analyze-only'), + rejectDeprecatedFeatures: + hasOption(options, '--reject-deprecated-language-features'), + checkDeprecationInSdk: + hasOption(options, + '--report-sdk-use-of-deprecated-language-features'), + strips: getStrips(options), + enableConcreteTypeInference: + hasOption(options, '--enable-concrete-type-inference'), + preserveComments: hasOption(options, '--preserve-comments')) { + if (!libraryRoot.path.endsWith("/")) { + throw new ArgumentError("libraryRoot must end with a /"); + } + if (packageRoot != null && !packageRoot.path.endsWith("/")) { + throw new ArgumentError("packageRoot must end with a /"); + } + } + + static List<String> getStrips(List<String> options) { + for (String option in options) { + if (option.startsWith('--force-strip=')) { + return option.substring('--force-strip='.length).split(','); + } + } + return const <String>[]; + } + + static Set<String> getAllowedLibraryCategories(List<String> options) { + for (String option in options) { + if (option.startsWith('--categories=')) { + var result = option.substring('--categories='.length).split(','); + result.add('Shared'); + result.add('Internal'); + return new Set<String>.from(result); + } + } + return new Set<String>.from(['Client', 'Shared', 'Internal']); + } + + static bool hasOption(List<String> options, String option) { + return options.indexOf(option) >= 0; + } + + // TODO(johnniwinther): Merge better with [translateDartUri] when + // [scanBuiltinLibrary] is removed. + String lookupLibraryPath(String dartLibraryName) { + LibraryInfo info = LIBRARIES[dartLibraryName]; + if (info == null) return null; + if (!info.isDart2jsLibrary) return null; + if (!allowedLibraryCategories.contains(info.category)) return null; + String path = info.dart2jsPath; + if (path == null) { + path = info.path; + } + return "lib/$path"; + } + + String lookupPatchPath(String dartLibraryName) { + LibraryInfo info = LIBRARIES[dartLibraryName]; + if (info == null) return null; + if (!info.isDart2jsLibrary) return null; + String path = info.dart2jsPatchPath; + if (path == null) return null; + return "lib/$path"; + } + + elements.LibraryElement scanBuiltinLibrary(String path) { + Uri uri = libraryRoot.resolve(lookupLibraryPath(path)); + Uri canonicalUri = new Uri.fromComponents(scheme: "dart", path: path); + elements.LibraryElement library = + libraryLoader.loadLibrary(uri, null, canonicalUri); + return library; + } + + void log(message) { + handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO); + } + + /// See [leg.Compiler.translateResolvedUri]. + Uri translateResolvedUri(elements.LibraryElement importingLibrary, + Uri resolvedUri, tree.Node node) { + if (resolvedUri.scheme == 'dart') { + return translateDartUri(importingLibrary, resolvedUri, node); + } + return resolvedUri; + } + + /** + * Reads the script designated by [readableUri]. + */ + leg.Script readScript(Uri readableUri, [tree.Node node]) { + if (!readableUri.isAbsolute()) { + internalError('Relative uri $readableUri provided to readScript(Uri)', + node: node); + } + return fileReadingTask.measure(() { + Uri resourceUri = translateUri(readableUri, node); + String text = ""; + try { + // TODO(ahe): We expect the future to be complete and call value + // directly. In effect, we don't support truly asynchronous API. + text = deprecatedFutureValue(provider(resourceUri)); + } catch (exception) { + if (node != null) { + cancel("$exception", node: node); + } else { + reportDiagnostic(null, "$exception", api.Diagnostic.ERROR); + throw new leg.CompilerCancelledException("$exception"); + } + } + SourceFile sourceFile = new SourceFile(resourceUri.toString(), text); + // We use [readableUri] as the URI for the script since need to preserve + // the scheme in the script because [Script.uri] is used for resolving + // relative URIs mentioned in the script. See the comment on + // [LibraryLoader] for more details. + return new leg.Script(readableUri, sourceFile); + }); + } + + /** + * Translates a readable URI into a resource URI. + * + * See [LibraryLoader] for terminology on URIs. + */ + Uri translateUri(Uri readableUri, tree.Node node) { + switch (readableUri.scheme) { + case 'package': return translatePackageUri(readableUri, node); + default: return readableUri; + } + } + + Uri translateDartUri(elements.LibraryElement importingLibrary, + Uri resolvedUri, tree.Node node) { + LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path]; + String path = lookupLibraryPath(resolvedUri.path); + if (libraryInfo != null && + libraryInfo.category == "Internal") { + bool allowInternalLibraryAccess = false; + if (importingLibrary != null) { + if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) { + allowInternalLibraryAccess = true; + } else if (importingLibrary.canonicalUri.path.contains( + 'dart/tests/compiler/dart2js_native')) { + allowInternalLibraryAccess = true; + } + } + if (!allowInternalLibraryAccess) { + if (node != null && importingLibrary != null) { + reportDiagnostic(spanFromNode(node), + 'Error: Internal library $resolvedUri is not accessible from ' + '${importingLibrary.canonicalUri}.', + api.Diagnostic.ERROR); + } else { + reportDiagnostic(null, + 'Error: Internal library $resolvedUri is not accessible.', + api.Diagnostic.ERROR); + } + //path = null; + } + } + if (path == null) { + if (node != null) { + reportError(node, 'library not found ${resolvedUri}'); + } else { + reportDiagnostic(null, 'library not found ${resolvedUri}', + api.Diagnostic.ERROR); + } + return null; + } + if (resolvedUri.path == 'html' || + resolvedUri.path == 'io') { + // TODO(ahe): Get rid of mockableLibraryUsed when test.dart + // supports this use case better. + mockableLibraryUsed = true; + } + return libraryRoot.resolve(path); + } + + Uri resolvePatchUri(String dartLibraryPath) { + String patchPath = lookupPatchPath(dartLibraryPath); + if (patchPath == null) return null; + return libraryRoot.resolve(patchPath); + } + + translatePackageUri(Uri uri, tree.Node node) => packageRoot.resolve(uri.path); + + bool run(Uri uri) { + log('Allowed library categories: $allowedLibraryCategories'); + bool success = super.run(uri); + int cumulated = 0; + for (final task in tasks) { + cumulated += task.timing; + log('${task.name} took ${task.timing}msec'); + } + int total = totalCompileTime.elapsedMilliseconds; + log('Total compile-time ${total}msec;' + ' unaccounted ${total - cumulated}msec'); + return success; + } + + void reportDiagnostic(leg.SourceSpan span, String message, + api.Diagnostic kind) { + if (identical(kind, api.Diagnostic.ERROR) + || identical(kind, api.Diagnostic.CRASH)) { + compilationFailed = true; + } + // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For + // instance in the [Types] constructor in typechecker.dart. + if (span == null || span.uri == null) { + handler(null, null, null, message, kind); + } else { + handler(translateUri(span.uri, null), span.begin, span.end, + message, kind); + } + } + + bool get isMockCompilation { + return mockableLibraryUsed + && (options.indexOf('--allow-mock-compilation') != -1); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/closure.dart b/pkgs/markdown/lib/src/compiler/implementation/closure.dart new file mode 100644 index 0000000..4ed1564 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/closure.dart
@@ -0,0 +1,663 @@ +// 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. + +library closureToClassMapper; + +import "elements/elements.dart"; +import "dart2jslib.dart"; +import "dart_types.dart"; +import "scanner/scannerlib.dart" show Token; +import "tree/tree.dart"; +import "util/util.dart"; +import "elements/modelx.dart" show ElementX, FunctionElementX, ClassElementX; + +abstract class ClosureNamer { + SourceString getClosureVariableName(SourceString name, int id); +} + + +class ClosureTask extends CompilerTask { + Map<Node, ClosureClassMap> closureMappingCache; + ClosureNamer namer; + ClosureTask(Compiler compiler, this.namer) + : closureMappingCache = new Map<Node, ClosureClassMap>(), + super(compiler); + + String get name => "Closure Simplifier"; + + ClosureClassMap computeClosureToClassMapping(Element element, + Expression node, + TreeElements elements) { + return measure(() { + ClosureClassMap cached = closureMappingCache[node]; + if (cached != null) return cached; + + ClosureTranslator translator = + new ClosureTranslator(compiler, elements, closureMappingCache, namer); + + // The translator will store the computed closure-mappings inside the + // cache. One for given node and one for each nested closure. + if (node is FunctionExpression) { + translator.translateFunction(element, node); + } else { + // Must be the lazy initializer of a static. + assert(node is SendSet); + translator.translateLazyInitializer(element, node); + } + assert(closureMappingCache[node] != null); + return closureMappingCache[node]; + }); + } + + ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { + return measure(() { + ClosureClassMap nestedClosureData = closureMappingCache[node]; + if (nestedClosureData == null) { + compiler.internalError("No closure cache", node: node); + } + return nestedClosureData; + }); + } +} + +class ClosureFieldElement extends ElementX { + ClosureFieldElement(SourceString name, ClassElement enclosing) + : super(name, ElementKind.FIELD, enclosing); + + bool isInstanceMember() => true; + bool isAssignable() => false; + // The names of closure variables don't need renaming, since their use is very + // simple and they have 1-character names in the minified mode. + bool hasFixedBackendName() => true; + String fixedBackendName() => name.slowToString(); + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; + + String toString() => "ClosureFieldElement($name)"; +} + +class ClosureClassElement extends ClassElementX { + ClosureClassElement(SourceString name, + Compiler compiler, + this.methodElement, + Element enclosingElement) + : super(name, + enclosingElement, + // By assigning a fresh class-id we make sure that the hashcode + // is unique, but also emit closure classes after all other + // classes (since the emitter sorts classes by their id). + compiler.getNextFreeClassId(), + STATE_DONE) { + compiler.closureClass.ensureResolved(compiler); + supertype = compiler.closureClass.computeType(compiler); + interfaces = const Link<DartType>(); + allSupertypes = const Link<DartType>().prepend(supertype); + } + + bool isClosure() => true; + + /** + * The most outer method this closure is declared into. + */ + Element methodElement; +} + +class BoxElement extends ElementX { + BoxElement(SourceString name, Element enclosingElement) + : super(name, ElementKind.VARIABLE, enclosingElement); +} + +class ThisElement extends ElementX { + ThisElement(Element enclosing) + : super(const SourceString('this'), ElementKind.PARAMETER, enclosing); + + bool isAssignable() => false; + + // Since there is no declaration corresponding to 'this', use the position of + // the enclosing method. + Token position() => enclosingElement.position(); +} + +class CheckVariableElement extends ElementX { + Element parameter; + CheckVariableElement(SourceString name, this.parameter, Element enclosing) + : super(name, ElementKind.VARIABLE, enclosing); + + // Since there is no declaration for the synthetic 'check' variable, use + // parameter. + Token position() => parameter.position(); +} + +// The box-element for a scope, and the captured variables that need to be +// stored in the box. +class ClosureScope { + Element boxElement; + Map<Element, Element> capturedVariableMapping; + // If the scope is attached to a [For] contains the variables that are + // declared in the initializer of the [For] and that need to be boxed. + // Otherwise contains the empty List. + List<Element> boxedLoopVariables; + + ClosureScope(this.boxElement, this.capturedVariableMapping) + : boxedLoopVariables = const <Element>[]; + + bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty; +} + +class ClosureClassMap { + // The closure's element before any translation. Will be null for methods. + final Element closureElement; + // The closureClassElement will be null for methods that are not local + // closures. + final ClassElement closureClassElement; + // The callElement will be null for methods that are not local closures. + final FunctionElement callElement; + // The [thisElement] makes handling 'this' easier by treating it like any + // other argument. It is only set for instance-members. + final ThisElement thisElement; + + // Maps free locals, arguments and function elements to their captured + // copies. + final Map<Element, Element> freeVariableMapping; + // Maps closure-fields to their captured elements. This is somehow the inverse + // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does + // not deal with boxes, here we map instance-fields (which might represent + // boxes) to their boxElement. + final Map<Element, Element> capturedFieldMapping; + + // Maps scopes ([Loop] and [FunctionExpression] nodes) to their + // [ClosureScope] which contains their box and the + // captured variables that are stored in the box. + // This map will be empty if the method/closure of this [ClosureData] does not + // contain any nested closure. + final Map<Node, ClosureScope> capturingScopes; + + final Set<Element> usedVariablesInTry; + + // A map from the parameter element to the variable element that + // holds the sentinel check. + final Map<Element, Element> parametersWithSentinel; + + ClosureClassMap(this.closureElement, + this.closureClassElement, + this.callElement, + this.thisElement) + : this.freeVariableMapping = new Map<Element, Element>(), + this.capturedFieldMapping = new Map<Element, Element>(), + this.capturingScopes = new Map<Node, ClosureScope>(), + this.usedVariablesInTry = new Set<Element>(), + this.parametersWithSentinel = new Map<Element, Element>(); + + bool isClosure() => closureElement != null; +} + +class ClosureTranslator extends Visitor { + final Compiler compiler; + final TreeElements elements; + int closureFieldCounter = 0; + int boxedFieldCounter = 0; + bool inTryStatement = false; + final Map<Node, ClosureClassMap> closureMappingCache; + + // Map of captured variables. Initially they will map to themselves. If + // a variable needs to be boxed then the scope declaring the variable + // will update this mapping. + Map<Element, Element> capturedVariableMapping; + // List of encountered closures. + List<Expression> closures; + + // The variables that have been declared in the current scope. + List<Element> scopeVariables; + + // Keep track of the mutated variables so that we don't need to box + // non-mutated variables. + Set<Element> mutatedVariables; + + Element outermostElement; + Element currentElement; + + // The closureData of the currentFunctionElement. + ClosureClassMap closureData; + + ClosureNamer namer; + + bool insideClosure = false; + + ClosureTranslator(this.compiler, this.elements, this.closureMappingCache, + this.namer) + : capturedVariableMapping = new Map<Element, Element>(), + closures = <Expression>[], + mutatedVariables = new Set<Element>(); + + void translateFunction(Element element, FunctionExpression node) { + // For constructors the [element] and the [:elements[node]:] may differ. + // The [:elements[node]:] always points to the generative-constructor + // element, whereas the [element] might be the constructor-body element. + visit(node); // [visitFunctionExpression] will call [visitInvokable]. + // When variables need to be boxed their [capturedVariableMapping] is + // updated, but we delay updating the similar freeVariableMapping in the + // closure datas that capture these variables. + // The closures don't have their fields (in the closure class) set, either. + updateClosures(); + } + + void translateLazyInitializer(Element element, SendSet node) { + assert(node.assignmentOperator.source == const SourceString("=")); + Expression initialValue = node.argumentsNode.nodes.head; + visitInvokable(element, node, () { visit(initialValue); }); + updateClosures(); + } + + // This function runs through all of the existing closures and updates their + // free variables to the boxed value. It also adds the field-elements to the + // class representing the closure. At the same time it fills the + // [capturedFieldMapping]. + void updateClosures() { + for (Expression closure in closures) { + // The captured variables that need to be stored in a field of the closure + // class. + Set<Element> fieldCaptures = new Set<Element>(); + Set<Element> boxes = new Set<Element>(); + ClosureClassMap data = closureMappingCache[closure]; + Map<Element, Element> freeVariableMapping = data.freeVariableMapping; + // We get a copy of the keys and iterate over it, to avoid modifications + // to the map while iterating over it. + freeVariableMapping.keys.toList().forEach((Element fromElement) { + assert(fromElement == freeVariableMapping[fromElement]); + Element updatedElement = capturedVariableMapping[fromElement]; + assert(updatedElement != null); + if (fromElement == updatedElement) { + assert(freeVariableMapping[fromElement] == updatedElement); + assert(Elements.isLocal(updatedElement) + || updatedElement.isTypeVariable()); + // The variable has not been boxed. + fieldCaptures.add(updatedElement); + } else { + // A boxed element. + freeVariableMapping[fromElement] = updatedElement; + Element boxElement = updatedElement.enclosingElement; + assert(boxElement.kind == ElementKind.VARIABLE); + boxes.add(boxElement); + } + }); + ClassElement closureElement = data.closureClassElement; + assert(closureElement != null || + (fieldCaptures.isEmpty && boxes.isEmpty)); + void addElement(Element element, SourceString name) { + Element fieldElement = new ClosureFieldElement(name, closureElement); + closureElement.addBackendMember(fieldElement); + data.capturedFieldMapping[fieldElement] = element; + freeVariableMapping[element] = fieldElement; + } + // Add the box elements first so we get the same ordering. + // TODO(sra): What is the canonical order of multiple boxes? + for (Element capturedElement in boxes) { + addElement(capturedElement, capturedElement.name); + } + for (Element capturedElement in + Elements.sortedByPosition(fieldCaptures)) { + int id = closureFieldCounter++; + SourceString name = + namer.getClosureVariableName(capturedElement.name, id); + addElement(capturedElement, name); + } + closureElement.reverseBackendMembers(); + } + } + + void useLocal(Element element) { + // If the element is not declared in the current function and the element + // is not the closure itself we need to mark the element as free variable. + // Note that the check on [insideClosure] is not just an + // optimization: factories have type parameters as function + // parameters, and type parameters are declared in the class, not + // the factory. + if (insideClosure && + element.enclosingElement != currentElement && + element != currentElement) { + assert(closureData.freeVariableMapping[element] == null || + closureData.freeVariableMapping[element] == element); + closureData.freeVariableMapping[element] = element; + } else if (inTryStatement) { + // Don't mark the this-element. This would complicate things in the + // builder. + if (element != closureData.thisElement) { + // TODO(ngeoffray): only do this if the variable is mutated. + closureData.usedVariablesInTry.add(element); + } + } + } + + void declareLocal(Element element) { + scopeVariables.add(element); + } + + visit(Node node) => node.accept(this); + + visitNode(Node node) => node.visitChildren(this); + + visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + Element element = elements[definition]; + assert(element != null); + declareLocal(element); + // We still need to visit the right-hand sides of the init-assignments. + // For SendSets don't visit the left again. Otherwise it would be marked + // as mutated. + if (definition is Send) { + Send assignment = definition; + Node arguments = assignment.argumentsNode; + if (arguments != null) { + visit(arguments); + } + } else { + visit(definition); + } + } + } + + visitIdentifier(Identifier node) { + if (node.isThis()) { + useLocal(closureData.thisElement); + } + node.visitChildren(this); + } + + visitSend(Send node) { + Element element = elements[node]; + if (Elements.isLocal(element)) { + useLocal(element); + } else if (node.receiver == null && + Elements.isInstanceSend(node, elements)) { + useLocal(closureData.thisElement); + } else if (node.isSuperCall) { + useLocal(closureData.thisElement); + } else if (node.isParameterCheck) { + Element parameter = elements[node.receiver]; + FunctionElement enclosing = parameter.enclosingElement; + FunctionExpression function = enclosing.parseNode(compiler); + ClosureClassMap cached = closureMappingCache[function]; + if (!cached.parametersWithSentinel.containsKey(parameter)) { + SourceString parameterName = parameter.name; + String name = '${parameterName.slowToString()}_check'; + Element newElement = new CheckVariableElement(new SourceString(name), + parameter, + enclosing); + useLocal(newElement); + cached.parametersWithSentinel[parameter] = newElement; + } + } + node.visitChildren(this); + } + + visitSendSet(SendSet node) { + Element element = elements[node]; + if (Elements.isLocal(element)) { + mutatedVariables.add(element); + } + super.visitSendSet(node); + } + + visitNewExpression(NewExpression node) { + DartType type = elements.getType(node); + + bool hasTypeVariable(DartType type) { + if (type is TypeVariableType) { + return true; + } else if (type is InterfaceType) { + InterfaceType ifcType = type; + for (DartType argument in ifcType.typeArguments) { + if (hasTypeVariable(argument)) { + return true; + } + } + } + return false; + } + + void analyzeTypeVariables(DartType type) { + if (type is TypeVariableType) { + useLocal(type.element); + } else if (type is InterfaceType) { + InterfaceType ifcType = type; + for (DartType argument in ifcType.typeArguments) { + analyzeTypeVariables(argument); + } + } + } + if (outermostElement.isMember() && + compiler.world.needsRti(outermostElement.getEnclosingClass())) { + if (outermostElement.isInstanceMember() + || outermostElement.isGenerativeConstructor()) { + if (hasTypeVariable(type)) useLocal(closureData.thisElement); + } else if (outermostElement.isFactoryConstructor()) { + analyzeTypeVariables(type); + } + } + + node.visitChildren(this); + } + + // If variables that are declared in the [node] scope are captured and need + // to be boxed create a box-element and update the [capturingScopes] in the + // current [closureData]. + // The boxed variables are updated in the [capturedVariableMapping]. + void attachCapturedScopeVariables(Node node) { + Element box = null; + Map<Element, Element> scopeMapping = new Map<Element, Element>(); + for (Element element in scopeVariables) { + // No need to box non-assignable elements. + if (!element.isAssignable()) continue; + if (!mutatedVariables.contains(element)) continue; + if (capturedVariableMapping.containsKey(element)) { + if (box == null) { + // TODO(floitsch): construct better box names. + SourceString boxName = + namer.getClosureVariableName(const SourceString('box'), + closureFieldCounter++); + box = new BoxElement(boxName, currentElement); + } + String elementName = element.name.slowToString(); + SourceString boxedName = + namer.getClosureVariableName(new SourceString(elementName), + boxedFieldCounter++); + // TODO(kasperl): Should this be a FieldElement instead? + Element boxed = new ElementX(boxedName, ElementKind.FIELD, box); + // No need to rename the fields of a box, so we give them a native name + // right now. + boxed.setFixedBackendName(boxedName.slowToString()); + scopeMapping[element] = boxed; + capturedVariableMapping[element] = boxed; + } + } + if (!scopeMapping.isEmpty) { + ClosureScope scope = new ClosureScope(box, scopeMapping); + closureData.capturingScopes[node] = scope; + } + } + + void inNewScope(Node node, Function action) { + List<Element> oldScopeVariables = scopeVariables; + scopeVariables = new List<Element>(); + action(); + attachCapturedScopeVariables(node); + for (Element element in scopeVariables) { + mutatedVariables.remove(element); + } + scopeVariables = oldScopeVariables; + } + + visitLoop(Loop node) { + inNewScope(node, () { + node.visitChildren(this); + }); + } + + visitFor(For node) { + visitLoop(node); + // See if we have declared loop variables that need to be boxed. + if (node.initializer == null) return; + VariableDefinitions definitions = node.initializer.asVariableDefinitions(); + if (definitions == null) return; + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + List<Element> result = <Element>[]; + for (Link<Node> link = definitions.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + Element element = elements[definition]; + if (capturedVariableMapping.containsKey(element)) { + result.add(element); + }; + } + scopeData.boxedLoopVariables = result; + } + + /** Returns a non-unique name for the given closure element. */ + String computeClosureName(Element element) { + Link<String> parts = const Link<String>(); + SourceString ownName = element.name; + if (ownName == null || ownName.stringValue == "") { + parts = parts.prepend("anon"); + } else { + parts = parts.prepend(ownName.slowToString()); + } + for (Element enclosingElement = element.enclosingElement; + enclosingElement != null && + (identical(enclosingElement.kind, + ElementKind.GENERATIVE_CONSTRUCTOR_BODY) + || identical(enclosingElement.kind, ElementKind.CLASS) + || identical(enclosingElement.kind, ElementKind.FUNCTION) + || identical(enclosingElement.kind, ElementKind.GETTER) + || identical(enclosingElement.kind, ElementKind.SETTER)); + enclosingElement = enclosingElement.enclosingElement) { + SourceString surroundingName = + Elements.operatorNameToIdentifier(enclosingElement.name); + parts = parts.prepend(surroundingName.slowToString()); + } + StringBuffer sb = new StringBuffer(); + parts.printOn(sb, '_'); + return sb.toString(); + } + + ClosureClassMap globalizeClosure(FunctionExpression node, Element element) { + SourceString closureName = new SourceString(computeClosureName(element)); + ClassElement globalizedElement = new ClosureClassElement( + closureName, compiler, element, element.getCompilationUnit()); + FunctionElement callElement = + new FunctionElementX.from(Compiler.CALL_OPERATOR_NAME, + element, + globalizedElement); + globalizedElement.addBackendMember(callElement); + // The nested function's 'this' is the same as the one for the outer + // function. It could be [null] if we are inside a static method. + Element thisElement = closureData.thisElement; + return new ClosureClassMap(element, globalizedElement, + callElement, thisElement); + } + + void visitInvokable(Element element, Expression node, void visitChildren()) { + bool oldInsideClosure = insideClosure; + Element oldFunctionElement = currentElement; + ClosureClassMap oldClosureData = closureData; + + insideClosure = outermostElement != null; + currentElement = element; + if (insideClosure) { + closures.add(node); + closureData = globalizeClosure(node, element); + } else { + outermostElement = element; + Element thisElement = null; + if (element.isInstanceMember() || element.isGenerativeConstructor()) { + thisElement = new ThisElement(element); + } + closureData = new ClosureClassMap(null, null, null, thisElement); + } + closureMappingCache[node] = closureData; + + inNewScope(node, () { + // We have to declare the implicit 'this' parameter. + if (!insideClosure && closureData.thisElement != null) { + declareLocal(closureData.thisElement); + } + // If we are inside a named closure we have to declare ourselve. For + // simplicity we declare the local even if the closure does not have a + // name. + // It will simply not be used. + if (insideClosure) { + declareLocal(element); + } + + if (currentElement.isFactoryConstructor() + && compiler.world.needsRti(currentElement.enclosingElement)) { + // Declare the type parameters in the scope. Generative + // constructors just use 'this'. + ClassElement cls = currentElement.enclosingElement; + cls.typeVariables.forEach((TypeVariableType typeVariable) { + declareLocal(typeVariable.element); + }); + } + + visitChildren(); + }); + + + ClosureClassMap savedClosureData = closureData; + bool savedInsideClosure = insideClosure; + + // Restore old values. + insideClosure = oldInsideClosure; + closureData = oldClosureData; + currentElement = oldFunctionElement; + + // Mark all free variables as captured and use them in the outer function. + Iterable<Element> freeVariables = savedClosureData.freeVariableMapping.keys; + assert(freeVariables.isEmpty || savedInsideClosure); + for (Element freeElement in freeVariables) { + if (capturedVariableMapping[freeElement] != null && + capturedVariableMapping[freeElement] != freeElement) { + compiler.internalError('In closure analyzer', node: node); + } + capturedVariableMapping[freeElement] = freeElement; + useLocal(freeElement); + } + } + + visitFunctionExpression(FunctionExpression node) { + Element element = elements[node]; + + if (element.isParameter()) { + // TODO(ahe): This is a hack. This method should *not* call + // visitChildren. + return node.name.accept(this); + } + + visitInvokable(element, node, () { + // TODO(ahe): This is problematic. The backend should not repeat + // the work of the resolver. It is the resolver's job to create + // parameters, etc. Other phases should only visit statements. + if (node.parameters != null) node.parameters.accept(this); + if (node.initializers != null) node.initializers.accept(this); + if (node.body != null) node.body.accept(this); + }); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + node.visitChildren(this); + declareLocal(elements[node]); + } + + visitTryStatement(TryStatement node) { + // TODO(ngeoffray): implement finer grain state. + bool oldInTryStatement = inTryStatement; + inTryStatement = true; + node.visitChildren(this); + inTryStatement = oldInTryStatement; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart b/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart new file mode 100644 index 0000000..c3465e8 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart
@@ -0,0 +1,106 @@ +// 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. + +part of dart2js; + +class CodeBuffer implements StringBuffer { + StringBuffer buffer; + List<CodeBufferMarker> markers; + int lastBufferOffset = 0; + int mappedRangeCounter = 0; + + CodeBuffer() + : buffer = new StringBuffer(), + markers = new List<CodeBufferMarker>(); + + int get length => buffer.length; + + bool get isEmpty { + return buffer.isEmpty; + } + + /** + * Converts [object] to a string and adds it to the buffer. If [object] is a + * [CodeBuffer], adds its markers to [markers]. + */ + CodeBuffer add(var object) { + if (object is CodeBuffer) { + return addBuffer(object); + } + if (mappedRangeCounter == 0) setSourceLocation(null); + buffer.add(object.toString()); + return this; + } + + CodeBuffer addBuffer(CodeBuffer other) { + if (other.markers.length > 0) { + CodeBufferMarker firstMarker = other.markers[0]; + int offsetDelta = + buffer.length + firstMarker.offsetDelta - lastBufferOffset; + markers.add(new CodeBufferMarker(offsetDelta, + firstMarker.sourcePosition)); + for (int i = 1; i < other.markers.length; ++i) { + markers.add(other.markers[i]); + } + lastBufferOffset = buffer.length + other.lastBufferOffset; + } + buffer.add(other.getText()); + } + + CodeBuffer addAll(Iterable<Object> iterable) { + for (Object obj in iterable) { + add(obj); + } + return this; + } + + CodeBuffer addCharCode(int charCode) { + return add(new String.fromCharCodes([charCode])); + } + + CodeBuffer clear() { + buffer.clear(); + markers.clear(); + lastBufferOffset = 0; + return this; + } + + String toString() { + throw "Don't use CodeBuffer.toString() since it drops sourcemap data."; + } + + String getText() { + return buffer.toString(); + } + + void beginMappedRange() { + ++mappedRangeCounter; + } + + void endMappedRange() { + assert(mappedRangeCounter > 0); + --mappedRangeCounter; + } + + void setSourceLocation(var sourcePosition) { + int offsetDelta = buffer.length - lastBufferOffset; + markers.add(new CodeBufferMarker(offsetDelta, sourcePosition)); + lastBufferOffset = buffer.length; + } + + void forEachSourceLocation(void f(int targetOffset, var sourcePosition)) { + int targetOffset = 0; + markers.forEach((marker) { + targetOffset += marker.offsetDelta; + f(targetOffset, marker.sourcePosition); + }); + } +} + +class CodeBufferMarker { + final int offsetDelta; + final sourcePosition; + + CodeBufferMarker(this.offsetDelta, this.sourcePosition); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/colors.dart b/pkgs/markdown/lib/src/compiler/implementation/colors.dart new file mode 100644 index 0000000..42b3e7f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/colors.dart
@@ -0,0 +1,15 @@ +// 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. + +library colors; + +const String GREEN_COLOR = '\u001b[32m'; +const String RED_COLOR = '\u001b[31m'; +const String MAGENTA_COLOR = '\u001b[35m'; +const String NO_COLOR = '\u001b[0m'; + +String wrap(String string, String color) => "${color}$string${NO_COLOR}"; +String green(String string) => wrap(string, GREEN_COLOR); +String red(String string) => wrap(string, RED_COLOR); +String magenta(String string) => wrap(string, MAGENTA_COLOR);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart b/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart new file mode 100644 index 0000000..691cbac --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart
@@ -0,0 +1,888 @@ +// 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. + +part of dart2js; + +/** + * The [ConstantHandler] keeps track of compile-time constants, + * initializations of global and static fields, and default values of + * optional parameters. + */ +class ConstantHandler extends CompilerTask { + final ConstantSystem constantSystem; + final bool isMetadata; + + /** + * Contains the initial value of fields. Must contain all static and global + * initializations of const fields. May contain eagerly compiled values for + * statics and instance fields. + */ + final Map<VariableElement, Constant> initialVariableValues; + + /** Set of all registered compiled constants. */ + final Set<Constant> compiledConstants; + + /** The set of variable elements that are in the process of being computed. */ + final Set<VariableElement> pendingVariables; + + /** Caches the statics where the initial value cannot be eagerly compiled. */ + final Set<VariableElement> lazyStatics; + + /** Caches the createRuntimeType function if registered. */ + Element createRuntimeTypeFunction = null; + + ConstantHandler(Compiler compiler, this.constantSystem, + { bool this.isMetadata: false }) + : initialVariableValues = new Map<VariableElement, dynamic>(), + compiledConstants = new Set<Constant>(), + pendingVariables = new Set<VariableElement>(), + lazyStatics = new Set<VariableElement>(), + super(compiler); + + String get name => 'ConstantHandler'; + + void registerCompileTimeConstant(Constant constant) { + registerInstantiatedClass(constant.computeType(compiler).element); + if (constant.isFunction()) { + FunctionConstant function = constant; + registerGetOfStaticFunction(function.element); + } + compiledConstants.add(constant); + } + + void registerInstantiatedClass(ClassElement element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerInstantiatedClass(element); + } + + void registerStaticUse(Element element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerStaticUse(element); + } + + void registerGetOfStaticFunction(FunctionElement element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerGetOfStaticFunction(element); + } + + void registerStringInstance() { + registerInstantiatedClass(compiler.stringClass); + } + + void registerCreateRuntimeTypeFunction() { + if (createRuntimeTypeFunction != null) return; + SourceString helperName = const SourceString('createRuntimeType'); + createRuntimeTypeFunction = compiler.findHelper(helperName); + registerStaticUse(createRuntimeTypeFunction); + } + + /** + * Compiles the initial value of the given field and stores it in an internal + * map. Returns the initial value (a constant) if it can be computed + * statically. Returns [:null:] if the variable must be initialized lazily. + * + * [work] must contain a [VariableElement] refering to a global or + * static field. + */ + Constant compileWorkItem(CodegenWorkItem work) { + return measure(() { + assert(work.element.kind == ElementKind.FIELD + || work.element.kind == ElementKind.PARAMETER + || work.element.kind == ElementKind.FIELD_PARAMETER); + VariableElement element = work.element; + // Shortcut if it has already been compiled. + Constant result = initialVariableValues[element]; + if (result != null) return result; + if (lazyStatics.contains(element)) return null; + result = compileVariableWithDefinitions(element, work.resolutionTree); + assert(pendingVariables.isEmpty); + return result; + }); + } + + /** + * Returns a compile-time constant, or reports an error if the element is not + * a compile-time constant. + */ + Constant compileConstant(VariableElement element) { + return compileVariable(element, isConst: true); + } + + /** + * Returns the a compile-time constant if the variable could be compiled + * eagerly. Otherwise returns `null`. + */ + Constant compileVariable(VariableElement element, {bool isConst: false}) { + return measure(() { + if (initialVariableValues.containsKey(element)) { + Constant result = initialVariableValues[element]; + return result; + } + return compiler.withCurrentElement(element, () { + TreeElements definitions = compiler.analyzeElement(element); + Constant constant = compileVariableWithDefinitions( + element, definitions, isConst: isConst); + return constant; + }); + }); + } + + /** + * Returns the a compile-time constant if the variable could be compiled + * eagerly. If the variable needs to be initialized lazily returns `null`. + * If the variable is `const` but cannot be compiled eagerly reports an + * error. + */ + Constant compileVariableWithDefinitions(VariableElement element, + TreeElements definitions, + {bool isConst: false}) { + return measure(() { + // Initializers for parameters must be const. + isConst = isConst || element.modifiers.isConst() + || !Elements.isStaticOrTopLevel(element); + if (!isConst && lazyStatics.contains(element)) return null; + + Node node = element.parseNode(compiler); + if (pendingVariables.contains(element)) { + if (isConst) { + MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS; + compiler.reportError(node, + new CompileTimeConstantError(kind)); + } else { + lazyStatics.add(element); + return null; + } + } + pendingVariables.add(element); + + SendSet assignment = node.asSendSet(); + Constant value; + if (assignment == null) { + // No initial value. + value = new NullConstant(); + } else { + Node right = assignment.arguments.head; + value = + compileNodeWithDefinitions(right, definitions, isConst: isConst); + if (compiler.enableTypeAssertions + && value != null + && element.isField()) { + DartType elementType = element.computeType(compiler); + DartType constantType = value.computeType(compiler); + if (elementType.isMalformed || constantType.isMalformed || + !constantSystem.isSubtype(compiler, constantType, elementType)) { + if (isConst) { + compiler.reportError(node, new CompileTimeConstantError( + MessageKind.NOT_ASSIGNABLE, + {'fromType': elementType, 'toType': constantType})); + } else { + // If the field can be lazily initialized, we will throw + // the exception at runtime. + value = null; + } + } + } + } + if (value != null) { + initialVariableValues[element] = value; + } else { + assert(!isConst); + lazyStatics.add(element); + } + pendingVariables.remove(element); + return value; + }); + } + + Constant compileNodeWithDefinitions(Node node, + TreeElements definitions, + {bool isConst: false}) { + return measure(() { + assert(node != null); + CompileTimeConstantEvaluator evaluator = new CompileTimeConstantEvaluator( + this, definitions, compiler, isConst: isConst); + return evaluator.evaluate(node); + }); + } + + /** Attempts to compile a constant expression. Returns null if not possible */ + Constant tryCompileNodeWithDefinitions(Node node, TreeElements definitions) { + return measure(() { + assert(node != null); + try { + TryCompileTimeConstantEvaluator evaluator = + new TryCompileTimeConstantEvaluator(this, definitions, compiler); + return evaluator.evaluate(node); + } on CompileTimeConstantError catch (exn) { + return null; + } + }); + } + + /** + * Returns an [Iterable] of static non final fields that need to be + * initialized. The fields list must be evaluated in order since they might + * depend on each other. + */ + Iterable<VariableElement> getStaticNonFinalFieldsForEmission() { + return initialVariableValues.keys.where((element) { + return element.kind == ElementKind.FIELD + && !element.isInstanceMember() + && !element.modifiers.isFinal() + // The const fields are all either emitted elsewhere or inlined. + && !element.modifiers.isConst(); + }); + } + + /** + * Returns an [Iterable] of static const fields that need to be initialized. + * The fields must be evaluated in order since they might depend on each + * other. + */ + Iterable<VariableElement> getStaticFinalFieldsForEmission() { + return initialVariableValues.keys.where((element) { + return element.kind == ElementKind.FIELD + && !element.isInstanceMember() + && element.modifiers.isFinal(); + }); + } + + List<VariableElement> getLazilyInitializedFieldsForEmission() { + return new List<VariableElement>.from(lazyStatics); + } + + List<Constant> getConstantsForEmission() { + // We must emit dependencies before their uses. + Set<Constant> seenConstants = new Set<Constant>(); + List<Constant> result = new List<Constant>(); + + void addConstant(Constant constant) { + if (!seenConstants.contains(constant)) { + constant.getDependencies().forEach(addConstant); + assert(!seenConstants.contains(constant)); + result.add(constant); + seenConstants.add(constant); + } + } + + compiledConstants.forEach(addConstant); + return result; + } + + Constant getInitialValueFor(VariableElement element) { + Constant initialValue = initialVariableValues[element]; + if (initialValue == null) { + compiler.internalError("No initial value for given element", + element: element); + } + return initialValue; + } +} + +class CompileTimeConstantEvaluator extends Visitor { + bool isEvaluatingConstant; + final ConstantHandler handler; + final TreeElements elements; + final Compiler compiler; + + CompileTimeConstantEvaluator(this.handler, + this.elements, + this.compiler, + {bool isConst: false}) + : this.isEvaluatingConstant = isConst; + + ConstantSystem get constantSystem => handler.constantSystem; + + Constant evaluate(Node node) { + return node.accept(this); + } + + Constant evaluateConstant(Node node) { + bool oldIsEvaluatingConstant = isEvaluatingConstant; + isEvaluatingConstant = true; + Constant result = node.accept(this); + isEvaluatingConstant = oldIsEvaluatingConstant; + assert(result != null); + return result; + } + + Constant visitNode(Node node) { + return signalNotCompileTimeConstant(node); + } + + Constant visitLiteralBool(LiteralBool node) { + handler.registerInstantiatedClass(compiler.boolClass); + return constantSystem.createBool(node.value); + } + + Constant visitLiteralDouble(LiteralDouble node) { + handler.registerInstantiatedClass(compiler.doubleClass); + return constantSystem.createDouble(node.value); + } + + Constant visitLiteralInt(LiteralInt node) { + handler.registerInstantiatedClass(compiler.intClass); + return constantSystem.createInt(node.value); + } + + Constant visitLiteralList(LiteralList node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + List<Constant> arguments = <Constant>[]; + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + arguments.add(evaluateConstant(link.head)); + } + // TODO(floitsch): get type parameters. + DartType type = new InterfaceType(compiler.listClass); + Constant constant = new ListConstant(type, arguments); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitLiteralMap(LiteralMap node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + List<StringConstant> keys = <StringConstant>[]; + Map<StringConstant, Constant> map = new Map<StringConstant, Constant>(); + for (Link<Node> link = node.entries.nodes; + !link.isEmpty; + link = link.tail) { + LiteralMapEntry entry = link.head; + Constant key = evaluateConstant(entry.key); + if (!key.isString() || entry.key.asStringNode() == null) { + MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL; + compiler.reportError(entry.key, new ResolutionError(kind)); + } + StringConstant keyConstant = key; + if (!map.containsKey(key)) keys.add(key); + map[key] = evaluateConstant(entry.value); + } + List<Constant> values = <Constant>[]; + Constant protoValue = null; + for (StringConstant key in keys) { + if (key.value == MapConstant.PROTO_PROPERTY) { + protoValue = map[key]; + } else { + values.add(map[key]); + } + } + bool hasProtoKey = (protoValue != null); + // TODO(floitsch): this should be a List<String> type. + DartType keysType = new InterfaceType(compiler.listClass); + ListConstant keysList = new ListConstant(keysType, keys); + handler.registerCompileTimeConstant(keysList); + SourceString className = hasProtoKey + ? MapConstant.DART_PROTO_CLASS + : MapConstant.DART_CLASS; + ClassElement classElement = compiler.jsHelperLibrary.find(className); + classElement.ensureResolved(compiler); + // TODO(floitsch): copy over the generic type. + DartType type = new InterfaceType(classElement); + handler.registerInstantiatedClass(classElement); + Constant constant = new MapConstant(type, keysList, values, protoValue); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitLiteralNull(LiteralNull node) { + return constantSystem.createNull(); + } + + Constant visitLiteralString(LiteralString node) { + handler.registerStringInstance(); + return constantSystem.createString(node.dartString, node); + } + + Constant visitStringJuxtaposition(StringJuxtaposition node) { + StringConstant left = evaluate(node.first); + StringConstant right = evaluate(node.second); + if (left == null || right == null) return null; + handler.registerStringInstance(); + return constantSystem.createString( + new DartString.concat(left.value, right.value), node); + } + + Constant visitStringInterpolation(StringInterpolation node) { + StringConstant initialString = evaluate(node.string); + if (initialString == null) return null; + DartString accumulator = initialString.value; + for (StringInterpolationPart part in node.parts) { + Constant expression = evaluate(part.expression); + DartString expressionString; + if (expression == null) { + return signalNotCompileTimeConstant(part.expression); + } else if (expression.isNum() || expression.isBool()) { + PrimitiveConstant primitive = expression; + expressionString = new DartString.literal(primitive.value.toString()); + } else if (expression.isString()) { + PrimitiveConstant primitive = expression; + expressionString = primitive.value; + } else { + return signalNotCompileTimeConstant(part.expression); + } + accumulator = new DartString.concat(accumulator, expressionString); + StringConstant partString = evaluate(part.string); + if (partString == null) return null; + accumulator = new DartString.concat(accumulator, partString.value); + }; + handler.registerStringInstance(); + return constantSystem.createString(accumulator, node); + } + + Constant makeTypeConstant(Element element) { + DartType elementType = element.computeType(compiler).asRaw(); + DartType constantType = compiler.typeClass.computeType(compiler); + Constant constant = new TypeConstant(elementType, constantType); + // If we use a type literal in a constant, the compile time + // constant emitter will generate a call to the createRuntimeType + // helper so we register a use of that. + handler.registerCreateRuntimeTypeFunction(); + handler.registerCompileTimeConstant(constant); + return constant; + } + + // TODO(floitsch): provide better error-messages. + Constant visitSend(Send send) { + Element element = elements[send]; + if (send.isPropertyAccess) { + if (Elements.isStaticOrTopLevelFunction(element)) { + Constant constant = new FunctionConstant(element); + handler.registerCompileTimeConstant(constant); + return constant; + } else if (Elements.isStaticOrTopLevelField(element)) { + Constant result; + if (element.modifiers.isConst()) { + result = handler.compileConstant(element); + } else if (element.modifiers.isFinal() && !isEvaluatingConstant) { + result = handler.compileVariable(element); + } + if (result != null) return result; + } else if (Elements.isClass(element) || Elements.isTypedef(element)) { + return makeTypeConstant(element); + } else if (send.receiver != null) { + // Fall through to error handling. + } else if (!Elements.isUnresolved(element) + && element.isVariable() + && element.modifiers.isConst()) { + Constant result = handler.compileConstant(element); + if (result != null) return result; + } + return signalNotCompileTimeConstant(send); + } else if (send.isCall) { + if (identical(element, compiler.identicalFunction) + && send.argumentCount() == 2) { + Constant left = evaluate(send.argumentsNode.nodes.head); + Constant right = evaluate(send.argumentsNode.nodes.tail.head); + Constant result = constantSystem.identity.fold(left, right); + if (result != null) return result; + } else if (Elements.isClass(element) || Elements.isTypedef(element)) { + return makeTypeConstant(element); + } + return signalNotCompileTimeConstant(send); + } else if (send.isPrefix) { + assert(send.isOperator); + Constant receiverConstant = evaluate(send.receiver); + if (receiverConstant == null) return null; + Operator op = send.selector; + Constant folded; + switch (op.source.stringValue) { + case "!": + folded = constantSystem.not.fold(receiverConstant); + break; + case "-": + folded = constantSystem.negate.fold(receiverConstant); + break; + case "~": + folded = constantSystem.bitNot.fold(receiverConstant); + break; + default: + compiler.internalError("Unexpected operator.", node: op); + break; + } + if (folded == null) return signalNotCompileTimeConstant(send); + return folded; + } else if (send.isOperator && !send.isPostfix) { + assert(send.argumentCount() == 1); + Constant left = evaluate(send.receiver); + Constant right = evaluate(send.argumentsNode.nodes.head); + if (left == null || right == null) return null; + Operator op = send.selector.asOperator(); + Constant folded = null; + switch (op.source.stringValue) { + case "+": + folded = constantSystem.add.fold(left, right); + break; + case "-": + folded = constantSystem.subtract.fold(left, right); + break; + case "*": + folded = constantSystem.multiply.fold(left, right); + break; + case "/": + folded = constantSystem.divide.fold(left, right); + break; + case "%": + folded = constantSystem.modulo.fold(left, right); + break; + case "~/": + folded = constantSystem.truncatingDivide.fold(left, right); + break; + case "|": + folded = constantSystem.bitOr.fold(left, right); + break; + case "&": + folded = constantSystem.bitAnd.fold(left, right); + break; + case "^": + folded = constantSystem.bitXor.fold(left, right); + break; + case "||": + folded = constantSystem.booleanOr.fold(left, right); + break; + case "&&": + folded = constantSystem.booleanAnd.fold(left, right); + break; + case "<<": + folded = constantSystem.shiftLeft.fold(left, right); + break; + case ">>": + folded = constantSystem.shiftRight.fold(left, right); + break; + case "<": + folded = constantSystem.less.fold(left, right); + break; + case "<=": + folded = constantSystem.lessEqual.fold(left, right); + break; + case ">": + folded = constantSystem.greater.fold(left, right); + break; + case ">=": + folded = constantSystem.greaterEqual.fold(left, right); + break; + case "==": + if (left.isPrimitive() && right.isPrimitive()) { + folded = constantSystem.equal.fold(left, right); + } + break; + case "===": + folded = constantSystem.identity.fold(left, right); + break; + case "!=": + if (left.isPrimitive() && right.isPrimitive()) { + BoolConstant areEquals = constantSystem.equal.fold(left, right); + if (areEquals == null) { + folded = null; + } else { + folded = areEquals.negate(); + } + } + break; + case "!==": + BoolConstant areIdentical = + constantSystem.identity.fold(left, right); + if (areIdentical == null) { + folded = null; + } else { + folded = areIdentical.negate(); + } + break; + } + if (folded == null) return signalNotCompileTimeConstant(send); + return folded; + } + return signalNotCompileTimeConstant(send); + } + + Constant visitSendSet(SendSet node) { + return signalNotCompileTimeConstant(node); + } + + /** + * Returns the list of constants that are passed to the static function. + * + * Invariant: [target] must be an implementation element. + */ + List<Constant> evaluateArgumentsToConstructor(Node node, + Selector selector, + Link<Node> arguments, + FunctionElement target) { + assert(invariant(node, target.isImplementation)); + List<Constant> compiledArguments = <Constant>[]; + + Function compileArgument = evaluateConstant; + Function compileConstant = handler.compileConstant; + bool succeeded = selector.addArgumentsToList(arguments, + compiledArguments, + target, + compileArgument, + compileConstant, + compiler); + if (!succeeded) { + MessageKind kind = MessageKind.INVALID_ARGUMENTS; + compiler.reportError(node, + new CompileTimeConstantError(kind, {'methodName': target.name})); + } + return compiledArguments; + } + + Constant visitNewExpression(NewExpression node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + + Send send = node.send; + FunctionElement constructor = elements[send]; + constructor = constructor.redirectionTarget; + ClassElement classElement = constructor.getEnclosingClass(); + if (classElement.isInterface()) { + compiler.resolver.resolveMethodElement(constructor); + constructor = constructor.defaultImplementation; + classElement = constructor.getEnclosingClass(); + } + // The constructor must be an implementation to ensure that field + // initializers are handled correctly. + constructor = constructor.implementation; + assert(invariant(node, constructor.isImplementation)); + + Selector selector = elements.getSelector(send); + List<Constant> arguments = evaluateArgumentsToConstructor( + node, selector, send.arguments, constructor); + ConstructorEvaluator evaluator = + new ConstructorEvaluator(node, constructor, handler, compiler); + evaluator.evaluateConstructorFieldValues(arguments); + List<Constant> jsNewArguments = evaluator.buildJsNewArguments(classElement); + + handler.registerInstantiatedClass(classElement); + // TODO(floitsch): take generic types into account. + DartType type = classElement.computeType(compiler); + Constant constant = new ConstructedConstant(type, jsNewArguments); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitParenthesizedExpression(ParenthesizedExpression node) { + return node.expression.accept(this); + } + + error(Node node) { + // TODO(floitsch): get the list of constants that are currently compiled + // and present some kind of stack-trace. + MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT; + compiler.reportError(node, new CompileTimeConstantError(kind)); + } + + Constant signalNotCompileTimeConstant(Node node) { + if (isEvaluatingConstant) { + error(node); + } + // Else we don't need to do anything. The final handler is only + // optimistically trying to compile constants. So it is normal that we + // sometimes see non-compile time constants. + // Simply return [:null:] which is used to propagate a failing + // compile-time compilation. + return null; + } +} + +class TryCompileTimeConstantEvaluator extends CompileTimeConstantEvaluator { + TryCompileTimeConstantEvaluator(ConstantHandler handler, + TreeElements elements, + Compiler compiler) + : super(handler, elements, compiler, isConst: true); + + error(Node node) { + // Just fail without reporting it anywhere. + throw new CompileTimeConstantError( + MessageKind.NOT_A_COMPILE_TIME_CONSTANT); + } +} + +class ConstructorEvaluator extends CompileTimeConstantEvaluator { + final FunctionElement constructor; + final Map<Element, Constant> definitions; + final Map<Element, Constant> fieldValues; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [constructor] must be an implementation element. + */ + ConstructorEvaluator(Node node, + FunctionElement constructor, + ConstantHandler handler, + Compiler compiler) + : this.constructor = constructor, + this.definitions = new Map<Element, Constant>(), + this.fieldValues = new Map<Element, Constant>(), + super(handler, + compiler.resolver.resolveMethodElement(constructor.declaration), + compiler, + isConst: true) { + assert(invariant(node, constructor.isImplementation)); + } + + Constant visitSend(Send send) { + Element element = elements[send]; + if (Elements.isLocal(element)) { + Constant constant = definitions[element]; + if (constant == null) { + compiler.internalError("Local variable without value", node: send); + } + return constant; + } + return super.visitSend(send); + } + + void potentiallyCheckType(Node node, Element element, Constant constant) { + if (compiler.enableTypeAssertions) { + DartType elementType = element.computeType(compiler); + DartType constantType = constant.computeType(compiler); + // TODO(ngeoffray): Handle type parameters. + if (elementType.element.isTypeVariable()) return; + if (elementType.isMalformed || constantType.isMalformed || + !constantSystem.isSubtype(compiler, constantType, elementType)) { + compiler.reportError(node, new CompileTimeConstantError( + MessageKind.NOT_ASSIGNABLE, + {'fromType': elementType, 'toType': constantType})); + } + } + } + + void updateFieldValue(Node node, Element element, Constant constant) { + potentiallyCheckType(node, element, constant); + fieldValues[element] = constant; + } + + /** + * Given the arguments (a list of constants) assigns them to the parameters, + * updating the definitions map. If the constructor has field-initializer + * parameters (like [:this.x:]), also updates the [fieldValues] map. + */ + void assignArgumentsToParameters(List<Constant> arguments) { + // Assign arguments to parameters. + FunctionSignature parameters = constructor.computeSignature(compiler); + int index = 0; + parameters.orderedForEachParameter((Element parameter) { + Constant argument = arguments[index++]; + Node node = parameter.parseNode(compiler); + potentiallyCheckType(node, parameter, argument); + definitions[parameter] = argument; + if (parameter.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParameterElement = parameter; + updateFieldValue(node, fieldParameterElement.fieldElement, argument); + } + }); + } + + void evaluateSuperOrRedirectSend(Node currentNode, + Selector selector, + Link<Node> arguments, + FunctionElement targetConstructor) { + List<Constant> compiledArguments = evaluateArgumentsToConstructor( + currentNode, selector, arguments, targetConstructor); + + ConstructorEvaluator evaluator = new ConstructorEvaluator( + currentNode, targetConstructor, handler, compiler); + evaluator.evaluateConstructorFieldValues(compiledArguments); + // Copy over the fieldValues from the super/redirect-constructor. + // No need to go through [updateFieldValue] because the + // assignments have already been checked in checked mode. + evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value); + } + + /** + * Runs through the initializers of the given [constructor] and updates + * the [fieldValues] map. + */ + void evaluateConstructorInitializers() { + FunctionExpression functionNode = constructor.parseNode(compiler); + NodeList initializerList = functionNode.initializers; + + bool foundSuperOrRedirect = false; + + if (initializerList != null) { + for (Link<Node> link = initializerList.nodes; + !link.isEmpty; + link = link.tail) { + assert(link.head is Send); + if (link.head is !SendSet) { + // A super initializer or constructor redirection. + Send call = link.head; + FunctionElement targetConstructor = elements[call]; + Selector selector = elements.getSelector(call); + Link<Node> arguments = call.arguments; + evaluateSuperOrRedirectSend( + call, selector, arguments, targetConstructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link<Node> initArguments = init.arguments; + assert(!initArguments.isEmpty && initArguments.tail.isEmpty); + Constant fieldValue = evaluate(initArguments.head); + updateFieldValue(init, elements[init], fieldValue); + } + } + } + + if (!foundSuperOrRedirect) { + // No super initializer found. Try to find the default constructor if + // the class is not Object. + ClassElement enclosingClass = constructor.getEnclosingClass(); + ClassElement superClass = enclosingClass.superclass; + if (enclosingClass != compiler.objectClass) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + + Selector selector = + new Selector.callDefaultConstructor(enclosingClass.getLibrary()); + + FunctionElement targetConstructor = + superClass.lookupConstructor(selector); + if (targetConstructor == null) { + compiler.internalError("no default constructor available", + node: functionNode); + } + + evaluateSuperOrRedirectSend(functionNode, + selector, + const Link<Node>(), + targetConstructor); + } + } + } + + /** + * Simulates the execution of the [constructor] with the given + * [arguments] to obtain the field values that need to be passed to the + * native JavaScript constructor. + */ + void evaluateConstructorFieldValues(List<Constant> arguments) { + compiler.withCurrentElement(constructor, () { + assignArgumentsToParameters(arguments); + evaluateConstructorInitializers(); + }); + } + + List<Constant> buildJsNewArguments(ClassElement classElement) { + List<Constant> jsNewArguments = <Constant>[]; + classElement.implementation.forEachInstanceField( + (ClassElement enclosing, Element field) { + Constant fieldValue = fieldValues[field]; + if (fieldValue == null) { + // Use the default value. + fieldValue = handler.compileConstant(field); + } + jsNewArguments.add(fieldValue); + }, + includeBackendMembers: true, + includeSuperMembers: true); + return jsNewArguments; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/compiler.dart b/pkgs/markdown/lib/src/compiler/implementation/compiler.dart new file mode 100644 index 0000000..4fef805 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/compiler.dart
@@ -0,0 +1,1130 @@ +// 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. + +part of dart2js; + +/** + * If true, print a warning for each method that was resolved, but not + * compiled. + */ +const bool REPORT_EXCESS_RESOLUTION = false; + +/** + * If true, dump the inferred types after compilation. + */ +const bool DUMP_INFERRED_TYPES = false; + +/** + * A string to identify the revision or build. + * + * This ID is displayed if the compiler crashes and in verbose mode, and is + * an aid in reproducing bug reports. + * + * The actual string is rewritten during the SDK build process. + */ +const String BUILD_ID = '0.3.5.1_r18300'; + +/** + * Contains backend-specific data that is used throughout the compilation of + * one work item. + */ +class ItemCompilationContext { +} + +abstract class WorkItem { + final ItemCompilationContext compilationContext; + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + final Element element; + TreeElements get resolutionTree; + + WorkItem(this.element, this.compilationContext) { + assert(invariant(element, element.isDeclaration)); + } + + bool isAnalyzed() => resolutionTree != null; + + void run(Compiler compiler, Enqueuer world); +} + +/// [WorkItem] used exclusively by the [ResolutionEnqueuer]. +class ResolutionWorkItem extends WorkItem { + TreeElements resolutionTree; + + ResolutionWorkItem(Element element, + ItemCompilationContext compilationContext) + : super(element, compilationContext); + + void run(Compiler compiler, ResolutionEnqueuer world) { + resolutionTree = compiler.analyze(this, world); + } +} + +/// [WorkItem] used exclusively by the [CodegenEnqueuer]. +class CodegenWorkItem extends WorkItem { + final TreeElements resolutionTree; + + bool allowSpeculativeOptimization = true; + List<HTypeGuard> guards = const <HTypeGuard>[]; + + CodegenWorkItem(Element element, + TreeElements this.resolutionTree, + ItemCompilationContext compilationContext) + : super(element, compilationContext) { + assert(invariant(element, resolutionTree != null, + message: 'Resolution tree is null for $element in codegen work item')); + } + + void run(Compiler compiler, CodegenEnqueuer world) { + if (world.isProcessed(element)) return; + compiler.codegen(this, world); + } +} + +class ReadingFilesTask extends CompilerTask { + ReadingFilesTask(Compiler compiler) : super(compiler); + String get name => 'Reading input files'; +} + +abstract class Backend { + final Compiler compiler; + final ConstantSystem constantSystem; + + Backend(this.compiler, + [ConstantSystem constantSystem = DART_CONSTANT_SYSTEM]) + : this.constantSystem = constantSystem; + + void enqueueAllTopLevelFunctions(LibraryElement lib, Enqueuer world) { + lib.forEachExport((Element e) { + if (e.isFunction()) world.addToWorkList(e); + }); + } + + void enqueueHelpers(ResolutionEnqueuer world); + void codegen(CodegenWorkItem work); + + // The backend determines the native resolution enqueuer, with a no-op + // default, so tools like dart2dart can ignore the native classes. + native.NativeEnqueuer nativeResolutionEnqueuer(world) { + return new native.NativeEnqueuer(); + } + native.NativeEnqueuer nativeCodegenEnqueuer(world) { + return new native.NativeEnqueuer(); + } + + void assembleProgram(); + List<CompilerTask> get tasks; + + // TODO(ahe,karlklose): rename this? + void dumpInferredTypes() {} + + ItemCompilationContext createItemCompilationContext() { + return new ItemCompilationContext(); + } + + SourceString getCheckedModeHelper(DartType type) => null; + void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {} +} + +/** + * Key class used in [TokenMap] in which the hash code for a token is based + * on the [charOffset]. + */ +class TokenKey { + final Token token; + TokenKey(this.token); + int get hashCode => token.charOffset; + operator==(other) => other is TokenKey && token == other.token; +} + +/// Map of tokens and the first associated comment. +/* + * This implementation was chosen among several candidates for its space/time + * efficiency by empirical tests of running dartdoc on dartdoc itself. Time + * measurements for the use of [Compiler.commentMap]: + * + * 1) Using [TokenKey] as key (this class): ~80 msec + * 2) Using [TokenKey] as key + storing a separate map in each script: ~120 msec + * 3) Using [Token] as key in a [Map]: ~38000 msec + * 4) Storing comments is new field in [Token]: ~20 msec + * (Abandoned due to the increased memory usage) + * 5) Storing comments in an [Expando]: ~14000 msec + * 6) Storing token/comments pairs in a linked list: ~5400 msec + */ +class TokenMap { + Map<TokenKey,Token> comments = new Map<TokenKey,Token>(); + + Token operator[] (Token key) { + if (key == null) return null; + return comments[new TokenKey(key)]; + } + + void operator[]= (Token key, Token value) { + if (key == null) return; + comments[new TokenKey(key)] = value; + } +} + +abstract class Compiler implements DiagnosticListener { + final Map<String, LibraryElement> libraries; + final Stopwatch totalCompileTime = new Stopwatch(); + int nextFreeClassId = 0; + World world; + String assembledCode; + Types types; + + /** + * Map from token to the first preceeding comment token. + */ + final TokenMap commentMap = new TokenMap(); + + final bool enableMinification; + final bool enableTypeAssertions; + final bool enableUserAssertions; + final bool enableConcreteTypeInference; + /** + * The maximum size of a concrete type before it widens to dynamic during + * concrete type inference. + */ + final int maxConcreteTypeSize; + final bool analyzeAll; + final bool analyzeOnly; + final bool enableNativeLiveTypeAnalysis; + final bool rejectDeprecatedFeatures; + final bool checkDeprecationInSdk; + + /** + * If [:true:], comment tokens are collected in [commentMap] during scanning. + */ + final bool preserveComments; + + final api.CompilerOutputProvider outputProvider; + + bool disableInlining = false; + + List<Uri> librariesToAnalyzeWhenRun; + + final Tracer tracer; + + CompilerTask measuredTask; + Element _currentElement; + LibraryElement coreLibrary; + LibraryElement isolateLibrary; + LibraryElement isolateHelperLibrary; + LibraryElement jsHelperLibrary; + LibraryElement interceptorsLibrary; + LibraryElement foreignLibrary; + LibraryElement mainApp; + + ClassElement objectClass; + ClassElement closureClass; + ClassElement dynamicClass; + ClassElement boolClass; + ClassElement numClass; + ClassElement intClass; + ClassElement doubleClass; + ClassElement stringClass; + ClassElement functionClass; + ClassElement nullClass; + ClassElement listClass; + ClassElement typeClass; + ClassElement mapClass; + ClassElement jsInvocationMirrorClass; + /// Document class from dart:mirrors. + ClassElement documentClass; + Element assertMethod; + Element identicalFunction; + Element functionApplyMethod; + Element invokeOnMethod; + Element createInvocationMirrorElement; + + Element get currentElement => _currentElement; + withCurrentElement(Element element, f()) { + Element old = currentElement; + _currentElement = element; + try { + return f(); + } on SpannableAssertionFailure catch (ex) { + if (!hasCrashed) { + SourceSpan span = spanFromSpannable(ex.node); + reportDiagnostic(span, ex.message, api.Diagnostic.ERROR); + pleaseReportCrash(); + } + hasCrashed = true; + throw; + } on CompilerCancelledException catch (ex) { + throw; + } on StackOverflowError catch (ex) { + // We cannot report anything useful in this case, because we + // do not have enough stack space. + throw; + } catch (ex) { + try { + unhandledExceptionOnElement(element); + } catch (doubleFault) { + // Ignoring exceptions in exception handling. + } + throw; + } finally { + _currentElement = old; + } + } + + List<CompilerTask> tasks; + ScannerTask scanner; + DietParserTask dietParser; + ParserTask parser; + PatchParserTask patchParser; + LibraryLoader libraryLoader; + TreeValidatorTask validator; + ResolverTask resolver; + closureMapping.ClosureTask closureToClassMapper; + TypeCheckerTask checker; + ti.TypesTask typesTask; + Backend backend; + ConstantHandler constantHandler; + ConstantHandler metadataHandler; + EnqueueTask enqueuer; + CompilerTask fileReadingTask; + + static const SourceString MAIN = const SourceString('main'); + static const SourceString CALL_OPERATOR_NAME = const SourceString('call'); + static const SourceString NO_SUCH_METHOD = const SourceString('noSuchMethod'); + static const int NO_SUCH_METHOD_ARG_COUNT = 1; + static const SourceString CREATE_INVOCATION_MIRROR = + const SourceString('createInvocationMirror'); + static const SourceString INVOKE_ON = const SourceString('invokeOn'); + static const SourceString RUNTIME_TYPE = const SourceString('runtimeType'); + static const SourceString START_ROOT_ISOLATE = + const SourceString('startRootIsolate'); + bool enabledNoSuchMethod = false; + bool enabledRuntimeType = false; + bool enabledFunctionApply = false; + bool enabledInvokeOn = false; + + Stopwatch progress; + + static const int PHASE_SCANNING = 0; + static const int PHASE_RESOLVING = 1; + static const int PHASE_COMPILING = 2; + int phase; + + bool compilationFailed = false; + + bool hasCrashed = false; + + Compiler({this.tracer: const Tracer(), + this.enableTypeAssertions: false, + this.enableUserAssertions: false, + this.enableConcreteTypeInference: false, + this.maxConcreteTypeSize: 5, + this.enableMinification: false, + this.enableNativeLiveTypeAnalysis: false, + bool emitJavaScript: true, + bool generateSourceMap: true, + bool disallowUnsafeEval: false, + this.analyzeAll: false, + this.analyzeOnly: false, + this.rejectDeprecatedFeatures: false, + this.checkDeprecationInSdk: false, + this.preserveComments: false, + outputProvider, + List<String> strips: const []}) + : libraries = new Map<String, LibraryElement>(), + progress = new Stopwatch(), + this.outputProvider = + (outputProvider == null) ? NullSink.outputProvider : outputProvider + { + progress.start(); + world = new World(this); + + closureMapping.ClosureNamer closureNamer; + if (emitJavaScript) { + js_backend.JavaScriptBackend jsBackend = + new js_backend.JavaScriptBackend(this, generateSourceMap, + disallowUnsafeEval); + closureNamer = jsBackend.namer; + backend = jsBackend; + } else { + backend = new dart_backend.DartBackend(this, strips); + } + + // No-op in production mode. + validator = new TreeValidatorTask(this); + + tasks = [ + fileReadingTask = new ReadingFilesTask(this), + libraryLoader = new LibraryLoaderTask(this), + scanner = new ScannerTask(this), + dietParser = new DietParserTask(this), + parser = new ParserTask(this), + patchParser = new PatchParserTask(this), + resolver = new ResolverTask(this), + closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer), + checker = new TypeCheckerTask(this), + typesTask = new ti.TypesTask(this), + constantHandler = new ConstantHandler(this, backend.constantSystem), + enqueuer = new EnqueueTask(this)]; + + tasks.addAll(backend.tasks); + metadataHandler = new ConstantHandler( + this, backend.constantSystem, isMetadata: true); + } + + Universe get resolverWorld => enqueuer.resolution.universe; + Universe get codegenWorld => enqueuer.codegen.universe; + + int getNextFreeClassId() => nextFreeClassId++; + + void ensure(bool condition) { + if (!condition) cancel('failed assertion in leg'); + } + + void unimplemented(String methodName, + {Node node, Token token, HInstruction instruction, + Element element}) { + internalError("$methodName not implemented", + node: node, token: token, + instruction: instruction, element: element); + } + + 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); + } + + void unhandledExceptionOnElement(Element element) { + if (hasCrashed) return; + hasCrashed = true; + reportDiagnostic(spanFromElement(element), + MessageKind.COMPILER_CRASHED.error().toString(), + api.Diagnostic.CRASH); + pleaseReportCrash(); + } + + void pleaseReportCrash() { + print(MessageKind.PLEASE_REPORT_THE_CRASH.message({'buildId': BUILD_ID})); + } + + void cancel(String reason, {Node node, Token token, + HInstruction instruction, Element element}) { + assembledCode = null; // Compilation failed. Make sure that we + // don't return a bogus result. + 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'; + } + reportDiagnostic(span, reason, api.Diagnostic.ERROR); + throw new CompilerCancelledException(reason); + } + + SourceSpan spanFromSpannable(Spannable node, [Uri uri]) { + if (node == CURRENT_ELEMENT_SPANNABLE) { + node = currentElement; + } + if (node is Node) { + return spanFromNode(node, uri); + } else if (node is Token) { + return spanFromTokens(node, node, uri); + } else if (node is HInstruction) { + return spanFromHInstruction(node); + } else if (node is Element) { + return spanFromElement(node); + } else if (node is MetadataAnnotation) { + return spanFromTokens(node.beginToken, node.endToken); + } else { + throw 'No error location.'; + } + } + + void reportFatalError(String reason, Element element, + {Node node, Token token, HInstruction instruction}) { + withCurrentElement(element, () { + cancel(reason, node: node, token: token, instruction: instruction, + element: element); + }); + } + + void log(message) { + reportDiagnostic(null, message, api.Diagnostic.VERBOSE_INFO); + } + + bool run(Uri uri) { + totalCompileTime.start(); + try { + runCompiler(uri); + } on CompilerCancelledException catch (exception) { + log('Error: $exception'); + return false; + } finally { + tracer.close(); + totalCompileTime.stop(); + } + return true; + } + + bool hasIsolateSupport() => isolateLibrary != null; + + /** + * This method is called before [library] import and export scopes have been + * set up. + */ + void onLibraryScanned(LibraryElement library, Uri uri) { + if (dynamicClass != null) { + // When loading the built-in libraries, dynamicClass is null. We + // take advantage of this as core imports js_helper and sees [dynamic] + // this way. + withCurrentElement(dynamicClass, () { + library.addToScope(dynamicClass, this); + }); + } + } + + LibraryElement scanBuiltinLibrary(String filename); + + void initializeSpecialClasses() { + final List missingCoreClasses = []; + ClassElement lookupCoreClass(String name) { + ClassElement result = coreLibrary.find(new SourceString(name)); + if (result == null) { + missingCoreClasses.add(name); + } + return result; + } + objectClass = lookupCoreClass('Object'); + boolClass = lookupCoreClass('bool'); + numClass = lookupCoreClass('num'); + intClass = lookupCoreClass('int'); + doubleClass = lookupCoreClass('double'); + stringClass = lookupCoreClass('String'); + functionClass = lookupCoreClass('Function'); + listClass = lookupCoreClass('List'); + typeClass = lookupCoreClass('Type'); + mapClass = lookupCoreClass('Map'); + if (!missingCoreClasses.isEmpty) { + internalErrorOnElement(coreLibrary, + 'dart:core library does not contain required classes: ' + '$missingCoreClasses'); + } + + final List missingHelperClasses = []; + ClassElement lookupHelperClass(String name) { + ClassElement result = jsHelperLibrary.find(new SourceString(name)); + if (result == null) { + missingHelperClasses.add(name); + } + return result; + } + jsInvocationMirrorClass = lookupHelperClass('JSInvocationMirror'); + closureClass = lookupHelperClass('Closure'); + dynamicClass = lookupHelperClass('Dynamic_'); + nullClass = lookupHelperClass('Null'); + if (!missingHelperClasses.isEmpty) { + internalErrorOnElement(jsHelperLibrary, + 'dart:_js_helper library does not contain required classes: ' + '$missingHelperClasses'); + } + + types = new Types(this, dynamicClass); + } + + void scanBuiltinLibraries() { + jsHelperLibrary = scanBuiltinLibrary('_js_helper'); + interceptorsLibrary = scanBuiltinLibrary('_interceptors'); + foreignLibrary = scanBuiltinLibrary('_foreign_helper'); + isolateHelperLibrary = scanBuiltinLibrary('_isolate_helper'); + // The helper library does not use the native language extension, + // so we manually set the native classes this library defines. + // TODO(ngeoffray): Enable annotations on these classes. + ClassElement cls = + isolateHelperLibrary.find(const SourceString('_WorkerStub')); + cls.setNative('"*Worker"'); + + assertMethod = jsHelperLibrary.find(const SourceString('assertHelper')); + identicalFunction = coreLibrary.find(const SourceString('identical')); + + initializeSpecialClasses(); + + functionClass.ensureResolved(this); + functionApplyMethod = + functionClass.lookupLocalMember(const SourceString('apply')); + jsInvocationMirrorClass.ensureResolved(this); + invokeOnMethod = jsInvocationMirrorClass.lookupLocalMember( + const SourceString('invokeOn')); + + if (preserveComments) { + var uri = new Uri.fromComponents(scheme: 'dart', path: 'mirrors'); + LibraryElement libraryElement = + libraryLoader.loadLibrary(uri, null, uri); + documentClass = libraryElement.find(const SourceString('Comment')); + } + } + + void importHelperLibrary(LibraryElement library) { + if (jsHelperLibrary != null) { + libraryLoader.importLibrary(library, jsHelperLibrary, null); + } + } + + /** + * Get an [Uri] pointing to a patch for the dart: library with + * the given path. Returns null if there is no patch. + */ + Uri resolvePatchUri(String dartLibraryPath); + + void runCompiler(Uri uri) { + assert(uri != null || analyzeOnly); + scanBuiltinLibraries(); + if (librariesToAnalyzeWhenRun != null) { + for (Uri libraryUri in librariesToAnalyzeWhenRun) { + log('analyzing $libraryUri ($BUILD_ID)'); + libraryLoader.loadLibrary(libraryUri, null, libraryUri); + } + } + if (uri != null) { + if (analyzeOnly) { + log('analyzing $uri ($BUILD_ID)'); + } else { + log('compiling $uri ($BUILD_ID)'); + } + mainApp = libraryLoader.loadLibrary(uri, null, uri); + } + Element main = null; + if (mainApp != null) { + main = mainApp.find(MAIN); + if (main == null) { + if (!analyzeOnly) { + // Allow analyze only of libraries with no main. + reportFatalError('Could not find $MAIN', mainApp); + } else if (!analyzeAll) { + reportFatalError( + "Could not find $MAIN. " + "No source will be analyzed. " + "Use '--analyze-all' to analyze all code in the library.", + mainApp); + } + } else { + if (!main.isFunction()) { + reportFatalError('main is not a function', main); + } + FunctionElement mainMethod = main; + FunctionSignature parameters = mainMethod.computeSignature(this); + parameters.forEachParameter((Element parameter) { + reportFatalError('main cannot have parameters', parameter); + }); + } + } + + log('Resolving...'); + phase = PHASE_RESOLVING; + if (analyzeAll) { + libraries.forEach((_, lib) => fullyEnqueueLibrary(lib)); + } + backend.enqueueHelpers(enqueuer.resolution); + processQueue(enqueuer.resolution, main); + enqueuer.resolution.logSummary(log); + + if (compilationFailed) return; + if (analyzeOnly) return; + assert(main != null); + + log('Inferring types...'); + typesTask.onResolutionComplete(main); + + // TODO(ahe): Remove this line. Eventually, enqueuer.resolution + // should know this. + world.populate(); + + log('Compiling...'); + phase = PHASE_COMPILING; + // TODO(johnniwinther): Move these to [CodegenEnqueuer]. + if (hasIsolateSupport()) { + enqueuer.codegen.addToWorkList( + isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE)); + } + if (enabledNoSuchMethod) { + Selector selector = new Selector.noSuchMethod(); + enqueuer.codegen.registerInvocation(NO_SUCH_METHOD, selector); + enqueuer.codegen.addToWorkList(createInvocationMirrorElement); + } + processQueue(enqueuer.codegen, main); + enqueuer.codegen.logSummary(log); + + if (compilationFailed) return; + + backend.assembleProgram(); + + checkQueues(); + } + + void fullyEnqueueLibrary(LibraryElement library) { + library.forEachLocalMember(fullyEnqueueTopLevelElement); + } + + void fullyEnqueueTopLevelElement(Element element) { + if (element.isClass()) { + ClassElement cls = element; + cls.ensureResolved(this); + cls.forEachLocalMember(enqueuer.resolution.addToWorkList); + } else { + enqueuer.resolution.addToWorkList(element); + } + } + + void processQueue(Enqueuer world, Element main) { + world.nativeEnqueuer.processNativeClasses(libraries.values); + if (main != null) { + world.addToWorkList(main); + } + progress.reset(); + world.forEach((WorkItem work) { + withCurrentElement(work.element, () => work.run(this, world)); + }); + world.queueIsClosed = true; + if (compilationFailed) return; + assert(world.checkNoEnqueuedInvokedInstanceMethods()); + if (DUMP_INFERRED_TYPES && phase == PHASE_COMPILING) { + backend.dumpInferredTypes(); + } + } + + /** + * Perform various checks of the queues. This includes checking that + * the queues are empty (nothing was added after we stopped + * processing the queues). Also compute the number of methods that + * were resolved, but not compiled (aka excess resolution). + */ + checkQueues() { + for (Enqueuer world in [enqueuer.resolution, enqueuer.codegen]) { + world.forEach((WorkItem work) { + internalErrorOnElement(work.element, "Work list is not empty."); + }); + } + if (!REPORT_EXCESS_RESOLUTION) return; + var resolved = new Set.from(enqueuer.resolution.resolvedElements.keys); + for (Element e in enqueuer.codegen.generatedCode.keys) { + resolved.remove(e); + } + for (Element e in new Set.from(resolved)) { + if (e.isClass() || + e.isField() || + e.isTypeVariable() || + e.isTypedef() || + identical(e.kind, ElementKind.ABSTRACT_FIELD)) { + resolved.remove(e); + } + if (identical(e.kind, ElementKind.GENERATIVE_CONSTRUCTOR)) { + ClassElement enclosingClass = e.getEnclosingClass(); + if (enclosingClass.isInterface()) { + resolved.remove(e); + } + resolved.remove(e); + + } + if (identical(e.getLibrary(), jsHelperLibrary)) { + resolved.remove(e); + } + if (identical(e.getLibrary(), interceptorsLibrary)) { + resolved.remove(e); + } + } + log('Excess resolution work: ${resolved.length}.'); + for (Element e in resolved) { + SourceSpan span = spanFromElement(e); + reportDiagnostic(span, 'Warning: $e resolved but not compiled.', + api.Diagnostic.WARNING); + } + } + + TreeElements analyzeElement(Element element) { + assert(invariant(element, element.isDeclaration)); + TreeElements elements = enqueuer.resolution.getCachedElements(element); + if (elements != null) return elements; + assert(parser != null); + Node tree = parser.parse(element); + validator.validate(tree); + elements = resolver.resolve(element); + if (elements != null) { + // Only analyze nodes with a corresponding [TreeElements]. + checker.check(tree, elements); + typesTask.analyze(tree, elements); + } + return elements; + } + + TreeElements analyze(ResolutionWorkItem work, ResolutionEnqueuer world) { + assert(invariant(work.element, identical(world, enqueuer.resolution))); + assert(invariant(work.element, !work.isAnalyzed(), + message: 'Element ${work.element} has already been analyzed')); + if (progress.elapsedMilliseconds > 500) { + // TODO(ahe): Add structured diagnostics to the compiler API and + // use it to separate this from the --verbose option. + if (phase == PHASE_RESOLVING) { + log('Resolved ${enqueuer.resolution.resolvedElements.length} ' + 'elements.'); + progress.reset(); + } + } + Element element = work.element; + TreeElements result = world.getCachedElements(element); + if (result != null) return result; + result = analyzeElement(element); + assert(invariant(element, element.isDeclaration)); + world.resolvedElements[element] = result; + return result; + } + + void codegen(CodegenWorkItem work, CodegenEnqueuer world) { + assert(invariant(work.element, identical(world, enqueuer.codegen))); + if (progress.elapsedMilliseconds > 500) { + // TODO(ahe): Add structured diagnostics to the compiler API and + // use it to separate this from the --verbose option. + log('Compiled ${enqueuer.codegen.generatedCode.length} methods.'); + progress.reset(); + } + backend.codegen(work); + } + + DartType resolveTypeAnnotation(Element element, + TypeAnnotation annotation) { + return resolver.resolveTypeAnnotation(element, annotation); + } + + DartType resolveReturnType(Element element, + TypeAnnotation annotation) { + return resolver.resolveReturnType(element, annotation); + } + + FunctionSignature resolveSignature(FunctionElement element) { + return withCurrentElement(element, + () => resolver.resolveSignature(element)); + } + + FunctionSignature resolveFunctionExpression(Element element, + FunctionExpression node) { + return withCurrentElement(element, + () => resolver.resolveFunctionExpression(element, node)); + } + + void resolveTypedef(TypedefElement element) { + withCurrentElement(element, + () => resolver.resolveTypedef(element)); + } + + FunctionType computeFunctionType(Element element, + FunctionSignature signature) { + return withCurrentElement(element, + () => resolver.computeFunctionType(element, signature)); + } + + reportWarning(Node node, var message) { + if (message is TypeWarning) { + // TODO(ahe): Don't supress these warning when the type checker + // is more complete. + if (identical(message.message.kind, MessageKind.NOT_ASSIGNABLE)) return; + if (identical(message.message.kind, MessageKind.MISSING_RETURN)) return; + if (identical(message.message.kind, MessageKind.MAYBE_MISSING_RETURN)) return; + if (identical(message.message.kind, MessageKind.METHOD_NOT_FOUND)) return; + } + SourceSpan span = spanFromNode(node); + + reportDiagnostic(span, 'Warning: $message', api.Diagnostic.WARNING); + } + + // TODO(ahe): Remove this method. + reportError(Node node, var message) { + SourceSpan span = spanFromNode(node); + reportDiagnostic(span, 'Error: $message', api.Diagnostic.ERROR); + throw new CompilerCancelledException(message.toString()); + } + + // TODO(ahe): Rename to reportError when that method has been removed. + void reportErrorCode(Spannable node, MessageKind errorCode, + [Map arguments = const {}]) { + reportMessage(spanFromSpannable(node), + errorCode.error(arguments), + api.Diagnostic.ERROR); + } + + void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) { + // TODO(ahe): The names Diagnostic and api.Diagnostic are in + // conflict. Fix it. + reportDiagnostic(span, "$message", kind); + } + + /// Returns true if a diagnostic was emitted. + bool onDeprecatedFeature(Spannable span, String feature) { + if (currentElement == null) + throw new SpannableAssertionFailure(span, feature); + if (!checkDeprecationInSdk && + currentElement.getLibrary().isPlatformLibrary) { + return false; + } + var kind = rejectDeprecatedFeatures + ? api.Diagnostic.ERROR : api.Diagnostic.WARNING; + var message = rejectDeprecatedFeatures + ? MessageKind.DEPRECATED_FEATURE_ERROR.error({'featureName': feature}) + : MessageKind.DEPRECATED_FEATURE_WARNING.error( + {'featureName': feature}); + reportMessage(spanFromSpannable(span), message, kind); + return true; + } + + void reportDiagnostic(SourceSpan span, String message, api.Diagnostic kind); + + 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); + } + + /** + * Translates the [resolvedUri] into a readable URI. + * + * The [importingLibrary] holds the library importing [resolvedUri] or + * [:null:] if [resolvedUri] is loaded as the main library. The + * [importingLibrary] is used to grant access to internal libraries from + * platform libraries and patch libraries. + * + * If the [resolvedUri] is not accessible from [importingLibrary], this method + * is responsible for reporting errors. + * + * See [LibraryLoader] for terminology on URIs. + */ + Uri translateResolvedUri(LibraryElement importingLibrary, + Uri resolvedUri, Node node) { + unimplemented('Compiler.translateResolvedUri'); + } + + /** + * Reads the script specified by the [readableUri]. + * + * See [LibraryLoader] for terminology on URIs. + */ + Script readScript(Uri readableUri, [Node node]) { + unimplemented('Compiler.readScript'); + } + + String get legDirectory { + unimplemented('Compiler.legDirectory'); + } + + // TODO(karlklose): split into findHelperFunction and findHelperClass and + // add a check that the element has the expected kind. + Element findHelper(SourceString name) + => jsHelperLibrary.findLocal(name); + Element findInterceptor(SourceString name) + => interceptorsLibrary.findLocal(name); + + Element lookupElementIn(ScopeContainerElement container, SourceString name) { + Element element = container.localLookup(name); + if (element == null) { + throw 'Could not find ${name.slowToString()} in $container'; + } + return element; + } + + bool get isMockCompilation => false; + + Token processAndStripComments(Token currentToken) { + Token firstToken = currentToken; + Token prevToken; + while (currentToken.kind != EOF_TOKEN) { + if (identical(currentToken.kind, COMMENT_TOKEN)) { + Token firstCommentToken = currentToken; + while (identical(currentToken.kind, COMMENT_TOKEN)) { + currentToken = currentToken.next; + } + commentMap[currentToken] = firstCommentToken; + if (prevToken == null) { + firstToken = currentToken; + } else { + prevToken.next = currentToken; + } + } + prevToken = currentToken; + currentToken = currentToken.next; + } + return firstToken; + } +} + +class CompilerTask { + final Compiler compiler; + final Stopwatch watch; + + CompilerTask(this.compiler) : watch = new Stopwatch(); + + String get name => 'Unknown task'; + int get timing => watch.elapsedMilliseconds; + + measure(Function action) { + CompilerTask previous = compiler.measuredTask; + if (identical(this, previous)) return action(); + compiler.measuredTask = this; + if (previous != null) previous.watch.stop(); + watch.start(); + try { + return action(); + } finally { + watch.stop(); + if (previous != null) previous.watch.start(); + compiler.measuredTask = previous; + } + } +} + +class CompilerCancelledException implements Exception { + final String reason; + CompilerCancelledException(this.reason); + + String toString() { + String banner = 'compiler cancelled'; + return (reason != null) ? '$banner: $reason' : '$banner'; + } +} + +class Tracer { + final bool enabled = false; + + const Tracer(); + + void traceCompilation(String methodName, ItemCompilationContext context) { + } + + void traceGraph(String name, var graph) { + } + + void close() { + } +} + +class SourceSpan { + final Uri uri; + final int begin; + final int end; + + const SourceSpan(this.uri, this.begin, this.end); + + static withCharacterOffsets(Token begin, Token end, + f(int beginOffset, int endOffset)) { + final beginOffset = begin.charOffset; + final endOffset = end.charOffset + end.slowCharCount; + + // [begin] and [end] might be the same for the same empty token. This + // happens for instance when scanning '$$'. + assert(endOffset >= beginOffset); + return f(beginOffset, endOffset); + } + + String toString() => 'SourceSpan($uri, $begin, $end)'; +} + +/** + * Throws an [InvariantException] if [condition] is [:false:]. [condition] must + * be either a [:bool:] or a no-arg function returning a [:bool:]. + * + * Use this method to provide better information for assertion by calling + * [invariant] as the argument to an [:assert:] statement: + * + * assert(invariant(position, isValid)); + * + * [spannable] must be non-null and will be used to provide positional + * information in the generated error message. + */ +bool invariant(Spannable spannable, var condition, {String message: null}) { + // TODO(johnniwinther): Use [spannable] and [message] to provide better + // information on assertion errors. + if (condition is Function){ + condition = condition(); + } + if (spannable == null || !condition) { + throw new SpannableAssertionFailure(spannable, message); + } + return true; +} + +/// A sink that drains into /dev/null. +class NullSink extends StreamSink<String> { + final String name; + + NullSink(this.name); + + add(String value) {} + + void signalError(AsyncError error) {} + + void close() {} + + toString() => name; + + /// Convenience method for getting an [api.CompilerOutputProvider]. + static NullSink outputProvider(String name, String extension) { + return new NullSink('$name.$extension'); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart b/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart new file mode 100644 index 0000000..0fff580 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/constant_system.dart
@@ -0,0 +1,80 @@ +// 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. + +part of dart2js; + +abstract class Operation { + SourceString get name; + bool isUserDefinable(); +} + +abstract class UnaryOperation extends Operation { + /** Returns [:null:] if it was unable to fold the operation. */ + Constant fold(Constant constant); + apply(value); +} + +abstract class BinaryOperation extends Operation { + /** Returns [:null:] if it was unable to fold the operation. */ + Constant fold(Constant left, Constant right); + apply(left, right); +} + +/** + * A [ConstantSystem] is responsible for creating constants and folding them. + */ +abstract class ConstantSystem { + BinaryOperation get add; + BinaryOperation get bitAnd; + UnaryOperation get bitNot; + BinaryOperation get bitOr; + BinaryOperation get bitXor; + BinaryOperation get booleanAnd; + BinaryOperation get booleanOr; + BinaryOperation get divide; + BinaryOperation get equal; + BinaryOperation get greaterEqual; + BinaryOperation get greater; + BinaryOperation get identity; + BinaryOperation get lessEqual; + BinaryOperation get less; + BinaryOperation get modulo; + BinaryOperation get multiply; + UnaryOperation get negate; + UnaryOperation get not; + BinaryOperation get shiftLeft; + BinaryOperation get shiftRight; + BinaryOperation get subtract; + BinaryOperation get truncatingDivide; + + const ConstantSystem(); + + Constant createInt(int i); + Constant createDouble(double d); + // We need a diagnostic node to report errors in case the string is malformed. + Constant createString(DartString string, Node diagnosticNode); + Constant createBool(bool value); + Constant createNull(); + + // We need to special case the subtype check for JavaScript constant + // system because an int is a double at runtime. + bool isSubtype(Compiler compiler, DartType s, DartType t); + + /** Returns true if the [constant] is an integer at runtime. */ + bool isInt(Constant constant); + /** Returns true if the [constant] is a double at runtime. */ + bool isDouble(Constant constant); + /** Returns true if the [constant] is a string at runtime. */ + bool isString(Constant constant); + /** Returns true if the [constant] is a boolean at runtime. */ + bool isBool(Constant constant); + /** Returns true if the [constant] is null at runtime. */ + bool isNull(Constant constant); + + Operation lookupUnary(SourceString operator) { + if (operator == const SourceString('-')) return negate; + if (operator == const SourceString('~')) return bitNot; + return null; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart b/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart new file mode 100644 index 0000000..6732b8c --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart
@@ -0,0 +1,380 @@ +// 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. + +part of dart2js; + +const DART_CONSTANT_SYSTEM = const DartConstantSystem(); + +class BitNotOperation implements UnaryOperation { + final SourceString name = const SourceString('~'); + bool isUserDefinable() => true; + const BitNotOperation(); + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + return DART_CONSTANT_SYSTEM.createInt(~intConstant.value); + } + return null; + } + apply(value) => ~value; +} + +class NegateOperation implements UnaryOperation { + final SourceString name = const SourceString('negate'); + bool isUserDefinable() => true; + const NegateOperation(); + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + return DART_CONSTANT_SYSTEM.createInt(-intConstant.value); + } + if (constant.isDouble()) { + DoubleConstant doubleConstant = constant; + return DART_CONSTANT_SYSTEM.createDouble(-doubleConstant.value); + } + return null; + } + apply(value) => -value; +} + +class NotOperation implements UnaryOperation { + final SourceString name = const SourceString('!'); + bool isUserDefinable() => true; + const NotOperation(); + Constant fold(Constant constant) { + if (constant.isBool()) { + BoolConstant boolConstant = constant; + return DART_CONSTANT_SYSTEM.createBool(!boolConstant.value); + } + return null; + } + apply(value) => !value; +} + +/** + * Operations that only work if both arguments are integers. + */ +abstract class BinaryBitOperation implements BinaryOperation { + bool isUserDefinable() => true; + const BinaryBitOperation(); + Constant fold(Constant left, Constant right) { + if (left.isInt() && right.isInt()) { + IntConstant leftInt = left; + IntConstant rightInt = right; + int resultValue = foldInts(leftInt.value, rightInt.value); + if (resultValue == null) return null; + return DART_CONSTANT_SYSTEM.createInt(resultValue); + } + return null; + } + + int foldInts(int left, int right); +} + +class BitOrOperation extends BinaryBitOperation { + final SourceString name = const SourceString('|'); + const BitOrOperation(); + int foldInts(int left, int right) => left | right; + apply(left, right) => left | right; +} + +class BitAndOperation extends BinaryBitOperation { + final SourceString name = const SourceString('&'); + const BitAndOperation(); + int foldInts(int left, int right) => left & right; + apply(left, right) => left & right; +} + +class BitXorOperation extends BinaryBitOperation { + final SourceString name = const SourceString('^'); + const BitXorOperation(); + int foldInts(int left, int right) => left ^ right; + apply(left, right) => left ^ right; +} + +class ShiftLeftOperation extends BinaryBitOperation { + final SourceString name = const SourceString('<<'); + const ShiftLeftOperation(); + int foldInts(int left, int right) { + // TODO(floitsch): find a better way to guard against excessive shifts to + // the left. + if (right > 100 || right < 0) return null; + return left << right; + } + apply(left, right) => left << right; +} + +class ShiftRightOperation extends BinaryBitOperation { + final SourceString name = const SourceString('>>'); + const ShiftRightOperation(); + int foldInts(int left, int right) { + if (right < 0) return null; + return left >> right; + } + apply(left, right) => left >> right; +} + +abstract class BinaryBoolOperation implements BinaryOperation { + bool isUserDefinable() => false; + const BinaryBoolOperation(); + Constant fold(Constant left, Constant right) { + if (left.isBool() && right.isBool()) { + BoolConstant leftBool = left; + BoolConstant rightBool = right; + bool resultValue = foldBools(leftBool.value, rightBool.value); + return DART_CONSTANT_SYSTEM.createBool(resultValue); + } + return null; + } + + bool foldBools(bool left, bool right); +} + +class BooleanAndOperation extends BinaryBoolOperation { + final SourceString name = const SourceString('&&'); + const BooleanAndOperation(); + bool foldBools(bool left, bool right) => left && right; + apply(left, right) => left && right; +} + +class BooleanOrOperation extends BinaryBoolOperation { + final SourceString name = const SourceString('||'); + const BooleanOrOperation(); + bool foldBools(bool left, bool right) => left || right; + apply(left, right) => left || right; +} + +abstract class ArithmeticNumOperation implements BinaryOperation { + bool isUserDefinable() => true; + const ArithmeticNumOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + num foldedValue; + if (left.isInt() && right.isInt()) { + foldedValue = foldInts(leftNum.value, rightNum.value); + } else { + foldedValue = foldNums(leftNum.value, rightNum.value); + } + // A division by 0 means that we might not have a folded value. + if (foldedValue == null) return null; + if (left.isInt() && right.isInt() && !isDivide() || + isTruncatingDivide()) { + assert(foldedValue is int); + return DART_CONSTANT_SYSTEM.createInt(foldedValue); + } else { + return DART_CONSTANT_SYSTEM.createDouble(foldedValue); + } + } + return null; + } + + bool isDivide() => false; + bool isTruncatingDivide() => false; + num foldInts(int left, int right) => foldNums(left, right); + num foldNums(num left, num right); +} + +class SubtractOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('-'); + const SubtractOperation(); + num foldNums(num left, num right) => left - right; + apply(left, right) => left - right; +} + +class MultiplyOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('*'); + const MultiplyOperation(); + num foldNums(num left, num right) => left * right; + apply(left, right) => left * right; +} + +class ModuloOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('%'); + const ModuloOperation(); + int foldInts(int left, int right) { + if (right == 0) return null; + return left % right; + } + num foldNums(num left, num right) => left % right; + apply(left, right) => left % right; +} + +class TruncatingDivideOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('~/'); + const TruncatingDivideOperation(); + int foldInts(int left, int right) { + if (right == 0) return null; + return left ~/ right; + } + num foldNums(num left, num right) { + num ratio = left / right; + if (ratio.isNaN || ratio.isInfinite) return null; + return ratio.truncate().toInt(); + } + apply(left, right) => left ~/ right; + bool isTruncatingDivide() => true; +} + +class DivideOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('/'); + const DivideOperation(); + num foldNums(num left, num right) => left / right; + bool isDivide() => true; + apply(left, right) => left / right; +} + +class AddOperation implements BinaryOperation { + final SourceString name = const SourceString('+'); + bool isUserDefinable() => true; + const AddOperation(); + Constant fold(Constant left, Constant right) { + if (left.isInt() && right.isInt()) { + IntConstant leftInt = left; + IntConstant rightInt = right; + int result = leftInt.value + rightInt.value; + return DART_CONSTANT_SYSTEM.createInt(result); + } else if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + double result = leftNum.value + rightNum.value; + return DART_CONSTANT_SYSTEM.createDouble(result); + } else { + return null; + } + } + apply(left, right) => left + right; +} + +abstract class RelationalNumOperation implements BinaryOperation { + bool isUserDefinable() => true; + const RelationalNumOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + bool foldedValue = foldNums(leftNum.value, rightNum.value); + assert(foldedValue != null); + return DART_CONSTANT_SYSTEM.createBool(foldedValue); + } + } + + bool foldNums(num left, num right); +} + +class LessOperation extends RelationalNumOperation { + final SourceString name = const SourceString('<'); + const LessOperation(); + bool foldNums(num left, num right) => left < right; + apply(left, right) => left < right; +} + +class LessEqualOperation extends RelationalNumOperation { + final SourceString name = const SourceString('<='); + const LessEqualOperation(); + bool foldNums(num left, num right) => left <= right; + apply(left, right) => left <= right; +} + +class GreaterOperation extends RelationalNumOperation { + final SourceString name = const SourceString('>'); + const GreaterOperation(); + bool foldNums(num left, num right) => left > right; + apply(left, right) => left > right; +} + +class GreaterEqualOperation extends RelationalNumOperation { + final SourceString name = const SourceString('>='); + const GreaterEqualOperation(); + bool foldNums(num left, num right) => left >= right; + apply(left, right) => left >= right; +} + +class EqualsOperation implements BinaryOperation { + final SourceString name = const SourceString('=='); + bool isUserDefinable() => true; + const EqualsOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0, + // and 1 == 1.0. + NumConstant leftNum = left; + NumConstant rightNum = right; + bool result = leftNum.value == rightNum.value; + return DART_CONSTANT_SYSTEM.createBool(result); + } + if (left.isConstructedObject()) { + // Unless we know that the user-defined object does not implement the + // equality operator we cannot fold here. + return null; + } + return DART_CONSTANT_SYSTEM.createBool(left == right); + } + apply(left, right) => left == right; +} + +class IdentityOperation implements BinaryOperation { + final SourceString name = const SourceString('==='); + bool isUserDefinable() => false; + const IdentityOperation(); + BoolConstant fold(Constant left, Constant right) { + // In order to preserve runtime semantics which says that NaN !== NaN don't + // constant fold NaN === NaN. Otherwise the output depends on inlined + // variables and other optimizations. + if (left.isNaN() && right.isNaN()) return null; + return DART_CONSTANT_SYSTEM.createBool(left == right); + } + apply(left, right) => identical(left, right); +} + +/** + * A constant system implementing the Dart semantics. This system relies on + * the underlying runtime-system. That is, if dart2js is run in an environment + * that doesn't correctly implement Dart's semantics this constant system will + * not return the correct values. + */ +class DartConstantSystem extends ConstantSystem { + const add = const AddOperation(); + const bitAnd = const BitAndOperation(); + const bitNot = const BitNotOperation(); + const bitOr = const BitOrOperation(); + const bitXor = const BitXorOperation(); + const booleanAnd = const BooleanAndOperation(); + const booleanOr = const BooleanOrOperation(); + const divide = const DivideOperation(); + const equal = const EqualsOperation(); + const greaterEqual = const GreaterEqualOperation(); + const greater = const GreaterOperation(); + const identity = const IdentityOperation(); + const lessEqual = const LessEqualOperation(); + const less = const LessOperation(); + const modulo = const ModuloOperation(); + const multiply = const MultiplyOperation(); + const negate = const NegateOperation(); + const not = const NotOperation(); + const shiftLeft = const ShiftLeftOperation(); + const shiftRight = const ShiftRightOperation(); + const subtract = const SubtractOperation(); + const truncatingDivide = const TruncatingDivideOperation(); + + const DartConstantSystem(); + + IntConstant createInt(int i) => new IntConstant(i); + DoubleConstant createDouble(double d) => new DoubleConstant(d); + StringConstant createString(DartString string, Node diagnosticNode) + => new StringConstant(string, diagnosticNode); + BoolConstant createBool(bool value) => new BoolConstant(value); + NullConstant createNull() => new NullConstant(); + + bool isInt(Constant constant) => constant.isInt(); + bool isDouble(Constant constant) => constant.isDouble(); + bool isString(Constant constant) => constant.isString(); + bool isBool(Constant constant) => constant.isBool(); + bool isNull(Constant constant) => constant.isNull(); + + bool isSubtype(Compiler compiler, DartType s, DartType t) { + return compiler.types.isSubtype(s, t); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/constants.dart b/pkgs/markdown/lib/src/compiler/implementation/constants.dart new file mode 100644 index 0000000..ad5dc74 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/constants.dart
@@ -0,0 +1,473 @@ +// 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. + +part of dart2js; + +abstract class ConstantVisitor<R> { + R visitSentinel(SentinelConstant constant); + R visitFunction(FunctionConstant constant); + R visitNull(NullConstant constant); + R visitInt(IntConstant constant); + R visitDouble(DoubleConstant constant); + R visitTrue(TrueConstant constant); + R visitFalse(FalseConstant constant); + R visitString(StringConstant constant); + R visitList(ListConstant constant); + R visitMap(MapConstant constant); + R visitConstructed(ConstructedConstant constant); + R visitType(TypeConstant constant); +} + +abstract class Constant { + const Constant(); + + bool isNull() => false; + bool isBool() => false; + bool isTrue() => false; + bool isFalse() => false; + bool isInt() => false; + bool isDouble() => false; + bool isNum() => false; + bool isString() => false; + bool isList() => false; + bool isMap() => false; + bool isConstructedObject() => false; + bool isFunction() => false; + /** Returns true if the constant is null, a bool, a number or a string. */ + bool isPrimitive() => false; + /** Returns true if the constant is a list, a map or a constructed object. */ + bool isObject() => false; + bool isType() => false; + bool isSentinel() => false; + + bool isNaN() => false; + bool isMinusZero() => false; + + DartType computeType(Compiler compiler); + + List<Constant> getDependencies(); + + accept(ConstantVisitor visitor); +} + +class SentinelConstant extends Constant { + const SentinelConstant(); + static final SENTINEL = const SentinelConstant(); + + List<Constant> getDependencies() => const <Constant>[]; + + // Just use a random value. + int get hashCode => 24297418; + + bool isSentinel() => true; + + accept(ConstantVisitor visitor) => visitor.visitSentinel(this); + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; +} + +class FunctionConstant extends Constant { + Element element; + + FunctionConstant(this.element); + + bool isFunction() => true; + + bool operator ==(var other) { + if (other is !FunctionConstant) return false; + return identical(other.element, element); + } + + String toString() => element.toString(); + List<Constant> getDependencies() => const <Constant>[]; + DartString toDartString() { + return new DartString.literal(element.name.slowToString()); + } + + DartType computeType(Compiler compiler) { + return compiler.functionClass.computeType(compiler); + } + + int get hashCode => (17 * element.hashCode) & 0x7fffffff; + + accept(ConstantVisitor visitor) => visitor.visitFunction(this); +} + +abstract class PrimitiveConstant extends Constant { + get value; + const PrimitiveConstant(); + bool isPrimitive() => true; + + bool operator ==(var other) { + if (other is !PrimitiveConstant) return false; + PrimitiveConstant otherPrimitive = other; + // We use == instead of 'identical' so that DartStrings compare correctly. + return value == otherPrimitive.value; + } + + String toString() => value.toString(); + // Primitive constants don't have dependencies. + List<Constant> getDependencies() => const <Constant>[]; + DartString toDartString(); +} + +class NullConstant extends PrimitiveConstant { + /** The value a Dart null is compiled to in JavaScript. */ + static const String JsNull = "null"; + + factory NullConstant() => const NullConstant._internal(); + const NullConstant._internal(); + bool isNull() => true; + get value => null; + + DartType computeType(Compiler compiler) { + return compiler.nullClass.computeType(compiler); + } + + void _writeJsCode(CodeBuffer buffer, ConstantHandler handler) { + buffer.add(JsNull); + } + + // The magic constant has no meaning. It is just a random value. + int get hashCode => 785965825; + DartString toDartString() => const LiteralDartString("null"); + + accept(ConstantVisitor visitor) => visitor.visitNull(this); +} + +abstract class NumConstant extends PrimitiveConstant { + num get value; + const NumConstant(); + bool isNum() => true; +} + +class IntConstant extends NumConstant { + final int value; + factory IntConstant(int value) { + switch (value) { + case 0: return const IntConstant._internal(0); + case 1: return const IntConstant._internal(1); + case 2: return const IntConstant._internal(2); + case 3: return const IntConstant._internal(3); + case 4: return const IntConstant._internal(4); + case 5: return const IntConstant._internal(5); + case 6: return const IntConstant._internal(6); + case 7: return const IntConstant._internal(7); + case 8: return const IntConstant._internal(8); + case 9: return const IntConstant._internal(9); + case 10: return const IntConstant._internal(10); + case -1: return const IntConstant._internal(-1); + case -2: return const IntConstant._internal(-2); + default: return new IntConstant._internal(value); + } + } + const IntConstant._internal(this.value); + bool isInt() => true; + + DartType computeType(Compiler compiler) { + return compiler.intClass.computeType(compiler); + } + + // We have to override the equality operator so that ints and doubles are + // treated as separate constants. + // The is [:!IntConstant:] check at the beginning of the function makes sure + // that we compare only equal to integer constants. + bool operator ==(var other) { + if (other is !IntConstant) return false; + IntConstant otherInt = other; + return value == otherInt.value; + } + + int get hashCode => value.hashCode; + DartString toDartString() => new DartString.literal(value.toString()); + + accept(ConstantVisitor visitor) => visitor.visitInt(this); +} + +class DoubleConstant extends NumConstant { + final double value; + factory DoubleConstant(double value) { + if (value.isNaN) { + return const DoubleConstant._internal(double.NAN); + } else if (value == double.INFINITY) { + return const DoubleConstant._internal(double.INFINITY); + } else if (value == -double.INFINITY) { + return const DoubleConstant._internal(-double.INFINITY); + } else if (value == 0.0 && !value.isNegative) { + return const DoubleConstant._internal(0.0); + } else if (value == 1.0) { + return const DoubleConstant._internal(1.0); + } else { + return new DoubleConstant._internal(value); + } + } + const DoubleConstant._internal(this.value); + bool isDouble() => true; + bool isNaN() => value.isNaN; + // We need to check for the negative sign since -0.0 == 0.0. + bool isMinusZero() => value == 0.0 && value.isNegative; + + DartType computeType(Compiler compiler) { + return compiler.doubleClass.computeType(compiler); + } + + bool operator ==(var other) { + if (other is !DoubleConstant) return false; + DoubleConstant otherDouble = other; + double otherValue = otherDouble.value; + if (value == 0.0 && otherValue == 0.0) { + return value.isNegative == otherValue.isNegative; + } else if (value.isNaN) { + return otherValue.isNaN; + } else { + return value == otherValue; + } + } + + int get hashCode => value.hashCode; + DartString toDartString() => new DartString.literal(value.toString()); + + accept(ConstantVisitor visitor) => visitor.visitDouble(this); +} + +abstract class BoolConstant extends PrimitiveConstant { + factory BoolConstant(value) { + return value ? new TrueConstant() : new FalseConstant(); + } + const BoolConstant._internal(); + bool isBool() => true; + + DartType computeType(Compiler compiler) { + return compiler.boolClass.computeType(compiler); + } + + BoolConstant negate(); +} + +class TrueConstant extends BoolConstant { + final bool value = true; + + factory TrueConstant() => const TrueConstant._internal(); + const TrueConstant._internal() : super._internal(); + bool isTrue() => true; + + FalseConstant negate() => new FalseConstant(); + + bool operator ==(var other) => identical(this, other); + // The magic constant is just a random value. It does not have any + // significance. + int get hashCode => 499; + DartString toDartString() => const LiteralDartString("true"); + + accept(ConstantVisitor visitor) => visitor.visitTrue(this); +} + +class FalseConstant extends BoolConstant { + final bool value = false; + + factory FalseConstant() => const FalseConstant._internal(); + const FalseConstant._internal() : super._internal(); + bool isFalse() => true; + + TrueConstant negate() => new TrueConstant(); + + bool operator ==(var other) => identical(this, other); + // The magic constant is just a random value. It does not have any + // significance. + int get hashCode => 536555975; + DartString toDartString() => const LiteralDartString("false"); + + accept(ConstantVisitor visitor) => visitor.visitFalse(this); +} + +class StringConstant extends PrimitiveConstant { + final DartString value; + final int hashCode; + final Node node; + + // TODO(floitsch): cache StringConstants. + // TODO(floitsch): compute hashcode without calling toString() on the + // DartString. + StringConstant(DartString value, this.node) + : this.value = value, + this.hashCode = value.slowToString().hashCode; + bool isString() => true; + + DartType computeType(Compiler compiler) { + return compiler.stringClass.computeType(compiler); + } + + bool operator ==(var other) { + if (other is !StringConstant) return false; + StringConstant otherString = other; + return (hashCode == otherString.hashCode) && (value == otherString.value); + } + + DartString toDartString() => value; + int get length => value.length; + + accept(ConstantVisitor visitor) => visitor.visitString(this); +} + +abstract class ObjectConstant extends Constant { + final DartType type; + + ObjectConstant(this.type); + bool isObject() => true; + + DartType computeType(Compiler compiler) => type; +} + +class TypeConstant extends ObjectConstant { + /// The user type that this constant represents. + final DartType representedType; + + TypeConstant(this.representedType, type) : super(type); + + bool isType() => true; + + bool operator ==(other) { + return other is TypeConstant && representedType == other.representedType; + } + + int get hashCode => representedType.hashCode * 13; + + List<Constant> getDependencies() => const <Constant>[]; + + accept(ConstantVisitor visitor) => visitor.visitType(this); +} + +class ListConstant extends ObjectConstant { + final List<Constant> entries; + final int hashCode; + + ListConstant(DartType type, List<Constant> entries) + : this.entries = entries, + hashCode = _computeHash(entries), + super(type); + bool isList() => true; + + static int _computeHash(List<Constant> entries) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant input in entries) hash ^= input.hashCode; + return hash; + } + + bool operator ==(var other) { + if (other is !ListConstant) return false; + ListConstant otherList = other; + if (hashCode != otherList.hashCode) return false; + // TODO(floitsch): verify that the generic types are the same. + if (entries.length != otherList.entries.length) return false; + for (int i = 0; i < entries.length; i++) { + if (entries[i] != otherList.entries[i]) return false; + } + return true; + } + + List<Constant> getDependencies() => entries; + + int get length => entries.length; + + accept(ConstantVisitor visitor) => visitor.visitList(this); +} + +class MapConstant extends ObjectConstant { + /** + * The [PROTO_PROPERTY] must not be used as normal property in any JavaScript + * object. It would change the prototype chain. + */ + static const LiteralDartString PROTO_PROPERTY = + const LiteralDartString("__proto__"); + + /** The dart class implementing constant map literals. */ + static const SourceString DART_CLASS = const SourceString("ConstantMap"); + static const SourceString DART_PROTO_CLASS = + const SourceString("ConstantProtoMap"); + static const SourceString LENGTH_NAME = const SourceString("length"); + static const SourceString JS_OBJECT_NAME = const SourceString("_jsObject"); + static const SourceString KEYS_NAME = const SourceString("_keys"); + static const SourceString PROTO_VALUE = const SourceString("_protoValue"); + + final ListConstant keys; + final List<Constant> values; + final Constant protoValue; + final int hashCode; + + MapConstant(DartType type, this.keys, List<Constant> values, this.protoValue) + : this.values = values, + this.hashCode = computeHash(values), + super(type); + bool isMap() => true; + + static int computeHash(List<Constant> values) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant value in values) hash ^= value.hashCode; + return hash; + } + + bool operator ==(var other) { + if (other is !MapConstant) return false; + MapConstant otherMap = other; + if (hashCode != otherMap.hashCode) return false; + // TODO(floitsch): verify that the generic types are the same. + if (keys != otherMap.keys) return false; + for (int i = 0; i < values.length; i++) { + if (values[i] != otherMap.values[i]) return false; + } + return true; + } + + List<Constant> getDependencies() { + List<Constant> result = <Constant>[keys]; + result.addAll(values); + return result; + } + + int get length => keys.length; + + accept(ConstantVisitor visitor) => visitor.visitMap(this); +} + +class ConstructedConstant extends ObjectConstant { + final List<Constant> fields; + final int hashCode; + + ConstructedConstant(DartType type, List<Constant> fields) + : this.fields = fields, + hashCode = computeHash(type, fields), + super(type) { + assert(type != null); + } + bool isConstructedObject() => true; + + static int computeHash(DartType type, List<Constant> fields) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant field in fields) { + hash ^= field.hashCode; + } + hash ^= type.element.hashCode; + return hash; + } + + bool operator ==(var otherVar) { + if (otherVar is !ConstructedConstant) return false; + ConstructedConstant other = otherVar; + if (hashCode != other.hashCode) return false; + // TODO(floitsch): verify that the (generic) types are the same. + if (type.element != other.type.element) return false; + if (fields.length != other.fields.length) return false; + for (int i = 0; i < fields.length; i++) { + if (fields[i] != other.fields[i]) return false; + } + return true; + } + + List<Constant> getDependencies() => fields; + + accept(ConstantVisitor visitor) => visitor.visitConstructed(this); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart new file mode 100644 index 0000000..ac7d422 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart
@@ -0,0 +1,497 @@ +// 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. + +library dart2js; + +import 'dart:async'; +import 'dart:collection' show Queue, LinkedHashMap; +import 'dart:io'; +import 'dart:uri'; +import 'dart:utf'; + +import '../compiler.dart' as api; +import 'source_file.dart'; +import 'source_file_provider.dart'; +import 'filenames.dart'; +import 'util/uri_extras.dart'; +import '../../libraries.dart'; + +const String LIBRARY_ROOT = '../../../../..'; +const String OUTPUT_LANGUAGE_DART = 'Dart'; + +typedef void HandleOption(String option); + +class OptionHandler { + String pattern; + HandleOption handle; + + OptionHandler(this.pattern, this.handle); +} + +/** + * Extract the parameter of an option. + * + * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters + * are ['fisk.js'] and ['hest.js'], respectively. + */ +String extractParameter(String argument) { + // m[0] is the entire match (which will be equal to argument). m[1] + // is something like "-o" or "--out=", and m[2] is the parameter. + Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument); + if (m == null) helpAndFail('Error: Unknown option "$argument".'); + return m[2]; +} + +String extractPath(String argument) { + String path = nativeToUriPath(extractParameter(argument)); + return path.endsWith("/") ? path : "$path/"; +} + +void parseCommandLine(List<OptionHandler> handlers, List<String> argv) { + // TODO(ahe): Use ../../args/args.dart for parsing options instead. + var patterns = <String>[]; + for (OptionHandler handler in handlers) { + patterns.add(handler.pattern); + } + var pattern = new RegExp('^(${Strings.join(patterns, ")\$|(")})\$'); + OUTER: for (String argument in argv) { + Match match = pattern.firstMatch(argument); + assert(match.groupCount == handlers.length); + for (int i = 0; i < handlers.length; i++) { + if (match[i + 1] != null) { + handlers[i].handle(argument); + continue OUTER; + } + } + throw 'Internal error: "$argument" did not match'; + } +} + +void compile(List<String> argv) { + bool isWindows = (Platform.operatingSystem == 'windows'); + Uri cwd = getCurrentDirectory(); + Uri libraryRoot = cwd; + Uri out = cwd.resolve('out.js'); + Uri sourceMapOut = cwd.resolve('out.js.map'); + Uri packageRoot = null; + List<String> options = new List<String>(); + bool explicitOut = false; + bool wantHelp = false; + String outputLanguage = 'JavaScript'; + bool stripArgumentSet = false; + bool analyzeOnly = false; + SourceFileProvider inputProvider = new SourceFileProvider(); + FormattingDiagnosticHandler diagnosticHandler = + new FormattingDiagnosticHandler(inputProvider); + + passThrough(String argument) => options.add(argument); + + setLibraryRoot(String argument) { + libraryRoot = cwd.resolve(extractPath(argument)); + } + + setPackageRoot(String argument) { + packageRoot = cwd.resolve(extractPath(argument)); + } + + setOutput(String argument) { + explicitOut = true; + out = cwd.resolve(nativeToUriPath(extractParameter(argument))); + sourceMapOut = Uri.parse('$out.map'); + } + + setOutputType(String argument) { + if (argument == '--output-type=dart') { + outputLanguage = OUTPUT_LANGUAGE_DART; + if (!explicitOut) { + out = cwd.resolve('out.dart'); + sourceMapOut = cwd.resolve('out.dart.map'); + } + } + passThrough(argument); + } + + String getDepsOutput(Map<String, SourceFile> sourceFiles) { + var filenames = new List.from(sourceFiles.keys); + filenames.sort(); + return Strings.join(filenames, "\n"); + } + + setStrip(String argument) { + stripArgumentSet = true; + passThrough(argument); + } + + setAnalyzeOnly(String argument) { + analyzeOnly = true; + passThrough(argument); + } + + setCategories(String argument) { + List<String> categories = extractParameter(argument).split(','); + Set<String> allowedCategories = + LIBRARIES.values.map((x) => x.category).toSet(); + allowedCategories.remove('Shared'); + allowedCategories.remove('Internal'); + List<String> allowedCategoriesList = + new List<String>.from(allowedCategories); + allowedCategoriesList.sort(); + if (categories.contains('all')) { + categories = allowedCategoriesList; + } else { + String allowedCategoriesString = + Strings.join(allowedCategoriesList, ', '); + for (String category in categories) { + if (!allowedCategories.contains(category)) { + fail('Error: unsupported library category "$category", ' + 'supported categories are: $allowedCategoriesString'); + } + } + } + return passThrough('--categories=${Strings.join(categories, ",")}'); + } + + handleShortOptions(String argument) { + var shortOptions = argument.substring(1).splitChars(); + for (var shortOption in shortOptions) { + switch (shortOption) { + case 'v': + diagnosticHandler.verbose = true; + break; + case 'h': + case '?': + wantHelp = true; + break; + case 'c': + passThrough('--enable-checked-mode'); + break; + default: + throw 'Internal error: "$shortOption" did not match'; + } + } + } + + List<String> arguments = <String>[]; + List<OptionHandler> handlers = <OptionHandler>[ + new OptionHandler('-[chv?]+', handleShortOptions), + new OptionHandler('--throw-on-error', + (_) => diagnosticHandler.throwOnError = true), + new OptionHandler('--suppress-warnings', + (_) => diagnosticHandler.showWarnings = false), + new OptionHandler('--output-type=dart|--output-type=js', setOutputType), + new OptionHandler('--verbose', (_) => diagnosticHandler.verbose = true), + new OptionHandler('--library-root=.+', setLibraryRoot), + new OptionHandler('--out=.+|-o.+', setOutput), + new OptionHandler('--allow-mock-compilation', passThrough), + new OptionHandler('--minify', passThrough), + new OptionHandler('--force-strip=.*', setStrip), + // TODO(ahe): Remove the --no-colors option. + new OptionHandler('--disable-diagnostic-colors', + (_) => diagnosticHandler.enableColors = false), + new OptionHandler('--enable-diagnostic-colors', + (_) => diagnosticHandler.enableColors = true), + new OptionHandler('--enable[_-]checked[_-]mode|--checked', + (_) => passThrough('--enable-checked-mode')), + new OptionHandler('--enable-concrete-type-inference', + (_) => passThrough('--enable-concrete-type-inference')), + new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true), + new OptionHandler('--package-root=.+|-p.+', setPackageRoot), + new OptionHandler('--disallow-unsafe-eval', passThrough), + new OptionHandler('--analyze-all', passThrough), + new OptionHandler('--analyze-only', setAnalyzeOnly), + new OptionHandler('--disable-native-live-type-analysis', passThrough), + new OptionHandler('--reject-deprecated-language-features', passThrough), + new OptionHandler('--report-sdk-use-of-deprecated-language-features', + passThrough), + new OptionHandler('--categories=.*', setCategories), + + // The following two options must come last. + new OptionHandler('-.*', (String argument) { + helpAndFail('Error: Unknown option "$argument".'); + }), + new OptionHandler('.*', (String argument) { + arguments.add(nativeToUriPath(argument)); + }) + ]; + + parseCommandLine(handlers, argv); + if (wantHelp) helpAndExit(diagnosticHandler.verbose); + + if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) { + helpAndFail('Error: --force-strip may only be used with ' + '--output-type=dart'); + } + if (arguments.isEmpty) { + helpAndFail('Error: No Dart file specified.'); + } + if (arguments.length > 1) { + var extra = arguments.getRange(1, arguments.length - 1); + helpAndFail('Error: Extra arguments: ${Strings.join(extra, " ")}'); + } + + void handler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { + diagnosticHandler.diagnosticHandler(uri, begin, end, message, kind); + } + + Uri uri = cwd.resolve(arguments[0]); + if (packageRoot == null) { + packageRoot = uri.resolve('./packages/'); + } + + diagnosticHandler.info('package root is $packageRoot'); + + int charactersWritten = 0; + + compilationDone(String code) { + if (analyzeOnly) return; + if (code == null) { + fail('Error: Compilation failed.'); + } + writeString(Uri.parse('$out.deps'), + getDepsOutput(inputProvider.sourceFiles)); + diagnosticHandler.info( + 'compiled ${inputProvider.dartCharactersRead} characters Dart ' + '-> $charactersWritten characters $outputLanguage ' + 'in ${relativize(cwd, out, isWindows)}'); + if (!explicitOut) { + String input = uriPathToNative(arguments[0]); + String output = relativize(cwd, out, isWindows); + print('Dart file $input compiled to $outputLanguage: $output'); + } + } + + StreamSink<String> outputProvider(String name, String extension) { + Uri uri; + String sourceMapFileName; + bool isPrimaryOutput = false; + if (name == '') { + if (extension == 'js' || extension == 'dart') { + isPrimaryOutput = true; + uri = out; + sourceMapFileName = + sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1); + } else if (extension == 'js.map' || extension == 'dart.map') { + uri = sourceMapOut; + } else { + fail('Error: Unknown extension: $extension'); + } + } else { + uri = out.resolve('$name.$extension'); + } + + if (uri.scheme != 'file') { + fail('Error: Unhandled scheme ${uri.scheme} in $uri.'); + } + var outputStream = new File(uriPathToNative(uri.path)).openOutputStream(); + + CountingSink sink; + + onDone() { + if (sourceMapFileName != null) { + String sourceMapTag = '//@ sourceMappingURL=$sourceMapFileName\n'; + sink.count += sourceMapTag.length; + outputStream.writeString(sourceMapTag); + } + outputStream.close(); + if (isPrimaryOutput) { + charactersWritten += sink.count; + } + } + + var controller = new StreamController<String>(); + controller.stream.listen(outputStream.writeString, onDone: onDone); + sink = new CountingSink(controller); + return sink; + } + + api.compile(uri, libraryRoot, packageRoot, + inputProvider.readStringFromUri, handler, + options, outputProvider) + .then(compilationDone); +} + +// TODO(ahe): Get rid of this class if http://dartbug.com/8118 is fixed. +class CountingSink implements StreamSink<String> { + final StreamSink<String> sink; + int count = 0; + + CountingSink(this.sink); + + add(String value) { + sink.add(value); + count += value.length; + } + + signalError(AsyncError error) => sink.signalError(error); + + close() => sink.close(); +} + +class AbortLeg { + final message; + AbortLeg(this.message); + toString() => 'Aborted due to --throw-on-error: $message'; +} + +void writeString(Uri uri, String text) { + if (uri.scheme != 'file') { + fail('Error: Unhandled scheme ${uri.scheme}.'); + } + var file = new File(uriPathToNative(uri.path)).openSync(FileMode.WRITE); + file.writeStringSync(text); + file.closeSync(); +} + +void fail(String message) { + print(message); + exit(1); +} + +void compilerMain(Options options) { + var root = uriPathToNative("/$LIBRARY_ROOT"); + List<String> argv = ['--library-root=${options.script}$root']; + argv.addAll(options.arguments); + compile(argv); +} + +void help() { + // This message should be no longer than 20 lines. The default + // terminal size normally 80x24. Two lines are used for the prompts + // before and after running the compiler. Another two lines may be + // used to print an error message. + print(''' +Usage: dart2js [options] dartfile + +Compiles Dart to JavaScript. + +Common options: + -o<file> Generate the output into <file>. + -c Insert runtime type checks and enable assertions (checked mode). + -h Display this message (add -v for information about all options).'''); +} + +void verboseHelp() { + print(''' +Usage: dart2js [options] dartfile + +Compiles Dart to JavaScript. + +Supported options: + -o<file>, --out=<file> + Generate the output into <file>. + + -c, --enable-checked-mode, --checked + Insert runtime type checks and enable assertions (checked mode). + + -h, /h, /?, --help + Display this message (add -v for information about all options). + + -v, --verbose + Display verbose information. + + -p<path>, --package-root=<path> + Where to find packages, that is, "package:..." imports. + + --analyze-all + Analyze all code. Without this option, the compiler only analyzes + code that is reachable from [main]. This option is useful for + finding errors in libraries, but using it can result in bigger and + slower output. + + --analyze-only + Analyze but do not generate code. + + --minify + Generate minified output. + + --suppress-warnings + Do not display any warnings. + + --enable-diagnostic-colors + Add colors to diagnostic messages. + +The following options are only used for compiler development and may +be removed in a future version: + + --output-type=dart + Output Dart code instead of JavaScript. + + --throw-on-error + Throw an exception if a compile-time error is detected. + + --library-root=<directory> + Where to find the Dart platform libraries. + + --allow-mock-compilation + Do not generate a call to main if either of the following + libraries are used: dart:dom, dart:html dart:io. + + --enable-concrete-type-inference + Enable experimental concrete type inference. + + --disable-native-live-type-analysis + Disable the optimization that removes unused native types from dart:html + and related libraries. + + --disallow-unsafe-eval + Disable dynamic generation of code in the generated output. This is + necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/). + This flag is not continuously tested. Please report breakages and we + will fix them as soon as possible. + + --reject-deprecated-language-features + Reject deprecated language features. Without this option, the + compiler will accept language features that are no longer valid + according to The Dart Programming Language Specification, version + 0.12, M1. + + --report-sdk-use-of-deprecated-language-features + Report use of deprecated features in Dart platform libraries. + Without this option, the compiler will silently accept use of + deprecated language features from these libraries. The option + --reject-deprecated-language-features controls if these usages are + reported as errors or warnings. + + --categories=<categories> + + A comma separated list of allowed library categories. The default + is "Client". Possible categories can be seen by providing an + unsupported category, for example, --categories=help. To enable + all categories, use --categories=all. + +'''.trim()); +} + +void helpAndExit(bool verbose) { + if (verbose) { + verboseHelp(); + } else { + help(); + } + exit(0); +} + +void helpAndFail(String message) { + help(); + print(''); + fail(message); +} + +void main() { + try { + compilerMain(new Options()); + } catch (exception, trace) { + try { + print('Internal error: $exception'); + } catch (ignored) { + print('Internal error: error while printing exception'); + } + try { + print(trace); + } finally { + exit(253); // 253 is recognized as a crash by our test scripts. + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot new file mode 100644 index 0000000..758a05a --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot Binary files differ
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart b/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart new file mode 100644 index 0000000..9b683f2 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart
@@ -0,0 +1,61 @@ +// 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. + +library dart2js; + +import 'dart:async'; +import 'dart:uri'; +import 'dart:collection' show Queue, LinkedHashMap; + +import 'closure.dart' as closureMapping; +import 'dart_backend/dart_backend.dart' as dart_backend; +import 'dart_types.dart'; +import 'elements/elements.dart'; +import 'elements/modelx.dart' + show ErroneousElementX, + CompilationUnitElementX, + LibraryElementX, + PrefixElementX, + VoidElementX; +import 'js_backend/js_backend.dart' as js_backend; +import 'native_handler.dart' as native; +import 'scanner/scanner_implementation.dart'; +import 'scanner/scannerlib.dart'; +import 'ssa/ssa.dart'; +import 'string_validator.dart'; +import 'source_file.dart'; +import 'tree/tree.dart'; +import 'universe/universe.dart'; +import 'util/characters.dart'; +import 'util/util.dart'; +import '../compiler.dart' as api; +import 'patch_parser.dart'; +import 'types/types.dart' as ti; +import 'resolution/resolution.dart'; +import 'js/js.dart' as js; + +export 'resolution/resolution.dart' show TreeElements, TreeElementMapping; +export 'scanner/scannerlib.dart' show SourceString, + isUserDefinableOperator, + isUnaryOperator, + isBinaryOperator, + isTernaryOperator, + isMinusOperator; +export 'universe/universe.dart' show Selector; + +part 'code_buffer.dart'; +part 'compile_time_constants.dart'; +part 'compiler.dart'; +part 'constants.dart'; +part 'constant_system.dart'; +part 'constant_system_dart.dart'; +part 'diagnostic_listener.dart'; +part 'enqueue.dart'; +part 'library_loader.dart'; +part 'resolved_visitor.dart'; +part 'script.dart'; +part 'tree_validator.dart'; +part 'typechecker.dart'; +part 'warnings.dart'; +part 'world.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart new file mode 100644 index 0000000..6a3b000 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart
@@ -0,0 +1,593 @@ +// 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. + +part of dart_backend; + +// TODO(ahe): This class is simply wrong. This backend should use +// elements when it can, not AST nodes. Perhaps a [Map<Element, +// TreeElements>] is what is needed. +class ElementAst { + final Node ast; + final TreeElements treeElements; + + ElementAst(this.ast, this.treeElements); + + factory ElementAst.rewrite(compiler, ast, treeElements, stripAsserts) { + final rewriter = + new FunctionBodyRewriter(compiler, treeElements, stripAsserts); + return new ElementAst(rewriter.visit(ast), rewriter.cloneTreeElements); + } + + ElementAst.forClassLike(this.ast) + : this.treeElements = new TreeElementMapping(null); +} + +// TODO(ahe): This class should not subclass [TreeElementMapping], if +// anything, it should implement TreeElements. +class AggregatedTreeElements extends TreeElementMapping { + final List<TreeElements> treeElements; + + AggregatedTreeElements() : treeElements = <TreeElements>[], super(null); + + Element operator[](Node node) { + final result = super[node]; + return result != null ? result : getFirstNotNullResult((e) => e[node]); + } + + Selector getSelector(Send send) { + final result = super.getSelector(send); + return result != null ? + result : getFirstNotNullResult((e) => e.getSelector(send)); + } + + DartType getType(Node node) { + final result = super.getType(node); + return result != null ? + result : getFirstNotNullResult((e) => e.getType(node)); + } + + getFirstNotNullResult(f(TreeElements element)) { + for (final element in treeElements) { + final result = f(element); + if (result != null) return result; + } + + return null; + } +} + +class VariableListAst extends ElementAst { + VariableListAst(ast) : super(ast, new AggregatedTreeElements()); + + add(VariableElement element, TreeElements treeElements) { + AggregatedTreeElements e = this.treeElements; + e[element.cachedNode] = element; + e.treeElements.add(treeElements); + } +} + +class FunctionBodyRewriter extends CloningVisitor { + final Compiler compiler; + final bool stripAsserts; + + FunctionBodyRewriter(this.compiler, originalTreeElements, this.stripAsserts) + : super(originalTreeElements); + + visitBlock(Block block) { + shouldOmit(Statement statement) { + if (statement is EmptyStatement) return true; + if (statement is ExpressionStatement) { + Send send = statement.expression.asSend(); + if (send != null) { + Element element = originalTreeElements[send]; + if (stripAsserts && identical(element, compiler.assertMethod)) { + return true; + } + } + } + return false; + } + + rewriteStatement(Statement statement) { + if (statement is Block) { + Link statements = statement.statements.nodes; + if (!statements.isEmpty && statements.tail.isEmpty) { + Statement single = statements.head; + bool isDeclaration = + single is VariableDefinitions || single is FunctionDeclaration; + if (!isDeclaration) return single; + } + } + return statement; + } + + NodeList statements = block.statements; + LinkBuilder<Statement> builder = new LinkBuilder<Statement>(); + for (Statement statement in statements.nodes) { + if (!shouldOmit(statement)) { + builder.addLast(visit(rewriteStatement(statement))); + } + } + return new Block(rewriteNodeList(statements, builder.toLink())); + } +} + +class DartBackend extends Backend { + final List<CompilerTask> tasks; + final bool forceStripTypes; + final bool stripAsserts; + // TODO(antonm): make available from command-line options. + final bool outputAst = false; + + Map<Element, TreeElements> get resolvedElements => + compiler.enqueuer.resolution.resolvedElements; + + /** + * Tells whether it is safe to remove type declarations from variables, + * functions parameters. It becomes not safe if: + * 1) TypeError is used somewhere in the code, + * 2) The code has typedefs in right hand side of IS checks, + * 3) The code has classes which extend typedefs, have type arguments typedefs + * or type variable bounds typedefs. + * These restrictions can be less strict. + */ + bool isSafeToRemoveTypeDeclarations( + Map<ClassElement, Set<Element>> classMembers) { + Set<DartType> processedTypes = new Set<DartType>(); + List<DartType> workQueue = new List<DartType>(); + workQueue.addAll( + classMembers.keys.map((classElement) => classElement.thisType)); + workQueue.addAll(compiler.resolverWorld.isChecks); + Element typeErrorElement = + compiler.coreLibrary.find(new SourceString('TypeError')); + DartType typeErrorType = typeErrorElement.computeType(compiler); + if (workQueue.indexOf(typeErrorType) != -1) { + return false; + } + + void processTypeArguments(Element classElement, NodeList typeArguments) { + if (typeArguments == null) return; + for (Node typeArgument in typeArguments.nodes) { + if (typeArgument is TypeVariable) { + typeArgument = typeArgument.bound; + } + if (typeArgument == null) continue; + assert(typeArgument is TypeAnnotation); + DartType argumentType = + compiler.resolveTypeAnnotation(classElement, typeArgument); + assert(argumentType != null); + workQueue.add(argumentType); + } + } + + void processTypeAnnotationList(Element classElement, NodeList annotations) { + for (Link link = annotations.nodes; !link.isEmpty; link = link.tail) { + TypeAnnotation typeAnnotation = link.head; + NodeList typeArguments = typeAnnotation.typeArguments; + processTypeArguments(classElement, typeArguments); + } + } + + void processSuperclassTypeArguments(Element classElement, Node superclass) { + if (superclass == null) return; + MixinApplication superMixinApplication = superclass.asMixinApplication(); + if (superMixinApplication != null) { + processTypeAnnotationList(classElement, superMixinApplication.mixins); + } else { + TypeAnnotation typeAnnotation = superclass; + NodeList typeArguments = typeAnnotation.typeArguments; + processTypeArguments(classElement, typeArguments); + } + } + + while (!workQueue.isEmpty) { + DartType type = workQueue.removeLast(); + if (processedTypes.contains(type)) continue; + processedTypes.add(type); + if (type is TypedefType) return false; + if (type is InterfaceType) { + ClassElement element = type.element; + Node node = element.parseNode(compiler); + if (node is ClassNode) { + ClassNode classNode = node; + processTypeArguments(element, classNode.typeParameters); + processSuperclassTypeArguments(element, classNode.superclass); + processTypeAnnotationList(element, classNode.interfaces); + } else { + MixinApplication mixinNode = node; + processSuperclassTypeArguments(element, mixinNode.superclass); + if (mixinNode is NamedMixinApplication) { + NamedMixinApplication namedMixinNode = mixinNode; + processTypeArguments(element, namedMixinNode.typeParameters); + } + } + // Check all supertypes. + if (element.allSupertypes != null) { + workQueue.addAll(element.allSupertypes.toList()); + } + } + } + return true; + } + + DartBackend(Compiler compiler, List<String> strips) + : tasks = <CompilerTask>[], + forceStripTypes = strips.indexOf('types') != -1, + stripAsserts = strips.indexOf('asserts') != -1, + super(compiler); + + void enqueueHelpers(ResolutionEnqueuer world) { + // Right now resolver doesn't always resolve interfaces needed + // for literals, so force them. TODO(antonm): fix in the resolver. + final LITERAL_TYPE_NAMES = const [ + 'Map', 'List', 'num', 'int', 'double', 'bool' + ]; + final coreLibrary = compiler.coreLibrary; + for (final name in LITERAL_TYPE_NAMES) { + ClassElement classElement = coreLibrary.findLocal(new SourceString(name)); + classElement.ensureResolved(compiler); + } + } + void codegen(CodegenWorkItem work) { } + void processNativeClasses(Enqueuer world, + Iterable<LibraryElement> libraries) { } + + bool isUserLibrary(LibraryElement lib) { + final INTERNAL_HELPERS = [ + compiler.jsHelperLibrary, + compiler.interceptorsLibrary, + ]; + return INTERNAL_HELPERS.indexOf(lib) == -1 && !lib.isPlatformLibrary; + } + + void assembleProgram() { + // Conservatively traverse all platform libraries and collect member names. + // TODO(antonm): ideally we should only collect names of used members, + // however as of today there are problems with names of some core library + // interfaces, most probably for interfaces of literals. + final fixedMemberNames = new Set<String>(); + for (final library in compiler.libraries.values) { + if (!library.isPlatformLibrary) continue; + library.implementation.forEachLocalMember((Element element) { + if (element.isClass()) { + ClassElement classElement = element; + // Make sure we parsed the class to initialize its local members. + // TODO(smok): Figure out if there is a better way to fill local + // members. + element.parseNode(compiler); + classElement.forEachLocalMember((member) { + final name = member.name.slowToString(); + // Skip operator names. + if (!name.startsWith(r'operator$')) { + // Fetch name of named constructors and factories if any, + // otherwise store regular name. + // TODO(antonm): better way to analyze the name. + fixedMemberNames.add(name.split(r'$').last); + } + }); + } + // Even class names are added due to a delicate problem we have: + // if one imports dart:core with a prefix, we cannot tell prefix.name + // from dynamic invocation (alas!). So we'd better err on preserving + // those names. + fixedMemberNames.add(element.name.slowToString()); + }); + } + // The VM will automatically invoke the call method of objects + // that are invoked as functions. Make sure to not rename that. + fixedMemberNames.add('call'); + // TODO(antonm): TypeError.srcType and TypeError.dstType are defined in + // runtime/lib/error.dart. Overall, all DartVM specific libs should be + // accounted for. + fixedMemberNames.add('srcType'); + fixedMemberNames.add('dstType'); + + /** + * Tells whether we should output given element. Corelib classes like + * Object should not be in the resulting code. + */ + bool shouldOutput(Element element) { + return !identical(element.kind, ElementKind.VOID) + && isUserLibrary(element.getLibrary()) + && !element.isSynthesized + && element is !AbstractFieldElement; + } + + final elementAsts = new Map<Element, ElementAst>(); + + parse(element) => element.parseNode(compiler); + + Set<Element> topLevelElements = new Set<Element>(); + Map<ClassElement, Set<Element>> classMembers = + new Map<ClassElement, Set<Element>>(); + + // Build all top level elements to emit and necessary class members. + var newTypedefElementCallback, newClassElementCallback; + + processElement(element, elementAst) { + new ReferencedElementCollector( + compiler, + element, elementAst.treeElements, + newTypedefElementCallback, newClassElementCallback).collect(); + elementAsts[element] = elementAst; + } + + addTopLevel(element, elementAst) { + if (topLevelElements.contains(element)) return; + topLevelElements.add(element); + processElement(element, elementAst); + } + + addClass(classElement) { + addTopLevel(classElement, + new ElementAst.forClassLike(parse(classElement))); + classMembers.putIfAbsent(classElement, () => new Set()); + } + + newTypedefElementCallback = (TypedefElement element) { + if (!shouldOutput(element)) return; + addTopLevel(element, + new ElementAst.forClassLike(parse(element))); + }; + newClassElementCallback = (ClassElement classElement) { + if (!shouldOutput(classElement)) return; + addClass(classElement); + }; + + compiler.resolverWorld.instantiatedClasses.forEach( + (ClassElement classElement) { + if (shouldOutput(classElement)) addClass(classElement); + }); + resolvedElements.forEach((element, treeElements) { + if (!shouldOutput(element) || treeElements == null) return; + var elementAst = new ElementAst.rewrite( + compiler, parse(element), treeElements, stripAsserts); + if (element.isField()) { + final list = (element as VariableElement).variables; + elementAst = elementAsts.putIfAbsent( + list, () => new VariableListAst(parse(list))); + (elementAst as VariableListAst).add(element, treeElements); + element = list; + } + + if (element.isMember()) { + ClassElement enclosingClass = element.getEnclosingClass(); + assert(enclosingClass.isClass()); + assert(enclosingClass.isTopLevel()); + assert(shouldOutput(enclosingClass)); + addClass(enclosingClass); + classMembers[enclosingClass].add(element); + processElement(element, elementAst); + } else { + if (!element.isTopLevel()) { + compiler.cancel('Cannot process $element', element: element); + } + addTopLevel(element, elementAst); + } + }); + + // Add synthesized constructors to classes with no resolved constructors, + // but which originally had any constructor. That should prevent + // those classes from being instantiable with default constructor. + Identifier synthesizedIdentifier = + new Identifier(new StringToken(IDENTIFIER_INFO, '', -1)); + + NextClassElement: + for (ClassElement classElement in classMembers.keys) { + for (Element member in classMembers[classElement]) { + if (member.isConstructor()) continue NextClassElement; + } + if (classElement.constructors.isEmpty) continue NextClassElement; + + // TODO(antonm): check with AAR team if there is better approach. + // As an idea: provide template as a Dart code---class C { C.name(); }--- + // and then overwrite necessary parts. + ClassNode classNode = classElement.parseNode(compiler); + SynthesizedConstructorElementX constructor = + new SynthesizedConstructorElementX(classElement); + constructor.type = new FunctionType( + constructor, + compiler.types.voidType, + const Link<DartType>(), + const Link<DartType>(), + const Link<SourceString>(), + const Link<DartType>() + ); + constructor.cachedNode = new FunctionExpression( + new Send(classNode.name, synthesizedIdentifier), + new NodeList(new StringToken(OPEN_PAREN_INFO, '(', -1), + const Link<Node>(), + new StringToken(CLOSE_PAREN_INFO, ')', -1)), + new EmptyStatement(new StringToken(SEMICOLON_INFO, ';', -1)), + null, Modifiers.EMPTY, null, null); + + classMembers[classElement].add(constructor); + elementAsts[constructor] = + new ElementAst(constructor.cachedNode, new TreeElementMapping(null)); + } + + // Create all necessary placeholders. + PlaceholderCollector collector = + new PlaceholderCollector(compiler, fixedMemberNames, elementAsts); + // Add synthesizedIdentifier to set of unresolved names to rename it to + // some unused identifier. + collector.unresolvedNodes.add(synthesizedIdentifier); + makePlaceholders(element) { + collector.collect(element); + if (element.isClass()) { + classMembers[element].forEach(makePlaceholders); + } + } + topLevelElements.forEach(makePlaceholders); + // Create renames. + Map<Node, String> renames = new Map<Node, String>(); + Map<LibraryElement, String> imports = new Map<LibraryElement, String>(); + bool shouldCutDeclarationTypes = forceStripTypes + || (compiler.enableMinification + && isSafeToRemoveTypeDeclarations(classMembers)); + renamePlaceholders( + compiler, collector, renames, imports, + fixedMemberNames, shouldCutDeclarationTypes); + + // Sort elements. + final sortedTopLevels = sortElements(topLevelElements); + final sortedClassMembers = new Map<ClassElement, List<Element>>(); + classMembers.forEach((classElement, members) { + sortedClassMembers[classElement] = sortElements(members); + }); + + if (outputAst) { + // TODO(antonm): Ideally XML should be a separate backend. + // TODO(antonm): obey renames and minification, at least as an option. + StringBuffer sb = new StringBuffer(); + outputElement(element) { sb.add(parse(element).toDebugString()); } + + // Emit XML for AST instead of the program. + for (final topLevel in sortedTopLevels) { + if (topLevel.isClass()) { + // TODO(antonm): add some class info. + sortedClassMembers[topLevel].forEach(outputElement); + } else { + outputElement(topLevel); + } + } + compiler.assembledCode = '<Program>\n$sb</Program>\n'; + return; + } + + final topLevelNodes = <Node>[]; + final memberNodes = new Map<ClassNode, List<Node>>(); + for (final element in sortedTopLevels) { + topLevelNodes.add(elementAsts[element].ast); + if (element.isClass() && !element.isMixinApplication) { + final members = <Node>[]; + for (final member in sortedClassMembers[element]) { + members.add(elementAsts[member].ast); + } + memberNodes[elementAsts[element].ast] = members; + } + } + + final unparser = new EmitterUnparser(renames); + emitCode(unparser, imports, topLevelNodes, memberNodes); + compiler.assembledCode = unparser.result; + + // Output verbose info about size ratio of resulting bundle to all + // referenced non-platform sources. + logResultBundleSizeInfo(topLevelElements); + } + + void logResultBundleSizeInfo(Set<Element> topLevelElements) { + Iterable<LibraryElement> referencedLibraries = + compiler.libraries.values.where(isUserLibrary); + // Sum total size of scripts in each referenced library. + int nonPlatformSize = 0; + for (LibraryElement lib in referencedLibraries) { + for (CompilationUnitElement compilationUnit in lib.compilationUnits) { + nonPlatformSize += compilationUnit.script.text.length; + } + } + int percentage = compiler.assembledCode.length * 100 ~/ nonPlatformSize; + log('Total used non-platform files size: ${nonPlatformSize} bytes, ' + 'bundle size: ${compiler.assembledCode.length} bytes (${percentage}%)'); + } + + log(String message) => compiler.log('[DartBackend] $message'); +} + +class EmitterUnparser extends Unparser { + final Map<Node, String> renames; + + EmitterUnparser(this.renames); + + visit(Node node) { + if (node != null && renames.containsKey(node)) { + sb.add(renames[node]); + } else { + super.visit(node); + } + } + + unparseSendReceiver(Send node, {bool spacesNeeded: false}) { + // TODO(smok): Remove ugly hack for library prefices. + if (node.receiver != null && renames[node.receiver] == '') return; + super.unparseSendReceiver(node, spacesNeeded: spacesNeeded); + } + + unparseFunctionName(Node name) { + if (name != null && renames.containsKey(name)) { + sb.add(renames[name]); + } else { + super.unparseFunctionName(name); + } + } +} + + +/** + * Some elements are not recorded by resolver now, + * for example, typedefs or classes which are only + * used in signatures, as/is operators or in super clauses + * (just to name a few). Retraverse AST to pick those up. + */ +class ReferencedElementCollector extends Visitor { + final Compiler compiler; + final Element rootElement; + final TreeElements treeElements; + final newTypedefElementCallback; + final newClassElementCallback; + + ReferencedElementCollector( + this.compiler, + Element rootElement, this.treeElements, + this.newTypedefElementCallback, this.newClassElementCallback) + : this.rootElement = (rootElement is VariableElement) + ? (rootElement as VariableElement).variables : rootElement; + + visitClassNode(ClassNode node) { + super.visitClassNode(node); + // Temporary hack which should go away once interfaces + // and default clauses are out. + if (node.defaultClause != null) { + // Resolver cannot resolve parameterized default clauses. + TypeAnnotation evilCousine = new TypeAnnotation( + node.defaultClause.typeName, null); + evilCousine.accept(this); + } + } + + visitNode(Node node) { node.visitChildren(this); } + + visitTypeAnnotation(TypeAnnotation typeAnnotation) { + // We call [resolveReturnType] to allow having 'void'. + final type = compiler.resolveReturnType(rootElement, typeAnnotation); + Element typeElement = type.element; + if (typeElement.isTypedef()) newTypedefElementCallback(typeElement); + if (typeElement.isClass()) newClassElementCallback(typeElement); + typeAnnotation.visitChildren(this); + } + + void collect() { + compiler.withCurrentElement(rootElement, () { + rootElement.parseNode(compiler).accept(this); + }); + } +} + +compareBy(f) => (x, y) => f(x).compareTo(f(y)); + +List sorted(Iterable l, comparison) { + final result = new List.from(l); + result.sort(comparison); + return result; +} + +compareElements(e0, e1) { + int result = compareBy((e) => e.getLibrary().canonicalUri.toString())(e0, e1); + if (result != 0) return result; + return compareBy((e) => e.position().charOffset)(e0, e1); +} + +List<Element> sortElements(Iterable<Element> elements) => + sorted(elements, compareElements);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart new file mode 100644 index 0000000..8a8abe4 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart
@@ -0,0 +1,25 @@ +// 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. + +library dart_backend; + +import '../elements/elements.dart'; +import '../elements/modelx.dart' show SynthesizedConstructorElementX; +import '../dart2jslib.dart'; +import '../dart_types.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; + +import '../scanner/scannerlib.dart' show StringToken, + Keyword, + OPEN_PAREN_INFO, + CLOSE_PAREN_INFO, + SEMICOLON_INFO, + IDENTIFIER_INFO; + +part 'backend.dart'; +part 'emitter.dart'; +part 'renamer.dart'; +part 'placeholder_collector.dart'; +part 'utils.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart new file mode 100644 index 0000000..c074af4 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart
@@ -0,0 +1,24 @@ +// 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. + +part of dart_backend; + +String emitCode( + Unparser unparser, + Map<LibraryElement, String> imports, + Collection<Node> topLevelNodes, + Map<ClassNode, Collection<Node>> classMembers) { + imports.forEach((libraryElement, prefix) { + unparser.unparseImportTag('${libraryElement.canonicalUri}', prefix); + }); + + for (final node in topLevelNodes) { + if (node is ClassNode) { + // TODO(smok): Filter out default constructors here. + unparser.unparseClassWithBody(node, classMembers[node]); + } else { + unparser.unparse(node); + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart new file mode 100644 index 0000000..d184663 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
@@ -0,0 +1,626 @@ +// 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. + +part of dart_backend; + +class LocalPlaceholder { + final String identifier; + final Set<Node> nodes; + LocalPlaceholder(this.identifier) : nodes = new Set<Node>(); + int get hashCode => identifier.hashCode; + String toString() => + 'local_placeholder[id($identifier), nodes($nodes)]'; +} + +class FunctionScope { + final Set<String> parameterIdentifiers; + final Set<LocalPlaceholder> localPlaceholders; + FunctionScope() + : parameterIdentifiers = new Set<String>(), + localPlaceholders = new Set<LocalPlaceholder>(); + void registerParameter(Identifier node) { + parameterIdentifiers.add(node.source.slowToString()); + } +} + +class ConstructorPlaceholder { + final Node node; + final DartType type; + final bool isRedirectingCall; + ConstructorPlaceholder(this.node, this.type) + : this.isRedirectingCall = false; + // Note: factory redirection is not redirecting call! + ConstructorPlaceholder.redirectingCall(this.node) + : this.type = null, this.isRedirectingCall = true; +} + +class DeclarationTypePlaceholder { + final TypeAnnotation typeNode; + final bool requiresVar; + DeclarationTypePlaceholder(this.typeNode, this.requiresVar); +} + +class SendVisitor extends ResolvedVisitor { + final PlaceholderCollector collector; + + get compiler => collector.compiler; + + SendVisitor(this.collector, TreeElements elements) : super(elements); + + visitOperatorSend(Send node) {} + visitForeignSend(Send node) {} + + visitSuperSend(Send node) { + Element element = elements[node]; + if (element != null && element.isConstructor()) { + collector.makeRedirectingConstructorPlaceholder(node.selector, element); + } else { + collector.tryMakeMemberPlaceholder(node.selector); + } + } + + visitDynamicSend(Send node) { + final element = elements[node]; + if (element == null || !element.isErroneous()) { + collector.tryMakeMemberPlaceholder(node.selector); + } + } + + visitClosureSend(Send node) { + final element = elements[node]; + if (element != null) { + collector.tryMakeLocalPlaceholder(element, node.selector); + } + } + + visitGetterSend(Send node) { + final element = elements[node]; + // element == null means dynamic property access. + if (element == null) { + collector.tryMakeMemberPlaceholder(node.selector); + } else if (element.isErroneous()) { + return; + } else if (element.isPrefix()) { + // Node is prefix part in case of source 'lib.somesetter = 5;' + collector.makeNullPlaceholder(node); + } else if (Elements.isStaticOrTopLevel(element)) { + // Unqualified or prefixed top level or static. + collector.makeElementPlaceholder(node.selector, element); + } else if (!element.isTopLevel()) { + if (element.isInstanceMember()) { + collector.tryMakeMemberPlaceholder(node.selector); + } else { + // May get FunctionExpression here in selector + // in case of A(int this.f()); + if (node.selector is Identifier) { + collector.tryMakeLocalPlaceholder(element, node.selector); + } else { + assert(node.selector is FunctionExpression); + } + } + } + } + + visitStaticSend(Send node) { + final element = elements[node]; + if (Elements.isUnresolved(element) + || identical(element, compiler.assertMethod)) { + return; + } + if (element.isConstructor() || element.isFactoryConstructor()) { + // Rename named constructor in redirection position: + // class C { C.named(); C.redirecting() : this.named(); } + if (node.receiver is Identifier + && node.receiver.asIdentifier().isThis()) { + assert(node.selector is Identifier); + collector.makeRedirectingConstructorPlaceholder(node.selector, element); + } + return; + } + collector.makeElementPlaceholder(node.selector, element); + // Another ugly case: <lib prefix>.<top level> is represented as + // receiver: lib prefix, selector: top level. + if (element.isTopLevel() && node.receiver != null) { + assert(elements[node.receiver].isPrefix()); + // Hack: putting null into map overrides receiver of original node. + collector.makeNullPlaceholder(node.receiver); + } + } + + internalError(String reason, {Node node}) { + collector.internalError(reason, node: node); + } + + visitTypeReferenceSend(Send node) { + collector.makeElementPlaceholder(node.selector, elements[node]); + } +} + +class PlaceholderCollector extends Visitor { + final Compiler compiler; + final Set<String> fixedMemberNames; // member names which cannot be renamed. + final Map<Element, ElementAst> elementAsts; + final Set<Node> nullNodes; // Nodes that should not be in output. + final Set<Node> unresolvedNodes; + final Map<Element, Set<Node>> elementNodes; + final Map<FunctionElement, FunctionScope> functionScopes; + final Map<LibraryElement, Set<Identifier>> privateNodes; + final List<DeclarationTypePlaceholder> declarationTypePlaceholders; + final Map<String, Set<Identifier>> memberPlaceholders; + final Map<Element, List<ConstructorPlaceholder>> constructorPlaceholders; + Map<String, LocalPlaceholder> currentLocalPlaceholders; + Element currentElement; + FunctionElement topmostEnclosingFunction; + TreeElements treeElements; + + LibraryElement get coreLibrary => compiler.coreLibrary; + FunctionElement get entryFunction => compiler.mainApp.find(Compiler.MAIN); + + get currentFunctionScope => functionScopes.putIfAbsent( + topmostEnclosingFunction, () => new FunctionScope()); + + PlaceholderCollector(this.compiler, this.fixedMemberNames, this.elementAsts) : + nullNodes = new Set<Node>(), + unresolvedNodes = new Set<Node>(), + elementNodes = new Map<Element, Set<Node>>(), + functionScopes = new Map<FunctionElement, FunctionScope>(), + privateNodes = new Map<LibraryElement, Set<Identifier>>(), + declarationTypePlaceholders = new List<DeclarationTypePlaceholder>(), + memberPlaceholders = new Map<String, Set<Identifier>>(), + constructorPlaceholders = + new Map<Element, List<ConstructorPlaceholder>>(); + + void collectFunctionDeclarationPlaceholders( + FunctionElement element, FunctionExpression node) { + if (element.isGenerativeConstructor() || element.isFactoryConstructor()) { + DartType type = element.getEnclosingClass().thisType.asRaw(); + makeConstructorPlaceholder(node.name, element, type); + Return bodyAsReturn = node.body.asReturn(); + if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) { + // Factory redirection. + FunctionElement redirectTarget = element.defaultImplementation; + assert(redirectTarget != null && redirectTarget != element); + type = redirectTarget.getEnclosingClass().thisType.asRaw(); + makeConstructorPlaceholder( + bodyAsReturn.expression, redirectTarget, type); + } + } else if (Elements.isStaticOrTopLevel(element)) { + // Note: this code should only rename private identifiers for class' + // fields/getters/setters/methods. Top-level identifiers are renamed + // just to escape conflicts and that should be enough as we shouldn't + // be able to resolve private identifiers for other libraries. + makeElementPlaceholder(node.name, element); + } else if (element.isMember()) { + if (node.name is Identifier) { + tryMakeMemberPlaceholder(node.name); + } else { + assert(node.name.asSend().isOperator); + } + } + } + + void collectFieldDeclarationPlaceholders(Element element, Node node) { + Identifier name = node is Identifier ? node : node.asSend().selector; + if (Elements.isStaticOrTopLevel(element)) { + makeElementPlaceholder(name, element); + } else if (Elements.isInstanceField(element)) { + tryMakeMemberPlaceholder(name); + } + } + + void collect(Element element) { + this.currentElement = element; + this.topmostEnclosingFunction = null; + final ElementAst elementAst = elementAsts[element]; + this.treeElements = elementAst.treeElements; + Node elementNode = elementAst.ast; + if (element is FunctionElement) { + collectFunctionDeclarationPlaceholders(element, elementNode); + } else if (element is VariableListElement) { + VariableDefinitions definitions = elementNode; + for (Node definition in definitions.definitions) { + final definitionElement = treeElements[definition]; + // definitionElement == null if variable is actually unused. + if (definitionElement == null) continue; + collectFieldDeclarationPlaceholders(definitionElement, definition); + } + makeVarDeclarationTypePlaceholder(definitions); + } else { + assert(element is ClassElement || element is TypedefElement); + } + currentLocalPlaceholders = new Map<String, LocalPlaceholder>(); + compiler.withCurrentElement(element, () { + elementNode.accept(this); + }); + } + + void tryMakeLocalPlaceholder(Element element, Identifier node) { + bool isOptionalParameter() { + FunctionElement function = element.enclosingElement; + for (Element parameter in function.functionSignature.optionalParameters) { + if (identical(parameter, element)) return true; + } + return false; + } + + // TODO(smok): Maybe we should rename privates as well, their privacy + // should not matter if they are local vars. + if (node.source.isPrivate()) return; + if (element.isParameter() && isOptionalParameter()) { + currentFunctionScope.registerParameter(node); + } else if (Elements.isLocal(element)) { + makeLocalPlaceholder(node); + } + } + + void tryMakeMemberPlaceholder(Identifier node) { + assert(node != null); + if (node.source.isPrivate()) return; + if (node is Operator) return; + final identifier = node.source.slowToString(); + if (fixedMemberNames.contains(identifier)) return; + memberPlaceholders.putIfAbsent( + identifier, () => new Set<Identifier>()).add(node); + } + + void makeTypePlaceholder(Node node, DartType type) { + if (node is Send) { + // Prefix. + assert(node.receiver is Identifier); + assert(node.selector is Identifier); + makeNullPlaceholder(node.receiver); + node = node.selector; + } + makeElementPlaceholder(node, type.element); + } + + void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) { + if (type == null) return; + declarationTypePlaceholders.add( + new DeclarationTypePlaceholder(type, false)); + } + + void makeVarDeclarationTypePlaceholder(VariableDefinitions node) { + // TODO(smok): Maybe instead of calling this method and + // makeDeclaratioTypePlaceholder have type declaration placeholder + // collector logic in visitVariableDefinitions when resolver becomes better + // and/or catch syntax changes. + if (node.type == null) return; + Element definitionElement = treeElements[node.definitions.nodes.head]; + bool requiresVar = !node.modifiers.isFinalOrConst(); + declarationTypePlaceholders.add( + new DeclarationTypePlaceholder(node.type, requiresVar)); + } + + void makeNullPlaceholder(Node node) { + assert(node is Identifier || node is Send); + nullNodes.add(node); + } + + void makeElementPlaceholder(Node node, Element element) { + assert(element != null); + if (identical(element, entryFunction)) return; + if (identical(element.getLibrary(), coreLibrary)) return; + if (element.getLibrary().isPlatformLibrary && !element.isTopLevel()) { + return; + } + if (element == compiler.types.dynamicType.element) { + internalError( + 'Should never make element placeholder for dynamic type element', + node: node); + } + elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node); + } + + void makePrivateIdentifier(Identifier node) { + assert(node != null); + privateNodes.putIfAbsent( + currentElement.getLibrary(), () => new Set<Identifier>()).add(node); + } + + void makeUnresolvedPlaceholder(Node node) { + unresolvedNodes.add(node); + } + + void makeLocalPlaceholder(Identifier identifier) { + LocalPlaceholder getLocalPlaceholder() { + String name = identifier.source.slowToString(); + return currentLocalPlaceholders.putIfAbsent(name, () { + LocalPlaceholder localPlaceholder = new LocalPlaceholder(name); + currentFunctionScope.localPlaceholders.add(localPlaceholder); + return localPlaceholder; + }); + } + + getLocalPlaceholder().nodes.add(identifier); + } + + void makeConstructorPlaceholder(Node node, Element element, DartType type) { + assert(type != null); + constructorPlaceholders + .putIfAbsent(element, () => <ConstructorPlaceholder>[]) + .add(new ConstructorPlaceholder(node, type)); + } + void makeRedirectingConstructorPlaceholder(Node node, Element element) { + constructorPlaceholders + .putIfAbsent(element, () => <ConstructorPlaceholder>[]) + .add(new ConstructorPlaceholder.redirectingCall(node)); + } + + void internalError(String reason, {Node node}) { + compiler.cancel(reason, node: node); + } + + void unreachable() { internalError('Unreachable case'); } + + visit(Node node) => (node == null) ? null : node.accept(this); + + visitNode(Node node) { node.visitChildren(this); } // We must go deeper. + + visitNewExpression(NewExpression node) { + Send send = node.send; + InterfaceType type = treeElements.getType(node); + assert(type != null); + Element constructor = treeElements[send]; + assert(constructor != null); + assert(send.receiver == null); + if (!Elements.isErroneousElement(constructor)) { + makeConstructorPlaceholder(node.send.selector, constructor, type); + // TODO(smok): Should this be in visitNamedArgument? + // Field names can be exposed as names of optional arguments, e.g. + // class C { + // final field; + // C([this.field]); + // } + // Do not forget to rename them as well. + FunctionElement constructorFunction = constructor; + Link<Element> optionalParameters = + constructorFunction.functionSignature.optionalParameters; + for (final argument in send.argumentsNode) { + NamedArgument named = argument.asNamedArgument(); + if (named == null) continue; + Identifier name = named.name; + String nameAsString = name.source.slowToString(); + for (final parameter in optionalParameters) { + if (identical(parameter.kind, ElementKind.FIELD_PARAMETER)) { + if (parameter.name.slowToString() == nameAsString) { + tryMakeMemberPlaceholder(name); + break; + } + } + } + } + } else { + makeUnresolvedPlaceholder(node.send.selector); + } + visit(node.send.argumentsNode); + } + + visitSend(Send send) { + new SendVisitor(this, treeElements).visitSend(send); + send.visitChildren(this); + } + + visitSendSet(SendSet send) { + Element element = treeElements[send]; + if (Elements.isErroneousElement(element)) { + // Complicated case: constructs like receiver.selector++ can resolve + // to ErroneousElement. Fortunately, receiver.selector still + // can be resoved via treeElements[send.selector], that's all + // that is needed to rename the construct properly. + element = treeElements[send.selector]; + } + if (element == null) { + if (send.receiver != null) tryMakeMemberPlaceholder(send.selector); + } else if (!element.isErroneous()) { + if (Elements.isStaticOrTopLevel(element)) { + // TODO(smok): Worth investigating why sometimes we get getter/setter + // here and sometimes abstract field. + assert(element.isClass() || element is VariableElement || + element.isAccessor() || element.isAbstractField() || + element.isFunction() || element.isTypedef() || + element is TypeVariableElement); + makeElementPlaceholder(send.selector, element); + } else { + assert(send.selector is Identifier); + if (Elements.isInstanceField(element)) { + tryMakeMemberPlaceholder(send.selector); + } else { + tryMakeLocalPlaceholder(element, send.selector); + } + } + } + send.visitChildren(this); + } + + visitIdentifier(Identifier identifier) { + if (identifier.source.isPrivate()) makePrivateIdentifier(identifier); + } + + static bool isPlainTypeName(TypeAnnotation typeAnnotation) { + if (typeAnnotation.typeName is !Identifier) return false; + if (typeAnnotation.typeArguments == null) return true; + if (typeAnnotation.typeArguments.isEmpty) return true; + return false; + } + + static bool isDynamicType(TypeAnnotation typeAnnotation) { + if (!isPlainTypeName(typeAnnotation)) return false; + String name = typeAnnotation.typeName.asIdentifier().source.slowToString(); + // TODO(aprelev@gmail.com): Removed deprecated Dynamic keyword support. + return name == 'Dynamic' || name == 'dynamic'; + } + + visitTypeAnnotation(TypeAnnotation node) { + // Poor man generic variables resolution. + // TODO(antonm): get rid of it once resolver can deal with it. + TypeDeclarationElement typeDeclarationElement; + if (currentElement is TypeDeclarationElement) { + typeDeclarationElement = currentElement; + } else { + typeDeclarationElement = currentElement.getEnclosingClass(); + } + if (typeDeclarationElement != null && isPlainTypeName(node) + && tryResolveAndCollectTypeVariable( + typeDeclarationElement, node.typeName)) { + return; + } + // We call [resolveReturnType] to allow having 'void'. + final type = compiler.resolveReturnType(currentElement, node); + if (type is InterfaceType || type is TypedefType) { + // TODO(antonm): is there a better way to detect unresolved types? + // Corner case: dart:core type with a prefix. + // Most probably there are some additional problems with + // coreLibPrefix.topLevels. + if (!identical(type.element, compiler.types.dynamicType.element)) { + makeTypePlaceholder(node.typeName, type); + } else { + if (!isDynamicType(node)) makeUnresolvedPlaceholder(node.typeName); + } + } + // Visit only type arguments, otherwise in case of lib.Class type + // annotation typeName is Send and we go to visitGetterSend, as a result + // "Class" is added to member placeholders. + visit(node.typeArguments); + } + + visitVariableDefinitions(VariableDefinitions node) { + // Collect only local placeholders. + for (Node definition in node.definitions.nodes) { + Element definitionElement = treeElements[definition]; + // definitionElement may be null if we're inside variable definitions + // of a function that is a parameter of another function. + // TODO(smok): Fix this when resolver correctly deals with + // such cases. + if (definitionElement == null) continue; + if (definition is Send) { + // May get FunctionExpression here in definition.selector + // in case of A(int this.f()); + if (definition.selector is Identifier) { + if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) { + tryMakeMemberPlaceholder(definition.selector); + } else { + tryMakeLocalPlaceholder(definitionElement, definition.selector); + } + } else { + assert(definition.selector is FunctionExpression); + if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) { + tryMakeMemberPlaceholder( + definition.selector.asFunctionExpression().name); + } + } + } else if (definition is Identifier) { + tryMakeLocalPlaceholder(definitionElement, definition); + } else if (definition is FunctionExpression) { + // Skip, it will be processed in visitFunctionExpression. + } else { + internalError('Unexpected definition structure $definition'); + } + } + node.visitChildren(this); + } + + visitFunctionExpression(FunctionExpression node) { + bool isKeyword(Identifier id) => + id != null && Keyword.keywords[id.source.slowToString()] != null; + + Element element = treeElements[node]; + // May get null here in case of A(int this.f()); + if (element != null) { + // Rename only local functions. + if (topmostEnclosingFunction == null) { + topmostEnclosingFunction = element; + } + if (!identical(element, currentElement)) { + if (node.name != null) { + assert(node.name is Identifier); + tryMakeLocalPlaceholder(element, node.name); + } + } + } + node.visitChildren(this); + // Make sure we don't omit return type of methods which names are + // identifiers, because the following works fine: + // int interface() => 1; + // But omitting 'int' makes VM unhappy. + // TODO(smok): Remove it when http://dartbug.com/5278 is fixed. + if (node.name == null || !isKeyword(node.name.asIdentifier())) { + makeOmitDeclarationTypePlaceholder(node.returnType); + } + collectFunctionParameters(node.parameters); + } + + void collectFunctionParameters(NodeList parameters) { + if (parameters == null) return; + for (Node parameter in parameters.nodes) { + if (parameter is NodeList) { + // Optional parameter list. + collectFunctionParameters(parameter); + } else { + assert(parameter is VariableDefinitions); + makeOmitDeclarationTypePlaceholder( + parameter.asVariableDefinitions().type); + } + } + } + + visitClassNode(ClassNode node) { + ClassElement classElement = currentElement; + makeElementPlaceholder(node.name, classElement); + node.visitChildren(this); + if (node.defaultClause != null) { + // Can't just visit class node's default clause because of the bug in the + // resolver, it just crashes when it meets type variable. + DartType defaultType = classElement.defaultClass; + assert(defaultType != null); + makeTypePlaceholder(node.defaultClause.typeName, defaultType); + visit(node.defaultClause.typeArguments); + } + } + + bool tryResolveAndCollectTypeVariable( + TypeDeclarationElement typeDeclaration, Identifier name) { + // Hack for case when interface and default class are in different + // libraries, try to resolve type variable to default class type arg. + // Example: + // lib1: interface I<K> default C<K> {...} + // lib2: class C<K> {...} + if (typeDeclaration is ClassElement + && (typeDeclaration as ClassElement).defaultClass != null) { + typeDeclaration = (typeDeclaration as ClassElement).defaultClass.element; + } + // Another poor man type resolution. + // Find this variable in enclosing type declaration parameters. + for (DartType type in typeDeclaration.typeVariables) { + if (type.name.slowToString() == name.source.slowToString()) { + makeTypePlaceholder(name, type); + return true; + } + } + return false; + } + + visitTypeVariable(TypeVariable node) { + assert(currentElement is TypedefElement || currentElement is ClassElement); + tryResolveAndCollectTypeVariable(currentElement, node.name); + node.visitChildren(this); + } + + visitTypedef(Typedef node) { + assert(currentElement is TypedefElement); + makeElementPlaceholder(node.name, currentElement); + node.visitChildren(this); + makeOmitDeclarationTypePlaceholder(node.returnType); + collectFunctionParameters(node.formals); + } + + visitBlock(Block node) { + for (Node statement in node.statements.nodes) { + if (statement is VariableDefinitions) { + makeVarDeclarationTypePlaceholder(statement); + } + } + node.visitChildren(this); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart new file mode 100644 index 0000000..886d39e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart
@@ -0,0 +1,359 @@ +// 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. + +part of dart_backend; + +Function get _compareNodes => + compareBy((n) => n.getBeginToken().charOffset); + +typedef String _Renamer(Renamable renamable); +abstract class Renamable { + const int RENAMABLE_TYPE_ELEMENT = 1; + const int RENAMABLE_TYPE_MEMBER = 2; + const int RENAMABLE_TYPE_LOCAL = 3; + + final Set<Node> nodes; + final _Renamer renamer; + + Renamable(this.nodes, this.renamer); + int compareTo(Renamable other) { + int nodesDiff = other.nodes.length.compareTo(this.nodes.length); + if (nodesDiff != 0) return nodesDiff; + int typeDiff = this.getTypeId().compareTo(other.getTypeId()); + return typeDiff != 0 ? typeDiff : compareInternals(other); + } + + int compareInternals(Renamable other); + int getTypeId(); + + String rename() => renamer(this); +} + +class ElementRenamable extends Renamable { + final Element element; + + ElementRenamable(this.element, Set<Node> nodes, _Renamer renamer) + : super(nodes, renamer); + + int compareInternals(ElementRenamable other) => + compareElements(this.element, other.element); + int getTypeId() => RENAMABLE_TYPE_ELEMENT; +} + +class MemberRenamable extends Renamable { + final String identifier; + MemberRenamable(this.identifier, Set<Node> nodes, _Renamer renamer) + : super(nodes, renamer); + int compareInternals(MemberRenamable other) => + this.identifier.compareTo(other.identifier); + int getTypeId() => RENAMABLE_TYPE_MEMBER; +} + +class LocalRenamable extends Renamable { + LocalRenamable(Set<Node> nodes, _Renamer renamer) : super(nodes, renamer); + int compareInternals(LocalRenamable other) => + _compareNodes(sorted(this.nodes, _compareNodes)[0], + sorted(other.nodes, _compareNodes)[0]); + int getTypeId() => RENAMABLE_TYPE_LOCAL; +} + +/** + * Renames only top-level elements that would let to ambiguity if not renamed. + */ +void renamePlaceholders( + Compiler compiler, + PlaceholderCollector placeholderCollector, + Map<Node, String> renames, + Map<LibraryElement, String> imports, + Set<String> fixedMemberNames, + bool cutDeclarationTypes) { + final Map<LibraryElement, Map<String, String>> renamed + = new Map<LibraryElement, Map<String, String>>(); + + renameNodes(Collection<Node> nodes, renamer) { + for (Node node in sorted(nodes, _compareNodes)) { + renames[node] = renamer(node); + } + } + + sortedForEach(Map<Element, dynamic> map, f) { + for (Element element in sortElements(map.keys)) { + f(element, map[element]); + } + } + + String renameType(DartType type, Function renameElement) { + // TODO(smok): Do not rename type if it is in platform library or + // js-helpers. + StringBuffer result = new StringBuffer(renameElement(type.element)); + if (type is InterfaceType) { + if (!type.isRaw) { + result.add('<'); + Link<DartType> argumentsLink = type.typeArguments; + result.add(renameType(argumentsLink.head, renameElement)); + for (Link<DartType> link = argumentsLink.tail; !link.isEmpty; + link = link.tail) { + result.add(','); + result.add(renameType(link.head, renameElement)); + } + result.add('>'); + } + } + return result.toString(); + } + + String renameConstructor(Element element, ConstructorPlaceholder placeholder, + Function renameString, Function renameElement) { + assert(element.isConstructor()); + StringBuffer result = new StringBuffer(); + String name = element.name.slowToString(); + if (element.name != element.getEnclosingClass().name) { + // Named constructor or factory. Is there a more reliable way to check + // this case? + if (!placeholder.isRedirectingCall) { + result.add(renameType(placeholder.type, renameElement)); + result.add('.'); + } + String prefix = '${element.getEnclosingClass().name.slowToString()}\$'; + if (!name.startsWith(prefix)) { + // Factory for another interface (that is going away soon). + compiler.internalErrorOnElement(element, + "Factory constructors for external interfaces are not supported."); + } + name = name.substring(prefix.length); + if (!element.getLibrary().isPlatformLibrary) { + name = renameString(element.getLibrary(), name); + } + result.add(name); + } else { + assert(!placeholder.isRedirectingCall); + result.add(renameType(placeholder.type, renameElement)); + } + return result.toString(); + } + + Function makeElementRenamer(rename, generateUniqueName) => (element) { + assert(Elements.isErroneousElement(element) || + Elements.isStaticOrTopLevel(element) || + element is TypeVariableElement); + // TODO(smok): We may want to reuse class static field and method names. + String originalName = element.name.slowToString(); + LibraryElement library = element.getLibrary(); + if (identical(element.getLibrary(), compiler.coreLibrary)) { + return originalName; + } + if (library.isPlatformLibrary && !library.isInternalLibrary) { + assert(element.isTopLevel()); + final prefix = + imports.putIfAbsent(library, () => generateUniqueName('p')); + return '$prefix.$originalName'; + } + + return rename(library, originalName); + }; + + Function makeRenamer(generateUniqueName) => + (library, originalName) => + renamed.putIfAbsent(library, () => {}) + .putIfAbsent(originalName, + () => generateUniqueName(originalName)); + + // Renamer function that takes library and original name and returns a new + // name for given identifier. + Function rename; + Function renameElement; + // A function that takes original identifier name and generates a new unique + // identifier. + Function generateUniqueName; + if (compiler.enableMinification) { + MinifyingGenerator generator = new MinifyingGenerator(); + Set<String> forbiddenIdentifiers = new Set<String>.from(['main']); + forbiddenIdentifiers.addAll(Keyword.keywords.keys); + forbiddenIdentifiers.addAll(fixedMemberNames); + generateUniqueName = (_) => + generator.generate(forbiddenIdentifiers.contains); + rename = makeRenamer(generateUniqueName); + renameElement = makeElementRenamer(rename, generateUniqueName); + + Set<String> allParameterIdentifiers = new Set<String>(); + for (var functionScope in placeholderCollector.functionScopes.values) { + allParameterIdentifiers.addAll(functionScope.parameterIdentifiers); + } + // Build a sorted (by usage) list of local nodes that will be renamed to + // the same identifier. So the top-used local variables in all functions + // will be renamed first and will all share the same new identifier. + List<Set<Node>> allSortedLocals = new List<Set<Node>>(); + for (var functionScope in placeholderCollector.functionScopes.values) { + // Add current sorted local identifiers to the whole sorted list + // of all local identifiers for all functions. + List<LocalPlaceholder> currentSortedPlaceholders = + sorted(functionScope.localPlaceholders, + compareBy((LocalPlaceholder ph) => -ph.nodes.length)); + List<Set<Node>> currentSortedNodes = + currentSortedPlaceholders.map((ph) => ph.nodes).toList(); + // Make room in all sorted locals list for new stuff. + while (currentSortedNodes.length > allSortedLocals.length) { + allSortedLocals.add(new Set<Node>()); + } + for (int i = 0; i < currentSortedNodes.length; i++) { + allSortedLocals[i].addAll(currentSortedNodes[i]); + } + } + + // Rename elements, members and locals together based on their usage count, + // otherwise when we rename elements first there will be no good identifiers + // left for members even if they are used often. + String elementRenamer(ElementRenamable elementRenamable) => + renameElement(elementRenamable.element); + String memberRenamer(MemberRenamable memberRenamable) => + generator.generate(forbiddenIdentifiers.contains); + String localRenamer(LocalRenamable localRenamable) => + generator.generate((name) => + allParameterIdentifiers.contains(name) + || forbiddenIdentifiers.contains(name)); + List<Renamable> renamables = []; + placeholderCollector.elementNodes.forEach( + (Element element, Set<Node> nodes) { + renamables.add(new ElementRenamable(element, nodes, elementRenamer)); + }); + placeholderCollector.memberPlaceholders.forEach( + (String memberName, Set<Identifier> identifiers) { + renamables.add( + new MemberRenamable(memberName, identifiers, memberRenamer)); + }); + for (Set<Node> localIdentifiers in allSortedLocals) { + renamables.add(new LocalRenamable(localIdentifiers, localRenamer)); + } + renamables.sort((Renamable renamable1, Renamable renamable2) => + renamable1.compareTo(renamable2)); + for (Renamable renamable in renamables) { + String newName = renamable.rename(); + renameNodes(renamable.nodes, (_) => newName); + } + } else { + // Never rename anything to 'main'. + final usedTopLevelOrMemberIdentifiers = new Set<String>(); + usedTopLevelOrMemberIdentifiers.add('main'); + usedTopLevelOrMemberIdentifiers.addAll(fixedMemberNames); + generateUniqueName = (originalName) { + String newName = conservativeGenerator( + originalName, usedTopLevelOrMemberIdentifiers.contains); + usedTopLevelOrMemberIdentifiers.add(newName); + return newName; + }; + rename = makeRenamer(generateUniqueName); + renameElement = makeElementRenamer(rename, generateUniqueName); + // Rename elements. + sortedForEach(placeholderCollector.elementNodes, + (Element element, Set<Node> nodes) { + renameNodes(nodes, (_) => renameElement(element)); + }); + + // Rename locals. + sortedForEach(placeholderCollector.functionScopes, + (functionElement, functionScope) { + Set<LocalPlaceholder> placeholders = functionScope.localPlaceholders; + Set<String> memberIdentifiers = new Set<String>(); + if (functionElement.getEnclosingClass() != null) { + functionElement.getEnclosingClass().forEachMember( + (enclosingClass, member) { + memberIdentifiers.add(member.name.slowToString()); + }); + } + Set<String> usedLocalIdentifiers = new Set<String>(); + for (LocalPlaceholder placeholder in placeholders) { + String nextId = + conservativeGenerator(placeholder.identifier, (name) => + functionScope.parameterIdentifiers.contains(name) + || usedTopLevelOrMemberIdentifiers.contains(name) + || usedLocalIdentifiers.contains(name) + || memberIdentifiers.contains(name)); + usedLocalIdentifiers.add(nextId); + renameNodes(placeholder.nodes, (_) => nextId); + } + }); + + final usedMemberIdentifiers = new Set<String>.from(fixedMemberNames); + // Do not rename members to top-levels, that allows to avoid renaming + // members to constructors. + usedMemberIdentifiers.addAll(usedTopLevelOrMemberIdentifiers); + placeholderCollector.memberPlaceholders.forEach((identifier, nodes) { + String newIdentifier = conservativeGenerator( + identifier, usedMemberIdentifiers.contains); + renameNodes(nodes, (_) => newIdentifier); + }); + } + + // Rename constructors. + sortedForEach(placeholderCollector.constructorPlaceholders, + (Element constructor, List<ConstructorPlaceholder> placeholders) { + for (ConstructorPlaceholder ph in placeholders) { + renames[ph.node] = + renameConstructor(constructor, ph, rename, renameElement); + } + }); + sortedForEach(placeholderCollector.privateNodes, (library, nodes) { + renameNodes(nodes, (node) => rename(library, node.source.slowToString())); + }); + renameNodes(placeholderCollector.unresolvedNodes, + (_) => generateUniqueName('Unresolved')); + renameNodes(placeholderCollector.nullNodes, (_) => ''); + if (cutDeclarationTypes) { + for (DeclarationTypePlaceholder placeholder in + placeholderCollector.declarationTypePlaceholders) { + renames[placeholder.typeNode] = placeholder.requiresVar ? 'var' : ''; + } + } +} + +/** Always tries to return original identifier name unless it is forbidden. */ +String conservativeGenerator( + String originalName, bool isForbidden(String name)) { + String newName = originalName; + while (isForbidden(newName)) { + newName = 'p_$newName'; + } + return newName; +} + +/** Always tries to generate the most compact identifier. */ +class MinifyingGenerator { + static const String firstCharAlphabet = + r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + static const String otherCharsAlphabet = + r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$'; + int nextIdIndex; + + MinifyingGenerator() : nextIdIndex = 0; + + String generate(bool isForbidden(String name)) { + String newName; + do { + newName = getNextId(); + } while(isForbidden(newName)); + return newName; + } + + /** + * Generates next mini ID with current index and alphabet. + * Advances current index. + * In other words, it converts index to visual representation + * as if digits are given characters. + */ + String getNextId() { + // It's like converting index in decimal to [chars] radix. + int index = nextIdIndex++; + StringBuffer resultBuilder = new StringBuffer(); + if (index < firstCharAlphabet.length) return firstCharAlphabet[index]; + resultBuilder.add(firstCharAlphabet[index % firstCharAlphabet.length]); + index ~/= firstCharAlphabet.length; + int length = otherCharsAlphabet.length; + while (index >= length) { + resultBuilder.add(otherCharsAlphabet[index % length]); + index ~/= length; + } + resultBuilder.add(otherCharsAlphabet[index]); + return resultBuilder.toString(); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart new file mode 100644 index 0000000..c30a2cf --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart
@@ -0,0 +1,292 @@ +// 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. + +part of dart_backend; + +class CloningVisitor implements Visitor<Node> { + final TreeElements originalTreeElements; + final TreeElementMapping cloneTreeElements; + + CloningVisitor(originalTreeElements) + : cloneTreeElements = + new TreeElementMapping(originalTreeElements.currentElement), + this.originalTreeElements = originalTreeElements; + + visit(Node node) { + if (node == null) return null; + final clone = node.accept(this); + + final originalElement = originalTreeElements[node]; + if (originalElement != null) cloneTreeElements[clone] = originalElement; + + final originalType = originalTreeElements.getType(node); + if (originalType != null) cloneTreeElements.setType(clone, originalType); + return clone; + } + + visitBlock(Block node) => new Block(visit(node.statements)); + + visitBreakStatement(BreakStatement node) => new BreakStatement( + visit(node.target), node.keywordToken, node.semicolonToken); + + visitCascade(Cascade node) => new Cascade(visit(node.expression)); + + visitCascadeReceiver(CascadeReceiver node) => new CascadeReceiver( + visit(node.expression), node.cascadeOperator); + + visitCaseMatch(CaseMatch node) => new CaseMatch( + node.caseKeyword, visit(node.expression), node.colonToken); + + visitCatchBlock(CatchBlock node) => new CatchBlock( + visit(node.type), visit(node.formals), visit(node.block), + node.onKeyword, node.catchKeyword); + + visitClassNode(ClassNode node) => new ClassNode( + visit(node.modifiers), visit(node.name), visit(node.typeParameters), + visit(node.superclass), visit(node.interfaces), visit(node.defaultClause), + node.beginToken, node.extendsKeyword, visit(node.body), node.endToken); + + visitConditional(Conditional node) => new Conditional( + visit(node.condition), visit(node.thenExpression), + visit(node.elseExpression), node.questionToken, node.colonToken); + + visitContinueStatement(ContinueStatement node) => new ContinueStatement( + visit(node.target), node.keywordToken, node.semicolonToken); + + visitDoWhile(DoWhile node) => new DoWhile( + visit(node.body), visit(node.condition), + node.doKeyword, node.whileKeyword, node.endToken); + + visitEmptyStatement(EmptyStatement node) => new EmptyStatement( + node.semicolonToken); + + visitExpressionStatement(ExpressionStatement node) => new ExpressionStatement( + visit(node.expression), node.endToken); + + visitFor(For node) => new For( + visit(node.initializer), visit(node.conditionStatement), + visit(node.update), visit(node.body), node.forToken); + + visitForIn(ForIn node) => new ForIn( + visit(node.declaredIdentifier), visit(node.expression), visit(node.body), + node.forToken, node.inToken); + + visitFunctionDeclaration(FunctionDeclaration node) => new FunctionDeclaration( + visit(node.function)); + + rewriteFunctionExpression(FunctionExpression node, Statement body) => + new FunctionExpression( + visit(node.name), visit(node.parameters), body, + visit(node.returnType), visit(node.modifiers), + visit(node.initializers), node.getOrSet); + + visitFunctionExpression(FunctionExpression node) => + rewriteFunctionExpression(node, visit(node.body)); + + visitIdentifier(Identifier node) => new Identifier(node.token); + + visitIf(If node) => new If( + visit(node.condition), visit(node.thenPart), visit(node.elsePart), + node.ifToken, node.elseToken); + + visitLabel(Label node) => new Label(visit(node.identifier), node.colonToken); + + visitLabeledStatement(LabeledStatement node) => new LabeledStatement( + visit(node.labels), visit(node.statement)); + + visitLiteralBool(LiteralBool node) => new LiteralBool( + node.token, node.handler); + + visitLiteralDouble(LiteralDouble node) => new LiteralDouble( + node.token, node.handler); + + visitLiteralInt(LiteralInt node) => new LiteralInt(node.token, node.handler); + + visitLiteralList(LiteralList node) => new LiteralList( + visit(node.typeArguments), visit(node.elements), node.constKeyword); + + visitLiteralMap(LiteralMap node) => new LiteralMap( + visit(node.typeArguments), visit(node.entries), node.constKeyword); + + visitLiteralMapEntry(LiteralMapEntry node) => new LiteralMapEntry( + visit(node.key), node.colonToken, visit(node.value)); + + visitLiteralNull(LiteralNull node) => new LiteralNull(node.token); + + visitLiteralString(LiteralString node) => new LiteralString( + node.token, node.dartString); + + visitMixinApplication(MixinApplication node) => new MixinApplication( + visit(node.superclass), visit(node.mixins)); + + visitNamedMixinApplication(NamedMixinApplication node) => + new NamedMixinApplication(visit(node.name), + visit(node.typeParameters), + visit(node.modifiers), + visit(node.mixinApplication), + visit(node.interfaces), + node.typedefKeyword, + node.endToken); + + visitModifiers(Modifiers node) => new Modifiers(visit(node.nodes)); + + visitNamedArgument(NamedArgument node) => new NamedArgument( + visit(node.name), node.colonToken, visit(node.expression)); + + visitNewExpression(NewExpression node) => new NewExpression( + node.newToken, visit(node.send)); + + rewriteNodeList(NodeList node, Link link) => + new NodeList(node.beginToken, link, node.endToken, node.delimiter); + + visitNodeList(NodeList node) { + // Special case for classes which exist in hierarchy, but not + // in the visitor. + if (node is Prefix) { + return node.nodes.isEmpty ? + new Prefix() : new Prefix.singleton(visit(node.nodes.head)); + } + if (node is Postfix) { + return node.nodes.isEmpty ? + new Postfix() : new Postfix.singleton(visit(node.nodes.head)); + } + LinkBuilder<Node> builder = new LinkBuilder<Node>(); + for (Node n in node.nodes) { + builder.addLast(visit(n)); + } + return rewriteNodeList(node, builder.toLink()); + } + + visitOperator(Operator node) => new Operator(node.token); + + visitParenthesizedExpression(ParenthesizedExpression node) => + new ParenthesizedExpression(visit(node.expression), node.beginToken); + + visitReturn(Return node) => new Return( + node.beginToken, node.endToken, visit(node.expression)); + + visitScriptTag(ScriptTag node) => new ScriptTag( + visit(node.tag), visit(node.argument), + visit(node.prefixIdentifier), visit(node.prefix), + node.beginToken, node.endToken); + + visitSend(Send node) => new Send( + visit(node.receiver), visit(node.selector), visit(node.argumentsNode)); + + visitSendSet(SendSet node) => new SendSet( + visit(node.receiver), visit(node.selector), + visit(node.assignmentOperator), visit(node.argumentsNode)); + + visitStringInterpolation(StringInterpolation node) => + new StringInterpolation(visit(node.string), visit(node.parts)); + + visitStringInterpolationPart(StringInterpolationPart node) => + new StringInterpolationPart(visit(node.expression), visit(node.string)); + + visitStringJuxtaposition(StringJuxtaposition node) => + new StringJuxtaposition(visit(node.first), visit(node.second)); + + visitSwitchCase(SwitchCase node) => new SwitchCase( + visit(node.labelsAndCases), node.defaultKeyword, visit(node.statements), + node.startToken); + + visitSwitchStatement(SwitchStatement node) => new SwitchStatement( + visit(node.parenthesizedExpression), visit(node.cases), + node.switchKeyword); + + visitThrow(Throw node) => new Throw( + visit(node.expression), node.throwToken, node.endToken); + + visitTryStatement(TryStatement node) => new TryStatement( + visit(node.tryBlock), visit(node.catchBlocks), visit(node.finallyBlock), + node.tryKeyword, node.finallyKeyword); + + visitTypeAnnotation(TypeAnnotation node) => new TypeAnnotation( + visit(node.typeName), visit(node.typeArguments)); + + visitTypedef(Typedef node) => new Typedef( + visit(node.returnType), visit(node.name), visit(node.typeParameters), + visit(node.formals), node.typedefKeyword, node.endToken); + + visitTypeVariable(TypeVariable node) => new TypeVariable( + visit(node.name), visit(node.bound)); + + visitVariableDefinitions(VariableDefinitions node) => new VariableDefinitions( + visit(node.type), visit(node.modifiers), visit(node.definitions)); + + visitWhile(While node) => new While( + visit(node.condition), visit(node.body), node.whileKeyword); + + Node visitNode(Node node) { + unimplemented('visitNode', node: node); + } + + Node visitCombinator(Combinator node) { + unimplemented('visitNode', node: node); + } + + Node visitExport(Export node) { + unimplemented('visitNode', node: node); + } + + Node visitExpression(Expression node) { + unimplemented('visitNode', node: node); + } + + Node visitGotoStatement(GotoStatement node) { + unimplemented('visitNode', node: node); + } + + Node visitImport(Import node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryDependency(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryName(LibraryName node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryTag(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + Node visitLiteral(Literal node) { + unimplemented('visitNode', node: node); + } + + Node visitLoop(Loop node) { + unimplemented('visitNode', node: node); + } + + Node visitPart(Part node) { + unimplemented('visitNode', node: node); + } + + Node visitPartOf(PartOf node) { + unimplemented('visitNode', node: node); + } + + Node visitPostfix(Postfix node) { + unimplemented('visitNode', node: node); + } + + Node visitPrefix(Prefix node) { + unimplemented('visitNode', node: node); + } + + Node visitStatement(Statement node) { + unimplemented('visitNode', node: node); + } + + Node visitStringNode(StringNode node) { + unimplemented('visitNode', node: node); + } + + unimplemented(String message, {Node node}) { + throw message; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart b/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart new file mode 100644 index 0000000..7c78bd5 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/dart_types.dart
@@ -0,0 +1,806 @@ +// 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. + +library dart_types; + +import 'dart2jslib.dart' show Compiler, invariant, Script, Message; +import 'elements/modelx.dart' show VoidElementX, LibraryElementX; +import 'elements/elements.dart'; +import 'scanner/scannerlib.dart' show SourceString; +import 'util/util.dart' show Link, LinkBuilder; + +class TypeKind { + final String id; + + const TypeKind(String this.id); + + static const TypeKind FUNCTION = const TypeKind('function'); + static const TypeKind INTERFACE = const TypeKind('interface'); + static const TypeKind STATEMENT = const TypeKind('statement'); + static const TypeKind TYPEDEF = const TypeKind('typedef'); + static const TypeKind TYPE_VARIABLE = const TypeKind('type variable'); + static const TypeKind MALFORMED_TYPE = const TypeKind('malformed'); + static const TypeKind VOID = const TypeKind('void'); + + String toString() => id; +} + +abstract class DartType { + SourceString get name; + + TypeKind get kind; + + const DartType(); + + /** + * Returns the [Element] which declared this type. + * + * This can be [ClassElement] for classes, [TypedefElement] for typedefs, + * [TypeVariableElement] for type variables and [FunctionElement] for + * function types. + * + * Invariant: [element] must be a declaration element. + */ + Element get element; + + /** + * Performs the substitution [: [arguments[i]/parameters[i]]this :]. + * + * The notation is known from this lambda calculus rule: + * + * (lambda x.e0)e1 -> [e1/x]e0. + * + * See [TypeVariableType] for a motivation for this method. + * + * Invariant: There must be the same number of [arguments] and [parameters]. + */ + DartType subst(Link<DartType> arguments, Link<DartType> parameters); + + /** + * Returns the unaliased type of this type. + * + * The unaliased type of a typedef'd type is the unaliased type to which its + * name is bound. The unaliased version of any other type is the type itself. + * + * For example, the unaliased type of [: typedef A Func<A,B>(B b) :] is the + * function type [: (B) -> A :] and the unaliased type of + * [: Func<int,String> :] is the function type [: (String) -> int :]. + */ + DartType unalias(Compiler compiler); + + /** + * A type is malformed if it is itself a malformed type or contains a + * malformed type. + */ + bool get isMalformed => false; + + /** + * Calls [f] with each [MalformedType] within this type. + * + * If [f] returns [: false :], the traversal stops prematurely. + * + * [forEachMalformedType] returns [: false :] if the traversal was stopped + * prematurely. + */ + bool forEachMalformedType(bool f(MalformedType type)) => true; + + bool operator ==(other); + + /** + * Is [: true :] if this type has no explict type arguments. + */ + bool get isRaw => true; + + DartType asRaw() => this; +} + +/** + * Represents a type variable, that is the type parameters of a class type. + * + * For example, in [: class Array<E> { ... } :], E is a type variable. + * + * Each class should have its own unique type variables, one for each type + * parameter. A class with type parameters is said to be parameterized or + * generic. + * + * Non-static members, constructors, and factories of generic + * class/interface can refer to type variables of the current class + * (not of supertypes). + * + * When using a generic type, also known as an application or + * instantiation of the type, the actual type arguments should be + * substituted for the type variables in the class declaration. + * + * For example, given a box, [: class Box<T> { T value; } :], the + * type of the expression [: new Box<String>().value :] is + * [: String :] because we must substitute [: String :] for the + * the type variable [: T :]. + */ +class TypeVariableType extends DartType { + final TypeVariableElement element; + + TypeVariableType(this.element); + + TypeKind get kind => TypeKind.TYPE_VARIABLE; + + SourceString get name => element.name; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + Link<DartType> parameterLink = parameters; + Link<DartType> argumentLink = arguments; + while (!argumentLink.isEmpty && !parameterLink.isEmpty) { + TypeVariableType parameter = parameterLink.head; + DartType argument = argumentLink.head; + if (parameter == this) { + assert(argumentLink.tail.isEmpty == parameterLink.tail.isEmpty); + return argument; + } + parameterLink = parameterLink.tail; + argumentLink = argumentLink.tail; + } + assert(argumentLink.isEmpty && parameterLink.isEmpty); + // The type variable was not substituted. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 17 * element.hashCode; + + bool operator ==(other) { + if (other is !TypeVariableType) return false; + return identical(other.element, element); + } + + String toString() => name.slowToString(); +} + +/** + * A statement type tracks whether a statement returns or may return. + */ +class StatementType extends DartType { + final String stringName; + + Element get element => null; + + TypeKind get kind => TypeKind.STATEMENT; + + SourceString get name => new SourceString(stringName); + + const StatementType(this.stringName); + + static const RETURNING = const StatementType('<returning>'); + static const NOT_RETURNING = const StatementType('<not returning>'); + static const MAYBE_RETURNING = const StatementType('<maybe returning>'); + + /** Combine the information about two control-flow edges that are joined. */ + StatementType join(StatementType other) { + return (identical(this, other)) ? this : MAYBE_RETURNING; + } + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Statement types are not substitutable. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 17 * stringName.hashCode; + + bool operator ==(other) { + if (other is !StatementType) return false; + return other.stringName == stringName; + } + + String toString() => stringName; +} + +class VoidType extends DartType { + const VoidType(this.element); + + TypeKind get kind => TypeKind.VOID; + + SourceString get name => element.name; + + final Element element; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Void cannot be substituted. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 1729; + + bool operator ==(other) => other is VoidType; + + String toString() => name.slowToString(); +} + +class MalformedType extends DartType { + final ErroneousElement element; + + /** + * [declaredType] holds the type which the user wrote in code. + * + * For instance, for a resolved but malformed type like [: Map<String> :] the + * [declaredType] is [: Map<String> :] whereas for an unresolved type + */ + final DartType userProvidedBadType; + + /** + * Type arguments for the malformed typed, if these cannot fit in the + * [declaredType]. + * + * This field is for instance used for [: dynamic<int> :] and [: T<int> :] + * where [: T :] is a type variable, in which case [declaredType] holds + * [: dynamic :] and [: T :], respectively, or for [: X<int> :] where [: X :] + * is not resolved or does not imply a type. + */ + final Link<DartType> typeArguments; + + MalformedType(this.element, this.userProvidedBadType, + [this.typeArguments = null]); + + TypeKind get kind => TypeKind.MALFORMED_TYPE; + + SourceString get name => element.name; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Malformed types are not substitutable. + return this; + } + + bool get isMalformed => true; + + bool forEachMalformedType(bool f(MalformedType type)) => f(this); + + DartType unalias(Compiler compiler) => this; + + String toString() { + var sb = new StringBuffer(); + if (typeArguments != null) { + if (userProvidedBadType != null) { + sb.add(userProvidedBadType.name.slowToString()); + } else { + sb.add(element.name.slowToString()); + } + if (!typeArguments.isEmpty) { + sb.add('<'); + typeArguments.printOn(sb, ', '); + sb.add('>'); + } + } else { + sb.add(userProvidedBadType.toString()); + } + return sb.toString(); + } +} + +bool hasMalformed(Link<DartType> types) { + for (DartType typeArgument in types) { + if (typeArgument.isMalformed) { + return true; + } + } + return false; +} + +abstract class GenericType extends DartType { + final Link<DartType> typeArguments; + final bool isMalformed; + + GenericType(Link<DartType> this.typeArguments, bool this.isMalformed); + + TypeDeclarationElement get element; + + /// Creates a new instance of this type using the provided type arguments. + GenericType _createType(Link<DartType> newTypeArguments); + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (typeArguments.isEmpty) { + // Return fast on non-generic types. + return this; + } + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + Link<DartType> newTypeArguments = + Types.substTypes(typeArguments, arguments, parameters); + if (!identical(typeArguments, newTypeArguments)) { + // Create a new type only if necessary. + return _createType(newTypeArguments); + } + return this; + } + + bool forEachMalformedType(bool f(MalformedType type)) { + for (DartType typeArgument in typeArguments) { + if (!typeArgument.forEachMalformedType(f)) { + return false; + } + } + return true; + } + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add(name.slowToString()); + if (!isRaw) { + sb.add('<'); + typeArguments.printOn(sb, ', '); + sb.add('>'); + } + return sb.toString(); + } + + int get hashCode { + int hash = element.hashCode; + for (Link<DartType> arguments = this.typeArguments; + !arguments.isEmpty; + arguments = arguments.tail) { + int argumentHash = arguments.head != null ? arguments.head.hashCode : 0; + hash = 17 * hash + 3 * argumentHash; + } + return hash; + } + + bool operator ==(other) { + if (!identical(element, other.element)) return false; + return typeArguments == other.typeArguments; + } + + bool get isRaw => typeArguments.isEmpty || identical(this, element.rawType); + + GenericType asRaw() => element.rawType; +} + +// TODO(johnniwinther): Add common supertype for InterfaceType and TypedefType. +class InterfaceType extends GenericType { + final ClassElement element; + + InterfaceType(this.element, + [Link<DartType> typeArguments = const Link<DartType>()]) + : super(typeArguments, hasMalformed(typeArguments)) { + assert(invariant(element, element.isDeclaration)); + } + + TypeKind get kind => TypeKind.INTERFACE; + + SourceString get name => element.name; + + InterfaceType _createType(Link<DartType> newTypeArguments) { + return new InterfaceType(element, newTypeArguments); + } + + /** + * Returns the type as an instance of class [other], if possible, null + * otherwise. + */ + DartType asInstanceOf(ClassElement other) { + if (element == other) return this; + for (InterfaceType supertype in element.allSupertypes) { + ClassElement superclass = supertype.element; + if (superclass == other) { + Link<DartType> arguments = Types.substTypes(supertype.typeArguments, + typeArguments, + element.typeVariables); + return new InterfaceType(superclass, arguments); + } + } + return null; + } + + DartType unalias(Compiler compiler) => this; + + bool operator ==(other) { + if (other is !InterfaceType) return false; + return super == other; + } + + InterfaceType asRaw() => super.asRaw(); +} + +class FunctionType extends DartType { + final Element element; + final DartType returnType; + final Link<DartType> parameterTypes; + final Link<DartType> optionalParameterTypes; + + /** + * The names of the named parameters ordered lexicographically. + */ + final Link<SourceString> namedParameters; + + /** + * The types of the named parameters in the order corresponding to the + * [namedParameters]. + */ + final Link<DartType> namedParameterTypes; + final bool isMalformed; + + factory FunctionType(Element element, + DartType returnType, + Link<DartType> parameterTypes, + Link<DartType> optionalParameterTypes, + Link<SourceString> namedParameters, + Link<DartType> namedParameterTypes) { + // Compute [isMalformed] eagerly since it is faster than a lazy computation + // and since [isMalformed] most likely will be accessed in [Types.isSubtype] + // anyway. + bool isMalformed = returnType != null && + returnType.isMalformed || + hasMalformed(parameterTypes) || + hasMalformed(optionalParameterTypes) || + hasMalformed(namedParameterTypes); + return new FunctionType.internal(element, + returnType, + parameterTypes, + optionalParameterTypes, + namedParameters, + namedParameterTypes, + isMalformed); + } + + FunctionType.internal(Element this.element, + DartType this.returnType, + Link<DartType> this.parameterTypes, + Link<DartType> this.optionalParameterTypes, + Link<SourceString> this.namedParameters, + Link<DartType> this.namedParameterTypes, + bool this.isMalformed) { + assert(element == null || invariant(element, element.isDeclaration)); + // Assert that optional and named parameters are not used at the same time. + assert(optionalParameterTypes.isEmpty || namedParameterTypes.isEmpty); + assert(namedParameters.slowLength() == namedParameterTypes.slowLength()); + } + + TypeKind get kind => TypeKind.FUNCTION; + + DartType getNamedParameterType(SourceString name) { + Link<SourceString> namedParameter = namedParameters; + Link<DartType> namedParameterType = namedParameterTypes; + while (!namedParameter.isEmpty && !namedParameterType.isEmpty) { + if (namedParameter.head == name) { + return namedParameterType.head; + } + namedParameter = namedParameter.tail; + namedParameterType = namedParameterType.tail; + } + return null; + } + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + var newReturnType = returnType.subst(arguments, parameters); + bool changed = !identical(newReturnType, returnType); + var newParameterTypes = + Types.substTypes(parameterTypes, arguments, parameters); + var newOptionalParameterTypes = + Types.substTypes(optionalParameterTypes, arguments, parameters); + var newNamedParameterTypes = + Types.substTypes(namedParameterTypes, arguments, parameters); + if (!changed && + (!identical(parameterTypes, newParameterTypes) || + !identical(optionalParameterTypes, newOptionalParameterTypes) || + !identical(namedParameterTypes, newNamedParameterTypes))) { + changed = true; + } + if (changed) { + // Create a new type only if necessary. + return new FunctionType(element, + newReturnType, + newParameterTypes, + newOptionalParameterTypes, + namedParameters, + newNamedParameterTypes); + } + return this; + } + + bool forEachMalformedType(bool f(MalformedType type)) { + if (!returnType.forEachMalformedType(f)) { + return false; + } + for (DartType parameterType in parameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + for (DartType parameterType in optionalParameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + for (DartType parameterType in namedParameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + return true; + } + + DartType unalias(Compiler compiler) => this; + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add('('); + parameterTypes.printOn(sb, ', '); + bool first = parameterTypes.isEmpty; + if (!optionalParameterTypes.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add('['); + optionalParameterTypes.printOn(sb, ', '); + sb.add(']'); + first = false; + } + if (!namedParameterTypes.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add('{'); + Link<SourceString> namedParameter = namedParameters; + Link<DartType> namedParameterType = namedParameterTypes; + first = true; + while (!namedParameter.isEmpty && !namedParameterType.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add(namedParameterType.head); + sb.add(' '); + sb.add(namedParameter.head.slowToString()); + namedParameter = namedParameter.tail; + namedParameterType = namedParameterType.tail; + first = false; + } + sb.add('}'); + } + sb.add(') -> ${returnType}'); + return sb.toString(); + } + + SourceString get name => const SourceString('Function'); + + int computeArity() { + int arity = 0; + parameterTypes.forEach((_) { arity++; }); + return arity; + } + + int get hashCode { + int hash = 17 * element.hashCode + 3 * returnType.hashCode; + for (DartType parameter in parameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + for (DartType parameter in optionalParameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + for (SourceString name in namedParameters) { + hash = 17 * hash + 3 * name.hashCode; + } + for (DartType parameter in namedParameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + return hash; + } + + bool operator ==(other) { + if (other is !FunctionType) return false; + return returnType == other.returnType + && parameterTypes == other.parameterTypes + && optionalParameterTypes == other.optionalParameterTypes + && namedParameters == other.namedParameters + && namedParameterTypes == other.namedParameterTypes; + } +} + +class TypedefType extends GenericType { + final TypedefElement element; + + TypedefType(this.element, + [Link<DartType> typeArguments = const Link<DartType>()]) + : super(typeArguments, hasMalformed(typeArguments)); + + TypedefType _createType(Link<DartType> newTypeArguments) { + return new TypedefType(element, newTypeArguments); + } + + TypeKind get kind => TypeKind.TYPEDEF; + + SourceString get name => element.name; + + DartType unalias(Compiler compiler) { + // TODO(ahe): This should be [ensureResolved]. + compiler.resolveTypedef(element); + DartType definition = element.alias.unalias(compiler); + TypedefType declaration = element.computeType(compiler); + return definition.subst(typeArguments, declaration.typeArguments); + } + + bool operator ==(other) { + if (other is !TypedefType) return false; + return super == other; + } + + TypedefType asRaw() => super.asRaw(); +} + +/** + * Special type to hold the [dynamic] type. Used for correctly returning + * 'dynamic' on [toString]. + */ +class DynamicType extends InterfaceType { + DynamicType(ClassElement element) : super(element); + + SourceString get name => const SourceString('dynamic'); +} + +class Types { + final Compiler compiler; + // TODO(karlklose): should we have a class Void? + final VoidType voidType; + final DynamicType dynamicType; + + factory Types(Compiler compiler, ClassElement dynamicElement) { + LibraryElement library = new LibraryElementX(new Script(null, null)); + VoidType voidType = new VoidType(new VoidElementX(library)); + DynamicType dynamicType = new DynamicType(dynamicElement); + dynamicElement.rawType = dynamicElement.thisType = dynamicType; + return new Types.internal(compiler, voidType, dynamicType); + } + + Types.internal(this.compiler, this.voidType, this.dynamicType); + + /** Returns true if t is a subtype of s */ + bool isSubtype(DartType t, DartType s) { + if (identical(t, s) || + identical(t, dynamicType) || + identical(s, dynamicType) || + t.isMalformed || + s.isMalformed || + identical(s.element, compiler.objectClass) || + identical(t.element, compiler.nullClass)) { + return true; + } + t = t.unalias(compiler); + s = s.unalias(compiler); + + if (t is VoidType) { + return false; + } else if (t is InterfaceType) { + if (s is !InterfaceType) return false; + ClassElement tc = t.element; + if (identical(tc, s.element)) return true; + for (Link<DartType> supertypes = tc.allSupertypes; + supertypes != null && !supertypes.isEmpty; + supertypes = supertypes.tail) { + DartType supertype = supertypes.head; + if (identical(supertype.element, s.element)) return true; + } + return false; + } else if (t is FunctionType) { + if (identical(s.element, compiler.functionClass)) return true; + if (s is !FunctionType) return false; + FunctionType tf = t; + FunctionType sf = s; + Link<DartType> tps = tf.parameterTypes; + Link<DartType> sps = sf.parameterTypes; + while (!tps.isEmpty && !sps.isEmpty) { + if (!isAssignable(tps.head, sps.head)) return false; + tps = tps.tail; + sps = sps.tail; + } + if (!tps.isEmpty || !sps.isEmpty) return false; + if (!isAssignable(sf.returnType, tf.returnType)) return false; + if (!sf.namedParameters.isEmpty) { + // Since named parameters are globally ordered we can determine the + // subset relation with a linear search for [:sf.NamedParameters:] + // within [:tf.NamedParameters:]. + Link<SourceString> tNames = tf.namedParameters; + Link<DartType> tTypes = tf.namedParameterTypes; + Link<SourceString> sNames = sf.namedParameters; + Link<DartType> sTypes = sf.namedParameterTypes; + while (!tNames.isEmpty && !sNames.isEmpty) { + if (sNames.head == tNames.head) { + if (!isAssignable(tTypes.head, sTypes.head)) return false; + + sNames = sNames.tail; + sTypes = sTypes.tail; + } + tNames = tNames.tail; + tTypes = tTypes.tail; + } + if (!sNames.isEmpty) { + // We didn't find all names. + return false; + } + } + if (!sf.optionalParameterTypes.isEmpty) { + Link<DartType> tOptionalParameterType = tf.optionalParameterTypes; + Link<DartType> sOptionalParameterType = sf.optionalParameterTypes; + while (!tOptionalParameterType.isEmpty && + !sOptionalParameterType.isEmpty) { + if (!isAssignable(tOptionalParameterType.head, + sOptionalParameterType.head)) { + return false; + } + sOptionalParameterType = sOptionalParameterType.tail; + tOptionalParameterType = tOptionalParameterType.tail; + } + if (!sOptionalParameterType.isEmpty) { + // We didn't find enough optional parameters. + return false; + } + } + return true; + } else if (t is TypeVariableType) { + if (s is !TypeVariableType) return false; + return (identical(t.element, s.element)); + } else { + throw 'internal error: unknown type kind'; + } + } + + bool isAssignable(DartType r, DartType s) { + return isSubtype(r, s) || isSubtype(s, r); + } + + + /** + * Helper method for performing substitution of a linked list of types. + * + * If no types are changed by the substitution, the [types] is returned + * instead of a newly created linked list. + */ + static Link<DartType> substTypes(Link<DartType> types, + Link<DartType> arguments, + Link<DartType> parameters) { + bool changed = false; + var builder = new LinkBuilder<DartType>(); + Link<DartType> typeLink = types; + while (!typeLink.isEmpty) { + var argument = typeLink.head.subst(arguments, parameters); + if (!changed && !identical(argument, typeLink.head)) { + changed = true; + } + builder.addLast(argument); + typeLink = typeLink.tail; + } + if (changed) { + // Create a new link only if necessary. + return builder.toLink(); + } + return types; + } + + /** + * Combine error messages in a malformed type to a single message string. + */ + static String fetchReasonsFromMalformedType(DartType type) { + // TODO(johnniwinther): Figure out how to produce good error message in face + // of multiple errors, and how to ensure non-localized error messages. + var reasons = new List<String>(); + type.forEachMalformedType((MalformedType malformedType) { + ErroneousElement error = malformedType.element; + Message message = error.messageKind.message(error.messageArguments); + reasons.add(message.toString()); + return true; + }); + return Strings.join(reasons, ', '); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart b/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart new file mode 100644 index 0000000..1d0c2b9 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart
@@ -0,0 +1,28 @@ +// 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. + +part of dart2js; + +abstract class DiagnosticListener { + // TODO(karlklose): replace cancel with better error reporting mechanism. + void cancel(String reason, {node, token, instruction, element}); + // TODO(karlklose): rename log to something like reportInfo. + void log(message); + // TODO(karlklose): add reportWarning and reportError to this interface. + + void internalErrorOnElement(Element element, String message); + void internalError(String message, + {Node node, Token token, HInstruction instruction, + Element element}); + + SourceSpan spanFromSpannable(Spannable node, [Uri uri]); + + void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind); + + // TODO(ahe): Rename to reportError when that method has been removed. + void reportErrorCode(Spannable node, MessageKind errorCode, [Map arguments]); + + /// Returns true if a diagnostic was emitted. + bool onDeprecatedFeature(Spannable span, String feature); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart b/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart new file mode 100644 index 0000000..2798f95 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart
@@ -0,0 +1,792 @@ +// 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. + +library elements; + +import 'dart:uri'; + +import 'modelx.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../resolution/resolution.dart'; + +import '../dart2jslib.dart' show InterfaceType, + DartType, + TypeVariableType, + TypedefType, + MessageKind, + DiagnosticListener, + Script, + FunctionType, + SourceString, + Selector, + Constant, + Compiler; + +import '../dart_types.dart'; + +import '../scanner/scannerlib.dart' show Token, + isUserDefinableOperator, + isMinusOperator; + +const int STATE_NOT_STARTED = 0; +const int STATE_STARTED = 1; +const int STATE_DONE = 2; + +class ElementCategory { + /** + * Represents things that we don't expect to find when looking in a + * scope. + */ + static const int NONE = 0; + + /** Field, parameter, or variable. */ + static const int VARIABLE = 1; + + /** Function, method, or foreign function. */ + static const int FUNCTION = 2; + + static const int CLASS = 4; + + static const int PREFIX = 8; + + /** Constructor or factory. */ + static const int FACTORY = 16; + + static const int ALIAS = 32; + + static const int SUPER = 64; + + /** Type variable */ + static const int TYPE_VARIABLE = 128; + + static const int IMPLIES_TYPE = CLASS | ALIAS | TYPE_VARIABLE; +} + +class ElementKind { + final String id; + final int category; + + const ElementKind(String this.id, this.category); + + static const ElementKind VARIABLE = + const ElementKind('variable', ElementCategory.VARIABLE); + static const ElementKind PARAMETER = + const ElementKind('parameter', ElementCategory.VARIABLE); + // Parameters in constructors that directly initialize fields. For example: + // [:A(this.field):]. + static const ElementKind FIELD_PARAMETER = + const ElementKind('field_parameter', ElementCategory.VARIABLE); + static const ElementKind FUNCTION = + const ElementKind('function', ElementCategory.FUNCTION); + static const ElementKind CLASS = + const ElementKind('class', ElementCategory.CLASS); + static const ElementKind GENERATIVE_CONSTRUCTOR = + const ElementKind('generative_constructor', ElementCategory.FACTORY); + static const ElementKind FIELD = + const ElementKind('field', ElementCategory.VARIABLE); + static const ElementKind VARIABLE_LIST = + const ElementKind('variable_list', ElementCategory.NONE); + static const ElementKind FIELD_LIST = + const ElementKind('field_list', ElementCategory.NONE); + static const ElementKind GENERATIVE_CONSTRUCTOR_BODY = + const ElementKind('generative_constructor_body', ElementCategory.NONE); + static const ElementKind COMPILATION_UNIT = + const ElementKind('compilation_unit', ElementCategory.NONE); + static const ElementKind GETTER = + const ElementKind('getter', ElementCategory.NONE); + static const ElementKind SETTER = + const ElementKind('setter', ElementCategory.NONE); + static const ElementKind TYPE_VARIABLE = + const ElementKind('type_variable', ElementCategory.TYPE_VARIABLE); + static const ElementKind ABSTRACT_FIELD = + const ElementKind('abstract_field', ElementCategory.VARIABLE); + static const ElementKind LIBRARY = + const ElementKind('library', ElementCategory.NONE); + static const ElementKind PREFIX = + const ElementKind('prefix', ElementCategory.PREFIX); + static const ElementKind TYPEDEF = + const ElementKind('typedef', ElementCategory.ALIAS); + + static const ElementKind STATEMENT = + const ElementKind('statement', ElementCategory.NONE); + static const ElementKind LABEL = + const ElementKind('label', ElementCategory.NONE); + static const ElementKind VOID = + const ElementKind('void', ElementCategory.NONE); + + static const ElementKind AMBIGUOUS = + const ElementKind('ambiguous', ElementCategory.NONE); + static const ElementKind ERROR = + const ElementKind('error', ElementCategory.NONE); + static const ElementKind MALFORMED_TYPE = + const ElementKind('malformed', ElementCategory.NONE); + + toString() => id; +} + +abstract class Element implements Spannable { + SourceString get name; + ElementKind get kind; + Modifiers get modifiers; + Element get enclosingElement; + Link<MetadataAnnotation> get metadata; + + Node parseNode(DiagnosticListener listener); + DartType computeType(Compiler compiler); + + bool isFunction(); + bool isConstructor(); + bool isClosure(); + bool isMember(); + bool isInstanceMember(); + bool isInStaticMember(); + + bool isFactoryConstructor(); + bool isGenerativeConstructor(); + bool isGenerativeConstructorBody(); + bool isCompilationUnit(); + bool isClass(); + bool isPrefix(); + bool isVariable(); + bool isParameter(); + bool isStatement(); + bool isTypedef(); + bool isTypeVariable(); + bool isField(); + bool isAbstractField(); + bool isGetter(); + bool isSetter(); + bool isAccessor(); + bool isLibrary(); + bool isErroneous(); + bool isAmbiguous(); + + bool isTopLevel(); + bool isAssignable(); + bool isNative(); + + bool impliesType(); + + Token position(); + + CompilationUnitElement getCompilationUnit(); + LibraryElement getLibrary(); + LibraryElement getImplementationLibrary(); + ClassElement getEnclosingClass(); + Element getEnclosingClassOrCompilationUnit(); + Element getEnclosingMember(); + Element getOutermostEnclosingMemberOrTopLevel(); + + FunctionElement asFunctionElement(); + + bool get isPatched; + bool get isPatch; + bool get isImplementation; + bool get isDeclaration; + bool get isSynthesized; + + Element get implementation; + Element get declaration; + Element get patch; + Element get origin; + + bool hasFixedBackendName(); + String fixedBackendName(); + + bool isAbstract(Compiler compiler); + bool isForeign(Compiler compiler); + + void addMetadata(MetadataAnnotation annotation); + void setNative(String name); + void setFixedBackendName(String name); + + Scope buildScope(); +} + +class Elements { + static bool isUnresolved(Element e) { + return e == null || e.isErroneous(); + } + static bool isErroneousElement(Element e) => e != null && e.isErroneous(); + + static bool isClass(Element e) => e != null && e.kind == ElementKind.CLASS; + static bool isTypedef(Element e) { + return e != null && e.kind == ElementKind.TYPEDEF; + } + + static bool isLocal(Element element) { + return !Elements.isUnresolved(element) + && !element.isInstanceMember() + && !isStaticOrTopLevelField(element) + && !isStaticOrTopLevelFunction(element) + && (identical(element.kind, ElementKind.VARIABLE) || + identical(element.kind, ElementKind.PARAMETER) || + identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceField(Element element) { + return !Elements.isUnresolved(element) + && element.isInstanceMember() + && (identical(element.kind, ElementKind.FIELD) + || identical(element.kind, ElementKind.GETTER) + || identical(element.kind, ElementKind.SETTER)); + } + + static bool isStaticOrTopLevel(Element element) { + // TODO(ager): This should not be necessary when patch support has + // been reworked. + if (!Elements.isUnresolved(element) + && element.modifiers.isStatic()) { + return true; + } + return !Elements.isUnresolved(element) + && !element.isInstanceMember() + && !element.isPrefix() + && element.enclosingElement != null + && (element.enclosingElement.kind == ElementKind.CLASS || + element.enclosingElement.kind == ElementKind.COMPILATION_UNIT || + element.enclosingElement.kind == ElementKind.LIBRARY); + } + + static bool isStaticOrTopLevelField(Element element) { + return isStaticOrTopLevel(element) + && (identical(element.kind, ElementKind.FIELD) + || identical(element.kind, ElementKind.GETTER) + || identical(element.kind, ElementKind.SETTER)); + } + + static bool isStaticOrTopLevelFunction(Element element) { + return isStaticOrTopLevel(element) + && (identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceMethod(Element element) { + return !Elements.isUnresolved(element) + && element.isInstanceMember() + && (identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceSend(Send send, TreeElements elements) { + Element element = elements[send]; + if (element == null) return !isClosureSend(send, element); + return isInstanceMethod(element) || isInstanceField(element); + } + + static bool isClosureSend(Send send, Element element) { + if (send.isPropertyAccess) return false; + if (send.receiver != null) return false; + // (o)() or foo()(). + if (element == null && send.selector.asIdentifier() == null) return true; + if (element == null) return false; + // foo() with foo a local or a parameter. + return isLocal(element); + } + + static SourceString constructConstructorName(SourceString receiver, + SourceString selector) { + String r = receiver.slowToString(); + String s = selector.slowToString(); + return new SourceString('$r\$$s'); + } + + static SourceString deconstructConstructorName(SourceString name, + ClassElement holder) { + String r = '${holder.name.slowToString()}\$'; + String s = name.slowToString(); + if (s.startsWith(r)) { + return new SourceString(s.substring(r.length)); + } + return null; + } + + /** + * Map an operator-name to a valid Dart identifier. + * + * For non-operator names, this metod just returns its input. + * + * The results returned from this method are guaranteed to be valid + * JavaScript identifers, except it may include reserved words for + * non-operator names. + */ + static SourceString operatorNameToIdentifier(SourceString name) { + if (name == null) return null; + String value = name.stringValue; + if (value == null) { + return name; + } else if (identical(value, '==')) { + return const SourceString(r'operator$eq'); + } else if (identical(value, '~')) { + return const SourceString(r'operator$not'); + } else if (identical(value, '[]')) { + return const SourceString(r'operator$index'); + } else if (identical(value, '[]=')) { + return const SourceString(r'operator$indexSet'); + } else if (identical(value, '*')) { + return const SourceString(r'operator$mul'); + } else if (identical(value, '/')) { + return const SourceString(r'operator$div'); + } else if (identical(value, '%')) { + return const SourceString(r'operator$mod'); + } else if (identical(value, '~/')) { + return const SourceString(r'operator$tdiv'); + } else if (identical(value, '+')) { + return const SourceString(r'operator$add'); + } else if (identical(value, '<<')) { + return const SourceString(r'operator$shl'); + } else if (identical(value, '>>')) { + return const SourceString(r'operator$shr'); + } else if (identical(value, '>=')) { + return const SourceString(r'operator$ge'); + } else if (identical(value, '>')) { + return const SourceString(r'operator$gt'); + } else if (identical(value, '<=')) { + return const SourceString(r'operator$le'); + } else if (identical(value, '<')) { + return const SourceString(r'operator$lt'); + } else if (identical(value, '&')) { + return const SourceString(r'operator$and'); + } else if (identical(value, '^')) { + return const SourceString(r'operator$xor'); + } else if (identical(value, '|')) { + return const SourceString(r'operator$or'); + } else if (identical(value, '-')) { + return const SourceString(r'operator$sub'); + } else if (identical(value, 'unary-')) { + return const SourceString(r'operator$negate'); + } else { + return name; + } + } + + static SourceString constructOperatorNameOrNull(SourceString op, + bool isUnary) { + String value = op.stringValue; + if (isMinusOperator(value)) { + return isUnary ? const SourceString('unary-') : op; + } else if (isUserDefinableOperator(value)) { + return op; + } else { + return null; + } + } + + static SourceString constructOperatorName(SourceString op, bool isUnary) { + SourceString operatorName = constructOperatorNameOrNull(op, isUnary); + if (operatorName == null) throw 'Unhandled operator: ${op.slowToString()}'; + else return operatorName; + } + + static SourceString mapToUserOperatorOrNull(SourceString op) { + String value = op.stringValue; + + if (identical(value, '!=')) return const SourceString('=='); + if (identical(value, '*=')) return const SourceString('*'); + if (identical(value, '/=')) return const SourceString('/'); + if (identical(value, '%=')) return const SourceString('%'); + if (identical(value, '~/=')) return const SourceString('~/'); + if (identical(value, '+=')) return const SourceString('+'); + if (identical(value, '-=')) return const SourceString('-'); + if (identical(value, '<<=')) return const SourceString('<<'); + if (identical(value, '>>=')) return const SourceString('>>'); + if (identical(value, '&=')) return const SourceString('&'); + if (identical(value, '^=')) return const SourceString('^'); + if (identical(value, '|=')) return const SourceString('|'); + + return null; + } + + static SourceString mapToUserOperator(SourceString op) { + SourceString userOperator = mapToUserOperatorOrNull(op); + if (userOperator == null) throw 'Unhandled operator: ${op.slowToString()}'; + else return userOperator; + } + + static bool isNumberOrStringSupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return (element == coreLibrary.find(const SourceString('Comparable'))); + } + + static bool isStringOnlySupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return element == coreLibrary.find(const SourceString('Pattern')); + } + + static bool isListSupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return (element == coreLibrary.find(const SourceString('Collection'))) + || (element == coreLibrary.find(const SourceString('Iterable'))); + } + + /// A `compareTo` function that places [Element]s in a consistent order based + /// on the source code order. + static int compareByPosition(Element a, Element b) { + CompilationUnitElement unitA = a.getCompilationUnit(); + CompilationUnitElement unitB = b.getCompilationUnit(); + if (!identical(unitA, unitB)) { + int r = unitA.script.uri.path.compareTo(unitB.script.uri.path); + if (r != 0) return r; + } + Token positionA = a.position(); + Token positionB = b.position(); + int r = positionA.charOffset.compareTo(positionB.charOffset); + if (r != 0) return r; + r = a.name.slowToString().compareTo(b.name.slowToString()); + if (r != 0) return r; + // Same file, position and name. If this happens, we should find out why + // and make the order total and independent of hashCode. + return a.hashCode.compareTo(b.hashCode); + } + + static List<Element> sortedByPosition(Iterable<Element> elements) { + return elements.toList()..sort(compareByPosition); + } +} + +abstract class ErroneousElement extends Element implements FunctionElement { + MessageKind get messageKind; + Map get messageArguments; +} + +abstract class AmbiguousElement extends Element { + MessageKind get messageKind; + Map get messageArguments; + Element get existingElement; + Element get newElement; +} + +// TODO(kasperl): This probably shouldn't be called an element. It's +// just an interface shared by classes and libraries. +abstract class ScopeContainerElement { + Element localLookup(SourceString elementName); +} + +abstract class CompilationUnitElement extends Element { + Script get script; + PartOf get partTag; + + void addMember(Element element, DiagnosticListener listener); + void setPartOf(PartOf tag, DiagnosticListener listener); + bool get hasMembers; +} + +abstract class LibraryElement extends Element implements ScopeContainerElement { + /** + * The canonical uri for this library. + * + * For user libraries the canonical uri is the script uri. For platform + * libraries the canonical uri is of the form [:dart:x:]. + */ + Uri get canonicalUri; + CompilationUnitElement get entryCompilationUnit; + Link<CompilationUnitElement> get compilationUnits; + Link<LibraryTag> get tags; + LibraryName get libraryTag; + Link<Element> get exports; + + /** + * [:true:] if this library is part of the platform, that is its canonical + * uri has the scheme 'dart'. + */ + bool get isPlatformLibrary; + + /** + * [:true:] if this library is a platform library whose path starts with + * an underscore. + */ + bool get isInternalLibrary; + bool get canUseNative; + bool get exportsHandled; + + // TODO(kasperl): We should try to get rid of these. + void set canUseNative(bool value); + void set libraryTag(LibraryName value); + + LibraryElement get implementation; + + void addCompilationUnit(CompilationUnitElement element); + void addTag(LibraryTag tag, DiagnosticListener listener); + void addImport(Element element, DiagnosticListener listener); + + void addMember(Element element, DiagnosticListener listener); + void addToScope(Element element, DiagnosticListener listener); + + // TODO(kasperl): Get rid of this method. + Iterable<Element> getNonPrivateElementsInScope(); + + void setExports(Iterable<Element> exportedElements); + + Element find(SourceString elementName); + Element findLocal(SourceString elementName); + void forEachExport(f(Element element)); + + void forEachLocalMember(f(Element element)); + + bool hasLibraryName(); + String getLibraryOrScriptName(); +} + +abstract class PrefixElement extends Element { + Map<SourceString, Element> get imported; + Element lookupLocalMember(SourceString memberName); +} + +abstract class TypedefElement extends Element + implements TypeDeclarationElement { + TypedefType get rawType; + DartType get alias; + FunctionSignature get functionSignature; + Link<DartType> get typeVariables; + + bool get isResolved; + bool get isBeingResolved; + + // TODO(kasperl): Try to get rid of these setters. + void set alias(DartType value); + void set isResolved(bool value); + void set isBeingResolved(bool value); + void set functionSignature(FunctionSignature value); +} + +abstract class VariableElement extends Element { + VariableListElement get variables; + + // TODO(kasperl): Try to get rid of this. + Expression get cachedNode; +} + +abstract class FieldParameterElement extends VariableElement { + VariableElement get fieldElement; +} + +abstract class VariableListElement extends Element { + DartType get type; + FunctionSignature get functionSignature; + + // TODO(kasperl): Try to get rid of this. + void set type(DartType value); +} + +abstract class AbstractFieldElement extends Element { + FunctionElement get getter; + FunctionElement get setter; +} + +abstract class FunctionSignature { + DartType get returnType; + Link<Element> get requiredParameters; + Link<Element> get optionalParameters; + + int get requiredParameterCount; + int get optionalParameterCount; + bool get optionalParametersAreNamed; + + int get parameterCount; + List<Element> get orderedOptionalParameters; + + void forEachParameter(void function(Element parameter)); + void forEachRequiredParameter(void function(Element parameter)); + void forEachOptionalParameter(void function(Element parameter)); + + void orderedForEachParameter(void function(Element parameter)); +} + +abstract class FunctionElement extends Element { + FunctionExpression get cachedNode; + DartType get type; + FunctionSignature get functionSignature; + FunctionElement get redirectionTarget; + FunctionElement get defaultImplementation; + + FunctionElement get patch; + FunctionElement get origin; + + // TODO(kasperl): These are bit fishy. Do we really need them? + void set patch(FunctionElement value); + void set origin(FunctionElement value); + void set defaultImplementation(FunctionElement value); + + void setPatch(FunctionElement patchElement); + FunctionSignature computeSignature(Compiler compiler); + int requiredParameterCount(Compiler compiler); + int optionalParameterCount(Compiler compiler); + int parameterCount(Compiler compiler); + + FunctionExpression parseNode(DiagnosticListener listener); +} + +abstract class ConstructorBodyElement extends FunctionElement { + FunctionElement get constructor; +} + +/** + * [TypeDeclarationElement] defines the common interface for class/interface + * declarations and typedefs. + */ +abstract class TypeDeclarationElement extends Element { + GenericType get rawType; + + /** + * The type variables declared on this declaration. The type variables are not + * available until the type of the element has been computed through + * [computeType]. + */ + Link<DartType> get typeVariables; +} + +abstract class ClassElement extends TypeDeclarationElement + implements ScopeContainerElement { + int get id; + + InterfaceType get rawType; + InterfaceType get thisType; + + ClassElement get superclass; + + DartType get supertype; + Link<DartType> get allSupertypes; + Link<DartType> get interfaces; + + bool get hasConstructor; + Link<Element> get constructors; + + ClassElement get patch; + ClassElement get origin; + ClassElement get declaration; + ClassElement get implementation; + + int get supertypeLoadState; + int get resolutionState; + SourceString get nativeTagInfo; + + bool get isMixinApplication; + bool get hasBackendMembers; + bool get hasLocalScopeMembers; + + // TODO(kasperl): These are bit fishy. Do we really need them? + void set rawType(InterfaceType value); + void set thisType(InterfaceType value); + void set supertype(DartType value); + void set allSupertypes(Link<DartType> value); + void set interfaces(Link<DartType> value); + void set patch(ClassElement value); + void set origin(ClassElement value); + void set supertypeLoadState(int value); + void set resolutionState(int value); + void set nativeTagInfo(SourceString value); + + // TODO(kasperl): These seem outdated. + bool isInterface(); + DartType get defaultClass; + void set defaultClass(DartType value); + + bool isObject(Compiler compiler); + bool isSubclassOf(ClassElement cls); + bool implementsInterface(ClassElement intrface); + bool isShadowedByField(Element fieldMember); + + ClassElement ensureResolved(Compiler compiler); + + void addMember(Element element, DiagnosticListener listener); + void addToScope(Element element, DiagnosticListener listener); + + /** + * Add a synthetic nullary constructor if there are no other + * constructors. + */ + void addDefaultConstructorIfNeeded(Compiler compiler); + + void addBackendMember(Element element); + void reverseBackendMembers(); + + Element lookupMember(SourceString memberName); + Element lookupSelector(Selector selector); + + Element lookupLocalMember(SourceString memberName); + Element lookupBackendMember(SourceString memberName); + Element lookupSuperMember(SourceString memberName); + + Element lookupSuperMemberInLibrary(SourceString memberName, + LibraryElement library); + + Element lookupSuperInterfaceMember(SourceString memberName, + LibraryElement fromLibrary); + + Element validateConstructorLookupResults(Selector selector, + Element result, + Element noMatch(Element)); + + Element lookupConstructor(Selector selector, [Element noMatch(Element)]); + Element lookupFactoryConstructor(Selector selector, + [Element noMatch(Element)]); + + void forEachMember(void f(ClassElement enclosingClass, Element member), + {includeBackendMembers: false, + includeSuperMembers: false}); + + void forEachInstanceField(void f(ClassElement enclosingClass, Element field), + {includeBackendMembers: false, + includeSuperMembers: false}); + + void forEachLocalMember(void f(Element member)); + void forEachBackendMember(void f(Element member)); +} + +abstract class MixinApplicationElement extends ClassElement { + ClassElement get mixin; + void set mixin(ClassElement value); +} + +abstract class LabelElement extends Element { + Label get label; + String get labelName; + TargetElement get target; + + bool get isTarget; + bool get isBreakTarget; + bool get isContinueTarget; + + void setBreakTarget(); + void setContinueTarget(); +} + +abstract class TargetElement extends Element { + Node get statement; + int get nestingLevel; + Link<LabelElement> get labels; + + bool get isTarget; + bool get isBreakTarget; + bool get isContinueTarget; + bool get isSwitch; + + // TODO(kasperl): Try to get rid of these. + void set isBreakTarget(bool value); + void set isContinueTarget(bool value); + + LabelElement addLabel(Label label, String labelName); +} + +abstract class TypeVariableElement extends Element { + TypeVariableType get type; + DartType get bound; + + // TODO(kasperl): Try to get rid of these. + void set type(TypeVariableType value); + void set bound(DartType value); +} + +abstract class MetadataAnnotation implements Spannable { + Constant get value; + Element get annotatedElement; + int get resolutionState; + Token get beginToken; + Token get endToken; + + // TODO(kasperl): Try to get rid of these. + void set annotatedElement(Element value); + void set resolutionState(int value); + + MetadataAnnotation ensureResolved(Compiler compiler); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart b/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart new file mode 100644 index 0000000..f007946 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart
@@ -0,0 +1,1981 @@ +// Copyright (c) 2013, 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. + +library elements.modelx; + +import 'dart:uri'; + +import 'elements.dart'; +import '../../compiler.dart' as api; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../resolution/resolution.dart'; + +import '../dart2jslib.dart' show invariant, + InterfaceType, + DartType, + TypeVariableType, + TypedefType, + MessageKind, + DiagnosticListener, + Script, + FunctionType, + SourceString, + Selector, + Constant, + Compiler; + +import '../dart_types.dart'; + +import '../scanner/scannerlib.dart' show Token, EOF_TOKEN; + + +class ElementX implements Element { + static int elementHashCode = 0; + + final SourceString name; + final ElementKind kind; + final Element enclosingElement; + final int hashCode = ++elementHashCode; + Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>(); + + ElementX(this.name, this.kind, this.enclosingElement) { + assert(isErroneous() || getImplementationLibrary() != null); + } + + Modifiers get modifiers => Modifiers.EMPTY; + + Node parseNode(DiagnosticListener listener) { + listener.internalErrorOnElement(this, 'not implemented'); + } + + DartType computeType(Compiler compiler) { + compiler.internalError("$this.computeType.", token: position()); + } + + void addMetadata(MetadataAnnotation annotation) { + assert(annotation.annotatedElement == null); + annotation.annotatedElement = this; + metadata = metadata.prepend(annotation); + } + + bool isFunction() => identical(kind, ElementKind.FUNCTION); + bool isConstructor() => isFactoryConstructor() || isGenerativeConstructor(); + bool isClosure() => false; + bool isMember() { + // Check that this element is defined in the scope of a Class. + return enclosingElement != null && enclosingElement.isClass(); + } + bool isInstanceMember() => false; + + /** + * Returns [:true:] if this element is enclosed in a static member or is + * itself a static member. + */ + bool isInStaticMember() { + Element member = getEnclosingMember(); + return member != null && member.modifiers.isStatic(); + } + + bool isFactoryConstructor() => modifiers.isFactory(); + bool isGenerativeConstructor() => + identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR); + bool isGenerativeConstructorBody() => + identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY); + bool isCompilationUnit() => identical(kind, ElementKind.COMPILATION_UNIT); + bool isClass() => identical(kind, ElementKind.CLASS); + bool isPrefix() => identical(kind, ElementKind.PREFIX); + bool isVariable() => identical(kind, ElementKind.VARIABLE); + bool isParameter() => identical(kind, ElementKind.PARAMETER); + bool isStatement() => identical(kind, ElementKind.STATEMENT); + bool isTypedef() => identical(kind, ElementKind.TYPEDEF); + bool isTypeVariable() => identical(kind, ElementKind.TYPE_VARIABLE); + bool isField() => identical(kind, ElementKind.FIELD); + bool isAbstractField() => identical(kind, ElementKind.ABSTRACT_FIELD); + bool isGetter() => identical(kind, ElementKind.GETTER); + bool isSetter() => identical(kind, ElementKind.SETTER); + bool isAccessor() => isGetter() || isSetter(); + bool isLibrary() => identical(kind, ElementKind.LIBRARY); + bool impliesType() => (kind.category & ElementCategory.IMPLIES_TYPE) != 0; + + /** See [ErroneousElement] for documentation. */ + bool isErroneous() => false; + + /** See [AmbiguousElement] for documentation. */ + bool isAmbiguous() => false; + + /** + * Is [:true:] if this element has a corresponding patch. + * + * If [:true:] this element has a non-null [patch] field. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isPatched => false; + + /** + * Is [:true:] if this element is a patch. + * + * If [:true:] this element has a non-null [origin] field. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isPatch => false; + + /** + * Is [:true:] if this element defines the implementation for the entity of + * this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isImplementation => !isPatched; + + /** + * Is [:true:] if this element introduces the entity of this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isDeclaration => !isPatch; + + bool get isSynthesized => false; + + /** + * Returns the element which defines the implementation for the entity of this + * element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + Element get implementation => isPatched ? patch : this; + + /** + * Returns the element which introduces the entity of this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + Element get declaration => isPatch ? origin : this; + + Element get patch { + throw new UnsupportedError('patch is not supported on $this'); + } + + Element get origin { + throw new UnsupportedError('origin is not supported on $this'); + } + + // TODO(johnniwinther): This breaks for libraries (for which enclosing + // elements are null) and is invalid for top level variable declarations for + // which the enclosing element is a VariableDeclarations and not a compilation + // unit. + bool isTopLevel() { + return enclosingElement != null && enclosingElement.isCompilationUnit(); + } + + bool isAssignable() { + if (modifiers.isFinalOrConst()) return false; + if (isFunction() || isGenerativeConstructor()) return false; + return true; + } + + Token position() => null; + + Token findMyName(Token token) { + for (Token t = token; !identical(t.kind, EOF_TOKEN); t = t.next) { + if (t.value == name) return t; + } + return token; + } + + CompilationUnitElement getCompilationUnit() { + Element element = this; + while (!element.isCompilationUnit()) { + element = element.enclosingElement; + } + return element; + } + + LibraryElement getLibrary() => enclosingElement.getLibrary(); + + LibraryElement getImplementationLibrary() { + Element element = this; + while (!identical(element.kind, ElementKind.LIBRARY)) { + element = element.enclosingElement; + } + return element; + } + + ClassElement getEnclosingClass() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isClass()) return e; + } + return null; + } + + Element getEnclosingClassOrCompilationUnit() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isClass() || e.isCompilationUnit()) return e; + } + return null; + } + + /** + * Returns the member enclosing this element or the element itself if it is a + * member. If no enclosing element is found, [:null:] is returned. + */ + Element getEnclosingMember() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isMember()) return e; + } + return null; + } + + Element getOutermostEnclosingMemberOrTopLevel() { + // TODO(lrn): Why is this called "Outermost"? + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isMember() || e.isTopLevel()) { + return e; + } + } + return null; + } + + /** + * Creates the scope for this element. + */ + Scope buildScope() => enclosingElement.buildScope(); + + String toString() { + // TODO(johnniwinther): Test for nullness of name, or make non-nullness an + // invariant for all element types? + var nameText = name != null ? name.slowToString() : '?'; + if (enclosingElement != null && !isTopLevel()) { + String holderName = enclosingElement.name != null + ? enclosingElement.name.slowToString() + : '${enclosingElement.kind}?'; + return '$kind($holderName#${nameText})'; + } else { + return '$kind(${nameText})'; + } + } + + String _fixedBackendName = null; + bool _isNative = false; + bool isNative() => _isNative; + bool hasFixedBackendName() => _fixedBackendName != null; + String fixedBackendName() => _fixedBackendName; + // Marks this element as a native element. + void setNative(String name) { + _isNative = true; + _fixedBackendName = name; + } + void setFixedBackendName(String name) { + _fixedBackendName = name; + } + + FunctionElement asFunctionElement() => null; + + static bool isInvalid(Element e) => e == null || e.isErroneous(); + + bool isAbstract(Compiler compiler) => modifiers.isAbstract(); + bool isForeign(Compiler compiler) => getLibrary() == compiler.foreignLibrary; +} + +/** + * Represents an unresolvable or duplicated element. + * + * An [ErroneousElement] is used instead of [null] to provide additional + * information about the error that caused the element to be unresolvable + * or otherwise invalid. + * + * Accessing any field or calling any method defined on [ErroneousElement] + * except [isErroneous] will currently throw an exception. (This might + * change when we actually want more information on the erroneous element, + * e.g., the name of the element we were trying to resolve.) + * + * Code that cannot not handle an [ErroneousElement] should use + * [: Element.isInvalid(element) :] + * to check for unresolvable elements instead of + * [: element == null :]. + */ +class ErroneousElementX extends ElementX implements ErroneousElement { + final MessageKind messageKind; + final Map messageArguments; + + ErroneousElementX(this.messageKind, this.messageArguments, + SourceString name, Element enclosing) + : super(name, ElementKind.ERROR, enclosing); + + isErroneous() => true; + + unsupported() { + throw 'unsupported operation on erroneous element'; + } + + Link<MetadataAnnotation> get metadata => unsupported(); + get type => unsupported(); + get cachedNode => unsupported(); + get functionSignature => unsupported(); + get patch => unsupported(); + get origin => unsupported(); + get defaultImplementation => unsupported(); + + bool get isPatched => unsupported(); + bool get isPatch => unsupported(); + + setPatch(patch) => unsupported(); + computeSignature(compiler) => unsupported(); + requiredParameterCount(compiler) => unsupported(); + optionalParameterCount(compiler) => unsupported(); + parameterCount(compiler) => unsupported(); + + // TODO(kasperl): These seem unnecessary. + set patch(value) => unsupported(); + set origin(value) => unsupported(); + set defaultImplementation(value) => unsupported(); + + get redirectionTarget => this; + + getLibrary() => enclosingElement.getLibrary(); + + String toString() { + String n = name.slowToString(); + return '<$n: ${messageKind.message(messageArguments)}>'; + } +} + +/** + * An ambiguous element represents multiple elements accessible by the same name. + * + * Ambiguous elements are created during handling of import/export scopes. If an + * ambiguous element is encountered during resolution a warning/error should be + * reported. + */ +class AmbiguousElementX extends ElementX implements AmbiguousElement { + /** + * The message to report on resolving this element. + */ + final MessageKind messageKind; + + /** + * The message arguments to report on resolving this element. + */ + final Map messageArguments; + + /** + * The first element that this ambiguous element might refer to. + */ + final Element existingElement; + + /** + * The second element that this ambiguous element might refer to. + */ + final Element newElement; + + AmbiguousElementX(this.messageKind, this.messageArguments, + Element enclosingElement, Element existingElement, Element newElement) + : this.existingElement = existingElement, + this.newElement = newElement, + super(existingElement.name, ElementKind.AMBIGUOUS, enclosingElement); + + bool isAmbiguous() => true; +} + +class ScopeX { + final Map<SourceString, Element> contents = new Map<SourceString, Element>(); + + bool get isEmpty => contents.isEmpty; + Iterable<Element> get values => contents.values; + + Element lookup(SourceString name) { + return contents[name]; + } + + void add(Element element, DiagnosticListener listener) { + if (element.isAccessor()) { + addAccessor(element, contents[element.name], listener); + } else { + Element existing = contents.putIfAbsent(element.name, () => element); + if (!identical(existing, element)) { + // TODO(ahe): Do something similar to Resolver.reportErrorWithContext. + listener.cancel('duplicate definition', token: element.position()); + listener.cancel('existing definition', token: existing.position()); + } + } + } + + /** + * Adds a definition for an [accessor] (getter or setter) to a scope. + * The definition binds to an abstract field that can hold both a getter + * and a setter. + * + * The abstract field is added once, for the first getter or setter, and + * reused if the other one is also added. + * The abstract field should not be treated as a proper member of the + * container, it's simply a way to return two results for one lookup. + * That is, the getter or setter does not have the abstract field as enclosing + * element, they are enclosed by the class or compilation unit, as is the + * abstract field. + */ + void addAccessor(Element accessor, + Element existing, + DiagnosticListener listener) { + void reportError(Element other) { + // TODO(ahe): Do something similar to Resolver.reportErrorWithContext. + listener.cancel('duplicate definition of ${accessor.name.slowToString()}', + element: accessor); + listener.cancel('existing definition', element: other); + } + + if (existing != null) { + if (!identical(existing.kind, ElementKind.ABSTRACT_FIELD)) { + reportError(existing); + } else { + AbstractFieldElementX field = existing; + if (accessor.isGetter()) { + if (field.getter != null && field.getter != accessor) { + reportError(field.getter); + } + field.getter = accessor; + } else { + assert(accessor.isSetter()); + if (field.setter != null && field.setter != accessor) { + reportError(field.setter); + } + field.setter = accessor; + } + } + } else { + Element container = accessor.getEnclosingClassOrCompilationUnit(); + AbstractFieldElementX field = + new AbstractFieldElementX(accessor.name, container); + if (accessor.isGetter()) { + field.getter = accessor; + } else { + field.setter = accessor; + } + add(field, listener); + } + } +} + +class CompilationUnitElementX extends ElementX + implements CompilationUnitElement { + final Script script; + PartOf partTag; + Link<Element> localMembers = const Link<Element>(); + + CompilationUnitElementX(Script script, LibraryElement library) + : this.script = script, + super(new SourceString(script.name), + ElementKind.COMPILATION_UNIT, + library) { + library.addCompilationUnit(this); + } + + void addMember(Element element, DiagnosticListener listener) { + // Keep a list of top level members. + localMembers = localMembers.prepend(element); + // Provide the member to the library to build scope. + if (enclosingElement.isPatch) { + getImplementationLibrary().addMember(element, listener); + } else { + getLibrary().addMember(element, listener); + } + } + + void setPartOf(PartOf tag, DiagnosticListener listener) { + LibraryElementX library = enclosingElement; + if (library.entryCompilationUnit == this) { + listener.reportMessage( + listener.spanFromSpannable(tag), + MessageKind.ILLEGAL_DIRECTIVE.error(), + api.Diagnostic.WARNING); + return; + } + if (!localMembers.isEmpty) { + listener.reportErrorCode(tag, MessageKind.BEFORE_TOP_LEVEL); + return; + } + if (partTag != null) { + listener.reportMessage( + listener.spanFromSpannable(tag), + MessageKind.DUPLICATED_PART_OF.error(), + api.Diagnostic.WARNING); + return; + } + partTag = tag; + LibraryName libraryTag = getLibrary().libraryTag; + if (libraryTag != null) { + String actualName = tag.name.toString(); + String expectedName = libraryTag.name.toString(); + if (expectedName != actualName) { + listener.reportMessage( + listener.spanFromSpannable(tag.name), + MessageKind.LIBRARY_NAME_MISMATCH.error( + {'libraryName': expectedName}), + api.Diagnostic.WARNING); + } + } + } + + bool get hasMembers => !localMembers.isEmpty; +} + +class LibraryElementX extends ElementX implements LibraryElement { + final Uri canonicalUri; + CompilationUnitElement entryCompilationUnit; + Link<CompilationUnitElement> compilationUnits = + const Link<CompilationUnitElement>(); + Link<LibraryTag> tags = const Link<LibraryTag>(); + LibraryName libraryTag; + bool canUseNative = false; + Link<Element> localMembers = const Link<Element>(); + final ScopeX localScope = new ScopeX(); + + /** + * If this library is patched, [patch] points to the patch library. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + LibraryElementX patch = null; + + /** + * If this is a patch library, [origin] points to the origin library. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + final LibraryElementX origin; + + /** + * Map for elements imported through import declarations. + * + * Addition to the map is performed by [addImport]. Lookup is done trough + * [find]. + */ + final Map<SourceString, Element> importScope; + + /** + * Link for elements exported either through export declarations or through + * declaration. This field should not be accessed directly but instead through + * the [exports] getter. + * + * [LibraryDependencyHandler] sets this field through [setExports] when the + * library is loaded. + */ + Link<Element> slotForExports; + + LibraryElementX(Script script, [Uri canonicalUri, LibraryElement this.origin]) + : this.canonicalUri = ((canonicalUri == null) ? script.uri : canonicalUri), + importScope = new Map<SourceString, Element>(), + super(new SourceString(script.name), ElementKind.LIBRARY, null) { + entryCompilationUnit = new CompilationUnitElementX(script, this); + if (isPatch) { + origin.patch = this; + } + } + + bool get isPatched => patch != null; + bool get isPatch => origin != null; + + LibraryElement get declaration => super.declaration; + LibraryElement get implementation => super.implementation; + + CompilationUnitElement getCompilationUnit() => entryCompilationUnit; + + void addCompilationUnit(CompilationUnitElement element) { + compilationUnits = compilationUnits.prepend(element); + } + + void addTag(LibraryTag tag, DiagnosticListener listener) { + tags = tags.prepend(tag); + } + + /** + * Adds [element] to the import scope of this library. + * + * If an element by the same name is already in the imported scope, an + * [ErroneousElement] will be put in the imported scope, allowing for the + * detection of ambiguous uses of imported names. + */ + void addImport(Element element, DiagnosticListener listener) { + Element existing = importScope[element.name]; + if (existing != null) { + // TODO(johnniwinther): Provide access to the import tags from which + // the elements came. + importScope[element.name] = new AmbiguousElementX( + MessageKind.DUPLICATE_IMPORT, {'name': element.name}, + this, existing, element); + } else { + importScope[element.name] = element; + } + } + + void addMember(Element element, DiagnosticListener listener) { + localMembers = localMembers.prepend(element); + addToScope(element, listener); + } + + void addToScope(Element element, DiagnosticListener listener) { + localScope.add(element, listener); + } + + Element localLookup(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result == null && isPatch) { + result = origin.localLookup(elementName); + } + return result; + } + + /** + * Returns [:true:] if the export scope has already been computed for this + * library. + */ + bool get exportsHandled => slotForExports != null; + + Link<Element> get exports { + assert(invariant(this, exportsHandled, + message: 'Exports not handled on $this')); + return slotForExports; + } + + /** + * Sets the export scope of this library. This method can only be called once. + */ + void setExports(Iterable<Element> exportedElements) { + assert(invariant(this, !exportsHandled, + message: 'Exports already set to $slotForExports on $this')); + assert(invariant(this, exportedElements != null)); + var builder = new LinkBuilder<Element>(); + for (Element export in exportedElements) { + builder.addLast(export); + } + slotForExports = builder.toLink(); + } + + LibraryElement getLibrary() => isPatch ? origin : this; + + /** + * Look up a top-level element in this library. The element could + * potentially have been imported from another library. Returns + * null if no such element exist and an [ErroneousElement] if multiple + * elements have been imported. + */ + Element find(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result != null) return result; + if (origin != null) { + result = origin.localScope.lookup(elementName); + if (result != null) return result; + } + result = importScope[elementName]; + if (result != null) return result; + if (origin != null) { + result = origin.importScope[elementName]; + if (result != null) return result; + } + return null; + } + + /** Look up a top-level element in this library, but only look for + * non-imported elements. Returns null if no such element exist. */ + Element findLocal(SourceString elementName) { + // TODO(johnniwinther): How to handle injected elements in the patch + // library? + Element result = localScope.lookup(elementName); + if (result == null || result.getLibrary() != this) return null; + return result; + } + + void forEachExport(f(Element element)) { + exports.forEach((Element e) => f(e)); + } + + void forEachLocalMember(f(Element element)) { + if (isPatch) { + // Patch libraries traverse both origin and injected members. + origin.localMembers.forEach(f); + + void filterPatch(Element element) { + if (!element.isPatch) { + // Do not traverse the patch members. + f(element); + } + } + localMembers.forEach(filterPatch); + } else { + localMembers.forEach(f); + } + } + + Iterable<Element> getNonPrivateElementsInScope() { + return localScope.values.where((Element element) { + // At this point [localScope] only contains members so we don't need + // to check for foreign or prefix elements. + return !element.name.isPrivate(); + }); + } + + bool hasLibraryName() => libraryTag != null; + + /** + * Returns the library name (as defined by the #library tag) or for script + * (which have no #library tag) the script file name. The latter case is used + * to private 'library name' for scripts to use for instance in dartdoc. + */ + String getLibraryOrScriptName() { + if (libraryTag != null) { + return libraryTag.name.toString(); + } else { + // Use the file name as script name. + String path = canonicalUri.path; + return path.substring(path.lastIndexOf('/') + 1); + } + } + + Scope buildScope() => new LibraryScope(this); + + bool get isPlatformLibrary => canonicalUri.scheme == "dart"; + + bool get isInternalLibrary => + isPlatformLibrary && canonicalUri.path.startsWith('_'); + + String toString() { + if (origin != null) { + return 'patch library(${getLibraryOrScriptName()})'; + } else if (patch != null) { + return 'origin library(${getLibraryOrScriptName()})'; + } else { + return 'library(${getLibraryOrScriptName()})'; + } + } +} + +class PrefixElementX extends ElementX implements PrefixElement { + Map<SourceString, Element> imported; + Token firstPosition; + + PrefixElementX(SourceString prefix, Element enclosing, this.firstPosition) + : imported = new Map<SourceString, Element>(), + super(prefix, ElementKind.PREFIX, enclosing); + + Element lookupLocalMember(SourceString memberName) => imported[memberName]; + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; + + Token position() => firstPosition; +} + +class TypedefElementX extends ElementX implements TypedefElement { + Typedef cachedNode; + TypedefType cachedType; + + /** + * Canonicalize raw version of [cachedType]. + * + * See [ClassElement.rawType] for motivation. + * + * The [rawType] is computed together with [cachedType] in [computeType]. + */ + TypedefType rawType; + + /** + * The type annotation which defines this typedef. + */ + DartType alias; + + bool isResolved = false; + bool isBeingResolved = false; + + TypedefElementX(SourceString name, Element enclosing) + : super(name, ElementKind.TYPEDEF, enclosing); + + /** + * Function signature for a typedef of a function type. The signature is + * kept to provide full information about parameter names through the mirror + * system. + * + * The [functionSignature] is not available until the typedef element has been + * resolved. + */ + FunctionSignature functionSignature; + + TypedefType computeType(Compiler compiler) { + if (cachedType != null) return cachedType; + Typedef node = parseNode(compiler); + Link<DartType> parameters = + TypeDeclarationElementX.createTypeVariables(this, node.typeParameters); + cachedType = new TypedefType(this, parameters); + if (parameters.isEmpty) { + rawType = cachedType; + } else { + var dynamicParameters = const Link<DartType>(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new TypedefType(this, dynamicParameters); + } + compiler.resolveTypedef(this); + return cachedType; + } + + Link<DartType> get typeVariables => cachedType.typeArguments; + + Scope buildScope() { + return new TypeDeclarationScope(enclosingElement.buildScope(), this); + } +} + +class VariableElementX extends ElementX implements VariableElement { + final VariableListElement variables; + Expression cachedNode; // The send or the identifier in the variables list. + + Modifiers get modifiers => variables.modifiers; + + VariableElementX(SourceString name, + VariableListElement variables, + ElementKind kind, + this.cachedNode) + : this.variables = variables, + super(name, kind, variables.enclosingElement); + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + VariableDefinitions definitions = variables.parseNode(listener); + for (Link<Node> link = definitions.definitions.nodes; + !link.isEmpty; link = link.tail) { + Expression initializedIdentifier = link.head; + Identifier identifier = initializedIdentifier.asIdentifier(); + if (identifier == null) { + identifier = initializedIdentifier.asSendSet().selector.asIdentifier(); + } + if (identical(name, identifier.source)) { + cachedNode = initializedIdentifier; + return cachedNode; + } + } + listener.cancel('internal error: could not find $name', node: variables); + } + + DartType computeType(Compiler compiler) { + return variables.computeType(compiler); + } + + DartType get type => variables.type; + + bool isInstanceMember() => variables.isInstanceMember(); + + // Note: cachedNode.getBeginToken() will not be correct in all + // cases, for example, for function typed parameters. + Token position() => findMyName(variables.position()); +} + +/** + * Parameters in constructors that directly initialize fields. For example: + * [:A(this.field):]. + */ +class FieldParameterElementX extends VariableElementX + implements FieldParameterElement { + VariableElement fieldElement; + + FieldParameterElementX(SourceString name, + this.fieldElement, + VariableListElement variables, + Node node) + : super(name, variables, ElementKind.FIELD_PARAMETER, node); +} + +// This element represents a list of variable or field declaration. +// It contains the node, and the type. A [VariableElement] always +// references its [VariableListElement]. It forwards its +// [computeType] and [parseNode] methods to this element. +class VariableListElementX extends ElementX implements VariableListElement { + VariableDefinitions cachedNode; + DartType type; + final Modifiers modifiers; + + /** + * Function signature for a variable with a function type. The signature is + * kept to provide full information about parameter names through the mirror + * system. + */ + FunctionSignature functionSignature; + + VariableListElementX(ElementKind kind, + Modifiers this.modifiers, + Element enclosing) + : super(null, kind, enclosing); + + VariableListElementX.node(VariableDefinitions node, + ElementKind kind, + Element enclosing) + : super(null, kind, enclosing), + this.cachedNode = node, + this.modifiers = node.modifiers { + assert(modifiers != null); + } + + VariableDefinitions parseNode(DiagnosticListener listener) { + return cachedNode; + } + + DartType computeType(Compiler compiler) { + if (type != null) return type; + compiler.withCurrentElement(this, () { + VariableDefinitions node = parseNode(compiler); + if (node.type != null) { + type = compiler.resolveTypeAnnotation(this, node.type); + } else { + // Is node.definitions exactly one FunctionExpression? + Link<Node> link = node.definitions.nodes; + if (!link.isEmpty && + link.head.asFunctionExpression() != null && + link.tail.isEmpty) { + FunctionExpression functionExpression = link.head; + // We found exactly one FunctionExpression + functionSignature = + compiler.resolveFunctionExpression(this, functionExpression); + type = compiler.computeFunctionType(compiler.functionClass, + functionSignature); + } else { + type = compiler.types.dynamicType; + } + } + }); + assert(type != null); + return type; + } + + Token position() => cachedNode.getBeginToken(); + + bool isInstanceMember() { + return isMember() && !modifiers.isStatic(); + } +} + +class AbstractFieldElementX extends ElementX implements AbstractFieldElement { + FunctionElement getter; + FunctionElement setter; + + AbstractFieldElementX(SourceString name, Element enclosing) + : super(name, ElementKind.ABSTRACT_FIELD, enclosing); + + DartType computeType(Compiler compiler) { + throw "internal error: AbstractFieldElement has no type"; + } + + Node parseNode(DiagnosticListener listener) { + throw "internal error: AbstractFieldElement has no node"; + } + + position() { + // The getter and setter may be defined in two different + // compilation units. However, we know that one of them is + // non-null and defined in the same compilation unit as the + // abstract element. + // TODO(lrn): No we don't know that if the element from the same + // compilation unit is patched. + // + // We need to make sure that the position returned is relative to + // the compilation unit of the abstract element. + if (getter != null + && identical(getter.getCompilationUnit(), getCompilationUnit())) { + return getter.position(); + } else { + return setter.position(); + } + } + + Modifiers get modifiers { + // The resolver ensures that the flags match (ignoring abstract). + if (getter != null) { + return new Modifiers.withFlags( + getter.modifiers.nodes, + getter.modifiers.flags | Modifiers.FLAG_ABSTRACT); + } else { + return new Modifiers.withFlags( + setter.modifiers.nodes, + setter.modifiers.flags | Modifiers.FLAG_ABSTRACT); + } + } +} + +// TODO(johnniwinther): [FunctionSignature] should be merged with +// [FunctionType]. +class FunctionSignatureX implements FunctionSignature { + final Link<Element> requiredParameters; + final Link<Element> optionalParameters; + final DartType returnType; + final int requiredParameterCount; + final int optionalParameterCount; + final bool optionalParametersAreNamed; + + List<Element> _orderedOptionalParameters; + + FunctionSignatureX(this.requiredParameters, + this.optionalParameters, + this.requiredParameterCount, + this.optionalParameterCount, + this.optionalParametersAreNamed, + this.returnType); + + void forEachRequiredParameter(void function(Element parameter)) { + for (Link<Element> link = requiredParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + void forEachOptionalParameter(void function(Element parameter)) { + for (Link<Element> link = optionalParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + List<Element> get orderedOptionalParameters { + if (_orderedOptionalParameters != null) return _orderedOptionalParameters; + List<Element> list = new List<Element>.from(optionalParameters); + if (optionalParametersAreNamed) { + list.sort((Element a, Element b) { + return a.name.slowToString().compareTo(b.name.slowToString()); + }); + } + _orderedOptionalParameters = list; + return list; + } + + void forEachParameter(void function(Element parameter)) { + forEachRequiredParameter(function); + forEachOptionalParameter(function); + } + + void orderedForEachParameter(void function(Element parameter)) { + forEachRequiredParameter(function); + orderedOptionalParameters.forEach(function); + } + + int get parameterCount => requiredParameterCount + optionalParameterCount; +} + +class FunctionElementX extends ElementX implements FunctionElement { + FunctionExpression cachedNode; + DartType type; + final Modifiers modifiers; + + FunctionSignature functionSignature; + + /** + * A function declaration that should be parsed instead of the current one. + * The patch should be parsed as if it was in the current scope. Its + * signature must match this function's signature. + */ + // TODO(lrn): Consider using [defaultImplementation] to store the patch. + FunctionElement patch = null; + FunctionElement origin = null; + + /** + * If this is a redirecting factory, [defaultImplementation] will be + * changed by the resolver to point to the redirection target. If + * this is an interface constructor, [defaultImplementation] will be + * changed by the resolver to point to the default implementation. + * Otherwise, [:identical(defaultImplementation, this):]. + */ + // TODO(ahe): Rename this field to redirectionTarget and remove + // mention of interface constructors above. + FunctionElement defaultImplementation; + + FunctionElementX(SourceString name, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : this.tooMuchOverloading(name, null, kind, modifiers, enclosing, null); + + FunctionElementX.node(SourceString name, + FunctionExpression node, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : this.tooMuchOverloading(name, node, kind, modifiers, enclosing, null); + + FunctionElementX.from(SourceString name, + FunctionElement other, + Element enclosing) + : this.tooMuchOverloading(name, other.cachedNode, other.kind, + other.modifiers, enclosing, + other.functionSignature); + + FunctionElementX.tooMuchOverloading(SourceString name, + FunctionExpression this.cachedNode, + ElementKind kind, + Modifiers this.modifiers, + Element enclosing, + FunctionSignature this.functionSignature) + : super(name, kind, enclosing) { + assert(modifiers != null); + defaultImplementation = this; + } + + bool get isPatched => patch != null; + bool get isPatch => origin != null; + + FunctionElement get redirectionTarget { + if (this == defaultImplementation) return this; + var target = defaultImplementation; + Set<Element> seen = new Set<Element>(); + seen.add(target); + while (!target.isErroneous() && target != target.defaultImplementation) { + target = target.defaultImplementation; + if (seen.contains(target)) { + // TODO(ahe): This is expedient for now, but it should be + // checked by the resolver. Keeping http://dartbug.com/3970 + // open to track this. + throw new SpannableAssertionFailure( + target, 'redirecting factory leads to cycle'); + } + } + return target; + } + + /** + * Applies a patch function to this function. The patch function's body + * is used as replacement when parsing this function's body. + * This method must not be called after the function has been parsed, + * and it must be called at most once. + */ + void setPatch(FunctionElement patchElement) { + // Sanity checks. The caller must check these things before calling. + assert(patch == null); + this.patch = patchElement; + } + + bool isInstanceMember() { + return isMember() + && !isConstructor() + && !modifiers.isStatic(); + } + + FunctionSignature computeSignature(Compiler compiler) { + if (functionSignature != null) return functionSignature; + compiler.withCurrentElement(this, () { + functionSignature = compiler.resolveSignature(this); + }); + return functionSignature; + } + + int requiredParameterCount(Compiler compiler) { + return computeSignature(compiler).requiredParameterCount; + } + + int optionalParameterCount(Compiler compiler) { + return computeSignature(compiler).optionalParameterCount; + } + + int parameterCount(Compiler compiler) { + return computeSignature(compiler).parameterCount; + } + + FunctionType computeType(Compiler compiler) { + if (type != null) return type; + type = compiler.computeFunctionType(declaration, + computeSignature(compiler)); + return type; + } + + FunctionExpression parseNode(DiagnosticListener listener) { + if (patch == null) { + if (modifiers.isExternal()) { + listener.cancel("Compiling external function with no implementation.", + element: this); + } + } + return cachedNode; + } + + Token position() => cachedNode.getBeginToken(); + + FunctionElement asFunctionElement() => this; + + String toString() { + if (isPatch) { + return 'patch ${super.toString()}'; + } else if (isPatched) { + return 'origin ${super.toString()}'; + } else { + return super.toString(); + } + } + + bool isAbstract(Compiler compiler) { + if (super.isAbstract(compiler)) return true; + if (modifiers.isExternal()) return false; + if (isFunction() || isAccessor()) { + return !parseNode(compiler).hasBody(); + } + return false; + } +} + +class ConstructorBodyElementX extends FunctionElementX + implements ConstructorBodyElement { + FunctionElement constructor; + + ConstructorBodyElementX(FunctionElement constructor) + : this.constructor = constructor, + super(constructor.name, + ElementKind.GENERATIVE_CONSTRUCTOR_BODY, + Modifiers.EMPTY, + constructor.enclosingElement) { + functionSignature = constructor.functionSignature; + } + + bool isInstanceMember() => true; + + FunctionType computeType(Compiler compiler) { + compiler.reportFatalError('Internal error: $this.computeType', this); + } + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = constructor.parseNode(listener); + assert(cachedNode != null); + return cachedNode; + } + + Token position() => constructor.position(); +} + +class SynthesizedConstructorElementX extends FunctionElementX { + SynthesizedConstructorElementX(Element enclosing) + : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR, + Modifiers.EMPTY, enclosing); + + SynthesizedConstructorElementX.forDefault(Element enclosing, + Compiler compiler) + : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR, + Modifiers.EMPTY, enclosing) { + type = new FunctionType(this, + compiler.types.voidType, + const Link<DartType>(), + const Link<DartType>(), + const Link<SourceString>(), + const Link<DartType>()); + cachedNode = new FunctionExpression( + new Identifier(enclosing.position()), + new NodeList.empty(), + new Block(new NodeList.empty()), + null, Modifiers.EMPTY, null, null); + } + + bool get isSynthesized => true; + + Token position() => enclosingElement.position(); +} + +class VoidElementX extends ElementX { + VoidElementX(Element enclosing) + : super(const SourceString('void'), ElementKind.VOID, enclosing); + DartType computeType(compiler) => compiler.types.voidType; + Node parseNode(_) { + throw 'internal error: parseNode on void'; + } + bool impliesType() => true; +} + +class TypeDeclarationElementX { + /** + * Creates the type variables, their type and corresponding element, for the + * type variables declared in [parameter] on [element]. The bounds of the type + * variables are not set until [element] has been resolved. + */ + static Link<DartType> createTypeVariables(TypeDeclarationElement element, + NodeList parameters) { + if (parameters == null) return const Link<DartType>(); + + // Create types and elements for type variable. + var arguments = new LinkBuilder<DartType>(); + for (Link link = parameters.nodes; !link.isEmpty; link = link.tail) { + TypeVariable node = link.head; + SourceString variableName = node.name.source; + TypeVariableElement variableElement = + new TypeVariableElementX(variableName, element, node); + TypeVariableType variableType = new TypeVariableType(variableElement); + variableElement.type = variableType; + arguments.addLast(variableType); + } + return arguments.toLink(); + } +} + +abstract class BaseClassElementX extends ElementX implements ClassElement { + final int id; + + /** + * The type of [:this:] for this class declaration. + * + * The type of [:this:] is the interface type based on this element in which + * the type arguments are the declared type variables. For instance, + * [:List<E>:] for [:List:] and [:Map<K,V>:] for [:Map:]. + * + * This type is computed in [computeType]. + */ + InterfaceType thisType; + + /** + * The raw type for this class declaration. + * + * The raw type is the interface type base on this element in which the type + * arguments are all [dynamic]. For instance [:List<dynamic>:] for [:List:] + * and [:Map<dynamic,dynamic>:] for [:Map:]. For non-generic classes [rawType] + * is the same as [thisType]. + * + * The [rawType] field is a canonicalization of the raw type and should be + * used to distinguish explicit and implicit uses of the [dynamic] + * type arguments. For instance should [:List:] be the [rawType] of the + * [:List:] class element whereas [:List<dynamic>:] should be its own + * instantiation of [InterfaceType] with [:dynamic:] as type argument. Using + * this distinction, we can print the raw type with type arguments only when + * the input source has used explicit type arguments. + * + * This type is computed together with [thisType] in [computeType]. + */ + InterfaceType rawType; + DartType supertype; + DartType defaultClass; + Link<DartType> interfaces; + SourceString nativeTagInfo; + int supertypeLoadState; + int resolutionState; + + // backendMembers are members that have been added by the backend to simplify + // compilation. They don't have any user-side counter-part. + Link<Element> backendMembers = const Link<Element>(); + + Link<DartType> allSupertypes; + + BaseClassElementX(SourceString name, + Element enclosing, + this.id, + int initialState) + : supertypeLoadState = initialState, + resolutionState = initialState, + super(name, ElementKind.CLASS, enclosing); + + int get hashCode => id; + ClassElement get patch => super.patch; + ClassElement get origin => super.origin; + ClassElement get declaration => super.declaration; + ClassElement get implementation => super.implementation; + + bool get hasBackendMembers => !backendMembers.isEmpty; + + InterfaceType computeType(Compiler compiler) { + if (thisType == null) { + if (origin == null) { + Link<DartType> parameters = computeTypeParameters(compiler); + thisType = new InterfaceType(this, parameters); + if (parameters.isEmpty) { + rawType = thisType; + } else { + var dynamicParameters = const Link<DartType>(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new InterfaceType(this, dynamicParameters); + } + } else { + thisType = origin.computeType(compiler); + rawType = origin.rawType; + } + } + return thisType; + } + + Link<DartType> computeTypeParameters(Compiler compiler); + + /** + * Return [:true:] if this element is the [:Object:] class for the [compiler]. + */ + bool isObject(Compiler compiler) => + identical(declaration, compiler.objectClass); + + Link<DartType> get typeVariables => thisType.typeArguments; + + ClassElement ensureResolved(Compiler compiler) { + if (resolutionState == STATE_NOT_STARTED) { + compiler.resolver.resolveClass(this); + } + return this; + } + + void addDefaultConstructorIfNeeded(Compiler compiler) { + if (hasConstructor) return; + FunctionElement constructor = + new SynthesizedConstructorElementX.forDefault(this, compiler); + setDefaultConstructor(constructor, compiler); + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler); + + void addBackendMember(Element member) { + backendMembers = backendMembers.prepend(member); + } + + void reverseBackendMembers() { + backendMembers = backendMembers.reverse(); + } + + /** + * Lookup local members in the class. This will ignore constructors. + */ + Element lookupLocalMember(SourceString memberName) { + var result = localLookup(memberName); + if (result != null && result.isConstructor()) return null; + return result; + } + + /// Lookup a synthetic element created by the backend. + Element lookupBackendMember(SourceString memberName) { + for (Element element in backendMembers) { + if (element.name == memberName) { + return element; + } + } + } + /** + * Lookup super members for the class. This will ignore constructors. + */ + Element lookupSuperMember(SourceString memberName) { + return lookupSuperMemberInLibrary(memberName, getLibrary()); + } + + /** + * Lookup super members for the class that is accessible in [library]. + * This will ignore constructors. + */ + Element lookupSuperMemberInLibrary(SourceString memberName, + LibraryElement library) { + bool includeInjectedMembers = isPatch; + bool isPrivate = memberName.isPrivate(); + for (ClassElement s = superclass; s != null; s = s.superclass) { + // Private members from a different library are not visible. + if (isPrivate && !identical(library, s.getLibrary())) continue; + s = includeInjectedMembers ? s.implementation : s; + Element e = s.lookupLocalMember(memberName); + if (e == null) continue; + // Static members are not inherited. + if (e.modifiers.isStatic()) continue; + return e; + } + if (isInterface()) { + return lookupSuperInterfaceMember(memberName, getLibrary()); + } + return null; + } + + Element lookupSuperInterfaceMember(SourceString memberName, + LibraryElement fromLibrary) { + bool includeInjectedMembers = isPatch; + bool isPrivate = memberName.isPrivate(); + for (InterfaceType t in interfaces) { + ClassElement cls = t.element; + cls = includeInjectedMembers ? cls.implementation : cls; + Element e = cls.lookupLocalMember(memberName); + if (e == null) continue; + // Private members from a different library are not visible. + if (isPrivate && !identical(fromLibrary, e.getLibrary())) continue; + // Static members are not inherited. + if (e.modifiers.isStatic()) continue; + return e; + } + return null; + } + + /** + * Find the first member in the class chain with the given [selector]. + * + * This method is NOT to be used for resolving + * unqualified sends because it does not implement the scoping + * rules, where library scope comes before superclass scope. + * + * When called on the implementation element both members declared in the + * origin and the patch class are returned. + */ + Element lookupSelector(Selector selector) { + SourceString memberName = selector.name; + LibraryElement library = selector.library; + Element localMember = lookupLocalMember(memberName); + if (localMember != null && + (!memberName.isPrivate() || getLibrary() == library)) { + return localMember; + } + return lookupSuperMemberInLibrary(memberName, library); + } + + /** + * Find the first member in the class chain with the given + * [memberName]. This method is NOT to be used for resolving + * unqualified sends because it does not implement the scoping + * rules, where library scope comes before superclass scope. + */ + Element lookupMember(SourceString memberName) { + Element localMember = lookupLocalMember(memberName); + return localMember == null ? lookupSuperMember(memberName) : localMember; + } + + /** + * Returns true if the [fieldMember] is shadowed by another field. The given + * [fieldMember] must be a member of this class. + * + * This method also works if the [fieldMember] is private. + */ + bool isShadowedByField(Element fieldMember) { + assert(fieldMember.isField()); + // Note that we cannot use [lookupMember] or [lookupSuperMember] since it + // will not do the right thing for private elements. + ClassElement lookupClass = this; + LibraryElement memberLibrary = fieldMember.getLibrary(); + if (fieldMember.name.isPrivate()) { + // We find a super class in the same library as the field. This way the + // lookupMember will work. + while (lookupClass.getLibrary() != memberLibrary) { + lookupClass = lookupClass.superclass; + } + } + SourceString fieldName = fieldMember.name; + while (true) { + Element foundMember = lookupClass.lookupMember(fieldName); + if (foundMember == fieldMember) return false; + if (foundMember.isField()) return true; + lookupClass = foundMember.getEnclosingClass().superclass; + } + } + + Element validateConstructorLookupResults(Selector selector, + Element result, + Element noMatch(Element)) { + if (result == null + || !result.isConstructor() + || (selector.name.isPrivate() + && result.getLibrary() != selector.library)) { + result = noMatch != null ? noMatch(result) : null; + } + return result; + } + + // TODO(aprelev@gmail.com): Peter believes that it would be great to + // make noMatch a required argument. Peter's suspicion is that most + // callers of this method would benefit from using the noMatch method. + Element lookupConstructor(Selector selector, [Element noMatch(Element)]) { + SourceString normalizedName; + SourceString className = this.name; + SourceString constructorName = selector.name; + if (constructorName != const SourceString('')) { + normalizedName = Elements.constructConstructorName(className, + constructorName); + } else { + normalizedName = className; + } + Element result = localLookup(normalizedName); + return validateConstructorLookupResults(selector, result, noMatch); + } + + Element lookupFactoryConstructor(Selector selector, + [Element noMatch(Element)]) { + SourceString constructorName = selector.name; + Element result = localLookup(constructorName); + return validateConstructorLookupResults(selector, result, noMatch); + } + + Link<Element> get constructors { + // TODO(ajohnsen): See if we can avoid this method at some point. + Link<Element> result = const Link<Element>(); + // TODO(johnniwinther): Should we include injected constructors? + forEachMember((_, Element member) { + if (member.isConstructor()) result = result.prepend(member); + }); + return result; + } + + /** + * Returns the super class, if any. + * + * The returned element may not be resolved yet. + */ + ClassElement get superclass { + assert(supertypeLoadState == STATE_DONE); + return supertype == null ? null : supertype.element; + } + + /** + * Runs through all members of this class. + * + * The enclosing class is passed to the callback. This is useful when + * [includeSuperMembers] is [:true:]. + * + * When called on an implementation element both the members in the origin + * and patch class are included. + */ + // TODO(johnniwinther): Clean up lookup to get rid of the include predicates. + void forEachMember(void f(ClassElement enclosingClass, Element member), + {includeBackendMembers: false, + includeSuperMembers: false}) { + bool includeInjectedMembers = isPatch; + Set<ClassElement> seen = new Set<ClassElement>(); + ClassElement classElement = declaration; + do { + if (seen.contains(classElement)) return; + seen.add(classElement); + + // Iterate through the members in textual order, which requires + // to reverse the data structure [localMembers] we created. + // Textual order may be important for certain operations, for + // example when emitting the initializers of fields. + classElement.forEachLocalMember((e) => f(classElement, e)); + if (includeBackendMembers) { + classElement.forEachBackendMember((e) => f(classElement, e)); + } + if (includeInjectedMembers) { + if (classElement.patch != null) { + classElement.patch.forEachLocalMember((e) { + if (!e.isPatch) f(classElement, e); + }); + } + } + classElement = includeSuperMembers ? classElement.superclass : null; + } while(classElement != null); + } + + /** + * Runs through all instance-field members of this class. + * + * The enclosing class is passed to the callback. This is useful when + * [includeSuperMembers] is [:true:]. + * + * When [includeBackendMembers] and [includeSuperMembers] are both [:true:] + * then the fields are visited in the same order as they need to be given + * to the JavaScript constructor. + * + * When called on the implementation element both the fields declared in the + * origin and in the patch are included. + */ + void forEachInstanceField(void f(ClassElement enclosingClass, Element field), + {includeBackendMembers: false, + includeSuperMembers: false}) { + // Filters so that [f] is only invoked with instance fields. + void fieldFilter(ClassElement enclosingClass, Element member) { + if (member.isInstanceMember() && member.kind == ElementKind.FIELD) { + f(enclosingClass, member); + } + } + + forEachMember(fieldFilter, + includeBackendMembers: includeBackendMembers, + includeSuperMembers: includeSuperMembers); + } + + void forEachBackendMember(void f(Element member)) { + backendMembers.forEach(f); + } + + bool implementsInterface(ClassElement intrface) { + for (DartType implementedInterfaceType in allSupertypes) { + ClassElement implementedInterface = implementedInterfaceType.element; + if (identical(implementedInterface, intrface)) { + return true; + } + } + return false; + } + + /** + * Returns true if [this] is a subclass of [cls]. + * + * This method is not to be used for checking type hierarchy and + * assignments, because it does not take parameterized types into + * account. + */ + bool isSubclassOf(ClassElement cls) { + // Use [declaration] for both [this] and [cls], because + // declaration classes hold the superclass hierarchy. + cls = cls.declaration; + for (ClassElement s = declaration; s != null; s = s.superclass) { + if (identical(s, cls)) return true; + } + return false; + } + + bool isInterface() => false; + bool isNative() => nativeTagInfo != null; + void setNative(String name) { + nativeTagInfo = new SourceString(name); + } +} + +abstract class ClassElementX extends BaseClassElementX { + // Lazily applied patch of class members. + ClassElement patch = null; + ClassElement origin = null; + + Link<Element> localMembers = const Link<Element>(); + final ScopeX localScope = new ScopeX(); + + ClassElementX(SourceString name, Element enclosing, int id, int initialState) + : super(name, enclosing, id, initialState); + + ClassNode parseNode(Compiler compiler); + + bool get isMixinApplication => false; + bool get isPatched => patch != null; + bool get isPatch => origin != null; + bool get hasLocalScopeMembers => !localScope.isEmpty; + + void addMember(Element element, DiagnosticListener listener) { + localMembers = localMembers.prepend(element); + addToScope(element, listener); + } + + void addToScope(Element element, DiagnosticListener listener) { + localScope.add(element, listener); + } + + Element localLookup(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result == null && isPatch) { + result = origin.localLookup(elementName); + } + return result; + } + + void forEachLocalMember(void f(Element member)) { + localMembers.reverse().forEach(f); + } + + bool get hasConstructor { + // Search in scope to be sure we search patched constructors. + for (var element in localScope.values) { + if (element.isConstructor()) return true; + } + return false; + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler) { + addToScope(constructor, compiler); + } + + Link<DartType> computeTypeParameters(Compiler compiler) { + ClassNode node = parseNode(compiler); + return TypeDeclarationElementX.createTypeVariables( + this, node.typeParameters); + } + + Scope buildScope() => new ClassScope(enclosingElement.buildScope(), this); + + String toString() { + if (origin != null) { + return 'patch ${super.toString()}'; + } else if (patch != null) { + return 'origin ${super.toString()}'; + } else { + return super.toString(); + } + } +} + +class MixinApplicationElementX extends BaseClassElementX + implements MixinApplicationElement { + final Node node; + final Modifiers modifiers; + + FunctionElement constructor; + ClassElement mixin; + + // TODO(kasperl): The analyzer complains when I don't have these two + // fields. This is pretty weird. I cannot replace them with getters. + final ClassElement patch = null; + final ClassElement origin = null; + + MixinApplicationElementX(SourceString name, Element enclosing, int id, + this.node, this.modifiers) + : super(name, enclosing, id, STATE_NOT_STARTED); + + bool get isMixinApplication => true; + bool get hasConstructor => constructor != null; + bool get hasLocalScopeMembers => false; + + Token position() => node.getBeginToken(); + + Node parseNode(DiagnosticListener listener) => node; + + Element localLookup(SourceString name) { + if (this.name == name) return constructor; + if (mixin == null) return null; + Element mixedInElement = mixin.localLookup(name); + if (mixedInElement == null) return null; + return mixedInElement.isInstanceMember() ? mixedInElement : null; + } + + void forEachLocalMember(void f(Element member)) { + if (mixin != null) mixin.forEachLocalMember((Element mixedInElement) { + if (mixedInElement.isInstanceMember()) f(mixedInElement); + }); + } + + void addMember(Element element, DiagnosticListener listener) { + throw new UnsupportedError("cannot add member to $this"); + } + + void addToScope(Element element, DiagnosticListener listener) { + throw new UnsupportedError("cannot add to scope of $this"); + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler) { + assert(!hasConstructor); + this.constructor = constructor; + } + + Link<DartType> computeTypeParameters(Compiler compiler) { + NamedMixinApplication named = node.asNamedMixinApplication(); + if (named == null) return const Link<DartType>(); + return TypeDeclarationElementX.createTypeVariables( + this, named.typeParameters); + } +} + +class LabelElementX extends ElementX implements LabelElement { + + // We store the original label here so it can be returned by [parseNode]. + final Label label; + final String labelName; + final TargetElement target; + bool isBreakTarget = false; + bool isContinueTarget = false; + LabelElementX(Label label, String labelName, this.target, + Element enclosingElement) + : this.label = label, + this.labelName = labelName, + // In case of a synthetic label, just use [labelName] for + // identifying the element. + super(label == null + ? new SourceString(labelName) + : label.identifier.source, + ElementKind.LABEL, + enclosingElement); + + void setBreakTarget() { + isBreakTarget = true; + target.isBreakTarget = true; + } + void setContinueTarget() { + isContinueTarget = true; + target.isContinueTarget = true; + } + + bool get isTarget => isBreakTarget || isContinueTarget; + Node parseNode(DiagnosticListener l) => label; + + Token position() => label.getBeginToken(); + String toString() => "${labelName}:"; +} + +// Represents a reference to a statement or switch-case, either by label or the +// default target of a break or continue. +class TargetElementX extends ElementX implements TargetElement { + final Node statement; + final int nestingLevel; + Link<LabelElement> labels = const Link<LabelElement>(); + bool isBreakTarget = false; + bool isContinueTarget = false; + + TargetElementX(this.statement, this.nestingLevel, Element enclosingElement) + : super(const SourceString(""), ElementKind.STATEMENT, enclosingElement); + bool get isTarget => isBreakTarget || isContinueTarget; + + LabelElement addLabel(Label label, String labelName) { + LabelElement result = new LabelElementX(label, labelName, this, + enclosingElement); + labels = labels.prepend(result); + return result; + } + + Node parseNode(DiagnosticListener l) => statement; + + bool get isSwitch => statement is SwitchStatement; + + Token position() => statement.getBeginToken(); + String toString() => statement.toString(); +} + +class TypeVariableElementX extends ElementX implements TypeVariableElement { + final Node cachedNode; + TypeVariableType type; + DartType bound; + + TypeVariableElementX(name, Element enclosing, this.cachedNode, + [this.type, this.bound]) + : super(name, ElementKind.TYPE_VARIABLE, enclosing); + + TypeVariableType computeType(compiler) => type; + + Node parseNode(compiler) => cachedNode; + + String toString() => "${enclosingElement.toString()}.${name.slowToString()}"; + + Token position() => cachedNode.getBeginToken(); +} + +/** + * A single metadata annotation. + * + * For example, consider: + * + * [: + * class Data { + * const Data(); + * } + * + * const data = const Data(); + * + * @data + * class Foo {} + * + * @data @data + * class Bar {} + * :] + * + * In this example, there are three instances of [MetadataAnnotation] + * and they correspond each to a location in the source code where + * there is an at-sign, '@'. The [value] of each of these instances + * are the same compile-time constant, [: const Data() :]. + * + * The mirror system does not have a concept matching this class. + */ +abstract class MetadataAnnotationX implements MetadataAnnotation { + /** + * The compile-time constant which this annotation resolves to. + * In the mirror system, this would be an object mirror. + */ + Constant get value; + Element annotatedElement; + int resolutionState; + + /** + * The beginning token of this annotation, or [:null:] if it is synthetic. + */ + Token get beginToken; + + MetadataAnnotationX([this.resolutionState = STATE_NOT_STARTED]); + + MetadataAnnotation ensureResolved(Compiler compiler) { + if (resolutionState == STATE_NOT_STARTED) { + compiler.resolver.resolveMetadataAnnotation(this); + } + return this; + } + + String toString() => 'MetadataAnnotation($value, $resolutionState)'; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart b/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart new file mode 100644 index 0000000..4a4fea8 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/enqueue.dart
@@ -0,0 +1,552 @@ +// 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. + +part of dart2js; + +class EnqueueTask extends CompilerTask { + final ResolutionEnqueuer resolution; + final CodegenEnqueuer codegen; + + String get name => 'Enqueue'; + + EnqueueTask(Compiler compiler) + : resolution = new ResolutionEnqueuer( + compiler, compiler.backend.createItemCompilationContext), + codegen = new CodegenEnqueuer( + compiler, compiler.backend.createItemCompilationContext), + super(compiler) { + codegen.task = this; + resolution.task = this; + + codegen.nativeEnqueuer = compiler.backend.nativeCodegenEnqueuer(codegen); + resolution.nativeEnqueuer = + compiler.backend.nativeResolutionEnqueuer(resolution); + } +} + +abstract class Enqueuer { + final String name; + final Compiler compiler; // TODO(ahe): Remove this dependency. + final Function itemCompilationContextCreator; + final Map<String, Link<Element>> instanceMembersByName; + final Set<ClassElement> seenClasses; + final Universe universe; + + bool queueIsClosed = false; + EnqueueTask task; + native.NativeEnqueuer nativeEnqueuer; // Set by EnqueueTask + + Enqueuer(this.name, this.compiler, + ItemCompilationContext itemCompilationContextCreator()) + : this.itemCompilationContextCreator = itemCompilationContextCreator, + instanceMembersByName = new Map<String, Link<Element>>(), + universe = new Universe(), + seenClasses = new Set<ClassElement>(); + + /// Returns [:true:] if this enqueuer is the resolution enqueuer. + bool get isResolutionQueue => false; + + /// Returns [:true:] if [member] has been processed by this enqueuer. + bool isProcessed(Element member); + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void addToWorkList(Element element, [TreeElements elements]) { + assert(invariant(element, element.isDeclaration)); + if (element.isForeign(compiler)) return; + + if (!addElementToWorkList(element, elements)) return; + + // Enable runtime type support if we discover a getter called runtimeType. + // We have to enable runtime type before hitting the codegen, so + // that constructors know whether they need to generate code for + // runtime type. + if (element.isGetter() && element.name == Compiler.RUNTIME_TYPE) { + compiler.enabledRuntimeType = true; + } else if (element == compiler.functionApplyMethod) { + compiler.enabledFunctionApply = true; + } else if (element == compiler.invokeOnMethod) { + compiler.enabledInvokeOn = true; + } + + nativeEnqueuer.registerElement(element); + } + + /** + * Adds [element] to the work list if it has not already been processed. + * + * Returns [:true:] if the [element] should be processed. + */ + // TODO(johnniwinther): Change to 'Returns true if the element was added to + // the work list'? + bool addElementToWorkList(Element element, [TreeElements elements]); + + void registerInstantiatedClass(ClassElement cls) { + if (universe.instantiatedClasses.contains(cls)) return; + if (!cls.isAbstract(compiler)) { + universe.instantiatedClasses.add(cls); + onRegisterInstantiatedClass(cls); + } + compiler.backend.registerInstantiatedClass(cls, this); + } + + bool checkNoEnqueuedInvokedInstanceMethods() { + task.measure(() { + // Run through the classes and see if we need to compile methods. + for (ClassElement classElement in universe.instantiatedClasses) { + for (ClassElement currentClass = classElement; + currentClass != null; + currentClass = currentClass.superclass) { + processInstantiatedClass(currentClass); + } + } + }); + return true; + } + + void processInstantiatedClass(ClassElement cls) { + cls.implementation.forEachMember(processInstantiatedClassMember); + } + + /** + * Documentation wanted -- johnniwinther + */ + void processInstantiatedClassMember(ClassElement cls, Element member) { + assert(invariant(member, member.isDeclaration)); + if (isProcessed(member)) return; + if (!member.isInstanceMember()) return; + if (member.isField()) { + // Native fields need to go into instanceMembersByName as they are virtual + // instantiation points and escape points. + // Test the enclosing class, since the metadata has not been parsed yet. + if (!member.enclosingElement.isNative()) return; + } + + String memberName = member.name.slowToString(); + Link<Element> members = instanceMembersByName.putIfAbsent( + memberName, () => const Link<Element>()); + instanceMembersByName[memberName] = members.prepend(member); + + if (member.kind == ElementKind.FUNCTION) { + if (member.name == Compiler.NO_SUCH_METHOD) { + enableNoSuchMethod(member); + } + if (universe.hasInvocation(member, compiler)) { + return addToWorkList(member); + } + // If there is a property access with the same name as a method we + // need to emit the method. + if (universe.hasInvokedGetter(member, compiler)) { + // We will emit a closure, so make sure the closure class is + // generated. + compiler.closureClass.ensureResolved(compiler); + registerInstantiatedClass(compiler.closureClass); + return addToWorkList(member); + } + } else if (member.kind == ElementKind.GETTER) { + if (universe.hasInvokedGetter(member, compiler)) { + return addToWorkList(member); + } + // We don't know what selectors the returned closure accepts. If + // the set contains any selector we have to assume that it matches. + if (universe.hasInvocation(member, compiler)) { + return addToWorkList(member); + } + } else if (member.kind == ElementKind.SETTER) { + if (universe.hasInvokedSetter(member, compiler)) { + return addToWorkList(member); + } + } else if (member.kind == ElementKind.FIELD && + member.enclosingElement.isNative()) { + nativeEnqueuer.handleFieldAnnotations(member); + if (universe.hasInvokedGetter(member, compiler) || + universe.hasInvocation(member, compiler)) { + nativeEnqueuer.registerFieldLoad(member); + // In handleUnseenSelector we can't tell if the field is loaded or + // stored. We need the basic algorithm to be Church-Rosser, since the + // resolution 'reduction' order is different to the codegen order. So + // register that the field is also stored. In other words: if we don't + // register the store here during resolution, the store could be + // registered during codegen on the handleUnseenSelector path, and cause + // the set of codegen elements to include unresolved elements. + nativeEnqueuer.registerFieldStore(member); + } + if (universe.hasInvokedSetter(member, compiler)) { + nativeEnqueuer.registerFieldStore(member); + // See comment after registerFieldLoad above. + nativeEnqueuer.registerFieldLoad(member); + } + } + } + + void enableNoSuchMethod(Element element) {} + + void onRegisterInstantiatedClass(ClassElement cls) { + task.measure(() { + // The class must be resolved to compute the set of all + // supertypes. + cls.ensureResolved(compiler); + + void processClass(ClassElement cls) { + if (seenClasses.contains(cls)) return; + + seenClasses.add(cls); + cls.ensureResolved(compiler); + cls.implementation.forEachMember(processInstantiatedClassMember); + if (isResolutionQueue) { + compiler.resolver.checkClass(cls); + } + + if (compiler.enableTypeAssertions) { + // We need to register is checks and helpers for checking + // assignments to fields. + // TODO(ngeoffray): This should really move to the backend. + cls.forEachLocalMember((Element member) { + if (!member.isInstanceMember() || !member.isField()) return; + DartType type = member.computeType(compiler); + registerIsCheck(type); + SourceString helper = compiler.backend.getCheckedModeHelper(type); + if (helper != null) { + Element helperElement = compiler.findHelper(helper); + registerStaticUse(helperElement); + } + }); + } + } + processClass(cls); + for (Link<DartType> supertypes = cls.allSupertypes; + !supertypes.isEmpty; supertypes = supertypes.tail) { + processClass(supertypes.head.element); + } + }); + } + + void registerNewSelector(SourceString name, + Selector selector, + Map<SourceString, Set<Selector>> selectorsMap) { + if (name != selector.name) { + String message = "$name != ${selector.name} (${selector.kind})"; + compiler.internalError("Wrong selector name: $message."); + } + Set<Selector> selectors = + selectorsMap.putIfAbsent(name, () => new Set<Selector>()); + if (!selectors.contains(selector)) { + selectors.add(selector); + handleUnseenSelector(name, selector); + } + } + + void registerInvocation(SourceString methodName, Selector selector) { + task.measure(() { + registerNewSelector(methodName, selector, universe.invokedNames); + }); + } + + void registerInvokedGetter(SourceString getterName, Selector selector) { + task.measure(() { + registerNewSelector(getterName, selector, universe.invokedGetters); + }); + } + + void registerInvokedSetter(SourceString setterName, Selector selector) { + task.measure(() { + registerNewSelector(setterName, selector, universe.invokedSetters); + }); + } + + processInstanceMembers(SourceString n, bool f(Element e)) { + String memberName = n.slowToString(); + Link<Element> members = instanceMembersByName[memberName]; + if (members != null) { + LinkBuilder<Element> remaining = new LinkBuilder<Element>(); + for (; !members.isEmpty; members = members.tail) { + if (!f(members.head)) remaining.addLast(members.head); + } + instanceMembersByName[memberName] = remaining.toLink(); + } + } + + void handleUnseenSelector(SourceString methodName, Selector selector) { + processInstanceMembers(methodName, (Element member) { + if (selector.appliesUnnamed(member, compiler)) { + if (member.isField() && member.enclosingElement.isNative()) { + if (selector.isGetter() || selector.isCall()) { + nativeEnqueuer.registerFieldLoad(member); + // We have to also handle storing to the field because we only get + // one look at each member and there might be a store we have not + // seen yet. + // TODO(sra): Process fields for storing separately. + nativeEnqueuer.registerFieldStore(member); + } else { + nativeEnqueuer.registerFieldStore(member); + // We have to also handle loading from the field because we only get + // one look at each member and there might be a load we have not + // seen yet. + // TODO(sra): Process fields for storing separately. + nativeEnqueuer.registerFieldLoad(member); + } + } else { + addToWorkList(member); + } + return true; + } + return false; + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void registerStaticUse(Element element) { + if (element == null) return; + assert(invariant(element, element.isDeclaration)); + addToWorkList(element); + } + + void registerGetOfStaticFunction(FunctionElement element) { + registerStaticUse(element); + universe.staticFunctionsNeedingGetter.add(element); + } + + void registerDynamicInvocation(SourceString methodName, Selector selector) { + assert(selector != null); + registerInvocation(methodName, selector); + } + + void registerDynamicInvocationOf(Element element, Selector selector) { + assert(selector.isCall() + || selector.isOperator() + || selector.isIndex() + || selector.isIndexSet()); + if (element.isFunction()) { + addToWorkList(element); + } else if (element.isAbstractField()) { + AbstractFieldElement field = element; + // Since the invocation is a dynamic call on a getter, we only + // need to schedule the getter on the work list. + addToWorkList(field.getter); + } else { + assert(element.isField()); + } + // We also need to add the selector to the invoked names map, + // because the emitter uses that map to generate parameter stubs. + Set<Selector> selectors = universe.invokedNames.putIfAbsent( + element.name, () => new Set<Selector>()); + selectors.add(selector); + } + + void registerDynamicGetter(SourceString methodName, Selector selector) { + registerInvokedGetter(methodName, selector); + } + + void registerDynamicSetter(SourceString methodName, Selector selector) { + registerInvokedSetter(methodName, selector); + } + + void registerFieldGetter(SourceString getterName, + LibraryElement library, + DartType type) { + task.measure(() { + Selector getter = new Selector.getter(getterName, library); + registerNewSelector(getterName, + new TypedSelector(type, getter), + universe.fieldGetters); + }); + } + + void registerFieldSetter(SourceString setterName, + LibraryElement library, + DartType type) { + task.measure(() { + Selector setter = new Selector.setter(setterName, library); + registerNewSelector(setterName, + new TypedSelector(type, setter), + universe.fieldSetters); + }); + } + + void registerIsCheck(DartType type) { + universe.isChecks.add(type); + } + + void forEach(f(WorkItem work)); + + void logSummary(log(message)) { + _logSpecificSummary(log); + nativeEnqueuer.logSummary(log); + } + + /// Log summary specific to the concrete enqueuer. + void _logSpecificSummary(log(message)); + + String toString() => 'Enqueuer($name)'; +} + +/// [Enqueuer] which is specific to resolution. +class ResolutionEnqueuer extends Enqueuer { + /** + * Map from declaration elements to the [TreeElements] object holding the + * resolution mapping for the element implementation. + * + * Invariant: Key elements are declaration elements. + */ + final Map<Element, TreeElements> resolvedElements; + + final Queue<ResolutionWorkItem> queue; + + ResolutionEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('resolution enqueuer', compiler, itemCompilationContextCreator), + resolvedElements = new Map<Element, TreeElements>(), + queue = new Queue<ResolutionWorkItem>(); + + bool get isResolutionQueue => true; + + bool isProcessed(Element member) => resolvedElements.containsKey(member); + + TreeElements getCachedElements(Element element) { + // TODO(ngeoffray): Get rid of this check. + if (element.enclosingElement.isClosure()) { + closureMapping.ClosureClassElement cls = element.enclosingElement; + element = cls.methodElement; + } + Element owner = element.getOutermostEnclosingMemberOrTopLevel(); + return resolvedElements[owner.declaration]; + } + + /** + * Sets the resolved elements of [element] to [elements], or if [elements] is + * [:null:], to the elements found through [getCachedElements]. + * + * Returns the resolved elements. + */ + TreeElements ensureCachedElements(Element element, TreeElements elements) { + if (elements == null) { + elements = getCachedElements(element); + } + resolvedElements[element] = elements; + return elements; + } + + bool addElementToWorkList(Element element, [TreeElements elements]) { + if (queueIsClosed) { + if (getCachedElements(element) != null) return false; + throw new SpannableAssertionFailure(element, + "Resolution work list is closed."); + } + if (elements == null) { + elements = getCachedElements(element); + } + compiler.world.registerUsedElement(element); + + if (elements == null) { + queue.add( + new ResolutionWorkItem(element, itemCompilationContextCreator())); + } + + // Enable isolate support if we start using something from the + // isolate library, or timers for the async library. + LibraryElement library = element.getLibrary(); + if (!compiler.hasIsolateSupport()) { + String uri = library.canonicalUri.toString(); + if (uri == 'dart:isolate') { + enableIsolateSupport(library); + } else if (uri == 'dart:async') { + ClassElement cls = element.getEnclosingClass(); + if (cls != null && cls.name == const SourceString('Timer')) { + // The [:Timer:] class uses the event queue of the isolate + // library, so we make sure that event queue is generated. + enableIsolateSupport(library); + } + } + } + + return true; + } + + void enableIsolateSupport(LibraryElement element) { + compiler.isolateLibrary = element.patch; + addToWorkList( + compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE)); + addToWorkList(compiler.isolateHelperLibrary.find( + const SourceString('_currentIsolate'))); + addToWorkList(compiler.isolateHelperLibrary.find( + const SourceString('_callInIsolate'))); + } + + void enableNoSuchMethod(Element element) { + if (compiler.enabledNoSuchMethod) return; + Selector selector = new Selector.noSuchMethod(); + if (identical(element.getEnclosingClass(), compiler.objectClass)) { + registerDynamicInvocationOf(element, selector); + return; + } + compiler.enabledNoSuchMethod = true; + registerInvocation(Compiler.NO_SUCH_METHOD, selector); + + compiler.createInvocationMirrorElement = + compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR); + addToWorkList(compiler.createInvocationMirrorElement); + } + + void forEach(f(WorkItem work)) { + while (!queue.isEmpty) { + // TODO(johnniwinther): Find an optimal process order for resolution. + f(queue.removeLast()); + } + } + + void registerJsCall(Send node, ResolverVisitor resolver) { + nativeEnqueuer.registerJsCall(node, resolver); + } + + void _logSpecificSummary(log(message)) { + log('Resolved ${resolvedElements.length} elements.'); + } +} + +/// [Enqueuer] which is specific to code generation. +class CodegenEnqueuer extends Enqueuer { + final Queue<CodegenWorkItem> queue; + final Map<Element, js.Expression> generatedCode = + new Map<Element, js.Expression>(); + + CodegenEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('codegen enqueuer', compiler, itemCompilationContextCreator), + queue = new Queue<CodegenWorkItem>(); + + bool isProcessed(Element member) => generatedCode.containsKey(member); + + bool addElementToWorkList(Element element, [TreeElements elements]) { + if (queueIsClosed) { + throw new SpannableAssertionFailure(element, + "Codegen work list is closed."); + } + elements = + compiler.enqueuer.resolution.ensureCachedElements(element, elements); + + CodegenWorkItem workItem = new CodegenWorkItem( + element, elements, itemCompilationContextCreator()); + queue.add(workItem); + + return true; + } + + void forEach(f(WorkItem work)) { + while(!queue.isEmpty) { + // TODO(johnniwinther): Find an optimal process order for codegen. + f(queue.removeLast()); + } + } + + void _logSpecificSummary(log(message)) { + log('Compiled ${generatedCode.length} methods.'); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/filenames.dart b/pkgs/markdown/lib/src/compiler/implementation/filenames.dart new file mode 100644 index 0000000..198a28f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/filenames.dart
@@ -0,0 +1,29 @@ +// 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. + +library filenames; + +import 'dart:io'; +import 'dart:uri'; + +// TODO(ahe): This library should be replaced by a general +// path-munging library. +// +// See also: +// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx + +String nativeToUriPath(String filename) { + return new Path(filename).toString(); +} + +String uriPathToNative(String path) { + return new Path(path).toNativePath(); +} + +Uri getCurrentDirectory() { + final String dir = nativeToUriPath(new File('.').fullPathSync()); + return new Uri.fromComponents(scheme: 'file', path: appendSlash(dir)); +} + +String appendSlash(String path) => path.endsWith('/') ? path : '$path/';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/js.dart b/pkgs/markdown/lib/src/compiler/implementation/js/js.dart new file mode 100644 index 0000000..b7df642 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js/js.dart
@@ -0,0 +1,15 @@ +// 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. + +library js; + +import 'precedence.dart'; +import '../util/characters.dart' as charCodes; + +// TODO(floitsch): remove this dependency (currently necessary for the +// CodeBuffer). +import '../dart2jslib.dart' as leg; + +part 'nodes.dart'; +part 'printer.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart new file mode 100644 index 0000000..da8cd44 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart
@@ -0,0 +1,906 @@ +// 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. + +part of js; + +abstract class NodeVisitor<T> { + T visitProgram(Program node); + + T visitBlock(Block node); + T visitExpressionStatement(ExpressionStatement node); + T visitEmptyStatement(EmptyStatement node); + T visitIf(If node); + T visitFor(For node); + T visitForIn(ForIn node); + T visitWhile(While node); + T visitDo(Do node); + T visitContinue(Continue node); + T visitBreak(Break node); + T visitReturn(Return node); + T visitThrow(Throw node); + T visitTry(Try node); + T visitCatch(Catch node); + T visitSwitch(Switch node); + T visitCase(Case node); + T visitDefault(Default node); + T visitFunctionDeclaration(FunctionDeclaration node); + T visitLabeledStatement(LabeledStatement node); + T visitLiteralStatement(LiteralStatement node); + + T visitLiteralExpression(LiteralExpression node); + T visitVariableDeclarationList(VariableDeclarationList node); + T visitSequence(Sequence node); + T visitAssignment(Assignment node); + T visitVariableInitialization(VariableInitialization node); + T visitConditional(Conditional cond); + T visitNew(New node); + T visitCall(Call node); + T visitBinary(Binary node); + T visitPrefix(Prefix node); + T visitPostfix(Postfix node); + + T visitVariableUse(VariableUse node); + T visitThis(This node); + T visitVariableDeclaration(VariableDeclaration node); + T visitParameter(Parameter node); + T visitAccess(PropertyAccess node); + + T visitNamedFunction(NamedFunction node); + T visitFun(Fun node); + + T visitLiteralBool(LiteralBool node); + T visitLiteralString(LiteralString node); + T visitLiteralNumber(LiteralNumber node); + T visitLiteralNull(LiteralNull node); + + T visitArrayInitializer(ArrayInitializer node); + T visitArrayElement(ArrayElement node); + T visitObjectInitializer(ObjectInitializer node); + T visitProperty(Property node); + T visitRegExpLiteral(RegExpLiteral node); +} + +class BaseVisitor<T> implements NodeVisitor<T> { + T visitNode(Node node) { + node.visitChildren(this); + return null; + } + + T visitProgram(Program node) => visitNode(node); + + T visitStatement(Statement node) => visitNode(node); + T visitLoop(Loop node) => visitStatement(node); + T visitJump(Statement node) => visitStatement(node); + + T visitBlock(Block node) => visitStatement(node); + T visitExpressionStatement(ExpressionStatement node) + => visitStatement(node); + T visitEmptyStatement(EmptyStatement node) => visitStatement(node); + T visitIf(If node) => visitStatement(node); + T visitFor(For node) => visitLoop(node); + T visitForIn(ForIn node) => visitLoop(node); + T visitWhile(While node) => visitLoop(node); + T visitDo(Do node) => visitLoop(node); + T visitContinue(Continue node) => visitJump(node); + T visitBreak(Break node) => visitJump(node); + T visitReturn(Return node) => visitJump(node); + T visitThrow(Throw node) => visitJump(node); + T visitTry(Try node) => visitStatement(node); + T visitSwitch(Switch node) => visitStatement(node); + T visitFunctionDeclaration(FunctionDeclaration node) + => visitStatement(node); + T visitLabeledStatement(LabeledStatement node) => visitStatement(node); + T visitLiteralStatement(LiteralStatement node) => visitStatement(node); + + T visitCatch(Catch node) => visitNode(node); + T visitCase(Case node) => visitNode(node); + T visitDefault(Default node) => visitNode(node); + + T visitExpression(Expression node) => visitNode(node); + T visitVariableReference(VariableReference node) => visitExpression(node); + + T visitLiteralExpression(LiteralExpression node) => visitExpression(node); + T visitVariableDeclarationList(VariableDeclarationList node) + => visitExpression(node); + T visitSequence(Sequence node) => visitExpression(node); + T visitAssignment(Assignment node) => visitExpression(node); + T visitVariableInitialization(VariableInitialization node) { + if (node.value != null) { + visitAssignment(node); + } else { + visitExpression(node); + } + } + T visitConditional(Conditional node) => visitExpression(node); + T visitNew(New node) => visitExpression(node); + T visitCall(Call node) => visitExpression(node); + T visitBinary(Binary node) => visitCall(node); + T visitPrefix(Prefix node) => visitCall(node); + T visitPostfix(Postfix node) => visitCall(node); + T visitAccess(PropertyAccess node) => visitExpression(node); + + T visitVariableUse(VariableUse node) => visitVariableReference(node); + T visitVariableDeclaration(VariableDeclaration node) + => visitVariableReference(node); + T visitParameter(Parameter node) => visitVariableDeclaration(node); + T visitThis(This node) => visitParameter(node); + + T visitNamedFunction(NamedFunction node) => visitExpression(node); + T visitFun(Fun node) => visitExpression(node); + + T visitLiteral(Literal node) => visitExpression(node); + + T visitLiteralBool(LiteralBool node) => visitLiteral(node); + T visitLiteralString(LiteralString node) => visitLiteral(node); + T visitLiteralNumber(LiteralNumber node) => visitLiteral(node); + T visitLiteralNull(LiteralNull node) => visitLiteral(node); + + T visitArrayInitializer(ArrayInitializer node) => visitExpression(node); + T visitArrayElement(ArrayElement node) => visitNode(node); + T visitObjectInitializer(ObjectInitializer node) => visitExpression(node); + T visitProperty(Property node) => visitNode(node); + T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node); +} + +abstract class Node { + var sourcePosition; + var endSourcePosition; + + accept(NodeVisitor visitor); + void visitChildren(NodeVisitor visitor); + + VariableUse asVariableUse() => null; +} + +class Program extends Node { + final List<Statement> body; + Program(this.body); + + accept(NodeVisitor visitor) => visitor.visitProgram(this); + void visitChildren(NodeVisitor visitor) { + for (Statement statement in body) statement.accept(visitor); + } +} + +abstract class Statement extends Node { +} + +class Block extends Statement { + final List<Statement> statements; + Block(this.statements); + Block.empty() : this.statements = <Statement>[]; + + accept(NodeVisitor visitor) => visitor.visitBlock(this); + void visitChildren(NodeVisitor visitor) { + for (Statement statement in statements) statement.accept(visitor); + } +} + +class ExpressionStatement extends Statement { + final Expression expression; + ExpressionStatement(this.expression); + + accept(NodeVisitor visitor) => visitor.visitExpressionStatement(this); + void visitChildren(NodeVisitor visitor) { expression.accept(visitor); } +} + +class EmptyStatement extends Statement { + EmptyStatement(); + + accept(NodeVisitor visitor) => visitor.visitEmptyStatement(this); + void visitChildren(NodeVisitor visitor) {} +} + +class If extends Statement { + final Expression condition; + final Node then; + final Node otherwise; + + If(this.condition, this.then, this.otherwise); + If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement(); + + bool get hasElse => otherwise is !EmptyStatement; + + accept(NodeVisitor visitor) => visitor.visitIf(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + then.accept(visitor); + otherwise.accept(visitor); + } +} + +abstract class Loop extends Statement { + final Statement body; + Loop(this.body); +} + +class For extends Loop { + final Expression init; + final Expression condition; + final Expression update; + + For(this.init, this.condition, this.update, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitFor(this); + + void visitChildren(NodeVisitor visitor) { + if (init != null) init.accept(visitor); + if (condition != null) condition.accept(visitor); + if (update != null) update.accept(visitor); + body.accept(visitor); + } +} + +class ForIn extends Loop { + // Note that [VariableDeclarationList] is a subclass of [Expression]. + // Therefore we can type the leftHandSide as [Expression]. + final Expression leftHandSide; + final Expression object; + + ForIn(this.leftHandSide, this.object, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitForIn(this); + + void visitChildren(NodeVisitor visitor) { + leftHandSide.accept(visitor); + object.accept(visitor); + body.accept(visitor); + } +} + +class While extends Loop { + final Node condition; + + While(this.condition, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitWhile(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + body.accept(visitor); + } +} + +class Do extends Loop { + final Expression condition; + + Do(Statement body, this.condition) : super(body); + + accept(NodeVisitor visitor) => visitor.visitDo(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + condition.accept(visitor); + } +} + +class Continue extends Statement { + final String targetLabel; // Can be null. + + Continue(this.targetLabel); + + accept(NodeVisitor visitor) => visitor.visitContinue(this); + void visitChildren(NodeVisitor visitor) {} +} + +class Break extends Statement { + final String targetLabel; // Can be null. + + Break(this.targetLabel); + + accept(NodeVisitor visitor) => visitor.visitBreak(this); + void visitChildren(NodeVisitor visitor) {} +} + +class Return extends Statement { + final Expression value; // Can be null. + + Return([this.value = null]); + + accept(NodeVisitor visitor) => visitor.visitReturn(this); + + void visitChildren(NodeVisitor visitor) { + if (value != null) value.accept(visitor); + } +} + +class Throw extends Statement { + final Expression expression; + + Throw(this.expression); + + accept(NodeVisitor visitor) => visitor.visitThrow(this); + + void visitChildren(NodeVisitor visitor) { + expression.accept(visitor); + } +} + +class Try extends Statement { + final Block body; + final Catch catchPart; // Can be null if [finallyPart] is non-null. + final Block finallyPart; // Can be null if [catchPart] is non-null. + + Try(this.body, this.catchPart, this.finallyPart) { + assert(catchPart != null || finallyPart != null); + } + + accept(NodeVisitor visitor) => visitor.visitTry(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + if (catchPart != null) catchPart.accept(visitor); + if (finallyPart != null) finallyPart.accept(visitor); + } +} + +class Catch extends Node { + final VariableDeclaration declaration; + final Block body; + + Catch(this.declaration, this.body); + + accept(NodeVisitor visitor) => visitor.visitCatch(this); + + void visitChildren(NodeVisitor visitor) { + declaration.accept(visitor); + body.accept(visitor); + } +} + +class Switch extends Statement { + final Expression key; + final List<SwitchClause> cases; + + Switch(this.key, this.cases); + + accept(NodeVisitor visitor) => visitor.visitSwitch(this); + + void visitChildren(NodeVisitor visitor) { + key.accept(visitor); + for (SwitchClause clause in cases) clause.accept(visitor); + } +} + +abstract class SwitchClause extends Node { + final Block body; + + SwitchClause(this.body); +} + +class Case extends SwitchClause { + final Expression expression; + + Case(this.expression, Block body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitCase(this); + + void visitChildren(NodeVisitor visitor) { + expression.accept(visitor); + body.accept(visitor); + } +} + +class Default extends SwitchClause { + Default(Block body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitDefault(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + } +} + +class FunctionDeclaration extends Statement { + final VariableDeclaration name; + final Fun function; + + FunctionDeclaration(this.name, this.function); + + accept(NodeVisitor visitor) => visitor.visitFunctionDeclaration(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + function.accept(visitor); + } +} + +class LabeledStatement extends Statement { + final String label; + final Statement body; + + LabeledStatement(this.label, this.body); + + accept(NodeVisitor visitor) => visitor.visitLabeledStatement(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + } +} + +class LiteralStatement extends Statement { + final String code; + + LiteralStatement(this.code); + + accept(NodeVisitor visitor) => visitor.visitLiteralStatement(this); + void visitChildren(NodeVisitor visitor) { } +} + +abstract class Expression extends Node { + int get precedenceLevel; + + PropertyAccess dot(String name) => new PropertyAccess.field(this, name); + Call callWith(List<Expression> arguments) => new Call(this, arguments); +} + +class LiteralExpression extends Expression { + final String template; + final List<Expression> inputs; + + LiteralExpression(this.template) : inputs = const []; + LiteralExpression.withData(this.template, this.inputs); + + accept(NodeVisitor visitor) => visitor.visitLiteralExpression(this); + + void visitChildren(NodeVisitor visitor) { + for (Expression expr in inputs) expr.accept(visitor); + } + + // Code that uses JS must take care of operator precedences, and + // put parenthesis if needed. + int get precedenceLevel => PRIMARY; +} + +/** + * [VariableDeclarationList] is a subclass of [Expression] to simplify the + * AST. + */ +class VariableDeclarationList extends Expression { + final List<VariableInitialization> declarations; + + VariableDeclarationList(this.declarations); + + accept(NodeVisitor visitor) => visitor.visitVariableDeclarationList(this); + + void visitChildren(NodeVisitor visitor) { + for (VariableInitialization declaration in declarations) { + declaration.accept(visitor); + } + } + + int get precedenceLevel => EXPRESSION; +} + +class Sequence extends Expression { + final List<Expression> expressions; + + Sequence(this.expressions); + + accept(NodeVisitor visitor) => visitor.visitSequence(this); + + void visitChildren(NodeVisitor visitor) { + for (Expression expr in expressions) expr.accept(visitor); + } + + int get precedenceLevel => EXPRESSION; +} + +class Assignment extends Expression { + final Expression leftHandSide; + // Null, if the assignment is not compound. + final VariableReference compoundTarget; + final Expression value; // May be null, for [VariableInitialization]s. + + Assignment(this.leftHandSide, this.value) : compoundTarget = null; + Assignment.compound(this.leftHandSide, String op, this.value) + : compoundTarget = new VariableUse(op); + + int get precedenceLevel => ASSIGNMENT; + + bool get isCompound => compoundTarget != null; + String get op => compoundTarget == null ? null : compoundTarget.name; + + accept(NodeVisitor visitor) => visitor.visitAssignment(this); + + void visitChildren(NodeVisitor visitor) { + leftHandSide.accept(visitor); + if (compoundTarget != null) compoundTarget.accept(visitor); + if (value != null) value.accept(visitor); + } +} + +class VariableInitialization extends Assignment { + /** [value] may be null. */ + VariableInitialization(VariableDeclaration declaration, Expression value) + : super(declaration, value); + + VariableDeclaration get declaration => leftHandSide; + + accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this); +} + +class Conditional extends Expression { + final Expression condition; + final Expression then; + final Expression otherwise; + + Conditional(this.condition, this.then, this.otherwise); + + accept(NodeVisitor visitor) => visitor.visitConditional(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + then.accept(visitor); + otherwise.accept(visitor); + } + + int get precedenceLevel => ASSIGNMENT; +} + +class Call extends Expression { + Expression target; + List<Expression> arguments; + + Call(this.target, this.arguments); + + accept(NodeVisitor visitor) => visitor.visitCall(this); + + void visitChildren(NodeVisitor visitor) { + target.accept(visitor); + for (Expression arg in arguments) arg.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class New extends Call { + New(Expression cls, List<Expression> arguments) : super(cls, arguments); + + accept(NodeVisitor visitor) => visitor.visitNew(this); +} + +class Binary extends Call { + Binary(String op, Expression left, Expression right) + : super(new VariableUse(op), <Expression>[left, right]); + + String get op { + VariableUse use = target; + return use.name; + } + + Expression get left => arguments[0]; + Expression get right => arguments[1]; + + accept(NodeVisitor visitor) => visitor.visitBinary(this); + + int get precedenceLevel { + // TODO(floitsch): switch to constant map. + switch (op) { + case "*": + case "/": + case "%": + return MULTIPLICATIVE; + case "+": + case "-": + return ADDITIVE; + case "<<": + case ">>": + case ">>>": + return SHIFT; + case "<": + case ">": + case "<=": + case ">=": + case "instanceof": + case "in": + return RELATIONAL; + case "==": + case "===": + case "!=": + case "!==": + return EQUALITY; + case "&": + return BIT_AND; + case "^": + return BIT_XOR; + case "|": + return BIT_OR; + case "&&": + return LOGICAL_AND; + case "||": + return LOGICAL_OR; + default: + throw new leg.CompilerCancelledException( + "Internal Error: Unhandled binary operator: $op"); + } + } +} + +class Prefix extends Call { + Prefix(String op, Expression arg) + : super(new VariableUse(op), <Expression>[arg]); + + String get op => (target as VariableUse).name; + Expression get argument => arguments[0]; + + accept(NodeVisitor visitor) => visitor.visitPrefix(this); + + int get precedenceLevel => UNARY; +} + +class Postfix extends Call { + Postfix(String op, Expression arg) + : super(new VariableUse(op), <Expression>[arg]); + + String get op => (target as VariableUse).name; + Expression get argument => arguments[0]; + + accept(NodeVisitor visitor) => visitor.visitPostfix(this); + + int get precedenceLevel => UNARY; +} + +abstract class VariableReference extends Expression { + final String name; + + // We treat operators as if they were special functions. They can thus be + // referenced like other variables. + VariableReference(this.name); + + accept(NodeVisitor visitor); + int get precedenceLevel => PRIMARY; + void visitChildren(NodeVisitor visitor) {} +} + +class VariableUse extends VariableReference { + VariableUse(String name) : super(name); + + accept(NodeVisitor visitor) => visitor.visitVariableUse(this); + + VariableUse asVariableUse() => this; +} + +class VariableDeclaration extends VariableReference { + VariableDeclaration(String name) : super(name); + + accept(NodeVisitor visitor) => visitor.visitVariableDeclaration(this); +} + +class Parameter extends VariableDeclaration { + Parameter(String id) : super(id); + + accept(NodeVisitor visitor) => visitor.visitParameter(this); +} + +class This extends Parameter { + This() : super("this"); + + accept(NodeVisitor visitor) => visitor.visitThis(this); +} + +class NamedFunction extends Expression { + final VariableDeclaration name; + final Fun function; + + NamedFunction(this.name, this.function); + + accept(NodeVisitor visitor) => visitor.visitNamedFunction(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + function.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class Fun extends Expression { + final List<Parameter> params; + final Block body; + + Fun(this.params, this.body); + + accept(NodeVisitor visitor) => visitor.visitFun(this); + + void visitChildren(NodeVisitor visitor) { + for (Parameter param in params) param.accept(visitor); + body.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class PropertyAccess extends Expression { + final Expression receiver; + final Expression selector; + + PropertyAccess(this.receiver, this.selector); + PropertyAccess.field(this.receiver, String fieldName) + : selector = new LiteralString("'$fieldName'"); + PropertyAccess.indexed(this.receiver, int index) + : selector = new LiteralNumber('$index'); + + accept(NodeVisitor visitor) => visitor.visitAccess(this); + + void visitChildren(NodeVisitor visitor) { + receiver.accept(visitor); + selector.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +abstract class Literal extends Expression { + void visitChildren(NodeVisitor visitor) {} + + int get precedenceLevel => PRIMARY; +} + +class LiteralBool extends Literal { + final bool value; + + LiteralBool(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralBool(this); + // [visitChildren] inherited from [Literal]. +} + +class LiteralNull extends Literal { + LiteralNull(); + + accept(NodeVisitor visitor) => visitor.visitLiteralNull(this); +} + +class LiteralString extends Literal { + final String value; + + LiteralString(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralString(this); +} + +class LiteralNumber extends Literal { + final String value; + + LiteralNumber(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralNumber(this); +} + +class ArrayInitializer extends Expression { + final int length; + // We represent the array as sparse list of elements. Each element knows its + // position in the array. + final List<ArrayElement> elements; + + ArrayInitializer(this.length, this.elements); + + factory ArrayInitializer.from(Iterable<Expression> expressions) => + new ArrayInitializer(expressions.length, _convert(expressions)); + + accept(NodeVisitor visitor) => visitor.visitArrayInitializer(this); + + void visitChildren(NodeVisitor visitor) { + for (ArrayElement element in elements) element.accept(visitor); + } + + int get precedenceLevel => PRIMARY; + + static List<ArrayElement> _convert(Iterable<Expression> expressions) { + int index = 0; + return expressions.map( + (expression) => new ArrayElement(index++, expression)) + .toList(); + } +} + +/** + * An expression inside an [ArrayInitialization]. An [ArrayElement] knows + * its position in the containing [ArrayInitialization]. + */ +class ArrayElement extends Node { + int index; + Expression value; + + ArrayElement(this.index, this.value); + + accept(NodeVisitor visitor) => visitor.visitArrayElement(this); + + void visitChildren(NodeVisitor visitor) { + value.accept(visitor); + } +} + +class ObjectInitializer extends Expression { + List<Property> properties; + + ObjectInitializer(this.properties); + + accept(NodeVisitor visitor) => visitor.visitObjectInitializer(this); + + void visitChildren(NodeVisitor visitor) { + for (Property init in properties) init.accept(visitor); + } + + int get precedenceLevel => PRIMARY; +} + +class Property extends Node { + Literal name; + Expression value; + + Property(this.name, this.value); + + accept(NodeVisitor visitor) => visitor.visitProperty(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + value.accept(visitor); + } +} + +/** + * [RegExpLiteral]s, despite being called "Literal", are not inheriting from + * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and + * are thus not in the same category as numbers or strings. + */ +class RegExpLiteral extends Expression { + /** Contains the pattern and the flags.*/ + String pattern; + + RegExpLiteral(this.pattern); + + accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this); + void visitChildren(NodeVisitor visitor) {} + + int get precedenceLevel => PRIMARY; +} + +Prefix typeOf(Expression argument) => new Prefix('typeof', argument); + +Binary equals(Expression left, Expression right) { + return new Binary('==', left, right); +} + +Binary strictEquals(Expression left, Expression right) { + return new Binary('===', left, right); +} + +LiteralString string(String value) => new LiteralString('"$value"'); + +If if_(Expression condition, Node then, [Node otherwise]) { + return (otherwise == null) + ? new If.noElse(condition, then) + : new If(condition, then, otherwise); +} + +Return return_([Expression value]) => new Return(value); + +VariableUse use(String name) => new VariableUse(name); + +PropertyAccess fieldAccess(Expression receiver, String fieldName) { + return new PropertyAccess.field(receiver, fieldName); +} + +Block emptyBlock() => new Block.empty(); + +Block block1(Statement statement) => new Block(<Statement>[statement]); + +Block block2(Statement s1, Statement s2) => new Block(<Statement>[s1, s2]); + +Call call(Expression target, List<Expression> arguments) { + return new Call(target, arguments); +} + +Fun fun(List<String> parameterNames, Block body) { + return new Fun(parameterNames.map((n) => new Parameter(n)).toList(), body); +} + +Assignment assign(Expression leftHandSide, Expression value) { + return new Assignment(leftHandSide, value); +} + +Expression undefined() => new Prefix('void', new LiteralNumber('0'));
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart b/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart new file mode 100644 index 0000000..6d66f1f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart
@@ -0,0 +1,25 @@ +// 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. + +library precedence; + +const EXPRESSION = 0; +const ASSIGNMENT = EXPRESSION + 1; +const LOGICAL_OR = ASSIGNMENT + 1; +const LOGICAL_AND = LOGICAL_OR + 1; +const BIT_OR = LOGICAL_AND + 1; +const BIT_XOR = BIT_OR + 1; +const BIT_AND = BIT_XOR + 1; +const EQUALITY = BIT_AND + 1; +const RELATIONAL = EQUALITY + 1; +const SHIFT = RELATIONAL + 1; +const ADDITIVE = SHIFT + 1; +const MULTIPLICATIVE = ADDITIVE + 1; +const UNARY = MULTIPLICATIVE + 1; +const LEFT_HAND_SIDE = UNARY + 1; +// We merge new, call and member expressions. +// This means that we have to emit parenthesis for 'new's. For example `new X;` +// should be printed as `new X();`. This simplifies the requirements. +const CALL = LEFT_HAND_SIDE; +const PRIMARY = CALL + 1;
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart b/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart new file mode 100644 index 0000000..504933d --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js/printer.dart
@@ -0,0 +1,1111 @@ +// 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. + +part of js; + +class Printer implements NodeVisitor { + final bool shouldCompressOutput; + leg.Compiler compiler; + leg.CodeBuffer outBuffer; + int indentLevel = 0; + bool inForInit = false; + bool atStatementBegin = false; + final DanglingElseVisitor danglingElseVisitor; + final LocalNamer localNamer; + bool pendingSemicolon = false; + bool pendingSpace = false; + static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); + static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); + + Printer(leg.Compiler compiler, { allowVariableMinification: true }) + : shouldCompressOutput = compiler.enableMinification, + this.compiler = compiler, + outBuffer = new leg.CodeBuffer(), + danglingElseVisitor = new DanglingElseVisitor(compiler), + localNamer = determineRenamer(compiler.enableMinification, + allowVariableMinification); + + static LocalNamer determineRenamer(bool shouldCompressOutput, + bool allowVariableMinification) { + return (shouldCompressOutput && allowVariableMinification) + ? new MinifyRenamer() : new IdentityNamer(); + } + + /// Always emit a newline, even under `enableMinification`. + void forceLine() { + out("\n"); + } + /// Emits a newline for readability. + void lineOut() { + if (!shouldCompressOutput) forceLine(); + } + void spaceOut() { + if (!shouldCompressOutput) out(" "); + } + + String lastAddedString = null; + int get lastCharCode { + if (lastAddedString == null) return 0; + assert(lastAddedString.length != ""); + return lastAddedString.charCodeAt(lastAddedString.length - 1); + } + + void out(String str) { + if (str != "") { + if (pendingSemicolon) { + if (!shouldCompressOutput) { + outBuffer.add(";"); + } else if (str != "}") { + // We want to output newline instead of semicolon because it makes + // the raw stack traces much easier to read and it also makes line- + // based tools like diff work much better. JavaScript will + // automatically insert the semicolon at the newline if it means a + // parsing error is avoided, so we can only do this trick if the + // next line is not something that can be glued onto a valid + // expression to make a new valid expression. + if (expressionContinuationRegExp.hasMatch(str)) { + outBuffer.add(";"); + } else { + outBuffer.add("\n"); + } + } + } + if (pendingSpace && + (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) { + outBuffer.add(" "); + } + pendingSpace = false; + pendingSemicolon = false; + outBuffer.add(str); + lastAddedString = str; + } + } + + void outLn(String str) { + out(str); + lineOut(); + } + + void outSemicolonLn() { + if (shouldCompressOutput) { + pendingSemicolon = true; + } else { + out(";"); + forceLine(); + } + } + + void outIndent(String str) { indent(); out(str); } + void outIndentLn(String str) { indent(); outLn(str); } + void indent() { + if (!shouldCompressOutput) { + for (int i = 0; i < indentLevel; i++) out(" "); + } + } + + void recordSourcePosition(var position) { + if (position != null) { + outBuffer.setSourceLocation(position); + } + } + + visit(Node node) { + if (node.sourcePosition != null) outBuffer.beginMappedRange(); + recordSourcePosition(node.sourcePosition); + node.accept(this); + recordSourcePosition(node.endSourcePosition); + if (node.sourcePosition != null) outBuffer.endMappedRange(); + } + + visitCommaSeparated(List<Node> nodes, int hasRequiredType, + {bool newInForInit, bool newAtStatementBegin}) { + for (int i = 0; i < nodes.length; i++) { + if (i != 0) { + atStatementBegin = false; + out(","); + spaceOut(); + } + visitNestedExpression(nodes[i], hasRequiredType, + newInForInit: newInForInit, + newAtStatementBegin: newAtStatementBegin); + } + } + + visitAll(List<Node> nodes) { + nodes.forEach(visit); + } + + visitProgram(Program program) { + visitAll(program.body); + } + + bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) { + if (body is Block) { + spaceOut(); + blockOut(body, false, needsNewline); + return true; + } + if (shouldCompressOutput && needsSeparation) { + // If [shouldCompressOutput] is false, then the 'lineOut' will insert + // the separation. + out(" "); + } else { + lineOut(); + } + indentLevel++; + visit(body); + indentLevel--; + return false; + } + + void blockOutWithoutBraces(Node node) { + if (node is Block) { + node.statements.forEach(blockOutWithoutBraces); + } else { + visit(node); + } + } + + void blockOut(Block node, bool shouldIndent, bool needsNewline) { + if (shouldIndent) indent(); + out("{"); + lineOut(); + indentLevel++; + node.statements.forEach(blockOutWithoutBraces); + indentLevel--; + indent(); + out("}"); + if (needsNewline) lineOut(); + } + + visitBlock(Block block) { + blockOut(block, true, true); + } + + visitExpressionStatement(ExpressionStatement expressionStatement) { + indent(); + visitNestedExpression(expressionStatement.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: true); + outSemicolonLn(); + } + + visitEmptyStatement(EmptyStatement nop) { + outIndentLn(";"); + } + + void ifOut(If node, bool shouldIndent) { + Node then = node.then; + Node elsePart = node.otherwise; + bool hasElse = node.hasElse; + + // Handle dangling elses. + if (hasElse) { + bool needsBraces = node.then.accept(danglingElseVisitor); + if (needsBraces) { + then = new Block(<Statement>[then]); + } + } + if (shouldIndent) indent(); + out("if"); + spaceOut(); + out("("); + visitNestedExpression(node.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + bool thenWasBlock = + blockBody(then, needsSeparation: false, needsNewline: !hasElse); + if (hasElse) { + if (thenWasBlock) { + spaceOut(); + } else { + indent(); + } + out("else"); + if (elsePart is If) { + pendingSpace = true; + ifOut(elsePart, false); + } else { + blockBody(elsePart, needsSeparation: true, needsNewline: true); + } + } + } + + visitIf(If node) { + ifOut(node, true); + } + + visitFor(For loop) { + outIndent("for"); + spaceOut(); + out("("); + if (loop.init != null) { + visitNestedExpression(loop.init, EXPRESSION, + newInForInit: true, newAtStatementBegin: false); + } + out(";"); + if (loop.condition != null) { + spaceOut(); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + out(";"); + if (loop.update != null) { + spaceOut(); + visitNestedExpression(loop.update, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitForIn(ForIn loop) { + outIndent("for"); + spaceOut(); + out("("); + visitNestedExpression(loop.leftHandSide, EXPRESSION, + newInForInit: true, newAtStatementBegin: false); + out(" in"); + pendingSpace = true; + visitNestedExpression(loop.object, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitWhile(While loop) { + outIndent("while"); + spaceOut(); + out("("); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitDo(Do loop) { + outIndent("do"); + if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) { + spaceOut(); + } else { + indent(); + } + out("while"); + spaceOut(); + out("("); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + outSemicolonLn(); + } + + visitContinue(Continue node) { + if (node.targetLabel == null) { + outIndent("continue"); + } else { + outIndent("continue ${node.targetLabel}"); + } + outSemicolonLn(); + } + + visitBreak(Break node) { + if (node.targetLabel == null) { + outIndent("break"); + } else { + outIndent("break ${node.targetLabel}"); + } + outSemicolonLn(); + } + + visitReturn(Return node) { + if (node.value == null) { + outIndent("return"); + } else { + outIndent("return"); + pendingSpace = true; + visitNestedExpression(node.value, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + outSemicolonLn(); + } + + visitThrow(Throw node) { + outIndent("throw"); + pendingSpace = true; + visitNestedExpression(node.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + outSemicolonLn(); + } + + visitTry(Try node) { + outIndent("try"); + blockBody(node.body, needsSeparation: true, needsNewline: false); + if (node.catchPart != null) { + visit(node.catchPart); + } + if (node.finallyPart != null) { + spaceOut(); + out("finally"); + blockBody(node.finallyPart, needsSeparation: true, needsNewline: true); + } else { + lineOut(); + } + } + + visitCatch(Catch node) { + spaceOut(); + out("catch"); + spaceOut(); + out("("); + visitNestedExpression(node.declaration, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(node.body, needsSeparation: false, needsNewline: true); + } + + visitSwitch(Switch node) { + outIndent("switch"); + spaceOut(); + out("("); + visitNestedExpression(node.key, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + spaceOut(); + outLn("{"); + indentLevel++; + visitAll(node.cases); + indentLevel--; + outIndentLn("}"); + } + + visitCase(Case node) { + outIndent("case"); + pendingSpace = true; + visitNestedExpression(node.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + outLn(":"); + if (!node.body.statements.isEmpty) { + indentLevel++; + blockOutWithoutBraces(node.body); + indentLevel--; + } + } + + visitDefault(Default node) { + outIndentLn("default:"); + if (!node.body.statements.isEmpty) { + indentLevel++; + blockOutWithoutBraces(node.body); + indentLevel--; + } + } + + visitLabeledStatement(LabeledStatement node) { + outIndent("${node.label}:"); + blockBody(node.body, needsSeparation: false, needsNewline: true); + } + + void functionOut(Fun fun, Node name, VarCollector vars) { + out("function"); + if (name != null) { + out(" "); + // Name must be a [Decl]. Therefore only test for primary expressions. + visitNestedExpression(name, PRIMARY, + newInForInit: false, newAtStatementBegin: false); + } + localNamer.enterScope(vars); + out("("); + if (fun.params != null) { + visitCommaSeparated(fun.params, PRIMARY, + newInForInit: false, newAtStatementBegin: false); + } + out(")"); + blockBody(fun.body, needsSeparation: false, needsNewline: false); + localNamer.leaveScope(); + } + + visitFunctionDeclaration(FunctionDeclaration declaration) { + VarCollector vars = new VarCollector(); + vars.visitFunctionDeclaration(declaration); + indent(); + functionOut(declaration.function, declaration.name, vars); + lineOut(); + } + + visitNestedExpression(Expression node, int requiredPrecedence, + {bool newInForInit, bool newAtStatementBegin}) { + bool needsParentheses = + // a - (b + c). + (requiredPrecedence != EXPRESSION && + node.precedenceLevel < requiredPrecedence) || + // for (a = (x in o); ... ; ... ) { ... } + (newInForInit && node is Binary && (node as Binary).op == "in") || + // (function() { ... })(). + // ({a: 2, b: 3}.toString()). + (newAtStatementBegin && (node is NamedFunction || + node is Fun || + node is ObjectInitializer)); + if (needsParentheses) { + inForInit = false; + atStatementBegin = false; + out("("); + visit(node); + out(")"); + } else { + inForInit = newInForInit; + atStatementBegin = newAtStatementBegin; + visit(node); + } + } + + visitVariableDeclarationList(VariableDeclarationList list) { + out("var "); + visitCommaSeparated(list.declarations, ASSIGNMENT, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitSequence(Sequence sequence) { + // Note that we only require that the entries are expressions and not + // assignments. This means that nested sequences are not put into + // parenthesis. + visitCommaSeparated(sequence.expressions, EXPRESSION, + newInForInit: false, + newAtStatementBegin: atStatementBegin); + } + + visitAssignment(Assignment assignment) { + visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + if (assignment.value != null) { + spaceOut(); + String op = assignment.op; + if (op != null) out(op); + out("="); + spaceOut(); + visitNestedExpression(assignment.value, ASSIGNMENT, + newInForInit: inForInit, + newAtStatementBegin: false); + } + } + + visitVariableInitialization(VariableInitialization initialization) { + visitAssignment(initialization); + } + + visitConditional(Conditional cond) { + visitNestedExpression(cond.condition, LOGICAL_OR, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + spaceOut(); + out("?"); + spaceOut(); + // The then part is allowed to have an 'in'. + visitNestedExpression(cond.then, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + spaceOut(); + out(":"); + spaceOut(); + visitNestedExpression(cond.otherwise, ASSIGNMENT, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitNew(New node) { + out("new "); + visitNestedExpression(node.target, CALL, + newInForInit: inForInit, newAtStatementBegin: false); + out("("); + visitCommaSeparated(node.arguments, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + out(")"); + } + + visitCall(Call call) { + visitNestedExpression(call.target, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + out("("); + visitCommaSeparated(call.arguments, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + out(")"); + } + + visitBinary(Binary binary) { + Expression left = binary.left; + Expression right = binary.right; + String op = binary.op; + int leftPrecedenceRequirement; + int rightPrecedenceRequirement; + switch (op) { + case "||": + leftPrecedenceRequirement = LOGICAL_OR; + // x || (y || z) <=> (x || y) || z. + rightPrecedenceRequirement = LOGICAL_OR; + break; + case "&&": + leftPrecedenceRequirement = LOGICAL_AND; + // x && (y && z) <=> (x && y) && z. + rightPrecedenceRequirement = LOGICAL_AND; + break; + case "|": + leftPrecedenceRequirement = BIT_OR; + // x | (y | z) <=> (x | y) | z. + rightPrecedenceRequirement = BIT_OR; + break; + case "^": + leftPrecedenceRequirement = BIT_XOR; + // x ^ (y ^ z) <=> (x ^ y) ^ z. + rightPrecedenceRequirement = BIT_XOR; + break; + case "&": + leftPrecedenceRequirement = BIT_AND; + // x & (y & z) <=> (x & y) & z. + rightPrecedenceRequirement = BIT_AND; + break; + case "==": + case "!=": + case "===": + case "!==": + leftPrecedenceRequirement = EQUALITY; + rightPrecedenceRequirement = RELATIONAL; + break; + case "<": + case ">": + case "<=": + case ">=": + case "instanceof": + case "in": + leftPrecedenceRequirement = RELATIONAL; + rightPrecedenceRequirement = SHIFT; + break; + case ">>": + case "<<": + case ">>>": + leftPrecedenceRequirement = SHIFT; + rightPrecedenceRequirement = ADDITIVE; + break; + case "+": + case "-": + leftPrecedenceRequirement = ADDITIVE; + // We cannot remove parenthesis for "+" because + // x + (y + z) <!=> (x + y) + z: + // Example: + // "a" + (1 + 2) => "a3"; + // ("a" + 1) + 2 => "a12"; + rightPrecedenceRequirement = MULTIPLICATIVE; + break; + case "*": + case "/": + case "%": + leftPrecedenceRequirement = MULTIPLICATIVE; + // We cannot remove parenthesis for "*" because of precision issues. + rightPrecedenceRequirement = UNARY; + break; + default: + compiler.internalError("Forgot operator: $op"); + } + + visitNestedExpression(left, leftPrecedenceRequirement, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + + if (op == "in" || op == "instanceof") { + // There are cases where the space is not required but without further + // analysis we cannot know. + out(" "); + out(op); + out(" "); + } else { + spaceOut(); + out(op); + spaceOut(); + } + visitNestedExpression(right, rightPrecedenceRequirement, + newInForInit: inForInit, + newAtStatementBegin: false); + } + + visitPrefix(Prefix unary) { + String op = unary.op; + switch (op) { + case "delete": + case "void": + case "typeof": + // There are cases where the space is not required but without further + // analysis we cannot know. + out(op); + out(" "); + break; + case "+": + case "++": + if (lastCharCode == charCodes.$PLUS) out(" "); + out(op); + break; + case "-": + case "--": + if (lastCharCode == charCodes.$MINUS) out(" "); + out(op); + break; + default: + out(op); + } + visitNestedExpression(unary.argument, UNARY, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitPostfix(Postfix postfix) { + visitNestedExpression(postfix.argument, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + out(postfix.op); + } + + visitVariableUse(VariableUse ref) { + out(localNamer.getName(ref.name)); + } + + visitThis(This node) { + out("this"); + } + + visitVariableDeclaration(VariableDeclaration decl) { + out(localNamer.getName(decl.name)); + } + + visitParameter(Parameter param) { + out(localNamer.getName(param.name)); + } + + bool isDigit(int charCode) { + return charCodes.$0 <= charCode && charCode <= charCodes.$9; + } + + bool isValidJavaScriptId(String field) { + if (field.length < 3) return false; + // Ignore the leading and trailing string-delimiter. + for (int i = 1; i < field.length - 1; i++) { + // TODO(floitsch): allow more characters. + int charCode = field.charCodeAt(i); + if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || + charCodes.$A <= charCode && charCode <= charCodes.$Z || + charCode == charCodes.$$ || + charCode == charCodes.$_ || + i != 1 && isDigit(charCode))) { + return false; + } + } + // TODO(floitsch): normally we should also check that the field is not a + // reserved word. We don't generate fields with reserved word names except + // for 'super'. + if (field == '"super"') return false; + return true; + } + + visitAccess(PropertyAccess access) { + visitNestedExpression(access.receiver, CALL, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + Node selector = access.selector; + if (selector is LiteralString) { + LiteralString selectorString = selector; + String fieldWithQuotes = selectorString.value; + if (isValidJavaScriptId(fieldWithQuotes)) { + if (access.receiver is LiteralNumber) out(" "); + out("."); + out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)); + return; + } + } + out("["); + visitNestedExpression(selector, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out("]"); + } + + visitNamedFunction(NamedFunction namedFunction) { + VarCollector vars = new VarCollector(); + vars.visitNamedFunction(namedFunction); + functionOut(namedFunction.function, namedFunction.name, vars); + } + + visitFun(Fun fun) { + VarCollector vars = new VarCollector(); + vars.visitFun(fun); + functionOut(fun, null, vars); + } + + visitLiteralBool(LiteralBool node) { + out(node.value ? "true" : "false"); + } + + visitLiteralString(LiteralString node) { + out(node.value); + } + + visitLiteralNumber(LiteralNumber node) { + int charCode = node.value.charCodeAt(0); + if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) { + out(" "); + } + out(node.value); + } + + visitLiteralNull(LiteralNull node) { + out("null"); + } + + visitArrayInitializer(ArrayInitializer node) { + out("["); + List<ArrayElement> elements = node.elements; + int elementIndex = 0; + for (int i = 0; i < node.length; i++) { + if (elementIndex < elements.length && + elements[elementIndex].index == i) { + visitNestedExpression(elements[elementIndex].value, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + elementIndex++; + // We can avoid a trailing "," if there was an element just before. So + // `[1]` and `[1,]` are the same, but `[,]` and `[]` are not. + if (i != node.length - 1) { + out(","); + spaceOut(); + } + } else { + out(","); + } + } + out("]"); + } + + visitArrayElement(ArrayElement node) { + throw "Unreachable"; + } + + visitObjectInitializer(ObjectInitializer node) { + // Print all the properties on one line until we see a function-valued + // property. Ideally, we would use a proper pretty-printer to make the + // decision based on layout. + bool onePerLine = false; + List<Property> properties = node.properties; + out("{"); + ++indentLevel; + for (int i = 0; i < properties.length; i++) { + Expression value = properties[i].value; + if (value is Fun || value is NamedFunction) onePerLine = true; + if (i != 0) { + out(","); + if (!onePerLine) spaceOut(); + } + if (onePerLine) { + forceLine(); + indent(); + } + visitProperty(properties[i]); + } + --indentLevel; + if (onePerLine) lineOut(); + out("}"); + } + + visitProperty(Property node) { + if (node.name is LiteralString) { + LiteralString nameString = node.name; + String name = nameString.value; + if (isValidJavaScriptId(name)) { + out(name.substring(1, name.length - 1)); + } else { + out(name); + } + } else { + assert(node.name is LiteralNumber); + LiteralNumber nameNumber = node.name; + out(nameNumber.value); + } + out(":"); + spaceOut(); + visitNestedExpression(node.value, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + } + + visitRegExpLiteral(RegExpLiteral node) { + out(node.pattern); + } + + visitLiteralExpression(LiteralExpression node) { + String template = node.template; + List<Expression> inputs = node.inputs; + + List<String> parts = template.split('#'); + if (parts.length != inputs.length + 1) { + compiler.internalError('Wrong number of arguments for JS: $template'); + } + // Code that uses JS must take care of operator precedences, and + // put parenthesis if needed. + out(parts[0]); + for (int i = 0; i < inputs.length; i++) { + visit(inputs[i]); + out(parts[i + 1]); + } + } + + visitLiteralStatement(LiteralStatement node) { + outLn(node.code); + } +} + + +class OrderedSet<T> { + final Set<T> set; + final List<T> list; + + OrderedSet() : set = new Set<T>(), list = <T>[]; + + void add(T x) { + if (!set.contains(x)) { + set.add(x); + list.add(x); + } + } + + void forEach(void fun(T x)) { + list.forEach(fun); + } +} + +// Collects all the var declarations in the function. We need to do this in a +// separate pass because JS vars are lifted to the top of the function. +class VarCollector extends BaseVisitor { + bool nested; + final OrderedSet<String> vars; + final OrderedSet<String> params; + + VarCollector() : nested = false, + vars = new OrderedSet<String>(), + params = new OrderedSet<String>(); + + void forEachVar(void fn(String v)) => vars.forEach(fn); + void forEachParam(void fn(String p)) => params.forEach(fn); + + void collectVarsInFunction(Fun fun) { + if (!nested) { + nested = true; + if (fun.params != null) { + for (int i = 0; i < fun.params.length; i++) { + params.add(fun.params[i].name); + } + } + visitBlock(fun.body); + nested = false; + } + } + + void visitFunctionDeclaration(FunctionDeclaration declaration) { + // Note that we don't bother collecting the name of the function. + collectVarsInFunction(declaration.function); + } + + void visitNamedFunction(NamedFunction namedFunction) { + // Note that we don't bother collecting the name of the function. + collectVarsInFunction(namedFunction.function); + } + + void visitFun(Fun fun) { + collectVarsInFunction(fun); + } + + void visitThis(This node) {} + + void visitVariableDeclaration(VariableDeclaration decl) { + vars.add(decl.name); + } +} + + +/** + * Returns true, if the given node must be wrapped into braces when used + * as then-statement in an [If] that has an else branch. + */ +class DanglingElseVisitor extends BaseVisitor<bool> { + leg.Compiler compiler; + + DanglingElseVisitor(this.compiler); + + bool visitProgram(Program node) => false; + + bool visitNode(Statement node) { + compiler.internalError("Forgot node: $node"); + } + + bool visitBlock(Block node) => false; + bool visitExpressionStatement(ExpressionStatement node) => false; + bool visitEmptyStatement(EmptyStatement node) => false; + bool visitIf(If node) { + if (!node.hasElse) return true; + return node.otherwise.accept(this); + } + bool visitFor(For node) => node.body.accept(this); + bool visitForIn(ForIn node) => node.body.accept(this); + bool visitWhile(While node) => node.body.accept(this); + bool visitDo(Do node) => false; + bool visitContinue(Continue node) => false; + bool visitBreak(Break node) => false; + bool visitReturn(Return node) => false; + bool visitThrow(Throw node) => false; + bool visitTry(Try node) { + if (node.finallyPart != null) { + return node.finallyPart.accept(this); + } else { + return node.catchPart.accept(this); + } + } + bool visitCatch(Catch node) => node.body.accept(this); + bool visitSwitch(Switch node) => false; + bool visitCase(Case node) => false; + bool visitDefault(Default node) => false; + bool visitFunctionDeclaration(FunctionDeclaration node) => false; + bool visitLabeledStatement(LabeledStatement node) + => node.body.accept(this); + bool visitLiteralStatement(LiteralStatement node) => true; + + bool visitExpression(Expression node) => false; +} + + +leg.CodeBuffer prettyPrint(Node node, leg.Compiler compiler, + { allowVariableMinification: true }) { + Printer printer = + new Printer(compiler, + allowVariableMinification: allowVariableMinification); + printer.visit(node); + return printer.outBuffer; +} + + +abstract class LocalNamer { + String getName(String oldName); + String declareVariable(String oldName); + String declareParameter(String oldName); + void enterScope(VarCollector vars); + void leaveScope(); +} + + +class IdentityNamer implements LocalNamer { + String getName(String oldName) => oldName; + String declareVariable(String oldName) => oldName; + String declareParameter(String oldName) => oldName; + void enterScope(VarCollector vars) {} + void leaveScope() {} +} + + +class MinifyRenamer implements LocalNamer { + final List<Map<String, String>> maps = []; + final List<int> parameterNumberStack = []; + final List<int> variableNumberStack = []; + int parameterNumber = 0; + int variableNumber = 0; + + MinifyRenamer(); + + void enterScope(VarCollector vars) { + maps.add(new Map<String, String>()); + variableNumberStack.add(variableNumber); + parameterNumberStack.add(parameterNumber); + vars.forEachVar(declareVariable); + vars.forEachParam(declareParameter); + } + + void leaveScope() { + maps.removeLast(); + variableNumber = variableNumberStack.removeLast(); + parameterNumber = parameterNumberStack.removeLast(); + } + + String getName(String oldName) { + // Go from inner scope to outer looking for mapping of name. + for (int i = maps.length - 1; i >= 0; i--) { + var map = maps[i]; + var replacement = map[oldName]; + if (replacement != null) return replacement; + } + return oldName; + } + + static const LOWER_CASE_LETTERS = 26; + static const LETTERS = 52; + static const DIGITS = 10; + + static int nthLetter(int n) { + return (n < LOWER_CASE_LETTERS) ? + charCodes.$a + n : + charCodes.$A + n - LOWER_CASE_LETTERS; + } + + // Parameters go from a to z and variables go from z to a. This makes each + // argument list and each top-of-function var declaration look similar and + // helps gzip compress the file. If we have more than 26 arguments and + // variables then we meet somewhere in the middle of the alphabet. After + // that we give up trying to be nice to the compression algorithm and just + // use the same namespace for arguments and variables, starting with A, and + // moving on to a0, a1, etc. + String declareVariable(String oldName) { + var newName; + if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { + // Variables start from z and go backwards, for better gzipability. + newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber); + } else { + // After 26 variables and parameters we allocate them in the same order. + newName = getNameNumber(oldName, variableNumber + parameterNumber); + } + variableNumber++; + return newName; + } + + String declareParameter(String oldName) { + var newName; + if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { + newName = getNameNumber(oldName, parameterNumber); + } else { + newName = getNameNumber(oldName, variableNumber + parameterNumber); + } + parameterNumber++; + return newName; + } + + String getNameNumber(String oldName, int n) { + if (maps.isEmpty) return oldName; + + String newName; + if (n < LETTERS) { + // Start naming variables a, b, c, ..., z, A, B, C, ..., Z. + newName = new String.fromCharCodes([nthLetter(n)]); + } else { + // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ... + // For all functions with fewer than 500 locals this is just as compact + // as using aa, ab, etc. but avoids clashes with keywords. + n -= LETTERS; + int digit = n % DIGITS; + n ~/= DIGITS; + int alphaChars = 1; + int nameSpaceSize = LETTERS; + // Find out whether we should use the 1-character namespace (size 52), the + // 2-character namespace (size 52*52), etc. + while (n >= nameSpaceSize) { + n -= nameSpaceSize; + alphaChars++; + nameSpaceSize *= LETTERS; + } + var codes = <int>[]; + for (var i = 0; i < alphaChars; i++) { + nameSpaceSize ~/= LETTERS; + codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); + } + codes.add(charCodes.$0 + digit); + newName = new String.fromCharCodes(codes); + } + assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); + maps.last[oldName] = newName; + return newName; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart new file mode 100644 index 0000000..2a681d8 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart
@@ -0,0 +1,1262 @@ +// 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. + +part of js_backend; + +typedef void Recompile(Element element); + +class ReturnInfo { + HType returnType; + List<Element> compiledFunctions; + + ReturnInfo(HType this.returnType) + : compiledFunctions = new List<Element>(); + + ReturnInfo.unknownType() : this(null); + + void update(HType type, Recompile recompile, Compiler compiler) { + HType newType = + returnType != null ? returnType.union(type, compiler) : type; + if (newType != returnType) { + if (returnType == null && identical(newType, HType.UNKNOWN)) { + // If the first actual piece of information is not providing any type + // information there is no need to recompile callers. + compiledFunctions.clear(); + } + returnType = newType; + if (recompile != null) { + compiledFunctions.forEach(recompile); + } + compiledFunctions.clear(); + } + } + + // Note that lazy initializers are treated like functions (but are not + // of type [FunctionElement]. + addCompiledFunction(Element function) => compiledFunctions.add(function); +} + +class OptionalParameterTypes { + final List<SourceString> names; + final List<HType> types; + + OptionalParameterTypes(int optionalArgumentsCount) + : names = new List<SourceString>.fixedLength(optionalArgumentsCount), + types = new List<HType>.fixedLength(optionalArgumentsCount); + + int get length => names.length; + SourceString name(int index) => names[index]; + HType type(int index) => types[index]; + int indexOf(SourceString name) => names.indexOf(name); + + HType typeFor(SourceString name) { + int index = indexOf(name); + if (index == -1) return null; + return type(index); + } + + void update(int index, SourceString name, HType type) { + names[index] = name; + types[index] = type; + } + + String toString() => "OptionalParameterTypes($names, $types)"; +} + +class HTypeList { + final List<HType> types; + final List<SourceString> namedArguments; + + HTypeList(int length) + : types = new List<HType>.fixedLength(length), + namedArguments = null; + HTypeList.withNamedArguments(int length, this.namedArguments) + : types = new List<HType>.fixedLength(length); + const HTypeList.withAllUnknown() + : types = null, + namedArguments = null; + + factory HTypeList.fromStaticInvocation(HInvokeStatic node, HTypeMap types) { + bool allUnknown = true; + for (int i = 1; i < node.inputs.length; i++) { + if (types[node.inputs[i]] != HType.UNKNOWN) { + allUnknown = false; + break; + } + } + if (allUnknown) return HTypeList.ALL_UNKNOWN; + + HTypeList result = new HTypeList(node.inputs.length - 1); + for (int i = 0; i < result.types.length; i++) { + result.types[i] = types[node.inputs[i + 1]]; + } + return result; + } + + factory HTypeList.fromDynamicInvocation(HInvokeDynamic node, + Selector selector, + HTypeMap types) { + HTypeList result; + int argumentsCount = node.inputs.length - 1; + int startInvokeIndex = HInvoke.ARGUMENTS_OFFSET; + + if (node.isInterceptorCall) { + argumentsCount--; + startInvokeIndex++; + } + + if (selector.namedArgumentCount > 0) { + result = + new HTypeList.withNamedArguments( + argumentsCount, selector.namedArguments); + } else { + result = new HTypeList(argumentsCount); + } + + for (int i = 0; i < result.types.length; i++) { + result.types[i] = types[node.inputs[i + startInvokeIndex]]; + } + return result; + } + + static const HTypeList ALL_UNKNOWN = const HTypeList.withAllUnknown(); + + bool get allUnknown => types == null; + bool get hasNamedArguments => namedArguments != null; + int get length => types.length; + HType operator[](int index) => types[index]; + void operator[]=(int index, HType type) { types[index] = type; } + + HTypeList union(HTypeList other, Compiler compiler) { + if (allUnknown) return this; + if (other.allUnknown) return other; + if (length != other.length) return HTypeList.ALL_UNKNOWN; + bool onlyUnknown = true; + HTypeList result = this; + for (int i = 0; i < length; i++) { + HType newType = this[i].union(other[i], compiler); + if (result == this && newType != this[i]) { + // Create a new argument types object with the matching types copied. + result = new HTypeList(length); + result.types.setRange(0, i, this.types); + } + if (result != this) { + result.types[i] = newType; + } + if (result[i] != HType.UNKNOWN) onlyUnknown = false; + } + return onlyUnknown ? HTypeList.ALL_UNKNOWN : result; + } + + HTypeList unionWithOptionalParameters( + Selector selector, + FunctionSignature signature, + OptionalParameterTypes defaultValueTypes) { + assert(allUnknown || selector.argumentCount == this.length); + // Create a new HTypeList for holding types for all parameters. + HTypeList result = new HTypeList(signature.parameterCount); + + // First fill in the type of the positional arguments. + int nextTypeIndex = -1; + if (allUnknown) { + for (int i = 0; i < selector.positionalArgumentCount; i++) { + result.types[i] = HType.UNKNOWN; + } + } else { + result.types.setRange(0, selector.positionalArgumentCount, this.types); + nextTypeIndex = selector.positionalArgumentCount; + } + + // Next fill the type of the optional arguments. + // As the selector can pass optional arguments positionally some of the + // optional arguments might already have a type set. We only need to look + // at the optional arguments not passed positionally. + // The variable 'index' is counting the signatures optional arguments, the + // variable 'next' is set to the next optional arguments to look at and + // is used to skip some optional arguments. + int next = selector.positionalArgumentCount; + int index = signature.requiredParameterCount; + signature.forEachOptionalParameter((Element element) { + // If some optional parameters were passed positionally these have + // already been filled. + if (index == next) { + assert(result.types[index] == null); + HType type = null; + if (hasNamedArguments && + selector.namedArguments.indexOf(element.name) >= 0) { + type = types[nextTypeIndex++]; + } else { + type = defaultValueTypes.typeFor(element.name); + } + result.types[index] = type; + next++; + } + index++; + }); + return result; + } + + String toString() => + allUnknown ? "HTypeList.ALL_UNKNOWN" : "HTypeList $types"; +} + +class FieldTypesRegistry { + final JavaScriptBackend backend; + + /** + * For each class, [constructors] holds the set of constructors. If there is + * more than one constructor for a class it is currently not possible to + * infer the field types from construction, as the information collected does + * not correlate the generative constructors and generative constructor + * body/bodies. + */ + final Map<ClassElement, Set<Element>> constructors; + + /** + * The collected type information is stored in three maps. One for types + * assigned in the initializer list(s) [fieldInitializerTypeMap], one for + * types assigned in the constructor(s) [fieldConstructorTypeMap], and one + * for types assigned in the rest of the code, where the field can be + * resolved [fieldTypeMap]. + * + * If a field has a type both from constructors and from the initializer + * list(s), then the type from the constructor(s) will owerride the one from + * the initializer list(s). + * + * Because the order in which generative constructors, generative constructor + * bodies and normal method/function bodies are compiled is undefined, and + * because they can all be recompiled, it is not possible to combine this + * information into one map at the moment. + */ + final Map<Element, HType> fieldInitializerTypeMap; + final Map<Element, HType> fieldConstructorTypeMap; + final Map<Element, HType> fieldTypeMap; + + /** + * The set of current names setter selectors used. If a named selector is + * used it is currently not possible to infer the type of the field. + */ + final Set<SourceString> setterSelectorsUsed; + + final Map<Element, Set<Element>> optimizedStaticFunctions; + final Map<Element, FunctionSet> optimizedFunctions; + + FieldTypesRegistry(JavaScriptBackend backend) + : constructors = new Map<ClassElement, Set<Element>>(), + fieldInitializerTypeMap = new Map<Element, HType>(), + fieldConstructorTypeMap = new Map<Element, HType>(), + fieldTypeMap = new Map<Element, HType>(), + setterSelectorsUsed = new Set<SourceString>(), + optimizedStaticFunctions = new Map<Element, Set<Element>>(), + optimizedFunctions = new Map<Element, FunctionSet>(), + this.backend = backend; + + Compiler get compiler => backend.compiler; + + void scheduleRecompilation(Element field) { + Set optimizedStatics = optimizedStaticFunctions[field]; + if (optimizedStatics != null) { + optimizedStatics.forEach(backend.scheduleForRecompilation); + optimizedStaticFunctions.remove(field); + } + FunctionSet optimized = optimizedFunctions[field]; + if (optimized != null) { + optimized.forEach(backend.scheduleForRecompilation); + optimizedFunctions.remove(field); + } + } + + int constructorCount(Element element) { + assert(element.isClass()); + Set<Element> ctors = constructors[element]; + return ctors == null ? 0 : ctors.length; + } + + void registerFieldType(Map<Element, HType> typeMap, + Element field, + HType type) { + assert(field.isField()); + HType before = optimisticFieldType(field); + + HType oldType = typeMap[field]; + HType newType; + + if (oldType != null) { + newType = oldType.union(type, compiler); + } else { + newType = type; + } + typeMap[field] = newType; + if (oldType != newType) { + scheduleRecompilation(field); + } + } + + void registerConstructor(Element element) { + assert(element.isGenerativeConstructor()); + Element cls = element.getEnclosingClass(); + constructors.putIfAbsent(cls, () => new Set<Element>()); + Set<Element> ctors = constructors[cls]; + if (ctors.contains(element)) return; + ctors.add(element); + // We cannot infer field types for classes with more than one constructor. + // When the second constructor is seen, recompile all functions relying on + // optimistic field types for that class. + // TODO(sgjesse): Handle field types for classes with more than one + // constructor. + if (ctors.length == 2) { + optimizedFunctions.forEach((Element field, _) { + if (identical(field.enclosingElement, cls)) { + scheduleRecompilation(field); + } + }); + } + } + + void registerFieldInitializer(Element field, HType type) { + registerFieldType(fieldInitializerTypeMap, field, type); + } + + void registerFieldConstructor(Element field, HType type) { + registerFieldType(fieldConstructorTypeMap, field, type); + } + + void registerFieldSetter(FunctionElement element, Element field, HType type) { + HType initializerType = fieldInitializerTypeMap[field]; + HType constructorType = fieldConstructorTypeMap[field]; + HType setterType = fieldTypeMap[field]; + if (type == HType.UNKNOWN + && initializerType == null + && constructorType == null + && setterType == null) { + // Don't register UNKONWN if there is currently no type information + // present for the field. Instead register the function holding the + // setter for recompilation if better type information for the field + // becomes available. + registerOptimizedFunction(element, field, type); + return; + } + registerFieldType(fieldTypeMap, field, type); + } + + void addedDynamicSetter(Selector setter, HType type) { + // Field type optimizations are disabled for all fields matching a + // setter selector. + assert(setter.isSetter()); + // TODO(sgjesse): Take the type of the setter into account. + if (setterSelectorsUsed.contains(setter.name)) return; + setterSelectorsUsed.add(setter.name); + optimizedStaticFunctions.forEach((Element field, _) { + if (field.name == setter.name) { + scheduleRecompilation(field); + } + }); + optimizedFunctions.forEach((Element field, _) { + if (field.name == setter.name) { + scheduleRecompilation(field); + } + }); + } + + HType optimisticFieldType(Element field) { + assert(field.isField()); + if (constructorCount(field.getEnclosingClass()) > 1) { + return HType.UNKNOWN; + } + if (setterSelectorsUsed.contains(field.name)) { + return HType.UNKNOWN; + } + HType initializerType = fieldInitializerTypeMap[field]; + HType constructorType = fieldConstructorTypeMap[field]; + if (initializerType == null && constructorType == null) { + // If there are no constructor type information return UNKNOWN. This + // ensures that the function will be recompiled if useful constructor + // type information becomes available. + return HType.UNKNOWN; + } + // A type set through the constructor overrides the type from the + // initializer list. + HType result = constructorType != null ? constructorType : initializerType; + HType type = fieldTypeMap[field]; + if (type != null) result = result.union(type, compiler); + return result; + } + + void registerOptimizedFunction(FunctionElement element, + Element field, + HType type) { + assert(field.isField()); + if (Elements.isStaticOrTopLevel(element)) { + optimizedStaticFunctions.putIfAbsent( + field, () => new Set<Element>()); + optimizedStaticFunctions[field].add(element); + } else { + optimizedFunctions.putIfAbsent( + field, () => new FunctionSet(backend.compiler)); + optimizedFunctions[field].add(element); + } + } + + void dump() { + Set<Element> allFields = new Set<Element>(); + fieldInitializerTypeMap.keys.forEach(allFields.add); + fieldConstructorTypeMap.keys.forEach(allFields.add); + fieldTypeMap.keys.forEach(allFields.add); + allFields.forEach((Element field) { + print("Inferred $field has type ${optimisticFieldType(field)}"); + }); + } +} + +class ArgumentTypesRegistry { + final JavaScriptBackend backend; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Keys must be declaration elements. + */ + final Map<Element, HTypeList> staticTypeMap; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final Set<Element> optimizedStaticFunctions; + final SelectorMap<HTypeList> selectorTypeMap; + final FunctionSet optimizedFunctions; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Keys must be declaration elements. + */ + final Map<Element, HTypeList> optimizedTypes; + final Map<Element, OptionalParameterTypes> optimizedDefaultValueTypes; + + ArgumentTypesRegistry(JavaScriptBackend backend) + : staticTypeMap = new Map<Element, HTypeList>(), + optimizedStaticFunctions = new Set<Element>(), + selectorTypeMap = new SelectorMap<HTypeList>(backend.compiler), + optimizedFunctions = new FunctionSet(backend.compiler), + optimizedTypes = new Map<Element, HTypeList>(), + optimizedDefaultValueTypes = + new Map<Element, OptionalParameterTypes>(), + this.backend = backend; + + Compiler get compiler => backend.compiler; + + bool updateTypes(HTypeList oldTypes, HTypeList newTypes, var key, var map) { + if (oldTypes.allUnknown) return false; + newTypes = oldTypes.union(newTypes, backend.compiler); + if (identical(newTypes, oldTypes)) return false; + map[key] = newTypes; + return true; + } + + void registerStaticInvocation(HInvokeStatic node, HTypeMap types) { + Element element = node.element; + assert(invariant(node, element.isDeclaration)); + HTypeList oldTypes = staticTypeMap[element]; + HTypeList newTypes = new HTypeList.fromStaticInvocation(node, types); + if (oldTypes == null) { + staticTypeMap[element] = newTypes; + } else if (updateTypes(oldTypes, newTypes, element, staticTypeMap)) { + if (optimizedStaticFunctions.contains(element)) { + backend.scheduleForRecompilation(element); + } + } + } + + void registerNonCallStaticUse(HStatic node) { + // When a static is used for anything else than a call target we cannot + // infer anything about its parameter types. + Element element = node.element; + assert(invariant(node, element.isDeclaration)); + if (optimizedStaticFunctions.contains(element)) { + backend.scheduleForRecompilation(element); + } + staticTypeMap[element] = HTypeList.ALL_UNKNOWN; + } + + void registerDynamicInvocation(HTypeList providedTypes, Selector selector) { + if (selector.isClosureCall()) { + // We cannot use the current framework to do optimizations based + // on the 'call' selector because we are also generating closure + // calls during the emitter phase, which at this point, does not + // track parameter types, nor invalidates optimized methods. + return; + } + if (!selectorTypeMap.containsKey(selector)) { + selectorTypeMap[selector] = providedTypes; + } else { + HTypeList oldTypes = selectorTypeMap[selector]; + updateTypes(oldTypes, providedTypes, selector, selectorTypeMap); + } + + // If we're not compiling, we don't have to do anything. + if (compiler.phase != Compiler.PHASE_COMPILING) return; + + // Run through all optimized functions and figure out if they need + // to be recompiled because of this new invocation. + optimizedFunctions.filterBySelector(selector).forEach((Element element) { + // TODO(kasperl): Maybe check if the element is already marked for + // recompilation? Could be pretty cheap compared to computing + // union types. + HTypeList newTypes = + parameterTypes(element, optimizedDefaultValueTypes[element]); + bool recompile = false; + if (newTypes.allUnknown) { + recompile = true; + } else { + HTypeList oldTypes = optimizedTypes[element]; + assert(newTypes.length == oldTypes.length); + for (int i = 0; i < oldTypes.length; i++) { + if (newTypes[i] != oldTypes[i]) { + recompile = true; + break; + } + } + } + if (recompile) backend.scheduleForRecompilation(element); + }); + } + + HTypeList parameterTypes(FunctionElement element, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + // Handle static functions separately. + if (Elements.isStaticOrTopLevelFunction(element) || + element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { + HTypeList types = staticTypeMap[element]; + if (types != null) { + if (!optimizedStaticFunctions.contains(element)) { + optimizedStaticFunctions.add(element); + } + return types; + } else { + return HTypeList.ALL_UNKNOWN; + } + } + + // Getters have no parameters. + if (element.isGetter()) return HTypeList.ALL_UNKNOWN; + + // TODO(kasperl): What kind of non-members do we get here? + if (!element.isMember()) return HTypeList.ALL_UNKNOWN; + + // If there are any getters for this method we cannot know anything about + // the types of the provided parameters. Use resolverWorld for now as that + // information does not change during compilation. + // TODO(ngeoffray): These checks should use the codegenWorld and keep track + // of changes to this information. + if (compiler.resolverWorld.hasInvokedGetter(element, compiler)) { + return HTypeList.ALL_UNKNOWN; + } + + FunctionSignature signature = element.computeSignature(compiler); + HTypeList found = null; + selectorTypeMap.visitMatching(element, + (Selector selector, HTypeList types) { + if (selector.argumentCount != signature.parameterCount || + selector.namedArgumentCount > 0) { + types = types.unionWithOptionalParameters(selector, + signature, + defaultValueTypes); + } + assert(types.allUnknown || types.length == signature.parameterCount); + found = (found == null) ? types : found.union(types, compiler); + return !found.allUnknown; + }); + return found != null ? found : HTypeList.ALL_UNKNOWN; + } + + void registerOptimizedFunction(Element element, + HTypeList parameterTypes, + OptionalParameterTypes defaultValueTypes) { + if (Elements.isStaticOrTopLevelFunction(element)) { + if (parameterTypes.allUnknown) { + optimizedStaticFunctions.remove(element); + } else { + optimizedStaticFunctions.add(element); + } + } + + // TODO(kasperl): What kind of non-members do we get here? + if (!element.isMember()) return; + + if (parameterTypes.allUnknown) { + optimizedFunctions.remove(element); + optimizedTypes.remove(element); + optimizedDefaultValueTypes.remove(element); + } else { + optimizedFunctions.add(element); + optimizedTypes[element] = parameterTypes; + optimizedDefaultValueTypes[element] = defaultValueTypes; + } + } + + void dump() { + optimizedFunctions.forEach((Element element) { + HTypeList types = optimizedTypes[element]; + print("Inferred $element has argument types ${types.types}"); + }); + } +} + +class JavaScriptItemCompilationContext extends ItemCompilationContext { + final HTypeMap types; + final Set<HInstruction> boundsChecked; + + JavaScriptItemCompilationContext() + : types = new HTypeMap(), + boundsChecked = new Set<HInstruction>(); +} + +class JavaScriptBackend extends Backend { + SsaBuilderTask builder; + SsaOptimizerTask optimizer; + SsaCodeGeneratorTask generator; + CodeEmitterTask emitter; + + /** + * The generated code as a js AST for compiled methods. + */ + Map<Element, js.Expression> get generatedCode { + return compiler.enqueuer.codegen.generatedCode; + } + + /** + * The generated code as a js AST for compiled bailout methods. + */ + final Map<Element, js.Expression> generatedBailoutCode = + new Map<Element, js.Expression>(); + + ClassElement jsStringClass; + ClassElement jsArrayClass; + ClassElement jsNumberClass; + ClassElement jsIntClass; + ClassElement jsDoubleClass; + ClassElement jsFunctionClass; + ClassElement jsNullClass; + ClassElement jsBoolClass; + ClassElement objectInterceptorClass; + Element jsArrayLength; + Element jsStringLength; + Element jsArrayRemoveLast; + Element jsArrayAdd; + Element jsStringSplit; + Element jsStringConcat; + Element jsStringToString; + Element getInterceptorMethod; + Element fixedLengthListConstructor; + bool seenAnyClass = false; + + final Namer namer; + + /** + * Interface used to determine if an object has the JavaScript + * indexing behavior. The interface is only visible to specific + * libraries. + */ + ClassElement jsIndexingBehaviorInterface; + + final Map<Element, ReturnInfo> returnInfo; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final List<Element> invalidateAfterCodegen; + ArgumentTypesRegistry argumentTypes; + FieldTypesRegistry fieldTypes; + + /** + * A collection of selectors of intercepted method calls. The + * emitter uses this set to generate the [:ObjectInterceptor:] class + * whose members just forward the call to the intercepted receiver. + */ + final Set<Selector> usedInterceptors; + + /** + * A collection of selectors that must have a one shot interceptor + * generated. + */ + final Set<Selector> oneShotInterceptors; + + /** + * The members of instantiated interceptor classes: maps a member + * name to the list of members that have that name. This map is used + * by the codegen to know whether a send must be intercepted or not. + */ + final Map<SourceString, Set<Element>> interceptedElements; + + /** + * A map of specialized versions of the [getInterceptorMethod]. + * Since [getInterceptorMethod] is a hot method at runtime, we're + * always specializing it based on the incoming type. The keys in + * the map are the names of these specialized versions. Note that + * the generic version that contains all possible type checks is + * also stored in this map. + */ + final Map<String, Collection<ClassElement>> specializedGetInterceptors; + + /** + * Set of classes whose instances are intercepted. Implemented as a + * [LinkedHashMap] to preserve the insertion order. + * TODO(ngeoffray): No need to preserve order anymore. + */ + final Map<ClassElement, ClassElement> interceptedClasses; + + List<CompilerTask> get tasks { + return <CompilerTask>[builder, optimizer, generator, emitter]; + } + + final RuntimeTypeInformation rti; + + JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval) + : namer = determineNamer(compiler), + returnInfo = new Map<Element, ReturnInfo>(), + invalidateAfterCodegen = new List<Element>(), + usedInterceptors = new Set<Selector>(), + oneShotInterceptors = new Set<Selector>(), + interceptedElements = new Map<SourceString, Set<Element>>(), + rti = new RuntimeTypeInformation(compiler), + specializedGetInterceptors = + new Map<String, Collection<ClassElement>>(), + interceptedClasses = new LinkedHashMap<ClassElement, ClassElement>(), + super(compiler, JAVA_SCRIPT_CONSTANT_SYSTEM) { + emitter = disableEval + ? new CodeEmitterNoEvalTask(compiler, namer, generateSourceMap) + : new CodeEmitterTask(compiler, namer, generateSourceMap); + builder = new SsaBuilderTask(this); + optimizer = new SsaOptimizerTask(this); + generator = new SsaCodeGeneratorTask(this); + argumentTypes = new ArgumentTypesRegistry(this); + fieldTypes = new FieldTypesRegistry(this); + } + + static Namer determineNamer(Compiler compiler) { + return compiler.enableMinification ? + new MinifyNamer(compiler) : + new Namer(compiler); + } + + bool isInterceptorClass(Element element) { + if (element == null) return false; + return interceptedClasses.containsKey(element); + } + + void addInterceptedSelector(Selector selector) { + usedInterceptors.add(selector); + } + + void addOneShotInterceptor(Selector selector) { + oneShotInterceptors.add(selector); + } + + /** + * Returns a set of interceptor classes that contain a member whose + * signature matches the given [selector]. Returns [:null:] if there + * is no class. + */ + Set<ClassElement> getInterceptedClassesOn(Selector selector) { + Set<Element> intercepted = interceptedElements[selector.name]; + if (intercepted == null) return null; + Set<ClassElement> result = new Set<ClassElement>(); + for (Element element in intercepted) { + if (selector.applies(element, compiler)) { + result.add(element.getEnclosingClass()); + } + } + if (result.isEmpty) return null; + return result; + } + + List<ClassElement> getListOfInterceptedClasses() { + return <ClassElement>[jsStringClass, jsArrayClass, jsIntClass, + jsDoubleClass, jsNumberClass, jsNullClass, + jsFunctionClass, jsBoolClass]; + } + + void initializeInterceptorElements() { + objectInterceptorClass = + compiler.findInterceptor(const SourceString('ObjectInterceptor')); + getInterceptorMethod = + compiler.findInterceptor(const SourceString('getInterceptor')); + List<ClassElement> classes = [ + jsStringClass = compiler.findInterceptor(const SourceString('JSString')), + jsArrayClass = compiler.findInterceptor(const SourceString('JSArray')), + // The int class must be before the double class, because the + // emitter relies on this list for the order of type checks. + jsIntClass = compiler.findInterceptor(const SourceString('JSInt')), + jsDoubleClass = compiler.findInterceptor(const SourceString('JSDouble')), + jsNumberClass = compiler.findInterceptor(const SourceString('JSNumber')), + jsNullClass = compiler.findInterceptor(const SourceString('JSNull')), + jsFunctionClass = + compiler.findInterceptor(const SourceString('JSFunction')), + jsBoolClass = compiler.findInterceptor(const SourceString('JSBool'))]; + + jsArrayClass.ensureResolved(compiler); + jsArrayLength = compiler.lookupElementIn( + jsArrayClass, const SourceString('length')); + jsArrayRemoveLast = compiler.lookupElementIn( + jsArrayClass, const SourceString('removeLast')); + jsArrayAdd = compiler.lookupElementIn( + jsArrayClass, const SourceString('add')); + + jsStringClass.ensureResolved(compiler); + jsStringLength = compiler.lookupElementIn( + jsStringClass, const SourceString('length')); + jsStringSplit = compiler.lookupElementIn( + jsStringClass, const SourceString('split')); + jsStringConcat = compiler.lookupElementIn( + jsStringClass, const SourceString('concat')); + jsStringToString = compiler.lookupElementIn( + jsStringClass, const SourceString('toString')); + + for (ClassElement cls in classes) { + if (cls != null) interceptedClasses[cls] = null; + } + } + + void addInterceptors(ClassElement cls, Enqueuer enqueuer) { + if (enqueuer.isResolutionQueue) { + cls.ensureResolved(compiler); + cls.forEachMember((ClassElement classElement, Element member) { + Set<Element> set = interceptedElements.putIfAbsent( + member.name, () => new Set<Element>()); + set.add(member); + }, + includeSuperMembers: true); + } + enqueuer.registerInstantiatedClass(cls); + } + + void registerSpecializedGetInterceptor(Set<ClassElement> classes) { + compiler.enqueuer.codegen.registerInstantiatedClass(objectInterceptorClass); + String name = namer.getInterceptorName(getInterceptorMethod, classes); + if (classes.contains(compiler.objectClass)) { + // We can't use a specialized [getInterceptorMethod], so we make + // sure we emit the one with all checks. + specializedGetInterceptors.putIfAbsent(name, () { + // It is important to take the order provided by the map, + // because we want the int type check to happen before the + // double type check: the double type check covers the int + // type check. Also we don't need to do a number type check + // because that is covered by the double type check. + List<ClassElement> keys = <ClassElement>[]; + interceptedClasses.forEach((ClassElement cls, _) { + if (cls != jsNumberClass) keys.add(cls); + }); + return keys; + }); + } else { + specializedGetInterceptors[name] = classes; + } + } + + void initializeNoSuchMethod() { + // In case the emitter generates noSuchMethod calls, we need to + // make sure all [noSuchMethod] methods know they might take a + // [JsInvocationMirror] as parameter. + HTypeList types = new HTypeList(1); + types[0] = new HType.fromBoundedType( + compiler.jsInvocationMirrorClass.computeType(compiler), + compiler, + false); + argumentTypes.registerDynamicInvocation(types, new Selector.noSuchMethod()); + } + + void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) { + if (!seenAnyClass) { + initializeInterceptorElements(); + initializeNoSuchMethod(); + seenAnyClass = true; + } + ClassElement result = null; + if (cls == compiler.stringClass) { + addInterceptors(jsStringClass, enqueuer); + } else if (cls == compiler.listClass) { + addInterceptors(jsArrayClass, enqueuer); + // The backend will try to optimize array access and use the + // `ioore` and `iae` helpers directly. + if (enqueuer.isResolutionQueue) { + enqueuer.registerStaticUse( + compiler.findHelper(const SourceString('ioore'))); + enqueuer.registerStaticUse( + compiler.findHelper(const SourceString('iae'))); + } + } else if (cls == compiler.intClass) { + addInterceptors(jsIntClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.doubleClass) { + addInterceptors(jsDoubleClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.functionClass) { + addInterceptors(jsFunctionClass, enqueuer); + } else if (cls == compiler.boolClass) { + addInterceptors(jsBoolClass, enqueuer); + } else if (cls == compiler.nullClass) { + addInterceptors(jsNullClass, enqueuer); + } else if (cls == compiler.numClass) { + addInterceptors(jsIntClass, enqueuer); + addInterceptors(jsDoubleClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.mapClass) { + // The backend will use a literal list to initialize the entries + // of the map. + if (enqueuer.isResolutionQueue) { + enqueuer.registerInstantiatedClass(compiler.listClass); + } + } + } + + Element get cyclicThrowHelper { + return compiler.findHelper(const SourceString("throwCyclicInit")); + } + + JavaScriptItemCompilationContext createItemCompilationContext() { + return new JavaScriptItemCompilationContext(); + } + + void enqueueHelpers(ResolutionEnqueuer world) { + enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world); + + jsIndexingBehaviorInterface = + compiler.findHelper(const SourceString('JavaScriptIndexingBehavior')); + if (jsIndexingBehaviorInterface != null) { + world.registerIsCheck(jsIndexingBehaviorInterface.computeType(compiler)); + } + + for (var helper in [const SourceString('Closure'), + const SourceString('ConstantMap'), + const SourceString('ConstantProtoMap')]) { + var e = compiler.findHelper(helper); + if (e != null) world.registerInstantiatedClass(e); + } + } + + void codegen(CodegenWorkItem work) { + Element element = work.element; + if (element.kind.category == ElementCategory.VARIABLE) { + Constant initialValue = compiler.constantHandler.compileWorkItem(work); + if (initialValue != null) { + return; + } else { + // If the constant-handler was not able to produce a result we have to + // go through the builder (below) to generate the lazy initializer for + // the static variable. + // We also need to register the use of the cyclic-error helper. + compiler.enqueuer.codegen.registerStaticUse(cyclicThrowHelper); + } + } + + HGraph graph = builder.build(work); + optimizer.optimize(work, graph, false); + if (work.allowSpeculativeOptimization + && optimizer.trySpeculativeOptimizations(work, graph)) { + js.Expression code = generator.generateBailoutMethod(work, graph); + generatedBailoutCode[element] = code; + optimizer.prepareForSpeculativeOptimizations(work, graph); + optimizer.optimize(work, graph, true); + } + js.Expression code = generator.generateCode(work, graph); + generatedCode[element] = code; + invalidateAfterCodegen.forEach(eagerRecompile); + invalidateAfterCodegen.clear(); + } + + native.NativeEnqueuer nativeResolutionEnqueuer(Enqueuer world) { + return new native.NativeResolutionEnqueuer(world, compiler); + } + + native.NativeEnqueuer nativeCodegenEnqueuer(Enqueuer world) { + return new native.NativeCodegenEnqueuer(world, compiler, emitter); + } + + /** + * Unit test hook that returns code of an element as a String. + * + * Invariant: [element] must be a declaration element. + */ + String assembleCode(Element element) { + assert(invariant(element, element.isDeclaration)); + return js.prettyPrint(generatedCode[element], compiler).getText(); + } + + void assembleProgram() { + emitter.assembleProgram(); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void scheduleForRecompilation(Element element) { + assert(invariant(element, element.isDeclaration)); + if (compiler.phase == Compiler.PHASE_COMPILING) { + invalidateAfterCodegen.add(element); + } + } + + /** + * Register a dynamic invocation and collect the provided types for the + * named selector. + */ + void registerDynamicInvocation(HInvokeDynamic node, + Selector selector, + HTypeMap types) { + HTypeList providedTypes = + new HTypeList.fromDynamicInvocation(node, selector, types); + argumentTypes.registerDynamicInvocation(providedTypes, selector); + } + + /** + * Register a static invocation and collect the provided types for the + * named selector. + */ + void registerStaticInvocation(HInvokeStatic node, HTypeMap types) { + argumentTypes.registerStaticInvocation(node, types); + } + + /** + * Register that a static is used for something else than a direct call + * target. + */ + void registerNonCallStaticUse(HStatic node) { + argumentTypes.registerNonCallStaticUse(node); + } + + /** + * Retrieve the types of the parameters used for calling the [element] + * function. The types are optimistic in the sense as they are based on the + * possible invocations of the function seen so far. + * + * Invariant: [element] must be a declaration element. + */ + HTypeList optimisticParameterTypes( + FunctionElement element, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + if (element.parameterCount(compiler) == 0) return HTypeList.ALL_UNKNOWN; + return argumentTypes.parameterTypes(element, defaultValueTypes); + } + + /** + * Register that the function [element] has been optimized under the + * assumptions that the types [parameterType] will be used for calling it. + * The passed [defaultValueTypes] holds the types of default values for + * the optional parameters. If this assumption fail the function will be + * scheduled for recompilation. + * + * Invariant: [element] must be a declaration element. + */ + registerParameterTypesOptimization( + FunctionElement element, + HTypeList parameterTypes, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + if (element.parameterCount(compiler) == 0) return; + argumentTypes.registerOptimizedFunction( + element, parameterTypes, defaultValueTypes); + } + + registerFieldTypesOptimization(FunctionElement element, + Element field, + HType type) { + fieldTypes.registerOptimizedFunction(element, field, type); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void registerReturnType(FunctionElement element, HType returnType) { + assert(invariant(element, element.isDeclaration)); + ReturnInfo info = returnInfo[element]; + if (info != null) { + info.update(returnType, scheduleForRecompilation, compiler); + } else { + returnInfo[element] = new ReturnInfo(returnType); + } + } + + /** + * Retrieve the return type of the function [callee]. The type is optimistic + * in the sense that is is based on the compilation of [callee]. If [callee] + * is recompiled the return type might change to someting broader. For that + * reason [caller] is registered for recompilation if this happens. If the + * function [callee] has not yet been compiled the returned type is [null]. + * + * Invariant: Both [caller] and [callee] must be declaration elements. + */ + HType optimisticReturnTypesWithRecompilationOnTypeChange( + Element caller, FunctionElement callee) { + assert(invariant(callee, callee.isDeclaration)); + returnInfo.putIfAbsent(callee, () => new ReturnInfo.unknownType()); + ReturnInfo info = returnInfo[callee]; + HType returnType = info.returnType; + if (returnType != HType.UNKNOWN && returnType != null && caller != null) { + assert(invariant(caller, caller.isDeclaration)); + info.addCompiledFunction(caller); + } + return info.returnType; + } + + void dumpReturnTypes() { + returnInfo.forEach((Element element, ReturnInfo info) { + if (info.returnType != HType.UNKNOWN) { + print("Inferred $element has return type ${info.returnType}"); + } + }); + } + + void registerConstructor(Element element) { + fieldTypes.registerConstructor(element); + } + + void registerFieldInitializer(Element field, HType type) { + fieldTypes.registerFieldInitializer(field, type); + } + + void registerFieldConstructor(Element field, HType type) { + fieldTypes.registerFieldConstructor(field, type); + } + + void registerFieldSetter(FunctionElement element, Element field, HType type) { + fieldTypes.registerFieldSetter(element, field, type); + } + + void addedDynamicSetter(Selector setter, HType type) { + fieldTypes.addedDynamicSetter(setter, type); + } + + HType optimisticFieldType(Element element) { + return fieldTypes.optimisticFieldType(element); + } + + /** + * Return the checked mode helper name that will be needed to do a + * type check on [type] at runtime. Note that this method is being + * called both by the resolver with interface types (int, String, + * ...), and by the SSA backend with implementation types (JSInt, + * JSString, ...). + */ + SourceString getCheckedModeHelper(DartType type) { + Element element = type.element; + bool nativeCheck = + emitter.nativeEmitter.requiresNativeIsCheck(element); + if (type.isMalformed) { + // Check for malformed types first, because the type may be a list type + // with a malformed argument type. + return const SourceString('malformedTypeCheck'); + } else if (type == compiler.types.voidType) { + return const SourceString('voidTypeCheck'); + } else if (element == jsStringClass || element == compiler.stringClass) { + return const SourceString('stringTypeCheck'); + } else if (element == jsDoubleClass || element == compiler.doubleClass) { + return const SourceString('doubleTypeCheck'); + } else if (element == jsNumberClass || element == compiler.numClass) { + return const SourceString('numTypeCheck'); + } else if (element == jsBoolClass || element == compiler.boolClass) { + return const SourceString('boolTypeCheck'); + } else if (element == jsFunctionClass + || element == compiler.functionClass) { + return const SourceString('functionTypeCheck'); + } else if (element == jsIntClass || element == compiler.intClass) { + return const SourceString('intTypeCheck'); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + return nativeCheck + ? const SourceString('numberOrStringSuperNativeTypeCheck') + : const SourceString('numberOrStringSuperTypeCheck'); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + return nativeCheck + ? const SourceString('stringSuperNativeTypeCheck') + : const SourceString('stringSuperTypeCheck'); + } else if (element == compiler.listClass || element == jsArrayClass) { + return const SourceString('listTypeCheck'); + } else { + if (Elements.isListSupertype(element, compiler)) { + return nativeCheck + ? const SourceString('listSuperNativeTypeCheck') + : const SourceString('listSuperTypeCheck'); + } else { + return nativeCheck + ? const SourceString('callTypeCheck') + : const SourceString('propertyTypeCheck'); + } + } + } + + void dumpInferredTypes() { + print("Inferred argument types:"); + print("------------------------"); + argumentTypes.dump(); + print(""); + print("Inferred return types:"); + print("----------------------"); + dumpReturnTypes(); + print(""); + print("Inferred field types:"); + print("------------------------"); + fieldTypes.dump(); + print(""); + } + + Element getExceptionUnwrapper() { + return compiler.findHelper(const SourceString('unwrapException')); + } + + Element getThrowRuntimeError() { + return compiler.findHelper(const SourceString('throwRuntimeError')); + } + + Element getThrowMalformedSubtypeError() { + return compiler.findHelper( + const SourceString('throwMalformedSubtypeError')); + } + + Element getThrowAbstractClassInstantiationError() { + return compiler.findHelper( + const SourceString('throwAbstractClassInstantiationError')); + } + + Element getClosureConverter() { + return compiler.findHelper(const SourceString('convertDartClosureToJS')); + } + + Element getTraceFromException() { + return compiler.findHelper(const SourceString('getTraceFromException')); + } + + Element getMapMaker() { + return compiler.findHelper(const SourceString('makeLiteralMap')); + } + + Element getSetRuntimeTypeInfo() { + return compiler.findHelper(const SourceString('setRuntimeTypeInfo')); + } + + Element getGetRuntimeTypeInfo() { + return compiler.findHelper(const SourceString('getRuntimeTypeInfo')); + } + + /** + * Remove [element] from the set of generated code, and put it back + * into the worklist. + * + * Invariant: [element] must be a declaration element. + */ + void eagerRecompile(Element element) { + assert(invariant(element, element.isDeclaration)); + generatedCode.remove(element); + generatedBailoutCode.remove(element); + compiler.enqueuer.codegen.addToWorkList(element); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart new file mode 100644 index 0000000..86717ae --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart
@@ -0,0 +1,325 @@ +// 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. + +part of js_backend; + +class ConstantEmitter { + ConstantReferenceEmitter _referenceEmitter; + ConstantInitializerEmitter _initializerEmitter; + + ConstantEmitter(Compiler compiler, Namer namer) { + _referenceEmitter = new ConstantReferenceEmitter(compiler, namer); + _initializerEmitter = new ConstantInitializerEmitter( + compiler, namer, _referenceEmitter); + } + + /** + * Constructs an expression that is a reference to the constant. Uses a + * canonical name unless the constant can be emitted multiple times (as for + * numbers and strings). + */ + js.Expression reference(Constant constant) { + return _referenceEmitter.generate(constant); + } + + /** + * Constructs an expression like [reference], but the expression is valid + * during isolate initialization. + */ + js.Expression referenceInInitializationContext(Constant constant) { + return _referenceEmitter.generateInInitializationContext(constant); + } + + /** + * Constructs an expression used to initialize a canonicalized constant. + */ + js.Expression initializationExpression(Constant constant) { + return _initializerEmitter.generate(constant); + } +} + +/** + * Visitor for generating JavaScript expressions to refer to [Constant]s. + * Do not use directly, use methods from [ConstantEmitter]. + */ +class ConstantReferenceEmitter implements ConstantVisitor<js.Expression> { + final Compiler compiler; + final Namer namer; + bool inIsolateInitializationContext = false; + + ConstantReferenceEmitter(this.compiler, this.namer); + + js.Expression generate(Constant constant) { + inIsolateInitializationContext = false; + return _visit(constant); + } + + js.Expression generateInInitializationContext(Constant constant) { + inIsolateInitializationContext = true; + return _visit(constant); + } + + js.Expression _visit(Constant constant) { + return constant.accept(this); + } + + js.Expression visitSentinel(SentinelConstant constant) { + return new js.VariableUse(namer.CURRENT_ISOLATE); + } + + js.Expression visitFunction(FunctionConstant constant) { + return inIsolateInitializationContext + ? new js.VariableUse(namer.isolatePropertiesAccess(constant.element)) + : new js.VariableUse(namer.isolateAccess(constant.element)); + } + + js.Expression visitNull(NullConstant constant) { + return new js.LiteralNull(); + } + + js.Expression visitInt(IntConstant constant) { + return new js.LiteralNumber('${constant.value}'); + } + + js.Expression visitDouble(DoubleConstant constant) { + double value = constant.value; + if (value.isNaN) { + return new js.LiteralNumber("(0/0)"); + } else if (value == double.INFINITY) { + return new js.LiteralNumber("(1/0)"); + } else if (value == -double.INFINITY) { + return new js.LiteralNumber("(-1/0)"); + } else { + return new js.LiteralNumber("$value"); + } + } + + js.Expression visitTrue(TrueConstant constant) { + if (compiler.enableMinification) { + // Use !0 for true. + return new js.Prefix("!", new js.LiteralNumber("0")); + } else { + return new js.LiteralBool(true); + } + + } + + js.Expression visitFalse(FalseConstant constant) { + if (compiler.enableMinification) { + // Use !1 for false. + return new js.Prefix("!", new js.LiteralNumber("1")); + } else { + return new js.LiteralBool(false); + } + } + + /** + * Write the contents of the quoted string to a [CodeBuffer] in + * a form that is valid as JavaScript string literal content. + * The string is assumed quoted by double quote characters. + */ + js.Expression visitString(StringConstant constant) { + // TODO(sra): If the string is long *and repeated* (and not on a hot path) + // then it should be assigned to a name. We don't have reference counts (or + // profile information) here, so this is the wrong place. + StringBuffer sb = new StringBuffer(); + writeJsonEscapedCharsOn(constant.value.slowToString(), sb); + return new js.LiteralString('"$sb"'); + } + + js.Expression emitCanonicalVersion(Constant constant) { + String name = namer.constantName(constant); + if (inIsolateInitializationContext) { + // $isolateName.$isolatePropertiesName.$name + return new js.PropertyAccess.field( + new js.PropertyAccess.field( + new js.VariableUse(namer.isolateName), + namer.isolatePropertiesName), + name); + } else { + return new js.PropertyAccess.field( + new js.VariableUse(namer.CURRENT_ISOLATE), + name); + } + } + + js.Expression visitList(ListConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitMap(MapConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitType(TypeConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitConstructed(ConstructedConstant constant) { + return emitCanonicalVersion(constant); + } +} + +/** + * Visitor for generating JavaScript expressions to initialize [Constant]s. + * Do not use directly; use methods from [ConstantEmitter]. + */ +class ConstantInitializerEmitter implements ConstantVisitor<js.Expression> { + final Compiler compiler; + final Namer namer; + final ConstantReferenceEmitter referenceEmitter; + + ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter); + + js.Expression generate(Constant constant) { + return _visit(constant); + } + + js.Expression _visit(Constant constant) { + return constant.accept(this); + } + + js.Expression _reference(Constant constant) { + return referenceEmitter.generateInInitializationContext(constant); + } + + js.Expression visitSentinel(SentinelConstant constant) { + compiler.internalError( + "The parameter sentinel constant does not need specific JS code"); + } + + js.Expression visitFunction(FunctionConstant constant) { + compiler.internalError( + "The function constant does not need specific JS code"); + } + + js.Expression visitNull(NullConstant constant) { + return _reference(constant); + } + + js.Expression visitInt(IntConstant constant) { + return _reference(constant); + } + + js.Expression visitDouble(DoubleConstant constant) { + return _reference(constant); + } + + js.Expression visitTrue(TrueConstant constant) { + return _reference(constant); + } + + js.Expression visitFalse(FalseConstant constant) { + return _reference(constant); + } + + js.Expression visitString(StringConstant constant) { + // TODO(sra): Some larger strings are worth sharing. + return _reference(constant); + } + + js.Expression visitList(ListConstant constant) { + return new js.Call( + new js.PropertyAccess.field( + new js.VariableUse(namer.isolateName), + 'makeConstantList'), + [new js.ArrayInitializer.from(_array(constant.entries))]); + } + + String getJsConstructor(ClassElement element) { + return namer.isolatePropertiesAccess(element); + } + + js.Expression visitMap(MapConstant constant) { + js.Expression jsMap() { + List<js.Property> properties = <js.Property>[]; + int valueIndex = 0; + for (int i = 0; i < constant.keys.entries.length; i++) { + StringConstant key = constant.keys.entries[i]; + if (key.value == MapConstant.PROTO_PROPERTY) continue; + + // Keys in literal maps must be emitted in place. + js.Literal keyExpression = _visit(key); + js.Expression valueExpression = + _reference(constant.values[valueIndex++]); + properties.add(new js.Property(keyExpression, valueExpression)); + } + if (valueIndex != constant.values.length) { + compiler.internalError("Bad value count."); + } + return new js.ObjectInitializer(properties); + } + + void badFieldCountError() { + compiler.internalError( + "Compiler and ConstantMap disagree on number of fields."); + } + + ClassElement classElement = constant.type.element; + + List<js.Expression> arguments = <js.Expression>[]; + + // The arguments of the JavaScript constructor for any given Dart class + // are in the same order as the members of the class element. + int emittedArgumentCount = 0; + classElement.implementation.forEachInstanceField( + (ClassElement enclosing, Element field) { + if (field.name == MapConstant.LENGTH_NAME) { + arguments.add( + new js.LiteralNumber('${constant.keys.entries.length}')); + } else if (field.name == MapConstant.JS_OBJECT_NAME) { + arguments.add(jsMap()); + } else if (field.name == MapConstant.KEYS_NAME) { + arguments.add(_reference(constant.keys)); + } else if (field.name == MapConstant.PROTO_VALUE) { + assert(constant.protoValue != null); + arguments.add(_reference(constant.protoValue)); + } else { + badFieldCountError(); + } + emittedArgumentCount++; + }, + includeBackendMembers: true, + includeSuperMembers: true); + + if ((constant.protoValue == null && emittedArgumentCount != 3) || + (constant.protoValue != null && emittedArgumentCount != 4)) { + badFieldCountError(); + } + + return new js.New( + new js.VariableUse(getJsConstructor(classElement)), + arguments); + } + + js.Expression visitType(TypeConstant constant) { + SourceString helperSourceName = const SourceString('createRuntimeType'); + Element helper = compiler.findHelper(helperSourceName); + JavaScriptBackend backend = compiler.backend; + String helperName = backend.namer.getName(helper); + DartType type = constant.representedType; + Element element = type.element; + String name = backend.rti.getRawTypeRepresentation(type); + js.Expression typeName = new js.LiteralString("'$name'"); + return new js.Call( + new js.PropertyAccess.field( + new js.VariableUse(namer.CURRENT_ISOLATE), + helperName), + [typeName]); + } + + js.Expression visitConstructed(ConstructedConstant constant) { + return new js.New( + new js.VariableUse(getJsConstructor(constant.type.element)), + _array(constant.fields)); + } + + List<js.Expression> _array(List<Constant> values) { + List<js.Expression> valueList = <js.Expression>[]; + for (int i = 0; i < values.length; i++) { + valueList.add(_reference(values[i])); + } + return valueList; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart new file mode 100644 index 0000000..30c8bc5 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
@@ -0,0 +1,240 @@ +// 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. + +part of js_backend; + +const JAVA_SCRIPT_CONSTANT_SYSTEM = const JavaScriptConstantSystem(); + +class JavaScriptBitNotOperation extends BitNotOperation { + const JavaScriptBitNotOperation(); + + Constant fold(Constant constant) { + if (JAVA_SCRIPT_CONSTANT_SYSTEM.isInt(constant)) { + // In JavaScript we don't check for -0 and treat it as if it was zero. + if (constant.isMinusZero()) constant = DART_CONSTANT_SYSTEM.createInt(0); + IntConstant intConstant = constant; + // We convert the result of bit-operations to 32 bit unsigned integers. + return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(~intConstant.value); + } + return null; + } +} + +/** + * In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0 + * is treated as if it was the integer 0. + */ +class JavaScriptBinaryBitOperation implements BinaryOperation { + final BinaryBitOperation dartBitOperation; + + const JavaScriptBinaryBitOperation(this.dartBitOperation); + + bool isUserDefinable() => dartBitOperation.isUserDefinable(); + SourceString get name => dartBitOperation.name; + + Constant fold(Constant left, Constant right) { + // In JavaScript we don't check for -0 and treat it as if it was zero. + if (left.isMinusZero()) left = DART_CONSTANT_SYSTEM.createInt(0); + if (right.isMinusZero()) right = DART_CONSTANT_SYSTEM.createInt(0); + IntConstant result = dartBitOperation.fold(left, right); + if (result != null) { + // We convert the result of bit-operations to 32 bit unsigned integers. + return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(result.value); + } + return result; + } + + apply(left, right) => dartBitOperation.apply(left, right); +} + +class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation { + const JavaScriptShiftRightOperation() : super(const ShiftRightOperation()); + + Constant fold(Constant left, Constant right) { + // Truncate the input value to 32 bits if necessary. + if (left.isInt()) { + IntConstant intConstant = left; + int value = intConstant.value; + int truncatedValue = value & JAVA_SCRIPT_CONSTANT_SYSTEM.BITS32; + // TODO(floitsch): we should treat the input to right shifts as unsigned. + + // Sign-extend. A 32 bit complement-two value x can be computed by: + // x_u - 2^32 (where x_u is its unsigned representation). + // Example: 0xFFFFFFFF - 0x100000000 => -1. + // We simply and with the sign-bit and multiply by two. If the sign-bit + // was set, then the result is 0. Otherwise it will become 2^32. + final int SIGN_BIT = 0x80000000; + truncatedValue -= 2 * (truncatedValue & SIGN_BIT); + if (value != truncatedValue) { + left = DART_CONSTANT_SYSTEM.createInt(truncatedValue); + } + } + return super.fold(left, right); + } +} + +class JavaScriptNegateOperation implements UnaryOperation { + final NegateOperation dartNegateOperation = const NegateOperation(); + + const JavaScriptNegateOperation(); + + bool isUserDefinable() => dartNegateOperation.isUserDefinable(); + SourceString get name => dartNegateOperation.name; + + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + if (intConstant.value == 0) { + return JAVA_SCRIPT_CONSTANT_SYSTEM.createDouble(-0.0); + } + } + return dartNegateOperation.fold(constant); + } + apply(value) => -value; +} + +class JavaScriptBinaryArithmeticOperation implements BinaryOperation { + final BinaryOperation dartArithmeticOperation; + + const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation); + + bool isUserDefinable() => dartArithmeticOperation.isUserDefinable(); + SourceString get name => dartArithmeticOperation.name; + + Constant fold(Constant left, Constant right) { + Constant result = dartArithmeticOperation.fold(left, right); + if (result == null) return result; + return JAVA_SCRIPT_CONSTANT_SYSTEM.convertToJavaScriptConstant(result); + } + + apply(left, right) => dartArithmeticOperation.apply(left, right); +} + +class JavaScriptIdentityOperation implements BinaryOperation { + final IdentityOperation dartIdentityOperation = const IdentityOperation(); + + const JavaScriptIdentityOperation(); + + bool isUserDefinable() => dartIdentityOperation.isUserDefinable(); + SourceString get name => dartIdentityOperation.name; + + BoolConstant fold(Constant left, Constant right) { + BoolConstant result = dartIdentityOperation.fold(left, right); + if (result == null || result.value) return result; + // In JavaScript -0.0 === 0 and all doubles are equal to their integer + // values. Furthermore NaN !== NaN. + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + double leftDouble = leftNum.value.toDouble(); + double rightDouble = rightNum.value.toDouble(); + return new BoolConstant(leftDouble == rightDouble); + } + return result; + } + + apply(left, right) => identical(left, right); +} + +/** + * Constant system following the semantics for Dart code that has been + * compiled to JavaScript. + */ +class JavaScriptConstantSystem extends ConstantSystem { + const int BITS31 = 0x8FFFFFFF; + const int BITS32 = 0xFFFFFFFF; + // The maximum integer value a double can represent without losing + // precision. + const int BITS53 = 0x1FFFFFFFFFFFFF; + + final add = const JavaScriptBinaryArithmeticOperation(const AddOperation()); + final bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation()); + final bitNot = const JavaScriptBitNotOperation(); + final bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation()); + final bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation()); + final booleanAnd = const BooleanAndOperation(); + final booleanOr = const BooleanOrOperation(); + final divide = + const JavaScriptBinaryArithmeticOperation(const DivideOperation()); + final equal = const EqualsOperation(); + final greaterEqual = const GreaterEqualOperation(); + final greater = const GreaterOperation(); + final identity = const JavaScriptIdentityOperation(); + final lessEqual = const LessEqualOperation(); + final less = const LessOperation(); + final modulo = + const JavaScriptBinaryArithmeticOperation(const ModuloOperation()); + final multiply = + const JavaScriptBinaryArithmeticOperation(const MultiplyOperation()); + final negate = const JavaScriptNegateOperation(); + final not = const NotOperation(); + final shiftLeft = + const JavaScriptBinaryBitOperation(const ShiftLeftOperation()); + final shiftRight = const JavaScriptShiftRightOperation(); + final subtract = + const JavaScriptBinaryArithmeticOperation(const SubtractOperation()); + final truncatingDivide = const JavaScriptBinaryArithmeticOperation( + const TruncatingDivideOperation()); + + const JavaScriptConstantSystem(); + + /** + * Returns true if the given [value] fits into a double without losing + * precision. + */ + bool integerFitsIntoDouble(int value) { + int absValue = value.abs(); + return (absValue & BITS53) == absValue; + } + + NumConstant convertToJavaScriptConstant(NumConstant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + int intValue = intConstant.value; + if (!integerFitsIntoDouble(intValue)) { + return new DoubleConstant(intValue.toDouble()); + } + } else if (constant.isDouble()) { + DoubleConstant doubleResult = constant; + double doubleValue = doubleResult.value; + if (!doubleValue.isInfinite && !doubleValue.isNaN && + !constant.isMinusZero()) { + int intValue = doubleValue.toInt(); + if (intValue == doubleValue && integerFitsIntoDouble(intValue)) { + return new IntConstant(intValue); + } + } + } + return constant; + } + + NumConstant createInt(int i) + => convertToJavaScriptConstant(new IntConstant(i)); + NumConstant createInt32(int i) => new IntConstant(i & BITS32); + NumConstant createDouble(double d) + => convertToJavaScriptConstant(new DoubleConstant(d)); + StringConstant createString(DartString string, Node diagnosticNode) + => new StringConstant(string, diagnosticNode); + BoolConstant createBool(bool value) => new BoolConstant(value); + NullConstant createNull() => new NullConstant(); + + // Integer checks don't verify that the number is not -0.0. + bool isInt(Constant constant) => constant.isInt() || constant.isMinusZero(); + bool isDouble(Constant constant) + => constant.isDouble() && !constant.isMinusZero(); + bool isString(Constant constant) => constant.isString(); + bool isBool(Constant constant) => constant.isBool(); + bool isNull(Constant constant) => constant.isNull(); + + bool isSubtype(Compiler compiler, DartType s, DartType t) { + // At runtime, an integer is both an integer and a double: the + // integer type check is Math.floor, which will return true only + // for real integers, and our double type check is 'typeof number' + // which will return true for both integers and doubles. + if (s.element == compiler.intClass && t.element == compiler.doubleClass) { + return true; + } + return compiler.types.isSubtype(s, t); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart new file mode 100644 index 0000000..f401235 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart
@@ -0,0 +1,2359 @@ +// 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. + +part of js_backend; + +/** + * A function element that represents a closure call. The signature is copied + * from the given element. + */ +class ClosureInvocationElement extends FunctionElementX { + ClosureInvocationElement(SourceString name, + FunctionElement other) + : super.from(name, other, other.enclosingElement), + methodElement = other; + + isInstanceMember() => true; + + Element getOutermostEnclosingMemberOrTopLevel() => methodElement; + + /** + * The [member] this invocation refers to. + */ + Element methodElement; +} + +/** + * A convenient type alias for some functions that emit keyed values. + */ +typedef void DefineStubFunction(String invocationName, js.Expression value); + +/** + * A data structure for collecting fragments of a class definition. + */ +class ClassBuilder { + final List<js.Property> properties = <js.Property>[]; + + // Has the same signature as [DefineStubFunction]. + void addProperty(String name, js.Expression value) { + properties.add(new js.Property(js.string(name), value)); + } + + js.Expression toObjectInitializer() => new js.ObjectInitializer(properties); +} + +/** + * Generates the code for all used classes in the program. Static fields (even + * in classes) are ignored, since they can be treated as non-class elements. + * + * The code for the containing (used) methods must exist in the [:universe:]. + */ +class CodeEmitterTask extends CompilerTask { + bool needsInheritFunction = false; + bool needsDefineClass = false; + bool needsClosureClass = false; + bool needsLazyInitializer = false; + final Namer namer; + ConstantEmitter constantEmitter; + NativeEmitter nativeEmitter; + CodeBuffer boundClosureBuffer; + CodeBuffer mainBuffer; + /** Shorter access to [isolatePropertiesName]. Both here in the code, as + well as in the generated code. */ + String isolateProperties; + String classesCollector; + Set<ClassElement> neededClasses; + // TODO(ngeoffray): remove this field. + Set<ClassElement> instantiatedClasses; + + String get _ => compiler.enableMinification ? "" : " "; + String get n => compiler.enableMinification ? "" : "\n"; + String get N => compiler.enableMinification ? "\n" : ";\n"; + + /** + * A cache of closures that are used to closurize instance methods. + * A closure is dynamically bound to the instance used when + * closurized. + */ + final Map<int, String> boundClosureCache; + + /** + * A cache of closures that are used to closurize instance methods + * of interceptors. These closures are dynamically bound to the + * interceptor instance, and the actual receiver of the method. + */ + final Map<int, String> interceptorClosureCache; + + /** + * Raw ClassElement symbols occuring in is-checks and type assertions. If the + * program contains parameterized checks `x is Set<int>` and + * `x is Set<String>` then the ClassElement `Set` will occur once in + * [checkedClasses]. + */ + Set<ClassElement> checkedClasses; + + /** + * Raw Typedef symbols occuring in is-checks and type assertions. If the + * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement + * `F` will occur once in [checkedTypedefs]. + */ + Set<TypedefElement> checkedTypedefs; + + final bool generateSourceMap; + + CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap) + : boundClosureBuffer = new CodeBuffer(), + mainBuffer = new CodeBuffer(), + this.namer = namer, + boundClosureCache = new Map<int, String>(), + interceptorClosureCache = new Map<int, String>(), + constantEmitter = new ConstantEmitter(compiler, namer), + super(compiler) { + nativeEmitter = new NativeEmitter(this); + } + + void computeRequiredTypeChecks() { + assert(checkedClasses == null); + checkedClasses = new Set<ClassElement>(); + checkedTypedefs = new Set<TypedefElement>(); + compiler.codegenWorld.isChecks.forEach((DartType t) { + if (t is InterfaceType) { + checkedClasses.add(t.element); + } else if (t is TypedefType) { + checkedTypedefs.add(t.element); + } + }); + } + + js.Expression constantReference(Constant value) { + return constantEmitter.reference(value); + } + + js.Expression constantInitializerExpression(Constant value) { + return constantEmitter.initializationExpression(value); + } + + String get name => 'CodeEmitter'; + + String get defineClassName + => '${namer.isolateName}.\$defineClass'; + String get currentGenerateAccessorName + => '${namer.CURRENT_ISOLATE}.\$generateAccessor'; + String get generateAccessorHolder + => '$isolatePropertiesName.\$generateAccessor'; + String get finishClassesName + => '${namer.isolateName}.\$finishClasses'; + String get finishIsolateConstructorName + => '${namer.isolateName}.\$finishIsolateConstructor'; + String get pendingClassesName + => '${namer.isolateName}.\$pendingClasses'; + String get isolatePropertiesName + => '${namer.isolateName}.${namer.isolatePropertiesName}'; + String get supportsProtoName + => 'supportsProto'; + String get lazyInitializerName + => '${namer.isolateName}.\$lazy'; + + // Property name suffixes. If the accessors are renaming then the format + // is <accessorName>:<fieldName><suffix>. We use the suffix to know whether + // to look for the ':' separator in order to avoid doing the indexOf operation + // on every single property (they are quite rare). None of these characters + // are legal in an identifier and they are related by bit patterns. + // setter < 0x3c + // both = 0x3d + // getter > 0x3e + // renaming setter | 0x7c + // renaming both } 0x7d + // renaming getter ~ 0x7e + const SUFFIX_MASK = 0x3f; + const FIRST_SUFFIX_CODE = 0x3c; + const SETTER_CODE = 0x3c; + const GETTER_SETTER_CODE = 0x3d; + const GETTER_CODE = 0x3e; + const RENAMING_FLAG = 0x40; + String needsGetterCode(String variable) => '($variable & 3) > 0'; + String needsSetterCode(String variable) => '($variable & 2) == 0'; + String isRenaming(String variable) => '($variable & $RENAMING_FLAG) != 0'; + + String get generateAccessorFunction { + return """ +function generateAccessor(field, prototype) { + var len = field.length; + var lastCharCode = field.charCodeAt(len - 1); + var needsAccessor = (lastCharCode & $SUFFIX_MASK) >= $FIRST_SUFFIX_CODE; + if (needsAccessor) { + var needsGetter = ${needsGetterCode('lastCharCode')}; + var needsSetter = ${needsSetterCode('lastCharCode')}; + var renaming = ${isRenaming('lastCharCode')}; + var accessorName = field = field.substring(0, len - 1); + if (renaming) { + var divider = field.indexOf(":"); + accessorName = field.substring(0, divider); + field = field.substring(divider + 1); + } + if (needsGetter) { + var getterString = "return this." + field + ";"; + prototype["get\$" + accessorName] = new Function(getterString); + } + if (needsSetter) { + var setterString = "this." + field + " = v;"; + prototype["set\$" + accessorName] = new Function("v", setterString); + } + } + return field; +}"""; + } + + String get defineClassFunction { + // First the class name, then the field names in an array and the members + // (inside an Object literal). + // The caller can also pass in the constructor as a function if needed. + // + // Example: + // defineClass("A", ["x", "y"], { + // foo$1: function(y) { + // print(this.x + y); + // }, + // bar$2: function(t, v) { + // this.x = t - v; + // }, + // }); + return """ +function(cls, fields, prototype) { + var constructor; + if (typeof fields == 'function') { + constructor = fields; + } else { + var str = "function " + cls + "("; + var body = ""; + for (var i = 0; i < fields.length; i++) { + if (i != 0) str += ", "; + var field = fields[i]; + field = generateAccessor(field, prototype); + str += field; + body += "this." + field + " = " + field + ";\\n"; + } + str += ") {" + body + "}\\n"; + str += "return " + cls + ";"; + constructor = new Function(str)(); + } + constructor.prototype = prototype; + constructor.builtin\$cls = cls; + return constructor; +}"""; + } + + /** Needs defineClass to be defined. */ + String get protoSupportCheck { + // On Firefox and Webkit browsers we can manipulate the __proto__ + // directly. Opera claims to have __proto__ support, but it is buggy. + // So we have to do more checks. + // Opera bug was filed as DSK-370158, and fixed as CORE-47615 + // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes). + // If the browser does not support __proto__ we need to instantiate an + // object with the correct (internal) prototype set up correctly, and then + // copy the members. + + return ''' +var $supportsProtoName = false; +var tmp = $defineClassName('c', ['f?'], {}).prototype; +if (tmp.__proto__) { + tmp.__proto__ = {}; + if (typeof tmp.get\$f !== 'undefined') $supportsProtoName = true; +} +'''; + } + + String get finishClassesFunction { + // 'defineClass' does not require the classes to be constructed in order. + // Classes are initially just stored in the 'pendingClasses' field. + // 'finishClasses' takes all pending classes and sets up the prototype. + // Once set up, the constructors prototype field satisfy: + // - it contains all (local) members. + // - its internal prototype (__proto__) points to the superclass' + // prototype field. + // - the prototype's constructor field points to the JavaScript + // constructor. + // For engines where we have access to the '__proto__' we can manipulate + // the object literal directly. For other engines we have to create a new + // object and copy over the members. + return ''' +function(collectedClasses) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + for (var cls in collectedClasses) { + if (hasOwnProperty.call(collectedClasses, cls)) { + var desc = collectedClasses[cls]; +'''/* The 'fields' are either a constructor function or a string encoding + fields, constructor and superclass. Get the superclass and the fields + in the format Super;field1,field2 from the null-string property on the + descriptor. */''' + var fields = desc[''], supr; + if (typeof fields == 'string') { + var s = fields.split(';'); supr = s[0]; + fields = s[1] == '' ? [] : s[1].split(','); + } else { + supr = desc['super']; + } + $isolatePropertiesName[cls] = $defineClassName(cls, fields, desc); + if (supr) $pendingClassesName[cls] = supr; + } + } + var pendingClasses = $pendingClassesName; +'''/* FinishClasses can be called multiple times. This means that we need to + clear the pendingClasses property. */''' + $pendingClassesName = {}; + var finishedClasses = {}; + function finishClass(cls) { +'''/* Opera does not support 'getOwnPropertyNames'. Therefore we use + hasOwnProperty instead. */''' + var hasOwnProperty = Object.prototype.hasOwnProperty; + if (hasOwnProperty.call(finishedClasses, cls)) return; + finishedClasses[cls] = true; + var superclass = pendingClasses[cls]; +'''/* The superclass is only false (empty string) for Dart's Object class. */''' + if (!superclass) return; + finishClass(superclass); + var constructor = $isolatePropertiesName[cls]; + var superConstructor = $isolatePropertiesName[superclass]; + var prototype = constructor.prototype; + if ($supportsProtoName) { + prototype.__proto__ = superConstructor.prototype; + prototype.constructor = constructor; + } else { + function tmp() {}; + tmp.prototype = superConstructor.prototype; + var newPrototype = new tmp(); + constructor.prototype = newPrototype; + newPrototype.constructor = constructor; + for (var member in prototype) { + if (!member) continue; '''/* Short version of: if (member == '') */''' + if (hasOwnProperty.call(prototype, member)) { + newPrototype[member] = prototype[member]; + } + } + } + } + for (var cls in pendingClasses) finishClass(cls); +}'''; + } + + String get finishIsolateConstructorFunction { + String isolate = namer.isolateName; + // We replace the old Isolate function with a new one that initializes + // all its field with the initial (and often final) value of all globals. + // This has two advantages: + // 1. the properties are in the object itself (thus avoiding to go through + // the prototype when looking up globals. + // 2. a new isolate goes through a (usually well optimized) constructor + // function of the form: "function() { this.x = ...; this.y = ...; }". + // + // Example: If [isolateProperties] is an object containing: x = 3 and + // A = function A() { /* constructor of class A. */ }, then we generate: + // str = "{ + // var isolateProperties = Isolate.$isolateProperties; + // this.x = isolateProperties.x; + // this.A = isolateProperties.A; + // }"; + // which is then dynamically evaluated: + // var newIsolate = new Function(str); + // + // We also copy over old values like the prototype, and the + // isolateProperties themselves. + return """function(oldIsolate) { + var isolateProperties = oldIsolate.${namer.isolatePropertiesName}; + var isolatePrototype = oldIsolate.prototype; + var str = "{\\n"; + str += "var properties = $isolate.${namer.isolatePropertiesName};\\n"; + for (var staticName in isolateProperties) { + if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) { + str += "this." + staticName + "= properties." + staticName + ";\\n"; + } + } + str += "}\\n"; + var newIsolate = new Function(str); + newIsolate.prototype = isolatePrototype; + isolatePrototype.constructor = newIsolate; + newIsolate.${namer.isolatePropertiesName} = isolateProperties; + return newIsolate; +}"""; + } + + String get lazyInitializerFunction { + String isolate = namer.CURRENT_ISOLATE; + return """ +function(prototype, staticName, fieldName, getterName, lazyValue) { + var getter = new Function("{ return $isolate." + fieldName + ";}"); +$lazyInitializerLogic +}"""; + } + + String get lazyInitializerLogic { + String isolate = namer.CURRENT_ISOLATE; + JavaScriptBackend backend = compiler.backend; + String cyclicThrow = namer.isolateAccess(backend.cyclicThrowHelper); + return """ + var sentinelUndefined = {}; + var sentinelInProgress = {}; + prototype[fieldName] = sentinelUndefined; + prototype[getterName] = function() { + var result = $isolate[fieldName]; + try { + if (result === sentinelUndefined) { + $isolate[fieldName] = sentinelInProgress; + try { + result = $isolate[fieldName] = lazyValue(); + } finally { +""" // Use try-finally, not try-catch/throw as it destroys the stack trace. +""" + if (result === sentinelUndefined) { + if ($isolate[fieldName] === sentinelInProgress) { + $isolate[fieldName] = null; + } + } + } + } else if (result === sentinelInProgress) { + $cyclicThrow(staticName); + } + return result; + } finally { + $isolate[getterName] = getter; + } + };"""; + } + + void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) { + if (needsDefineClass) { + // Declare function called generateAccessor. This is used in + // defineClassFunction (it's a local declaration in init()). + buffer.add("$generateAccessorFunction$N"); + buffer.add("$generateAccessorHolder = generateAccessor$N"); + buffer.add("$defineClassName = $defineClassFunction$N"); + buffer.add(protoSupportCheck); + buffer.add("$pendingClassesName = {}$N"); + buffer.add("$finishClassesName = $finishClassesFunction$N"); + } + } + + void addLazyInitializerFunctionIfNecessary(CodeBuffer buffer) { + if (needsLazyInitializer) { + buffer.add("$lazyInitializerName = $lazyInitializerFunction$N"); + } + } + + void emitFinishIsolateConstructor(CodeBuffer buffer) { + String name = finishIsolateConstructorName; + String value = finishIsolateConstructorFunction; + buffer.add("$name = $value$N"); + } + + void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) { + String isolate = namer.isolateName; + buffer.add("$isolate = $finishIsolateConstructorName($isolate)$N"); + } + + /** + * Generate stubs to handle invocation of methods with optional + * arguments. + * + * A method like [: foo([x]) :] may be invoked by the following + * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this + * function for detailed examples. + */ + void addParameterStub(FunctionElement member, + Selector selector, + DefineStubFunction defineStub, + Set<String> alreadyGenerated) { + FunctionSignature parameters = member.computeSignature(compiler); + int positionalArgumentCount = selector.positionalArgumentCount; + if (positionalArgumentCount == parameters.parameterCount) { + assert(selector.namedArgumentCount == 0); + return; + } + if (parameters.optionalParametersAreNamed + && selector.namedArgumentCount == parameters.optionalParameterCount) { + // If the selector has the same number of named arguments as + // the element, we don't need to add a stub. The call site will + // hit the method directly. + return; + } + ConstantHandler handler = compiler.constantHandler; + List<SourceString> names = selector.getOrderedNamedArguments(); + + String invocationName = namer.invocationName(selector); + if (alreadyGenerated.contains(invocationName)) return; + alreadyGenerated.add(invocationName); + + JavaScriptBackend backend = compiler.backend; + bool isInterceptorClass = + backend.isInterceptorClass(member.getEnclosingClass()); + + // If the method is in an interceptor class, we need to also pass + // the actual receiver. + int extraArgumentCount = isInterceptorClass ? 1 : 0; + // Use '$receiver' to avoid clashes with other parameter names. Using + // '$receiver' works because [:namer.safeName:] used for getting parameter + // names never returns a name beginning with a single '$'. + String receiverArgumentName = r'$receiver'; + + // The parameters that this stub takes. + List<js.Parameter> parametersBuffer = + new List<js.Parameter>.fixedLength( + selector.argumentCount + extraArgumentCount); + // The arguments that will be passed to the real method. + List<js.Expression> argumentsBuffer = + new List<js.Expression>.fixedLength( + parameters.parameterCount + extraArgumentCount); + + int count = 0; + if (isInterceptorClass) { + count++; + parametersBuffer[0] = new js.Parameter(receiverArgumentName); + argumentsBuffer[0] = new js.VariableUse(receiverArgumentName); + } + + int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1; + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(member); + + parameters.orderedForEachParameter((Element element) { + String jsName = backend.namer.safeName(element.name.slowToString()); + assert(jsName != receiverArgumentName); + int optionalParameterStart = positionalArgumentCount + extraArgumentCount; + if (count < optionalParameterStart) { + parametersBuffer[count] = new js.Parameter(jsName); + argumentsBuffer[count] = new js.VariableUse(jsName); + } else { + int index = names.indexOf(element.name); + if (index != -1) { + indexOfLastOptionalArgumentInParameters = count; + // The order of the named arguments is not the same as the + // one in the real method (which is in Dart source order). + argumentsBuffer[count] = new js.VariableUse(jsName); + parametersBuffer[optionalParameterStart + index] = + new js.Parameter(jsName); + // Note that [elements] may be null for a synthesized [member]. + } else if (elements != null && elements.isParameterChecked(element)) { + argumentsBuffer[count] = constantReference(SentinelConstant.SENTINEL); + } else { + Constant value = handler.initialVariableValues[element]; + if (value == null) { + argumentsBuffer[count] = constantReference(new NullConstant()); + } else { + if (!value.isNull()) { + // If the value is the null constant, we should not pass it + // down to the native method. + indexOfLastOptionalArgumentInParameters = count; + } + argumentsBuffer[count] = constantReference(value); + } + } + } + count++; + }); + + List<js.Statement> body; + if (member.hasFixedBackendName()) { + body = nativeEmitter.generateParameterStubStatements( + member, invocationName, parametersBuffer, argumentsBuffer, + indexOfLastOptionalArgumentInParameters); + } else { + body = <js.Statement>[ + new js.Return( + new js.VariableUse('this') + .dot(namer.getName(member)) + .callWith(argumentsBuffer))]; + } + + js.Fun function = new js.Fun(parametersBuffer, new js.Block(body)); + + defineStub(invocationName, function); + } + + void addParameterStubs(FunctionElement member, + DefineStubFunction defineStub) { + // We fill the lists depending on the selector. For example, + // take method foo: + // foo(a, b, {c, d}); + // + // We may have multiple ways of calling foo: + // (1) foo(1, 2); + // (2) foo(1, 2, c: 3); + // (3) foo(1, 2, d: 4); + // (4) foo(1, 2, c: 3, d: 4); + // (5) foo(1, 2, d: 4, c: 3); + // + // What we generate at the call sites are: + // (1) foo$2(1, 2); + // (2) foo$3$c(1, 2, 3); + // (3) foo$3$d(1, 2, 4); + // (4) foo$4$c$d(1, 2, 3, 4); + // (5) foo$4$c$d(1, 2, 3, 4); + // + // The stubs we generate are (expressed in Dart): + // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) + // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); + // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); + // (4) No stub generated, call is direct. + // (5) No stub generated, call is direct. + + // Keep a cache of which stubs have already been generated, to + // avoid duplicates. Note that even if selectors are + // canonicalized, we would still need this cache: a typed selector + // on A and a typed selector on B could yield the same stub. + Set<String> generatedStubNames = new Set<String>(); + if (compiler.enabledFunctionApply + && member.name == namer.closureInvocationSelectorName) { + // If [Function.apply] is called, we pessimistically compile all + // possible stubs for this closure. + FunctionSignature signature = member.computeSignature(compiler); + Set<Selector> selectors = signature.optionalParametersAreNamed + ? computeNamedSelectors(signature, member) + : computeOptionalSelectors(signature, member); + for (Selector selector in selectors) { + addParameterStub(member, selector, defineStub, generatedStubNames); + } + } else { + Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; + if (selectors == null) return; + for (Selector selector in selectors) { + if (!selector.applies(member, compiler)) continue; + addParameterStub(member, selector, defineStub, generatedStubNames); + } + } + } + + /** + * Compute the set of possible selectors in the presence of named + * parameters. + */ + Set<Selector> computeNamedSelectors(FunctionSignature signature, + FunctionElement element) { + Set<Selector> selectors = new Set<Selector>(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + <SourceString>[])); + + // For each optional parameter, we iterator over the set of + // already computed selectors and create new selectors with that + // parameter now being passed. + signature.forEachOptionalParameter((Element element) { + Set<Selector> newSet = new Set<Selector>(); + selectors.forEach((Selector other) { + List<SourceString> namedArguments = [element.name]; + namedArguments.addAll(other.namedArguments); + newSet.add(new Selector(other.kind, + other.name, + other.library, + other.argumentCount + 1, + namedArguments)); + }); + selectors.addAll(newSet); + }); + return selectors; + } + + /** + * Compute the set of possible selectors in the presence of optional + * non-named parameters. + */ + Set<Selector> computeOptionalSelectors(FunctionSignature signature, + FunctionElement element) { + Set<Selector> selectors = new Set<Selector>(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + <SourceString>[])); + + // For each optional parameter, we increment the number of passed + // argument. + for (int i = 1; i <= signature.optionalParameterCount; i++) { + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount + i, + <SourceString>[])); + } + return selectors; + } + + bool instanceFieldNeedsGetter(Element member) { + assert(member.isField()); + return compiler.codegenWorld.hasInvokedGetter(member, compiler); + } + + bool instanceFieldNeedsSetter(Element member) { + assert(member.isField()); + return (!member.modifiers.isFinalOrConst()) + && compiler.codegenWorld.hasInvokedSetter(member, compiler); + } + + String compiledFieldName(Element member) { + assert(member.isField()); + return member.hasFixedBackendName() + ? member.fixedBackendName() + : namer.getName(member); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void addInstanceMember(Element member, ClassBuilder builder) { + assert(invariant(member, member.isDeclaration)); + // TODO(floitsch): we don't need to deal with members of + // uninstantiated classes, that have been overwritten by subclasses. + + if (member.isFunction() + || member.isGenerativeConstructorBody() + || member.isAccessor()) { + if (member.isAbstract(compiler)) return; + JavaScriptBackend backend = compiler.backend; + js.Expression code = backend.generatedCode[member]; + if (code == null) return; + builder.addProperty(namer.getName(member), code); + code = backend.generatedBailoutCode[member]; + if (code != null) { + builder.addProperty(namer.getBailoutName(member), code); + } + FunctionElement function = member; + FunctionSignature parameters = function.computeSignature(compiler); + if (!parameters.optionalParameters.isEmpty) { + addParameterStubs(member, builder.addProperty); + } + } else if (!member.isField()) { + compiler.internalError('unexpected kind: "${member.kind}"', + element: member); + } + emitExtraAccessors(member, builder); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void emitInstanceMembers(ClassElement classElement, + ClassBuilder builder) { + assert(invariant(classElement, classElement.isDeclaration)); + JavaScriptBackend backend = compiler.backend; + if (classElement == backend.objectInterceptorClass) { + emitInterceptorMethods(builder); + // The ObjectInterceptor does not have any instance methods. + return; + } + + void visitMember(ClassElement enclosing, Element member) { + assert(invariant(classElement, member.isDeclaration)); + if (member.isInstanceMember()) { + addInstanceMember(member, builder); + } + } + + // TODO(kasperl): We should make sure to only emit one version of + // overridden methods. Right now, we rely on the ordering so the + // methods pulled in from mixins are replaced with the members + // from the class definition. + + // If the class is a native class, we have to add the instance + // members defined in the non-native mixin applications used by + // the class. + visitNativeMixins(classElement, (MixinApplicationElement mixin) { + mixin.forEachMember( + visitMember, + includeBackendMembers: true, + includeSuperMembers: false); + }); + + classElement.implementation.forEachMember( + visitMember, + includeBackendMembers: true, + includeSuperMembers: false); + + generateIsTestsOn(classElement, (Element other) { + js.Expression code; + if (compiler.objectClass == other) return; + if (nativeEmitter.requiresNativeIsCheck(other)) { + code = js.fun([], js.block1(js.return_(new js.LiteralBool(true)))); + } else { + code = new js.LiteralBool(true); + } + builder.addProperty(namer.operatorIs(other), code); + }); + + if (identical(classElement, compiler.objectClass) + && compiler.enabledNoSuchMethod) { + // Emit the noSuchMethod handlers on the Object prototype now, + // so that the code in the dynamicFunction helper can find + // them. Note that this helper is invoked before analyzing the + // full JS script. + if (!nativeEmitter.handleNoSuchMethod) { + emitNoSuchMethodHandlers(builder.addProperty); + } + } + + if (backend.isInterceptorClass(classElement)) { + // The operator== method in [:Object:] does not take the same + // number of arguments as an intercepted method, therefore we + // explicitely add one to all interceptor classes. Note that we + // would not have do do that if all intercepted methods had + // a calling convention where the receiver is the first + // parameter. + String name = backend.namer.publicInstanceMethodNameByArity( + const SourceString('=='), 1); + Function kind = (classElement == backend.jsNullClass) + ? js.equals + : js.strictEquals; + builder.addProperty(name, js.fun(['receiver', 'a'], + js.block1(js.return_(kind(js.use('receiver'), js.use('a')))))); + } + } + + void emitRuntimeClassesAndTests(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + RuntimeTypeInformation rti = backend.rti; + + TypeChecks typeChecks = rti.computeRequiredChecks(); + + bool needsHolder(ClassElement cls) { + return !neededClasses.contains(cls) || cls.isNative() || + rti.isJsNative(cls); + } + + void maybeGenerateHolder(ClassElement cls) { + if (!needsHolder(cls)) return; + + String holder = namer.isolateAccess(cls); + String name = namer.getName(cls); + buffer.add("$holder$_=$_{builtin\$cls:$_'$name'"); + for (ClassElement check in typeChecks[cls]) { + buffer.add(',$_${namer.operatorIs(check)}:${_}true'); + }; + buffer.add('}$N'); + } + + // Create representation objects for classes that we do not have a class + // definition for (because they are uninstantiated or native). + for (ClassElement cls in rti.allArguments) { + maybeGenerateHolder(cls); + } + + // Add checks to the constructors of instantiated classes. + for (ClassElement cls in typeChecks) { + if (needsHolder(cls)) { + // We already emitted the is-checks in the object definition for this + // class. + continue; + } + String holder = namer.isolateAccess(cls); + for (ClassElement check in typeChecks[cls]) { + buffer.add('$holder.${namer.operatorIs(check)}$_=${_}true$N'); + }; + } + } + + void visitNativeMixins(ClassElement classElement, + void visit(MixinApplicationElement mixinApplication)) { + if (!classElement.isNative()) return; + // Use recursion to make sure to visit the superclasses before the + // subclasses. Once we start keeping track of the emitted fields + // and members, we're going to want to visit these in the other + // order so we get the most specialized definition first. + void recurse(ClassElement cls) { + if (cls == null || !cls.isMixinApplication) return; + recurse(cls.superclass); + assert(!cls.isNative()); + visit(cls); + } + recurse(classElement.superclass); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void visitClassFields(ClassElement classElement, + void addField(Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter)) { + assert(invariant(classElement, classElement.isDeclaration)); + // If the class is never instantiated we still need to set it up for + // inheritance purposes, but we can simplify its JavaScript constructor. + bool isInstantiated = + compiler.codegenWorld.instantiatedClasses.contains(classElement); + + void visitField(ClassElement enclosingClass, Element member) { + assert(invariant(classElement, member.isDeclaration)); + LibraryElement library = member.getLibrary(); + SourceString name = member.name; + bool isPrivate = name.isPrivate(); + + // Keep track of whether or not we're dealing with a field mixin + // into a native class. + bool isMixinNativeField = + classElement.isNative() && enclosingClass.isMixinApplication; + + // See if we can dynamically create getters and setters. + // We can only generate getters and setters for [classElement] since + // the fields of super classes could be overwritten with getters or + // setters. + bool needsGetter = false; + bool needsSetter = false; + // We need to name shadowed fields differently, so they don't clash with + // the non-shadowed field. + bool isShadowed = false; + if (isMixinNativeField || identical(enclosingClass, classElement)) { + needsGetter = instanceFieldNeedsGetter(member); + needsSetter = instanceFieldNeedsSetter(member); + } else { + isShadowed = classElement.isShadowedByField(member); + } + + if ((isInstantiated && !enclosingClass.isNative()) + || needsGetter + || needsSetter) { + String accessorName = isShadowed + ? namer.shadowedFieldName(member) + : namer.getName(member); + String fieldName = member.hasFixedBackendName() + ? member.fixedBackendName() + : (isMixinNativeField ? member.name.slowToString() : accessorName); + bool needsCheckedSetter = false; + if (needsSetter && compiler.enableTypeAssertions + && canGenerateCheckedSetter(member)) { + needsCheckedSetter = true; + needsSetter = false; + } + // Getters and setters with suffixes will be generated dynamically. + addField(member, + fieldName, + accessorName, + needsGetter, + needsSetter, + needsCheckedSetter); + } + } + + // TODO(kasperl): We should make sure to only emit one version of + // overridden fields. Right now, we rely on the ordering so the + // fields pulled in from mixins are replaced with the fields from + // the class definition. + + // If the class is a native class, we have to add the fields + // defined in the non-native mixin applications used by the class. + visitNativeMixins(classElement, (MixinApplicationElement mixin) { + mixin.forEachInstanceField( + visitField, + includeBackendMembers: true, + includeSuperMembers: false); + }); + + // If a class is not instantiated then we add the field just so we can + // generate the field getter/setter dynamically. Since this is only + // allowed on fields that are in [classElement] we don't need to visit + // superclasses for non-instantiated classes. + classElement.implementation.forEachInstanceField( + visitField, + includeBackendMembers: true, + includeSuperMembers: isInstantiated && !classElement.isNative()); + } + + void generateGetter(Element member, String fieldName, String accessorName, + ClassBuilder builder) { + String getterName = namer.getterNameFromAccessorName(accessorName); + builder.addProperty(getterName, + js.fun([], js.block1(js.return_(js.use('this').dot(fieldName))))); + } + + void generateSetter(Element member, String fieldName, String accessorName, + ClassBuilder builder) { + String setterName = namer.setterNameFromAccessorName(accessorName); + builder.addProperty(setterName, + js.fun(['v'], + js.block1( + new js.ExpressionStatement( + js.assign(js.use('this').dot(fieldName), js.use('v')))))); + } + + bool canGenerateCheckedSetter(Element member) { + DartType type = member.computeType(compiler); + if (type.element.isTypeVariable() + || type.element == compiler.dynamicClass + || type.element == compiler.objectClass) { + // TODO(ngeoffray): Support type checks on type parameters. + return false; + } + return true; + } + + void generateCheckedSetter(Element member, + String fieldName, + String accessorName, + ClassBuilder builder) { + assert(canGenerateCheckedSetter(member)); + DartType type = member.computeType(compiler); + // TODO(ahe): Generate a dynamic type error here. + if (type.element.isErroneous()) return; + SourceString helper = compiler.backend.getCheckedModeHelper(type); + FunctionElement helperElement = compiler.findHelper(helper); + String helperName = namer.isolateAccess(helperElement); + List<js.Expression> arguments = <js.Expression>[js.use('v')]; + if (helperElement.computeSignature(compiler).parameterCount != 1) { + arguments.add(js.string(namer.operatorIs(type.element))); + } + + String setterName = namer.setterNameFromAccessorName(accessorName); + builder.addProperty(setterName, + js.fun(['v'], + js.block1( + new js.ExpressionStatement( + js.assign( + js.use('this').dot(fieldName), + js.call(js.use(helperName), arguments)))))); + } + + void emitClassConstructor(ClassElement classElement, ClassBuilder builder) { + /* Do nothing. */ + } + + void emitSuper(String superName, ClassBuilder builder) { + /* Do nothing. */ + } + + void emitClassFields(ClassElement classElement, + ClassBuilder builder, + { String superClass: "", + bool classIsNative: false}) { + bool isFirstField = true; + StringBuffer buffer = new StringBuffer(); + if (!classIsNative) { + buffer.add('$superClass;'); + } + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + // Ignore needsCheckedSetter - that is handled below. + bool needsAccessor = (needsGetter || needsSetter); + // We need to output the fields for non-native classes so we can auto- + // generate the constructor. For native classes there are no + // constructors, so we don't need the fields unless we are generating + // accessors at runtime. + if (!classIsNative || needsAccessor) { + // Emit correct commas. + if (isFirstField) { + isFirstField = false; + } else { + buffer.add(','); + } + int flag = 0; + if (!needsAccessor) { + // Emit field for constructor generation. + assert(!classIsNative); + buffer.add(name); + } else { + // Emit (possibly renaming) field name so we can add accessors at + // runtime. + buffer.add(accessorName); + if (name != accessorName) { + buffer.add(':$name'); + // Only the native classes can have renaming accessors. + assert(classIsNative); + flag = RENAMING_FLAG; + } + } + if (needsGetter && needsSetter) { + buffer.addCharCode(GETTER_SETTER_CODE + flag); + } else if (needsGetter) { + buffer.addCharCode(GETTER_CODE + flag); + } else if (needsSetter) { + buffer.addCharCode(SETTER_CODE + flag); + } + } + }); + + String compactClassData = buffer.toString(); + if (compactClassData.length > 0) { + builder.addProperty('', js.string(compactClassData)); + } + } + + void emitClassGettersSetters(ClassElement classElement, + ClassBuilder builder) { + + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + compiler.withCurrentElement(member, () { + if (needsCheckedSetter) { + assert(!needsSetter); + generateCheckedSetter(member, name, accessorName, builder); + } + if (!getterAndSetterCanBeImplementedByFieldSpec) { + if (needsGetter) { + generateGetter(member, name, accessorName, builder); + } + if (needsSetter) { + generateSetter(member, name, accessorName, builder); + } + } + }); + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void generateClass(ClassElement classElement, CodeBuffer buffer) { + assert(invariant(classElement, classElement.isDeclaration)); + if (classElement.isNative()) { + nativeEmitter.generateNativeClass(classElement); + return; + } + + needsDefineClass = true; + String className = namer.getName(classElement); + + // Find the first non-native superclass. + ClassElement superclass = classElement.superclass; + while (superclass != null && superclass.isNative()) { + superclass = superclass.superclass; + } + + String superName = ""; + if (superclass != null) { + superName = namer.getName(superclass); + } + + ClassBuilder builder = new ClassBuilder(); + + emitClassConstructor(classElement, builder); + emitSuper(superName, builder); + emitClassFields(classElement, builder, + superClass: superName, classIsNative: false); + emitClassGettersSetters(classElement, builder); + emitInstanceMembers(classElement, builder); + + js.Expression init = + js.assign( + js.use(classesCollector).dot(className), + builder.toObjectInitializer()); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N$n'); + } + + bool get getterAndSetterCanBeImplementedByFieldSpec => true; + + int _selectorRank(Selector selector) { + int arity = selector.argumentCount * 3; + if (selector.isGetter()) return arity + 2; + if (selector.isSetter()) return arity + 1; + return arity; + } + + int _compareSelectorNames(Selector selector1, Selector selector2) { + String name1 = selector1.name.toString(); + String name2 = selector2.name.toString(); + if (name1 != name2) return Comparable.compare(name1, name2); + return _selectorRank(selector1) - _selectorRank(selector2); + } + + void emitInterceptorMethods(ClassBuilder builder) { + JavaScriptBackend backend = compiler.backend; + // Emit forwarders for the ObjectInterceptor class. We need to + // emit all possible sends on intercepted methods. + for (Selector selector in + backend.usedInterceptors.toList()..sort(_compareSelectorNames)) { + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + parameters.add(new js.Parameter('receiver')); + + String name = backend.namer.invocationName(selector); + if (selector.isSetter()) { + parameters.add(new js.Parameter('value')); + arguments.add(new js.VariableUse('value')); + } else { + for (int i = 0; i < selector.argumentCount; i++) { + String argName = 'a$i'; + parameters.add(new js.Parameter(argName)); + arguments.add(new js.VariableUse(argName)); + } + } + js.Fun function = + new js.Fun(parameters, + new js.Block( + <js.Statement>[ + new js.Return( + new js.VariableUse('receiver') + .dot(name) + .callWith(arguments))])); + builder.addProperty(name, function); + } + } + + Iterable<Element> getTypedefChecksOn(DartType type) { + bool isSubtype(TypedefElement typedef) { + FunctionType typedefType = + typedef.computeType(compiler).unalias(compiler); + return compiler.types.isSubtype(type, typedefType); + } + return checkedTypedefs.where(isSubtype).toList() + ..sort(Elements.compareByPosition); + } + + /** + * Generate "is tests" for [cls]: itself, and the "is tests" for the + * classes it implements. We don't need to add the "is tests" of the + * super class because they will be inherited at runtime. + */ + void generateIsTestsOn(ClassElement cls, + void emitIsTest(Element element)) { + if (checkedClasses.contains(cls)) { + emitIsTest(cls); + } + + Set<Element> generated = new Set<Element>(); + // A class that defines a [:call:] method implicitly implements + // [Function] and needs checks for all typedefs that are used in is-checks. + if (checkedClasses.contains(compiler.functionClass) || + !checkedTypedefs.isEmpty) { + FunctionElement call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); + if (call == null) { + // If [cls] is a closure, it has a synthetic call operator method. + call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); + } + if (call != null) { + generateInterfacesIsTests(compiler.functionClass, + emitIsTest, + generated); + getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest); + } + } + + for (DartType interfaceType in cls.interfaces) { + generateInterfacesIsTests(interfaceType.element, emitIsTest, generated); + } + + // For native classes, we also have to run through their mixin + // applications and make sure we deal with 'is' tests correctly + // for those. + visitNativeMixins(cls, (MixinApplicationElement mixin) { + for (DartType interfaceType in mixin.interfaces) { + ClassElement interfaceElement = interfaceType.element; + generateInterfacesIsTests(interfaceType.element, emitIsTest, generated); + } + }); + } + + /** + * Generate "is tests" where [cls] is being implemented. + */ + void generateInterfacesIsTests(ClassElement cls, + void emitIsTest(ClassElement element), + Set<Element> alreadyGenerated) { + void tryEmitTest(ClassElement cls) { + if (!alreadyGenerated.contains(cls) && checkedClasses.contains(cls)) { + alreadyGenerated.add(cls); + emitIsTest(cls); + } + }; + + tryEmitTest(cls); + + for (DartType interfaceType in cls.interfaces) { + Element element = interfaceType.element; + tryEmitTest(element); + generateInterfacesIsTests(element, emitIsTest, alreadyGenerated); + } + + // We need to also emit "is checks" for the superclass and its supertypes. + ClassElement superclass = cls.superclass; + if (superclass != null) { + tryEmitTest(superclass); + generateInterfacesIsTests(superclass, emitIsTest, alreadyGenerated); + } + } + + /** + * Return a function that returns true if its argument is a class + * that needs to be emitted. + */ + Function computeClassFilter() { + Set<ClassElement> unneededClasses = new Set<ClassElement>(); + // The [Bool] class is not marked as abstract, but has a factory + // constructor that always throws. We never need to emit it. + unneededClasses.add(compiler.boolClass); + + JavaScriptBackend backend = compiler.backend; + + // Go over specialized interceptors and then constants to know which + // interceptors are needed. + Set<ClassElement> needed = new Set<ClassElement>(); + backend.specializedGetInterceptors.forEach( + (_, Collection<ClassElement> elements) { + needed.addAll(elements); + } + ); + + ConstantHandler handler = compiler.constantHandler; + List<Constant> constants = handler.getConstantsForEmission(); + for (Constant constant in constants) { + if (constant is ConstructedConstant) { + Element element = constant.computeType(compiler).element; + if (backend.isInterceptorClass(element)) { + needed.add(element); + } + } + } + + // Add unneeded interceptors to the [unneededClasses] set. + for (ClassElement interceptor in backend.interceptedClasses.keys) { + if (!needed.contains(interceptor)) { + unneededClasses.add(interceptor); + } + } + + return (ClassElement cls) => !unneededClasses.contains(cls); + } + + void emitClasses(CodeBuffer buffer) { + // Compute the required type checks to know which classes need a + // 'is$' method. + computeRequiredTypeChecks(); + List<ClassElement> sortedClasses = + new List<ClassElement>.from(neededClasses); + sortedClasses.sort((ClassElement class1, ClassElement class2) { + // We sort by the ids of the classes. There is no guarantee that these + // ids are meaningful (or even deterministic), but in the current + // implementation they are increasing within a source file. + return class1.id - class2.id; + }); + + // If we need noSuchMethod support, we run through all needed + // classes to figure out if we need the support on any native + // class. If so, we let the native emitter deal with it. + if (compiler.enabledNoSuchMethod) { + SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; + Selector noSuchMethodSelector = new Selector.noSuchMethod(); + for (ClassElement element in sortedClasses) { + if (!element.isNative()) continue; + Element member = element.lookupLocalMember(noSuchMethodName); + if (member == null) continue; + if (noSuchMethodSelector.applies(member, compiler)) { + nativeEmitter.handleNoSuchMethod = true; + break; + } + } + } + + for (ClassElement element in sortedClasses) { + generateClass(element, buffer); + } + + // The closure class could have become necessary because of the generation + // of stubs. + ClassElement closureClass = compiler.closureClass; + if (needsClosureClass && !instantiatedClasses.contains(closureClass)) { + generateClass(closureClass, buffer); + } + } + + void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) { + if (needsDefineClass) { + buffer.add("$finishClassesName($classesCollector)$N"); + // Reset the map. + buffer.add("$classesCollector$_=$_{}$N"); + } + } + + void emitStaticFunction(CodeBuffer buffer, + String name, + js.Expression functionExpression) { + js.Expression assignment = + js.assign(js.use(isolateProperties).dot(name), functionExpression); + buffer.add(js.prettyPrint(assignment, compiler)); + buffer.add('$N$n'); + } + + void emitStaticFunctions(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + bool isStaticFunction(Element element) => + !element.isInstanceMember() && !element.isField(); + + Iterable<Element> elements = + backend.generatedCode.keys.where(isStaticFunction); + Set<Element> pendingElementsWithBailouts = + backend.generatedBailoutCode.keys + .where(isStaticFunction) + .toSet(); + + for (Element element in Elements.sortedByPosition(elements)) { + js.Expression code = backend.generatedCode[element]; + emitStaticFunction(buffer, namer.getName(element), code); + js.Expression bailoutCode = backend.generatedBailoutCode[element]; + if (bailoutCode != null) { + pendingElementsWithBailouts.remove(element); + emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode); + } + } + + // Is it possible the primary function was inlined but the bailout was not? + for (Element element in + Elements.sortedByPosition(pendingElementsWithBailouts)) { + js.Expression bailoutCode = backend.generatedBailoutCode[element]; + emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode); + } + } + + void emitStaticFunctionGetters(CodeBuffer buffer) { + Set<FunctionElement> functionsNeedingGetter = + compiler.codegenWorld.staticFunctionsNeedingGetter; + for (FunctionElement element in + Elements.sortedByPosition(functionsNeedingGetter)) { + // The static function does not have the correct name. Since + // [addParameterStubs] use the name to create its stubs we simply + // create a fake element with the correct name. + // Note: the callElement will not have any enclosingElement. + FunctionElement callElement = + new ClosureInvocationElement(namer.closureInvocationSelectorName, + element); + String staticName = namer.getName(element); + String invocationName = namer.instanceMethodName(callElement); + String fieldAccess = '$isolateProperties.$staticName'; + buffer.add("$fieldAccess.$invocationName$_=$_$fieldAccess$N"); + + addParameterStubs(callElement, (String name, js.Expression value) { + js.Expression assignment = + js.assign( + js.use(isolateProperties).dot(staticName).dot(name), + value); + buffer.add( + js.prettyPrint(new js.ExpressionStatement(assignment), compiler)); + buffer.add('$N'); + }); + + // If a static function is used as a closure we need to add its name + // in case it is used in spawnFunction. + String fieldName = namer.STATIC_CLOSURE_NAME_NAME; + buffer.add('$fieldAccess.$fieldName$_=$_"$staticName"$N'); + getTypedefChecksOn(element.computeType(compiler)).forEach( + (Element typedef) { + String operator = namer.operatorIs(typedef); + buffer.add('$fieldAccess.$operator$_=${_}true$N'); + } + ); + } + } + + void emitBoundClosureClassHeader(String mangledName, + String superName, + List<String> fieldNames, + ClassBuilder builder) { + builder.addProperty('', + js.string("$superName;${Strings.join(fieldNames,',')}")); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitDynamicFunctionGetter(FunctionElement member, + DefineStubFunction defineStub) { + assert(invariant(member, member.isDeclaration)); + // For every method that has the same name as a property-get we create a + // getter that returns a bound closure. Say we have a class 'A' with method + // 'foo' and somewhere in the code there is a dynamic property get of + // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript): + // + // class A { + // foo(x, y, z) { ... } // Original function. + // get foo { return new BoundClosure499(this, "foo"); } + // } + // class BoundClosure499 extends Closure { + // var self; + // BoundClosure499(this.self, this.name); + // $call3(x, y, z) { return self[name](x, y, z); } + // } + + // TODO(floitsch): share the closure classes with other classes + // if they share methods with the same signature. Currently we do this only + // if there are no optional parameters. Closures with optional parameters + // are more difficult to canonicalize because they would need to have the + // same default values. + + bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0; + int parameterCount = member.parameterCount(compiler); + + Map<int, String> cache; + String extraArg = null; + // Methods on interceptor classes take an extra parameter, which is the + // actual receiver of the call. + JavaScriptBackend backend = compiler.backend; + bool inInterceptor = backend.isInterceptorClass(member.getEnclosingClass()); + if (inInterceptor) { + cache = interceptorClosureCache; + extraArg = 'receiver'; + } else { + cache = boundClosureCache; + } + List<String> fieldNames = compiler.enableMinification + ? inInterceptor ? const ['a', 'b', 'c'] + : const ['a', 'b'] + : inInterceptor ? const ['self', 'target', 'receiver'] + : const ['self', 'target']; + + Iterable<Element> typedefChecks = + getTypedefChecksOn(member.computeType(compiler)); + bool hasTypedefChecks = !typedefChecks.isEmpty; + + bool canBeShared = !hasOptionalParameters && !hasTypedefChecks; + + String closureClass = canBeShared ? cache[parameterCount] : null; + if (closureClass == null) { + // Either the class was not cached yet, or there are optional parameters. + // Create a new closure class. + String name; + if (canBeShared) { + if (inInterceptor) { + name = 'BoundClosure\$i${parameterCount}'; + } else { + name = 'BoundClosure\$${parameterCount}'; + } + } else { + name = 'Bound_${member.name.slowToString()}' + '_${member.enclosingElement.name.slowToString()}'; + } + + ClassElement closureClassElement = new ClosureClassElement( + new SourceString(name), compiler, member, member.getCompilationUnit()); + String mangledName = namer.getName(closureClassElement); + String superName = namer.getName(closureClassElement.superclass); + needsClosureClass = true; + + // Define the constructor with a name so that Object.toString can + // find the class name of the closure class. + ClassBuilder boundClosureBuilder = new ClassBuilder(); + emitBoundClosureClassHeader( + mangledName, superName, fieldNames, boundClosureBuilder); + // Now add the methods on the closure class. The instance method does not + // have the correct name. Since [addParameterStubs] use the name to create + // its stubs we simply create a fake element with the correct name. + // Note: the callElement will not have any enclosingElement. + FunctionElement callElement = + new ClosureInvocationElement(namer.closureInvocationSelectorName, + member); + + String invocationName = namer.instanceMethodName(callElement); + + List<String> parameters = <String>[]; + List<js.Expression> arguments = <js.Expression>[]; + if (inInterceptor) { + arguments.add(js.use('this').dot(fieldNames[2])); + } + for (int i = 0; i < parameterCount; i++) { + String name = 'p$i'; + parameters.add(name); + arguments.add(js.use(name)); + } + + js.Expression fun = + js.fun(parameters, + js.block1( + js.return_( + new js.PropertyAccess( + js.use('this').dot(fieldNames[0]), + js.use('this').dot(fieldNames[1])) + .callWith(arguments)))); + boundClosureBuilder.addProperty(invocationName, fun); + + addParameterStubs(callElement, boundClosureBuilder.addProperty); + typedefChecks.forEach((Element typedef) { + String operator = namer.operatorIs(typedef); + boundClosureBuilder.addProperty(operator, new js.LiteralBool(true)); + }); + + js.Expression init = + js.assign( + js.use(classesCollector).dot(mangledName), + boundClosureBuilder.toObjectInitializer()); + boundClosureBuffer.add(js.prettyPrint(init, compiler)); + boundClosureBuffer.add("$N"); + + closureClass = namer.isolateAccess(closureClassElement); + + // Cache it. + if (canBeShared) { + cache[parameterCount] = closureClass; + } + } + + // And finally the getter. + String getterName = namer.getterName(member); + String targetName = namer.instanceMethodName(member); + + List<String> parameters = <String>[]; + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(js.use('this')); + arguments.add(js.string(targetName)); + if (inInterceptor) { + parameters.add(extraArg); + arguments.add(js.use(extraArg)); + } + + js.Expression getterFunction = + js.fun(parameters, + js.block1( + js.return_( + new js.New(js.use(closureClass), arguments)))); + + defineStub(getterName, getterFunction); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitCallStubForGetter(Element member, + Set<Selector> selectors, + DefineStubFunction defineStub) { + assert(invariant(member, member.isDeclaration)); + LibraryElement memberLibrary = member.getLibrary(); + JavaScriptBackend backend = compiler.backend; + // If the class is an interceptor class, the stub gets the + // receiver explicitely and we need to pass it to the getter call. + bool isInterceptorClass = + backend.isInterceptorClass(member.getEnclosingClass()); + + const String receiverArgumentName = r'$receiver'; + + js.Expression buildGetter() { + if (member.isGetter()) { + String getterName = namer.getterName(member); + return new js.VariableUse('this').dot(getterName).callWith( + isInterceptorClass + ? <js.Expression>[new js.VariableUse(receiverArgumentName)] + : <js.Expression>[]); + } else { + String fieldName = member.hasFixedBackendName() + ? member.fixedBackendName() + : namer.instanceFieldName(member); + return new js.VariableUse('this').dot(fieldName); + } + } + + // Two selectors may match but differ only in type. To avoid generating + // identical stubs for each we track untyped selectors which already have + // stubs. + Set<Selector> generatedSelectors = new Set<Selector>(); + + for (Selector selector in selectors) { + if (selector.applies(member, compiler)) { + selector = selector.asUntyped; + if (generatedSelectors.contains(selector)) continue; + generatedSelectors.add(selector); + + String invocationName = namer.invocationName(selector); + Selector callSelector = new Selector.callClosureFrom(selector); + String closureCallName = namer.invocationName(callSelector); + + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + if (isInterceptorClass) { + parameters.add(new js.Parameter(receiverArgumentName)); + } + + for (int i = 0; i < selector.argumentCount; i++) { + String name = 'arg$i'; + parameters.add(new js.Parameter(name)); + arguments.add(new js.VariableUse(name)); + } + + js.Fun function = + new js.Fun(parameters, + new js.Block( + <js.Statement>[ + new js.Return( + buildGetter().dot(closureCallName) + .callWith(arguments))])); + + defineStub(invocationName, function); + } + } + } + + void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + Iterable<VariableElement> staticNonFinalFields = + handler.getStaticNonFinalFieldsForEmission(); + for (Element element in Elements.sortedByPosition(staticNonFinalFields)) { + compiler.withCurrentElement(element, () { + Constant initialValue = handler.getInitialValueFor(element); + js.Expression init = + new js.Assignment( + new js.PropertyAccess.field( + new js.VariableUse(isolateProperties), + namer.getName(element)), + constantEmitter.referenceInInitializationContext(initialValue)); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N'); + }); + } + } + + void emitLazilyInitializedStaticFields(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + List<VariableElement> lazyFields = + handler.getLazilyInitializedFieldsForEmission(); + JavaScriptBackend backend = compiler.backend; + if (!lazyFields.isEmpty) { + needsLazyInitializer = true; + for (VariableElement element in Elements.sortedByPosition(lazyFields)) { + assert(backend.generatedBailoutCode[element] == null); + js.Expression code = backend.generatedCode[element]; + assert(code != null); + // The code only computes the initial value. We build the lazy-check + // here: + // lazyInitializer(prototype, 'name', fieldName, getterName, initial); + // The name is used for error reporting. The 'initial' must be a + // closure that constructs the initial value. + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(js.use(isolateProperties)); + arguments.add(js.string(element.name.slowToString())); + arguments.add(js.string(namer.getName(element))); + arguments.add(js.string(namer.getLazyInitializerName(element))); + arguments.add(code); + js.Expression getter = buildLazyInitializedGetter(element); + if (getter != null) { + arguments.add(getter); + } + js.Expression init = js.call(js.use(lazyInitializerName), arguments); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add("$N"); + } + } + } + + js.Expression buildLazyInitializedGetter(VariableElement element) { + // Nothing to do, the 'lazy' function will create the getter. + return null; + } + + void emitCompileTimeConstants(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + List<Constant> constants = handler.getConstantsForEmission(); + bool addedMakeConstantList = false; + for (Constant constant in constants) { + // No need to emit functions. We already did that. + if (constant.isFunction()) continue; + // Numbers, strings and booleans are currently always inlined. + if (constant.isPrimitive()) continue; + + String name = namer.constantName(constant); + // The name is null when the constant is already a JS constant. + // TODO(floitsch): every constant should be registered, so that we can + // share the ones that take up too much space (like some strings). + if (name == null) continue; + if (!addedMakeConstantList && constant.isList()) { + addedMakeConstantList = true; + emitMakeConstantList(buffer); + } + js.Expression init = + new js.Assignment( + new js.PropertyAccess.field( + new js.VariableUse(isolateProperties), + name), + constantInitializerExpression(constant)); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N'); + } + } + + void emitMakeConstantList(CodeBuffer buffer) { + buffer.add(namer.isolateName); + buffer.add(r'''.makeConstantList = function(list) { + list.immutable$list = true; + list.fixed$length = true; + return list; +}; +'''); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitExtraAccessors(Element member, ClassBuilder builder) { + assert(invariant(member, member.isDeclaration)); + if (member.isGetter() || member.isField()) { + Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; + if (selectors != null && !selectors.isEmpty) { + emitCallStubForGetter(member, selectors, builder.addProperty); + } + } else if (member.isFunction()) { + if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) { + emitDynamicFunctionGetter(member, builder.addProperty); + } + } + } + + void emitNoSuchMethodHandlers(DefineStubFunction defineStub) { + // Do not generate no such method handlers if there is no class. + if (compiler.codegenWorld.instantiatedClasses.isEmpty) return; + + String noSuchMethodName = namer.publicInstanceMethodNameByArity( + Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); + + Element createInvocationMirrorElement = + compiler.findHelper(const SourceString("createInvocationMirror")); + String createInvocationMirrorName = + namer.getName(createInvocationMirrorElement); + + // Keep track of the JavaScript names we've already added so we + // do not introduce duplicates (bad for code size). + Set<String> addedJsNames = new Set<String>(); + + // Keep track of the noSuchMethod holders for each possible + // receiver type. + Map<ClassElement, Set<ClassElement>> noSuchMethodHolders = + new Map<ClassElement, Set<ClassElement>>(); + Set<ClassElement> noSuchMethodHoldersFor(DartType type) { + ClassElement element = type.element; + Set<ClassElement> result = noSuchMethodHolders[element]; + if (result == null) { + // For now, we check the entire world to see if an object of + // the given type may have a user-defined noSuchMethod + // implementation. We could do better by only looking at + // instantiated (or otherwise needed) classes. + result = compiler.world.findNoSuchMethodHolders(type); + noSuchMethodHolders[element] = result; + } + return result; + } + + js.Expression generateMethod(String jsName, Selector selector) { + // Values match JSInvocationMirror in js-helper library. + int type = selector.invocationMirrorKind; + String methodName = selector.invocationMirrorMemberName; + List<js.Parameter> parameters = <js.Parameter>[]; + CodeBuffer args = new CodeBuffer(); + for (int i = 0; i < selector.argumentCount; i++) { + parameters.add(new js.Parameter('\$$i')); + } + + List<js.Expression> argNames = + selector.getOrderedNamedArguments().map((SourceString name) => + js.string(name.slowToString())).toList(); + + String internalName = namer.invocationMirrorInternalName(selector); + + String createInvocationMirror = namer.getName( + compiler.createInvocationMirrorElement); + + js.Expression expression = + new js.This() + .dot(noSuchMethodName) + .callWith( + <js.Expression>[ + new js.VariableUse(namer.CURRENT_ISOLATE) + .dot(createInvocationMirror) + .callWith( + <js.Expression>[ + js.string(methodName), + js.string(internalName), + new js.LiteralNumber('$type'), + new js.ArrayInitializer.from( + parameters.map((param) => js.use(param.name)) + .toList()), + new js.ArrayInitializer.from(argNames)])]); + js.Expression function = + new js.Fun(parameters, + new js.Block(<js.Statement>[new js.Return(expression)])); + return function; + } + + void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) { + // Cache the object class and type. + ClassElement objectClass = compiler.objectClass; + DartType objectType = objectClass.computeType(compiler); + + for (Selector selector in selectors) { + // Introduce a helper function that determines if the given + // class has a member that matches the current name and + // selector (grabbed from the scope). + bool hasMatchingMember(ClassElement holder) { + Element element = holder.lookupMember(selector.name); + if (element == null) return false; + + // TODO(kasperl): Consider folding this logic into the + // Selector.applies() method. + if (element is AbstractFieldElement) { + AbstractFieldElement field = element; + if (selector.isGetter()) { + return field.getter != null; + } else if (selector.isSetter()) { + return field.setter != null; + } else { + return false; + } + } else if (element is VariableElement) { + if (selector.isSetter() && element.modifiers.isFinalOrConst()) { + return false; + } + } + return selector.applies(element, compiler); + } + + // If the selector is typed, we check to see if that type may + // have a user-defined noSuchMethod implementation. If not, we + // skip the selector altogether. + DartType receiverType = objectType; + ClassElement receiverClass = objectClass; + if (selector is TypedSelector) { + TypedSelector typedSelector = selector; + receiverType = typedSelector.receiverType; + receiverClass = receiverType.element; + } + + // If the receiver class is guaranteed to have a member that + // matches what we're looking for, there's no need to + // introduce a noSuchMethod handler. It will never be called. + // + // As an example, consider this class hierarchy: + // + // A <-- noSuchMethod + // / \ + // C B <-- foo + // + // If we know we're calling foo on an object of type B we + // don't have to worry about the noSuchMethod method in A + // because objects of type B implement foo. On the other hand, + // if we end up calling foo on something of type C we have to + // add a handler for it. + if (hasMatchingMember(receiverClass)) continue; + + // If the holders of all user-defined noSuchMethod + // implementations that might be applicable to the receiver + // type have a matching member for the current name and + // selector, we avoid introducing a noSuchMethod handler. + // + // As an example, consider this class hierarchy: + // + // A <-- foo + // / \ + // noSuchMethod --> B C <-- bar + // | | + // C D <-- noSuchMethod + // + // When calling foo on an object of type A, we know that the + // implementations of noSuchMethod are in the classes B and D + // that also (indirectly) implement foo, so we do not need a + // handler for it. + // + // If we're calling bar on an object of type D, we don't need + // the handler either because all objects of type D implement + // bar through inheritance. + // + // If we're calling bar on an object of type A we do need the + // handler because we may have to call B.noSuchMethod since B + // does not implement bar. + Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType); + if (holders.every(hasMatchingMember)) continue; + String jsName = namer.invocationMirrorInternalName(selector); + if (!addedJsNames.contains(jsName)) { + js.Expression method = generateMethod(jsName, selector); + defineStub(jsName, method); + addedJsNames.add(jsName); + } + } + } + + compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers); + compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers); + compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers); + } + + String buildIsolateSetup(CodeBuffer buffer, + Element appMain, + Element isolateMain) { + String mainAccess = "${namer.isolateAccess(appMain)}"; + String currentIsolate = "${namer.CURRENT_ISOLATE}"; + // Since we pass the closurized version of the main method to + // the isolate method, we must make sure that it exists. + if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) { + Selector selector = new Selector.callClosure(0); + String invocationName = namer.invocationName(selector); + buffer.add("$mainAccess.$invocationName = $mainAccess$N"); + } + return "${namer.isolateAccess(isolateMain)}($mainAccess)"; + } + + emitMain(CodeBuffer buffer) { + if (compiler.isMockCompilation) return; + Element main = compiler.mainApp.find(Compiler.MAIN); + String mainCall = null; + if (compiler.hasIsolateSupport()) { + Element isolateMain = + compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE); + mainCall = buildIsolateSetup(buffer, main, isolateMain); + } else { + mainCall = '${namer.isolateAccess(main)}()'; + } + if (!compiler.enableMinification) { + buffer.add(""" + +// +// BEGIN invoke [main]. +// +"""); + } + buffer.add(""" +if (typeof document !== 'undefined' && document.readyState !== 'complete') { + document.addEventListener('readystatechange', function () { + if (document.readyState == 'complete') { + if (typeof dartMainRunner === 'function') { + dartMainRunner(function() { ${mainCall}; }); + } else { + ${mainCall}; + } + } + }, false); +} else { + if (typeof dartMainRunner === 'function') { + dartMainRunner(function() { ${mainCall}; }); + } else { + ${mainCall}; + } +} +"""); + if (!compiler.enableMinification) { + buffer.add(""" +// +// END invoke [main]. +// + +"""); + } + } + + void emitGetInterceptorMethod(CodeBuffer buffer, + String objectName, + String key, + Collection<ClassElement> classes) { + js.Statement buildReturnInterceptor(ClassElement cls) { + return js.return_(js.fieldAccess(js.use(namer.isolateAccess(cls)), + 'prototype')); + } + + js.VariableUse receiver = js.use('receiver'); + JavaScriptBackend backend = compiler.backend; + + /** + * Build a JavaScrit AST node for doing a type check on + * [cls]. [cls] must be an interceptor class. + */ + js.Statement buildInterceptorCheck(ClassElement cls) { + js.Expression condition; + assert(backend.isInterceptorClass(cls)); + if (cls == backend.jsBoolClass) { + condition = js.equals(js.typeOf(receiver), js.string('boolean')); + } else if (cls == backend.jsIntClass || + cls == backend.jsDoubleClass || + cls == backend.jsNumberClass) { + throw 'internal error'; + } else if (cls == backend.jsArrayClass) { + condition = js.equals(js.fieldAccess(receiver, 'constructor'), + js.use('Array')); + } else if (cls == backend.jsStringClass) { + condition = js.equals(js.typeOf(receiver), js.string('string')); + } else if (cls == backend.jsNullClass) { + condition = js.equals(receiver, new js.LiteralNull()); + } else if (cls == backend.jsFunctionClass) { + condition = js.equals(js.typeOf(receiver), js.string('function')); + } else { + throw 'internal error'; + } + return js.if_(condition, buildReturnInterceptor(cls)); + } + + bool hasArray = false; + bool hasBool = false; + bool hasDouble = false; + bool hasFunction = false; + bool hasInt = false; + bool hasNull = false; + bool hasNumber = false; + bool hasString = false; + for (ClassElement cls in classes) { + if (cls == backend.jsArrayClass) hasArray = true; + else if (cls == backend.jsBoolClass) hasBool = true; + else if (cls == backend.jsDoubleClass) hasDouble = true; + else if (cls == backend.jsFunctionClass) hasFunction = true; + else if (cls == backend.jsIntClass) hasInt = true; + else if (cls == backend.jsNullClass) hasNull = true; + else if (cls == backend.jsNumberClass) hasNumber = true; + else if (cls == backend.jsStringClass) hasString = true; + else throw 'Internal error: $cls'; + } + if (hasDouble) { + assert(!hasNumber); + hasNumber = true; + } + if (hasInt) hasNumber = true; + + js.Block block = new js.Block.empty(); + + if (hasNumber) { + js.Statement whenNumber; + + /// Note: there are two number classes in play: Dart's [num], + /// and JavaScript's Number (typeof receiver == 'number'). This + /// is the fallback used when we have determined that receiver + /// is a JavaScript Number. + js.Return returnNumberClass = buildReturnInterceptor( + hasDouble ? backend.jsDoubleClass : backend.jsNumberClass); + + if (hasInt) { + js.Expression isInt = + js.equals(js.call(js.fieldAccess(js.use('Math'), 'floor'), + [receiver]), + receiver); + (whenNumber = js.emptyBlock()).statements + ..add(js.if_(isInt, buildReturnInterceptor(backend.jsIntClass))) + ..add(returnNumberClass); + } else { + whenNumber = returnNumberClass; + } + block.statements.add( + js.if_(js.equals(js.typeOf(receiver), js.string('number')), + whenNumber)); + } + + if (hasString) { + block.statements.add(buildInterceptorCheck(backend.jsStringClass)); + } + if (hasNull) { + block.statements.add(buildInterceptorCheck(backend.jsNullClass)); + } else { + // Returning "undefined" here will provoke a JavaScript + // TypeError which is later identified as a null-error by + // [unwrapException] in js_helper.dart. + block.statements.add(js.if_(js.equals(receiver, new js.LiteralNull()), + js.return_(js.undefined()))); + } + if (hasFunction) { + block.statements.add(buildInterceptorCheck(backend.jsFunctionClass)); + } + if (hasBool) { + block.statements.add(buildInterceptorCheck(backend.jsBoolClass)); + } + // TODO(ahe): It might be faster to check for Array before + // function and bool. + if (hasArray) { + block.statements.add(buildInterceptorCheck(backend.jsArrayClass)); + } + block.statements.add(js.return_(js.fieldAccess(js.use(objectName), + 'prototype'))); + + js.PropertyAccess name = js.fieldAccess(js.use(isolateProperties), key); + buffer.add(js.prettyPrint(js.assign(name, js.fun(['receiver'], block)), + compiler)); + buffer.add(N); + } + + /** + * Emit all versions of the [:getInterceptor:] method. + */ + void emitGetInterceptorMethods(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + // If no class needs to be intercepted, just return. + if (backend.objectInterceptorClass == null) return; + String objectName = namer.isolateAccess(backend.objectInterceptorClass); + var specializedGetInterceptors = backend.specializedGetInterceptors; + for (String name in specializedGetInterceptors.keys.toList()..sort()) { + Collection<ClassElement> classes = specializedGetInterceptors[name]; + emitGetInterceptorMethod(buffer, objectName, name, classes); + } + } + + void computeNeededClasses() { + instantiatedClasses = + compiler.codegenWorld.instantiatedClasses.where(computeClassFilter()) + .toSet(); + neededClasses = new Set<ClassElement>.from(instantiatedClasses); + for (ClassElement element in instantiatedClasses) { + for (ClassElement superclass = element.superclass; + superclass != null; + superclass = superclass.superclass) { + if (neededClasses.contains(superclass)) break; + neededClasses.add(superclass); + } + } + } + + int _compareSelectors(Selector selector1, Selector selector2) { + int comparison = _compareSelectorNames(selector1, selector2); + if (comparison != 0) return comparison; + + JavaScriptBackend backend = compiler.backend; + Set<ClassElement> classes1 = backend.getInterceptedClassesOn(selector1); + Set<ClassElement> classes2 = backend.getInterceptedClassesOn(selector2); + if (classes1.length != classes2.length) { + return classes1.length - classes2.length; + } + String getInterceptor1 = + namer.getInterceptorName(backend.getInterceptorMethod, classes1); + String getInterceptor2 = + namer.getInterceptorName(backend.getInterceptorMethod, classes2); + return Comparable.compare(getInterceptor1, getInterceptor2); + } + + void emitOneShotInterceptors(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + for (Selector selector in + backend.oneShotInterceptors.toList()..sort(_compareSelectors)) { + Set<ClassElement> classes = backend.getInterceptedClassesOn(selector); + String oneShotInterceptorName = namer.oneShotInterceptorName(selector); + String getInterceptorName = + namer.getInterceptorName(backend.getInterceptorMethod, classes); + + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + parameters.add(new js.Parameter('receiver')); + arguments.add(js.use('receiver')); + + if (selector.isSetter()) { + parameters.add(new js.Parameter('value')); + arguments.add(js.use('value')); + } else { + for (int i = 0; i < selector.argumentCount; i++) { + String argName = 'a$i'; + parameters.add(new js.Parameter(argName)); + arguments.add(js.use(argName)); + } + } + + String invocationName = backend.namer.invocationName(selector); + js.Fun function = + new js.Fun(parameters, + js.block1(js.return_( + js.use(isolateProperties) + .dot(getInterceptorName) + .callWith([js.use('receiver')]) + .dot(invocationName) + .callWith(arguments)))); + + js.PropertyAccess property = + js.fieldAccess(js.use(isolateProperties), oneShotInterceptorName); + + buffer.add(js.prettyPrint(js.assign(property, function), compiler)); + buffer.add(N); + } + } + + String assembleProgram() { + measure(() { + computeNeededClasses(); + + mainBuffer.add(GENERATED_BY); + if (!compiler.enableMinification) mainBuffer.add(HOOKS_API_USAGE); + mainBuffer.add('function ${namer.isolateName}()$_{}\n'); + mainBuffer.add('init()$N$n'); + // Shorten the code by using "$$" as temporary. + classesCollector = r"$$"; + mainBuffer.add('var $classesCollector$_=$_{}$N'); + // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary. + isolateProperties = namer.CURRENT_ISOLATE; + mainBuffer.add( + 'var $isolateProperties$_=$_$isolatePropertiesName$N'); + emitClasses(mainBuffer); + mainBuffer.add(boundClosureBuffer); + // Clear the buffer, so that we can reuse it for the native classes. + boundClosureBuffer.clear(); + emitStaticFunctions(mainBuffer); + emitStaticFunctionGetters(mainBuffer); + // We need to finish the classes before we construct compile time + // constants. + emitFinishClassesInvocationIfNecessary(mainBuffer); + emitRuntimeClassesAndTests(mainBuffer); + emitCompileTimeConstants(mainBuffer); + // Static field initializations require the classes and compile-time + // constants to be set up. + emitStaticNonFinalFieldInitializations(mainBuffer); + emitOneShotInterceptors(mainBuffer); + emitGetInterceptorMethods(mainBuffer); + emitLazilyInitializedStaticFields(mainBuffer); + + isolateProperties = isolatePropertiesName; + // The following code should not use the short-hand for the + // initialStatics. + mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=${_}null$N'); + mainBuffer.add(boundClosureBuffer); + emitFinishClassesInvocationIfNecessary(mainBuffer); + // After this assignment we will produce invalid JavaScript code if we use + // the classesCollector variable. + classesCollector = 'classesCollector should not be used from now on'; + + emitFinishIsolateConstructorInvocation(mainBuffer); + mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=' + '${_}new ${namer.isolateName}()$N'); + + nativeEmitter.assembleCode(mainBuffer); + emitMain(mainBuffer); + mainBuffer.add('function init()$_{\n'); + mainBuffer.add('$isolateProperties$_=$_{}$N'); + addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer); + addLazyInitializerFunctionIfNecessary(mainBuffer); + emitFinishIsolateConstructor(mainBuffer); + mainBuffer.add('}\n'); + compiler.assembledCode = mainBuffer.getText(); + + if (generateSourceMap) { + SourceFile compiledFile = new SourceFile(null, compiler.assembledCode); + String sourceMap = buildSourceMap(mainBuffer, compiledFile); + compiler.outputProvider('', 'js.map') + ..add(sourceMap) + ..close(); + } + }); + return compiler.assembledCode; + } + + String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) { + SourceMapBuilder sourceMapBuilder = new SourceMapBuilder(); + buffer.forEachSourceLocation(sourceMapBuilder.addMapping); + return sourceMapBuilder.build(compiledFile); + } +} + +const String GENERATED_BY = """ +// Generated by dart2js, the Dart to JavaScript compiler. +"""; +const String HOOKS_API_USAGE = """ +// The code supports the following hooks: +// dartPrint(message) - if this function is defined it is called +// instead of the Dart [print] method. +// dartMainRunner(main) - if this function is defined, the Dart [main] +// method will not be invoked directly. +// Instead, a closure that will invoke [main] is +// passed to [dartMainRunner]. +""";
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart new file mode 100644 index 0000000..06d5d01 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
@@ -0,0 +1,138 @@ +// 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. + +part of js_backend; + +class CodeEmitterNoEvalTask extends CodeEmitterTask { + CodeEmitterNoEvalTask(Compiler compiler, + Namer namer, + bool generateSourceMap) + : super(compiler, namer, generateSourceMap); + + String get generateGetterSetterFunction { + return """ +function() { + throw 'Internal Error: no dynamic generation of getters and setters allowed'; +}"""; + } + + String get defineClassFunction { + return """ +function(cls, constructor, prototype) { + constructor.prototype = prototype; + constructor.builtin\$cls = cls; + return constructor; +}"""; + } + + String get protoSupportCheck { + // We don't modify the prototypes in CSP mode. Therefore we can have an + // easier prototype-check. + return 'var $supportsProtoName = !!{}.__proto__;\n'; + } + + String get finishIsolateConstructorFunction { + // We replace the old Isolate function with a new one that initializes + // all its field with the initial (and often final) value of all globals. + // + // We also copy over old values like the prototype, and the + // isolateProperties themselves. + return """ +function(oldIsolate) { + var isolateProperties = oldIsolate.${namer.isolatePropertiesName}; + function Isolate() { + for (var staticName in isolateProperties) { + if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) { + this[staticName] = isolateProperties[staticName]; + } + } + // Use the newly created object as prototype. In Chrome this creates a + // hidden class for the object and makes sure it is fast to access. + function ForceEfficientMap() {} + ForceEfficientMap.prototype = this; + new ForceEfficientMap; + } + Isolate.prototype = oldIsolate.prototype; + Isolate.prototype.constructor = Isolate; + Isolate.${namer.isolatePropertiesName} = isolateProperties; + return Isolate; +}"""; + } + + String get lazyInitializerFunction { + return """ +function(prototype, staticName, fieldName, getterName, lazyValue, getter) { +$lazyInitializerLogic +}"""; + } + + js.Expression buildLazyInitializedGetter(VariableElement element) { + String isolate = namer.CURRENT_ISOLATE; + return js.fun([], + js.block1( + js.return_( + js.fieldAccess(js.use(isolate), namer.getName(element))))); + } + + js.Expression buildConstructor(String mangledName, List<String> fieldNames) { + return new js.NamedFunction( + new js.VariableDeclaration(mangledName), + new js.Fun( + fieldNames + .map((fieldName) => new js.Parameter(fieldName)) + .toList(), + new js.Block( + fieldNames.map((fieldName) => + new js.ExpressionStatement( + new js.Assignment( + new js.This().dot(fieldName), + new js.VariableUse(fieldName)))) + .toList()))); + } + + void emitBoundClosureClassHeader(String mangledName, + String superName, + List<String> fieldNames, + ClassBuilder builder) { + builder.addProperty('', buildConstructor(mangledName, fieldNames)); + builder.addProperty('super', js.string(superName)); + } + + void emitClassConstructor(ClassElement classElement, ClassBuilder builder) { + // Say we have a class A with fields b, c and d, where c needs a getter and + // d needs both a getter and a setter. Then we produce: + // - a constructor (directly into the given [buffer]): + // function A(b, c, d) { this.b = b, this.c = c, this.d = d; } + // - getters and setters (stored in the [explicitGettersSetters] list): + // get$c : function() { return this.c; } + // get$d : function() { return this.d; } + // set$d : function(x) { this.d = x; } + List<String> fields = <String>[]; + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + fields.add(name); + }); + String constructorName = namer.safeName(classElement.name.slowToString()); + + builder.addProperty('', buildConstructor(constructorName, fields)); + } + + void emitSuper(String superName, ClassBuilder builder) { + if (superName != '') { + builder.addProperty('super', js.string(superName)); + } + } + + void emitClassFields(ClassElement classElement, + ClassBuilder builder, + { String superClass: "", + bool classIsNative: false}) { + } + + bool get getterAndSetterCanBeImplementedByFieldSpec => false; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart new file mode 100644 index 0000000..52e6ee0 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart
@@ -0,0 +1,33 @@ +// 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. + +library js_backend; + +import 'dart:collection' show LinkedHashMap; + +import '../closure.dart'; +import '../../compiler.dart' as api; +import '../elements/elements.dart'; +import '../elements/modelx.dart' show FunctionElementX; +import '../dart2jslib.dart' hide Selector; +import '../dart_types.dart'; +import '../js/js.dart' as js; +import '../native_handler.dart' as native; +import '../source_file.dart'; +import '../source_map_builder.dart'; +import '../ssa/ssa.dart'; +import '../tree/tree.dart'; +import '../universe/universe.dart'; +import '../util/characters.dart'; +import '../util/util.dart'; + +part 'backend.dart'; +part 'constant_emitter.dart'; +part 'constant_system_javascript.dart'; +part 'emitter.dart'; +part 'emitter_no_eval.dart'; +part 'minify_namer.dart'; +part 'namer.dart'; +part 'native_emitter.dart'; +part 'runtime_types.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart new file mode 100644 index 0000000..98344a1 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart
@@ -0,0 +1,200 @@ +// Copyright (c) 2011, 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. + +part of js_backend; + +/** + * Assigns JavaScript identifiers to Dart variables, class-names and members. + */ +class MinifyNamer extends Namer { + MinifyNamer(Compiler compiler) : super(compiler) { + reserveBackendNames(); + } + + String get isolateName => 'I'; + String get isolatePropertiesName => 'p'; + bool get shouldMinify => true; + + const ALPHABET_CHARACTERS = 52; // a-zA-Z. + const ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. + + // You can pass an invalid identifier to this and unlike its non-minifying + // counterpart it will never return the proposedName as the new fresh name. + String getFreshName(String proposedName, + Set<String> usedNames, + Map<String, String> suggestedNames, + {bool ensureSafe: true}) { + var freshName; + var suggestion = suggestedNames[proposedName]; + if (suggestion != null && !usedNames.contains(suggestion)) { + freshName = suggestion; + } else { + freshName = _getUnusedName(proposedName, usedNames); + } + usedNames.add(freshName); + return freshName; + } + + SourceString getClosureVariableName(SourceString name, int id) { + if (id < ALPHABET_CHARACTERS) { + return new SourceString(new String.fromCharCodes([_letterNumber(id)])); + } + return new SourceString("${getMappedInstanceName('closure')}_$id"); + } + + void reserveBackendNames() { + // From issue 7554. These should not be used on objects (as instance + // variables) because they clash with names from the DOM. + const reservedNativeProperties = const <String>[ + 'Q', 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z', + // 2-letter: + 'ch', 'cx', 'cy', 'db', 'dx', 'dy', 'fr', 'fx', 'fy', 'go', 'id', 'k1', + 'k2', 'k3', 'k4', 'r1', 'r2', 'rx', 'ry', 'x1', 'x2', 'y1', 'y2', + // 3-letter: + 'add', 'all', 'alt', 'arc', 'CCW', 'cmp', 'dir', 'end', 'get', 'in1', + 'in2', 'INT', 'key', 'log', 'low', 'm11', 'm12', 'm13', 'm14', 'm21', + 'm22', 'm23', 'm24', 'm31', 'm32', 'm33', 'm34', 'm41', 'm42', 'm43', + 'm44', 'max', 'min', 'now', 'ONE', 'put', 'red', 'rel', 'rev', 'RGB', + 'sdp', 'set', 'src', 'tag', 'top', 'uid', 'uri', 'url', 'URL', + // 4-letter: + 'abbr', 'atob', 'Attr', 'axes', 'axis', 'back', 'BACK', 'beta', 'bias', + 'Blob', 'blue', 'blur', 'BLUR', 'body', 'BOOL', 'BOTH', 'btoa', 'BYTE', + 'cite', 'clip', 'code', 'cols', 'cues', 'data', 'DECR', 'DONE', 'face', + 'file', 'File', 'fill', 'find', 'font', 'form', 'gain', 'hash', 'head', + 'high', 'hint', 'host', 'href', 'HRTF', 'IDLE', 'INCR', 'info', 'INIT', + 'isId', 'item', 'KEEP', 'kind', 'knee', 'lang', 'left', 'LESS', 'line', + 'link', 'list', 'load', 'loop', 'mode', 'name', 'Node', 'None', 'NONE', + 'only', 'open', 'OPEN', 'ping', 'play', 'port', 'rect', 'Rect', 'refX', + 'refY', 'RGBA', 'root', 'rows', 'save', 'seed', 'seek', 'self', 'send', + 'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text', + 'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO']; + for (var name in reservedNativeProperties) { + if (name.length < 2) { + instanceNameMap[name] = name; + } + usedInstanceNames.add(name); + } + + // This list of popular instance variable names generated with: + // cat out.js | + // perl -ne '$_=~s/(?<![^a-z0-9_\$]\$)\.([a-z0-9_\$]+)/print("$1\n")/gei' | + // sort | uniq -c | sort -nr | head -40 + // Removed: html, call*, hasOwnProperty. + _populateSuggestedNames( + suggestedInstanceNames, + usedInstanceNames, + const <String>[ + r'$add', r'add$1', r'box_0', r'charCodeAt$1', r'constructor', + r'current', r'$defineNativeClass', r'$eq', r'$ne', + r'getPrototypeOf', r'hasOwnProperty', r'$index', r'$indexSet', + r'$isJavaScriptIndexingBehavior', r'$isolateProperties', + r'iterator', r'length', r'$lt', r'$gt', r'$le', r'$ge', + r'moveNext$0', r'node', r'on', r'prototype', r'push', r'self', + r'start', r'target', r'this_0', r'value', r'width', r'style']); + + _populateSuggestedNames( + suggestedGlobalNames, + usedGlobalNames, + const <String>[ + r'Object', r'$throw', r'$eq', r'S', r'ioore', r'UnsupportedError$', + r'length', r'$sub', r'getInterceptor$JSStringJSArray', r'$add', + r'$gt', r'$ge', r'$lt', r'$le', r'add', r'getInterceptor$JSNumber', + r'iterator', r'$index', r'iae', r'getInterceptor$JSArray', + r'ArgumentError$', r'BoundClosure', r'StateError$', + r'getInterceptor', r'max', r'$mul', r'List_List', r'Map_Map', + r'getInterceptor$JSString', r'$div', r'$indexSet', + r'List_List$from', r'Set_Set$from', r'toString', r'toInt', r'min', + r'StringBuffer_StringBuffer', r'contains1', r'WhereIterable$', + r'RangeError$value', r'JSString', r'JSNumber', + r'JSArray' + ]); + } + + void _populateSuggestedNames(Map<String, String> suggestionMap, + Set<String> used, + List<String> suggestions) { + int c = $a - 1; + String letter; + for (String name in suggestions) { + do { + assert(c != $Z); + c = (c == $z) ? $A : c + 1; + letter = new String.fromCharCodes([c]); + } while (used.contains(letter)); + assert(suggestionMap[name] == null); + suggestionMap[name] = letter; + } + } + + + // This gets a minified name based on a hash of the proposed name. This + // is slightly less efficient than just getting the next name in a series, + // but it means that small changes in the input program will give smallish + // changes in the output, which can be useful for diffing etc. + String _getUnusedName(String proposedName, Set<String> usedNames) { + int hash = _calculateHash(proposedName); + // Avoid very small hashes that won't try many names. + hash = hash < 1000 ? hash * 314159 : hash; // Yes, it's prime. + + // Try other n-character names based on the hash. We try one to three + // character identifiers. For each length we try around 10 different names + // in a predictable order determined by the proposed name. This is in order + // to make the renamer stable: small changes in the input should nornally + // result in relatively small changes in the output. + for (var n = 2; n <= 3; n++) { + int h = hash; + while (h > 10) { + var codes = <int>[_letterNumber(h)]; + int h2 = h ~/ ALPHABET_CHARACTERS; + for (var i = 1; i < n; i++) { + codes.add(_alphaNumericNumber(h2)); + h2 ~/= ALPHANUMERIC_CHARACTERS; + } + final candidate = new String.fromCharCodes(codes); + if (!usedNames.contains(candidate) && !jsReserved.contains(candidate)) { + return candidate; + } + // Try again with a slightly different hash. After around 10 turns + // around this loop h is zero and we try a longer name. + h ~/= 7; + } + } + + // If we can't find a hash based name in the three-letter space, then base + // the name on a letter and a counter. + var startLetter = new String.fromCharCodes([_letterNumber(hash)]); + var i = 0; + while (usedNames.contains("$startLetter$i")) { + i++; + } + return "$startLetter$i"; + } + + int _calculateHash(String name) { + int h = 0; + for (int i = 0; i < name.length; i++) { + h += name.charCodeAt(i); + h &= 0xffffffff; + h += h << 10; + h &= 0xffffffff; + h ^= h >> 6; + h &= 0xffffffff; + } + return h; + } + + int _letterNumber(int x) { + if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS; + if (x < 26) return $a + x; + return $A + x - 26; + } + + int _alphaNumericNumber(int x) { + if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS; + if (x < 26) return $a + x; + if (x < 52) return $A + x - 26; + return $0 + x - 52; + } + +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart new file mode 100644 index 0000000..e5bb967 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart
@@ -0,0 +1,754 @@ +// Copyright (c) 2011, 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. + +part of js_backend; + +/** + * Assigns JavaScript identifiers to Dart variables, class-names and members. + */ +class Namer implements ClosureNamer { + + static const javaScriptKeywords = const <String>[ + // These are current keywords. + "break", "delete", "function", "return", "typeof", "case", "do", "if", + "switch", "var", "catch", "else", "in", "this", "void", "continue", + "false", "instanceof", "throw", "while", "debugger", "finally", "new", + "true", "with", "default", "for", "null", "try", + + // These are future keywords. + "abstract", "double", "goto", "native", "static", "boolean", "enum", + "implements", "package", "super", "byte", "export", "import", "private", + "synchronized", "char", "extends", "int", "protected", "throws", + "class", "final", "interface", "public", "transient", "const", "float", + "long", "short", "volatile" + ]; + + static const reservedPropertySymbols = + const <String>["__proto__", "prototype", "constructor", "call"]; + + // Symbols that we might be using in our JS snippets. + static const reservedGlobalSymbols = const <String>[ + // Section references are from Ecma-262 + // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) + + // 15.1.1 Value Properties of the Global Object + "NaN", "Infinity", "undefined", + + // 15.1.2 Function Properties of the Global Object + "eval", "parseInt", "parseFloat", "isNaN", "isFinite", + + // 15.1.3 URI Handling Function Properties + "decodeURI", "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + + // 15.1.4 Constructor Properties of the Global Object + "Object", "Function", "Array", "String", "Boolean", "Number", "Date", + "RegExp", "Error", "EvalError", "RangeError", "ReferenceError", + "SyntaxError", "TypeError", "URIError", + + // 15.1.5 Other Properties of the Global Object + "Math", + + // 10.1.6 Activation Object + "arguments", + + // B.2 Additional Properties (non-normative) + "escape", "unescape", + + // Window props (https://developer.mozilla.org/en/DOM/window) + "applicationCache", "closed", "Components", "content", "controllers", + "crypto", "defaultStatus", "dialogArguments", "directories", + "document", "frameElement", "frames", "fullScreen", "globalStorage", + "history", "innerHeight", "innerWidth", "length", + "location", "locationbar", "localStorage", "menubar", + "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel", + "name", "navigator", "opener", "outerHeight", "outerWidth", + "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11", + "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY", + "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar", + "top", "window", + + // Window methods (https://developer.mozilla.org/en/DOM/window) + "alert", "addEventListener", "atob", "back", "blur", "btoa", + "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", + "disableExternalCapture", "dispatchEvent", "dump", + "enableExternalCapture", "escape", "find", "focus", "forward", + "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount", + "getComputedStyle", "getSelection", "home", "maximize", "minimize", + "moveBy", "moveTo", "open", "openDialog", "postMessage", "print", + "prompt", "QueryInterface", "releaseEvents", "removeEventListener", + "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy", + "scrollByLines", "scrollByPages", "scrollTo", "setInterval", + "setResizeable", "setTimeout", "showModalDialog", "sizeToContent", + "stop", "uuescape", "updateCommands", "XPCNativeWrapper", + "XPCSafeJSOjbectWrapper", + + // Mozilla Window event handlers, same cite + "onabort", "onbeforeunload", "onchange", "onclick", "onclose", + "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange", + "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown", + "onmousemove", "onmouseout", "onmouseover", "onmouseup", + "onmozorientation", "onpaint", "onreset", "onresize", "onscroll", + "onselect", "onsubmit", "onunload", + + // Safari Web Content Guide + // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf + // WebKit Window member data, from WebKit DOM Reference + // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html) + "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart", + "ongesturestart", "ongesturechange", "ongestureend", + + // extra window methods + "uneval", + + // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7, + // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1 + "getPrototypeOf", "let", "yield", + + // "future reserved words" + "abstract", "int", "short", "boolean", "interface", "static", "byte", + "long", "char", "final", "native", "synchronized", "float", "package", + "throws", "goto", "private", "transient", "implements", "protected", + "volatile", "double", "public", + + // IE methods + // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#) + "attachEvent", "clientInformation", "clipboardData", "createPopup", + "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth", + "onafterprint", "onbeforedeactivate", "onbeforeprint", + "oncontrolselect", "ondeactivate", "onhelp", "onresizeend", + + // Common browser-defined identifiers not defined in ECMAScript + "event", "external", "Debug", "Enumerator", "Global", "Image", + "ActiveXObject", "VBArray", "Components", + + // Functions commonly defined on Object + "toString", "getClass", "constructor", "prototype", "valueOf", + + // Client-side JavaScript identifiers + "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient", + "CanvasPattern", "CanvasRenderingContext2D", "CDATASection", + "CharacterData", "Comment", "CSS2Properties", "CSSRule", + "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType", + "DOMException", "DOMImplementation", "DOMParser", "Element", "Event", + "ExternalInterface", "FlashPlayer", "Form", "Frame", "History", + "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image", + "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType", + "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin", + "ProcessingInstruction", "Range", "RangeException", "Screen", "Select", + "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea", + "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer", + "XPathException", "XPathResult", "XSLTProcessor", + + // These keywords trigger the loading of the java-plugin. For the + // next-generation plugin, this results in starting a new Java process. + "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass", + "JavaArray", "JavaMember" + ]; + + Set<String> _jsReserved = null; + /// Names that cannot be used by members, top level and static + /// methods. + Set<String> get jsReserved { + if (_jsReserved == null) { + _jsReserved = new Set<String>(); + _jsReserved.addAll(javaScriptKeywords); + _jsReserved.addAll(reservedPropertySymbols); + } + return _jsReserved; + } + + Set<String> _jsVariableReserved = null; + /// Names that cannot be used by local variables and parameters. + Set<String> get jsVariableReserved { + if (_jsVariableReserved == null) { + _jsVariableReserved = new Set<String>(); + _jsVariableReserved.addAll(javaScriptKeywords); + _jsVariableReserved.addAll(reservedPropertySymbols); + _jsVariableReserved.addAll(reservedGlobalSymbols); + } + return _jsVariableReserved; + } + + final String CURRENT_ISOLATE = r'$'; + + /** + * Map from top-level or static elements to their unique identifiers provided + * by [getName]. + * + * Invariant: Keys must be declaration elements. + */ + final Compiler compiler; + final Map<Element, String> globals; + final Map<Selector, String> oneShotInterceptorNames; + final Map<String, LibraryElement> shortPrivateNameOwners; + + final Set<String> usedGlobalNames; + final Set<String> usedInstanceNames; + final Map<String, String> globalNameMap; + final Map<String, String> suggestedGlobalNames; + final Map<String, String> instanceNameMap; + final Map<String, String> suggestedInstanceNames; + + final Map<String, String> operatorNameMap; + final Map<String, int> popularNameCounters; + + final Map<Element, String> bailoutNames; + + final Map<Constant, String> constantNames; + + Namer(this.compiler) + : globals = new Map<Element, String>(), + oneShotInterceptorNames = new Map<Selector, String>(), + shortPrivateNameOwners = new Map<String, LibraryElement>(), + bailoutNames = new Map<Element, String>(), + usedGlobalNames = new Set<String>(), + usedInstanceNames = new Set<String>(), + instanceNameMap = new Map<String, String>(), + operatorNameMap = new Map<String, String>(), + globalNameMap = new Map<String, String>(), + suggestedGlobalNames = new Map<String, String>(), + suggestedInstanceNames = new Map<String, String>(), + constantNames = new Map<Constant, String>(), + popularNameCounters = new Map<String, int>(); + + String get isolateName => 'Isolate'; + String get isolatePropertiesName => r'$isolateProperties'; + /** + * Some closures must contain their name. The name is stored in + * [STATIC_CLOSURE_NAME_NAME]. + */ + String get STATIC_CLOSURE_NAME_NAME => r'$name'; + SourceString get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME; + bool get shouldMinify => false; + + bool isReserved(String name) => name == isolateName; + + String constantName(Constant constant) { + // In the current implementation it doesn't make sense to give names to + // function constants since the function-implementation itself serves as + // constant and can be accessed directly. + assert(!constant.isFunction()); + String result = constantNames[constant]; + if (result == null) { + String longName; + if (shouldMinify) { + if (constant.isString()) { + StringConstant stringConstant = constant; + // The minifier always constructs a new name, using the argument as + // input to its hashing algorithm. The given name does not need to be + // valid. + longName = stringConstant.value.slowToString(); + } else { + longName = "C"; + } + } else { + longName = "CONSTANT"; + } + result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames, + ensureSafe: true); + constantNames[constant] = result; + } + return result; + } + + String breakLabelName(LabelElement label) { + return '\$${label.labelName}\$${label.target.nestingLevel}'; + } + + String implicitBreakLabelName(TargetElement target) { + return '\$${target.nestingLevel}'; + } + + // We sometimes handle continue targets differently from break targets, + // so we have special continue-only labels. + String continueLabelName(LabelElement label) { + return 'c\$${label.labelName}\$${label.target.nestingLevel}'; + } + + String implicitContinueLabelName(TargetElement target) { + return 'c\$${target.nestingLevel}'; + } + + /** + * If the [name] is not private returns [:name.slowToString():]. Otherwise + * mangles the [name] so that each library has a unique name. + */ + String privateName(LibraryElement library, SourceString name) { + // Public names are easy. + String nameString = name.slowToString(); + if (!name.isPrivate()) return nameString; + + // The first library asking for a short private name wins. + LibraryElement owner = shouldMinify + ? library + : shortPrivateNameOwners.putIfAbsent(nameString, () => library); + + // If a private name could clash with a mangled private name we don't + // use the short name. For example a private name "_lib3_foo" would + // clash with "_foo" from "lib3". + if (owner == library && + !nameString.startsWith('_$LIBRARY_PREFIX') && + !shouldMinify) { + return nameString; + } + + // If a library name does not start with the [LIBRARY_PREFIX] then our + // assumptions about clashing with mangled private members do not hold. + String libraryName = getName(library); + assert(shouldMinify || libraryName.startsWith(LIBRARY_PREFIX)); + // TODO(erikcorry): Fix this with other manglings to avoid clashes. + return '_lib$libraryName\$$nameString'; + } + + String instanceMethodName(FunctionElement element) { + SourceString elementName = element.name; + SourceString name = operatorNameToIdentifier(elementName); + if (name != elementName) return getMappedOperatorName(name.slowToString()); + + LibraryElement library = element.getLibrary(); + if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { + ConstructorBodyElement bodyElement = element; + name = bodyElement.constructor.name; + } + FunctionSignature signature = element.computeSignature(compiler); + String methodName = + '${privateName(library, name)}\$${signature.parameterCount}'; + if (signature.optionalParametersAreNamed && + !signature.optionalParameters.isEmpty) { + StringBuffer buffer = new StringBuffer(); + signature.orderedOptionalParameters.forEach((Element element) { + buffer.add('\$${safeName(element.name.slowToString())}'); + }); + methodName = '$methodName$buffer'; + } + if (name == closureInvocationSelectorName) return methodName; + return getMappedInstanceName(methodName); + } + + String publicInstanceMethodNameByArity(SourceString name, int arity) { + SourceString newName = operatorNameToIdentifier(name); + if (newName != name) return getMappedOperatorName(newName.slowToString()); + assert(!name.isPrivate()); + var base = name.slowToString(); + // We don't mangle the closure invoking function name because it + // is generated by string concatenation in applyFunction from + // js_helper.dart. + var proposedName = '$base\$$arity'; + if (name == closureInvocationSelectorName) return proposedName; + return getMappedInstanceName(proposedName); + } + + String invocationName(Selector selector) { + if (selector.isGetter()) { + String proposedName = privateName(selector.library, selector.name); + return 'get\$${getMappedInstanceName(proposedName)}'; + } else if (selector.isSetter()) { + String proposedName = privateName(selector.library, selector.name); + return 'set\$${getMappedInstanceName(proposedName)}'; + } else { + SourceString name = selector.name; + if (selector.kind == SelectorKind.OPERATOR + || selector.kind == SelectorKind.INDEX) { + name = operatorNameToIdentifier(name); + assert(name != selector.name); + return getMappedOperatorName(name.slowToString()); + } + assert(name == operatorNameToIdentifier(name)); + StringBuffer buffer = new StringBuffer(); + for (SourceString argumentName in selector.getOrderedNamedArguments()) { + buffer.add(r'$'); + argumentName.printOn(buffer); + } + String suffix = '\$${selector.argumentCount}$buffer'; + // We don't mangle the closure invoking function name because it + // is generated by string concatenation in applyFunction from + // js_helper.dart. + if (selector.isClosureCall()) { + return "${name.slowToString()}$suffix"; + } else { + String proposedName = privateName(selector.library, name); + return getMappedInstanceName('$proposedName$suffix'); + } + } + } + + /** + * Returns the internal name used for an invocation mirror of this selector. + */ + String invocationMirrorInternalName(Selector selector) + => invocationName(selector); + + String instanceFieldName(Element element) { + String proposedName = privateName(element.getLibrary(), element.name); + return getMappedInstanceName(proposedName); + } + + // Construct a new name for the element based on the library and class it is + // in. The name here is not important, we just need to make sure it is + // unique. If we are minifying, we actually construct the name from the + // minified versions of the class and instance names, but the result is + // minified once again, so that is not visible in the end result. + String shadowedFieldName(Element fieldElement) { + // Check for following situation: Native field ${fieldElement.name} has + // fixed JSName ${fieldElement.nativeName()}, but a subclass shadows this + // name. We normally handle that by renaming the superclass field, but we + // can't do that because native fields have fixed JavaScript names. + // In practice this can't happen because we can't inherit from native + // classes. + assert (!fieldElement.hasFixedBackendName()); + + String libraryName = getName(fieldElement.getLibrary()); + String className = getName(fieldElement.getEnclosingClass()); + String instanceName = instanceFieldName(fieldElement); + return getMappedInstanceName('$libraryName\$$className\$$instanceName'); + } + + String setterName(Element element) { + // We dynamically create setters from the field-name. The setter name must + // therefore be derived from the instance field-name. + LibraryElement library = element.getLibrary(); + String name = getMappedInstanceName(privateName(library, element.name)); + return 'set\$$name'; + } + + String setterNameFromAccessorName(String name) { + // We dynamically create setters from the field-name. The setter name must + // therefore be derived from the instance field-name. + return 'set\$$name'; + } + + String publicGetterName(SourceString name) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + String fieldName = getMappedInstanceName(name.slowToString()); + return 'get\$$fieldName'; + } + + String getterNameFromAccessorName(String name) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + return 'get\$$name'; + } + + String getterName(Element element) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + LibraryElement library = element.getLibrary(); + String name = getMappedInstanceName(privateName(library, element.name)); + return 'get\$$name'; + } + + String getMappedGlobalName(String proposedName) { + var newName = globalNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedGlobalNames, + suggestedGlobalNames, ensureSafe: true); + globalNameMap[proposedName] = newName; + } + return newName; + } + + String getMappedInstanceName(String proposedName) { + var newName = instanceNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedInstanceNames, + suggestedInstanceNames, ensureSafe: true); + instanceNameMap[proposedName] = newName; + } + return newName; + } + + String getMappedOperatorName(String proposedName) { + var newName = operatorNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedInstanceNames, + suggestedInstanceNames, ensureSafe: false); + operatorNameMap[proposedName] = newName; + } + return newName; + } + + String getFreshName(String proposedName, + Set<String> usedNames, + Map<String, String> suggestedNames, + {bool ensureSafe: true}) { + var candidate; + if (ensureSafe) { + proposedName = safeName(proposedName); + } + assert(!jsReserved.contains(proposedName)); + if (!usedNames.contains(proposedName)) { + candidate = proposedName; + } else { + var counter = popularNameCounters[proposedName]; + var i = counter == null ? 0 : counter; + while (usedNames.contains("$proposedName$i")) { + i++; + } + popularNameCounters[proposedName] = i + 1; + candidate = "$proposedName$i"; + } + usedNames.add(candidate); + return candidate; + } + + SourceString getClosureVariableName(SourceString name, int id) { + return new SourceString("${name.slowToString()}_$id"); + } + + static const String LIBRARY_PREFIX = "lib"; + + /** + * Returns a preferred JS-id for the given top-level or static element. + * The returned id is guaranteed to be a valid JS-id. + */ + String _computeGuess(Element element) { + assert(!element.isInstanceMember()); + String name; + if (element.isGenerativeConstructor()) { + if (element.name == element.getEnclosingClass().name) { + // Keep the class name for the class and not the factory. + name = "${element.name.slowToString()}\$"; + } else { + name = element.name.slowToString(); + } + } else if (Elements.isStaticOrTopLevel(element)) { + if (element.isMember()) { + ClassElement enclosingClass = element.getEnclosingClass(); + name = "${enclosingClass.name.slowToString()}_" + "${element.name.slowToString()}"; + } else { + name = element.name.slowToString(); + } + } else if (element.isLibrary()) { + name = LIBRARY_PREFIX; + } else { + name = element.name.slowToString(); + } + return name; + } + + String getInterceptorName(Element element, Collection<ClassElement> classes) { + if (classes.contains(compiler.objectClass)) { + // If the object class is in the set of intercepted classes, we + // need to go through the generic getInterceptorMethod. + return getName(element); + } + // Use the unminified names here to construct the interceptor names. This + // helps ensure that they don't all suddenly change names due to a name + // clash in the minifier, which would affect the diff size. + StringBuffer buffer = new StringBuffer('${element.name.slowToString()}\$'); + for (ClassElement cls in classes) { + buffer.add(cls.name.slowToString()); + } + return getMappedGlobalName(buffer.toString()); + } + + String getBailoutName(Element element) { + String name = bailoutNames[element]; + if (name != null) return name; + bool global = !element.isInstanceMember(); + // Despite the name of the variable, this gets the minified name when we + // are minifying, but it doesn't really make much difference. The + // important thing is that it is a unique name. We add $bailout and, if we + // are minifying, we minify the minified name and '$bailout'. + String unminifiedName = '${getName(element)}\$bailout'; + if (global) { + name = getMappedGlobalName(unminifiedName); + } else { + // Make sure two bailout methods on the same inheritance chain do not have + // the same name to prevent a subclass bailout method being accidentally + // called from the superclass main method. Use the count of the number of + // elements with the same name on the superclass chain to disambiguate + // based on 'level'. + int level = 0; + ClassElement classElement = element.getEnclosingClass().superclass; + while (classElement != null) { + if (classElement.localLookup(element.name) != null) level++; + classElement = classElement.superclass; + } + name = unminifiedName; + if (level != 0) { + name = '$unminifiedName$level'; + } + name = getMappedInstanceName(name); + } + bailoutNames[element] = name; + return name; + } + + /** + * Returns a preferred JS-id for the given element. The returned id is + * guaranteed to be a valid JS-id. Globals and static fields are furthermore + * guaranteed to be unique. + * + * For accessing statics consider calling + * [isolateAccess]/[isolateBailoutAccess] or [isolatePropertyAccess] instead. + */ + String getName(Element element) { + if (element.isInstanceMember()) { + if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY + || element.kind == ElementKind.FUNCTION) { + return instanceMethodName(element); + } else if (element.kind == ElementKind.GETTER) { + return getterName(element); + } else if (element.kind == ElementKind.SETTER) { + return setterName(element); + } else if (element.kind == ElementKind.FIELD) { + return instanceFieldName(element); + } else { + compiler.internalError('getName for bad kind: ${element.kind}', + node: element.parseNode(compiler)); + } + } else { + // Use declaration element to ensure invariant on [globals]. + element = element.declaration; + // Dealing with a top-level or static element. + String cached = globals[element]; + if (cached != null) return cached; + + String guess = _computeGuess(element); + ElementKind kind = element.kind; + if (kind == ElementKind.VARIABLE || + kind == ElementKind.PARAMETER) { + // The name is not guaranteed to be unique. + return safeName(guess); + } + if (kind == ElementKind.GENERATIVE_CONSTRUCTOR || + kind == ElementKind.FUNCTION || + kind == ElementKind.CLASS || + kind == ElementKind.FIELD || + kind == ElementKind.GETTER || + kind == ElementKind.SETTER || + kind == ElementKind.TYPEDEF || + kind == ElementKind.LIBRARY || + kind == ElementKind.MALFORMED_TYPE) { + bool fixedName = false; + if (kind == ElementKind.CLASS) { + ClassElement classElement = element; + } + if (Elements.isInstanceField(element)) { + fixedName = element.hasFixedBackendName(); + } + String result = fixedName + ? guess + : getFreshName(guess, usedGlobalNames, suggestedGlobalNames, + ensureSafe: true); + globals[element] = result; + return result; + } + compiler.internalError('getName for unknown kind: ${element.kind}', + node: element.parseNode(compiler)); + } + } + + String getLazyInitializerName(Element element) { + assert(Elements.isStaticOrTopLevelField(element)); + return getMappedGlobalName("get\$${getName(element)}"); + } + + String isolatePropertiesAccess(Element element) { + return "$isolateName.$isolatePropertiesName.${getName(element)}"; + } + + String isolateAccess(Element element) { + return "$CURRENT_ISOLATE.${getName(element)}"; + } + + String isolateBailoutAccess(Element element) { + String newName = getMappedGlobalName('${getName(element)}\$bailout'); + return '$CURRENT_ISOLATE.$newName'; + } + + String isolateLazyInitializerAccess(Element element) { + return "$CURRENT_ISOLATE.${getLazyInitializerName(element)}"; + } + + String operatorIsPrefix() => r'$is'; + + String operatorIs(Element element) { + // TODO(erikcorry): Reduce from $isx to ix when we are minifying. + return '${operatorIsPrefix()}${getName(element)}'; + } + + /* + * Returns a name that does not clash with reserved JS keywords, + * and also ensures it won't clash with other identifiers. + */ + String _safeName(String name, Set<String> reserved) { + if (reserved.contains(name) || name.startsWith(r'$')) { + name = '\$$name'; + } + assert(!reserved.contains(name)); + return name; + } + + String safeName(String name) => _safeName(name, jsReserved); + String safeVariableName(String name) => _safeName(name, jsVariableReserved); + + String oneShotInterceptorName(Selector selector) { + // TODO(ngeoffray): What to do about typed selectors? We could + // filter them out, or keep them and hope the generated one shot + // interceptor takes advantage of the type. + String cached = oneShotInterceptorNames[selector]; + if (cached != null) return cached; + SourceString name = operatorNameToIdentifier(selector.name); + String result = getFreshName(name.slowToString(), usedGlobalNames, + suggestedGlobalNames); + oneShotInterceptorNames[selector] = result; + return result; + } + + SourceString operatorNameToIdentifier(SourceString name) { + if (name == null) return null; + String value = name.stringValue; + if (value == null) { + return name; + } else if (value == '==') { + return const SourceString(r'$eq'); + } else if (value == '~') { + return const SourceString(r'$not'); + } else if (value == '[]') { + return const SourceString(r'$index'); + } else if (value == '[]=') { + return const SourceString(r'$indexSet'); + } else if (value == '*') { + return const SourceString(r'$mul'); + } else if (value == '/') { + return const SourceString(r'$div'); + } else if (value == '%') { + return const SourceString(r'$mod'); + } else if (value == '~/') { + return const SourceString(r'$tdiv'); + } else if (value == '+') { + return const SourceString(r'$add'); + } else if (value == '<<') { + return const SourceString(r'$shl'); + } else if (value == '>>') { + return const SourceString(r'$shr'); + } else if (value == '>=') { + return const SourceString(r'$ge'); + } else if (value == '>') { + return const SourceString(r'$gt'); + } else if (value == '<=') { + return const SourceString(r'$le'); + } else if (value == '<') { + return const SourceString(r'$lt'); + } else if (value == '&') { + return const SourceString(r'$and'); + } else if (value == '^') { + return const SourceString(r'$xor'); + } else if (value == '|') { + return const SourceString(r'$or'); + } else if (value == '-') { + return const SourceString(r'$sub'); + } else if (value == 'unary-') { + return const SourceString(r'$negate'); + } else { + return name; + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart new file mode 100644 index 0000000..9400f5f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart
@@ -0,0 +1,546 @@ +// 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. + +part of js_backend; + +class NativeEmitter { + + CodeEmitterTask emitter; + CodeBuffer nativeBuffer; + + // Classes that participate in dynamic dispatch. These are the + // classes that contain used members. + Set<ClassElement> classesWithDynamicDispatch; + + // Native classes found in the application. + Set<ClassElement> nativeClasses; + + // Caches the native subtypes of a native class. + Map<ClassElement, List<ClassElement>> subtypes; + + // Caches the direct native subtypes of a native class. + Map<ClassElement, List<ClassElement>> directSubtypes; + + // Caches the native methods that are overridden by a native class. + // Note that the method that overrides does not have to be native: + // it's the overridden method that must make sure it will dispatch + // to its subclass if it sees an instance whose class is a subclass. + Set<FunctionElement> overriddenMethods; + + // Caches the methods that have a native body. + Set<FunctionElement> nativeMethods; + + // Do we need the native emitter to take care of handling + // noSuchMethod for us? This flag is set to true in the emitter if + // it finds any native class that needs noSuchMethod handling. + bool handleNoSuchMethod = false; + + NativeEmitter(this.emitter) + : classesWithDynamicDispatch = new Set<ClassElement>(), + nativeClasses = new Set<ClassElement>(), + subtypes = new Map<ClassElement, List<ClassElement>>(), + directSubtypes = new Map<ClassElement, List<ClassElement>>(), + overriddenMethods = new Set<FunctionElement>(), + nativeMethods = new Set<FunctionElement>(), + nativeBuffer = new CodeBuffer(); + + Compiler get compiler => emitter.compiler; + JavaScriptBackend get backend => compiler.backend; + + String get _ => emitter._; + String get n => emitter.n; + String get N => emitter.N; + + String get dynamicName { + Element element = compiler.findHelper( + const SourceString('dynamicFunction')); + return backend.namer.isolateAccess(element); + } + + String get dynamicSetMetadataName { + Element element = compiler.findHelper( + const SourceString('dynamicSetMetadata')); + return backend.namer.isolateAccess(element); + } + + String get typeNameOfName { + Element element = compiler.findHelper( + const SourceString('getTypeNameOf')); + return backend.namer.isolateAccess(element); + } + + String get defPropName { + Element element = compiler.findHelper( + const SourceString('defineProperty')); + return backend.namer.isolateAccess(element); + } + + String get toStringHelperName { + Element element = compiler.findHelper( + const SourceString('toStringForNativeObject')); + return backend.namer.isolateAccess(element); + } + + String get hashCodeHelperName { + Element element = compiler.findHelper( + const SourceString('hashCodeForNativeObject')); + return backend.namer.isolateAccess(element); + } + + String get defineNativeClassName + => '${backend.namer.CURRENT_ISOLATE}.\$defineNativeClass'; + + String get defineNativeClassFunction { + return """ +function(cls, desc) { + var fields = desc['']; + var fields_array = fields ? fields.split(',') : []; + for (var i = 0; i < fields_array.length; i++) { + ${emitter.currentGenerateAccessorName}(fields_array[i], desc); + } + var hasOwnProperty = Object.prototype.hasOwnProperty; + for (var method in desc) { + if (method) { + if (hasOwnProperty.call(desc, method)) { + $dynamicName(method)[cls] = desc[method]; + } + } + } +}"""; + } + + bool isNativeGlobal(String quotedName) { + return identical(quotedName[1], '@'); + } + + String toNativeTag(ClassElement cls) { + String quotedName = cls.nativeTagInfo.slowToString(); + if (isNativeGlobal(quotedName)) { + // Global object, just be like the other types for now. + return quotedName.substring(3, quotedName.length - 1); + } else { + return quotedName.substring(2, quotedName.length - 1); + } + } + + void generateNativeClass(ClassElement classElement) { + assert(!classElement.hasBackendMembers); + nativeClasses.add(classElement); + + ClassBuilder builder = new ClassBuilder(); + emitter.emitClassFields(classElement, builder, classIsNative: true); + emitter.emitClassGettersSetters(classElement, builder); + emitter.emitInstanceMembers(classElement, builder); + + // An empty native class may be omitted since the superclass methods can be + // located via the dispatch metadata. + if (builder.properties.isEmpty) return; + + String nativeTag = toNativeTag(classElement); + js.Expression definition = + js.call(js.use(defineNativeClassName), + [js.string(nativeTag), builder.toObjectInitializer()]); + + nativeBuffer.add(js.prettyPrint(definition, compiler)); + nativeBuffer.add('$N$n'); + + classesWithDynamicDispatch.add(classElement); + } + + List<ClassElement> getDirectSubclasses(ClassElement cls) { + List<ClassElement> result = directSubtypes[cls]; + return result == null ? const<ClassElement>[] : result; + } + + void potentiallyConvertDartClosuresToJs(List<js.Statement> statements, + FunctionElement member, + List<js.Parameter> stubParameters) { + FunctionSignature parameters = member.computeSignature(compiler); + Element converter = + compiler.findHelper(const SourceString('convertDartClosureToJS')); + String closureConverter = backend.namer.isolateAccess(converter); + Set<String> stubParameterNames = new Set<String>.from( + stubParameters.map((param) => param.name)); + parameters.forEachParameter((Element parameter) { + String name = parameter.name.slowToString(); + // If [name] is not in [stubParameters], then the parameter is an optional + // parameter that was not provided for this stub. + for (js.Parameter stubParameter in stubParameters) { + if (stubParameter.name == name) { + DartType type = parameter.computeType(compiler).unalias(compiler); + if (type is FunctionType) { + // The parameter type is a function type either directly or through + // typedef(s). + int arity = type.computeArity(); + + statements.add( + new js.ExpressionStatement( + js.assign( + js.use(name), + js.use(closureConverter).callWith( + [js.use(name), new js.LiteralNumber('$arity')])))); + break; + } + } + } + }); + } + + List<js.Statement> generateParameterStubStatements( + Element member, + String invocationName, + List<js.Parameter> stubParameters, + List<js.Expression> argumentsBuffer, + int indexOfLastOptionalArgumentInParameters) { + // The target JS function may check arguments.length so we need to + // make sure not to pass any unspecified optional arguments to it. + // For example, for the following Dart method: + // foo([x, y, z]); + // The call: + // foo(y: 1) + // must be turned into a JS call to: + // foo(null, y). + + ClassElement classElement = member.enclosingElement; + String nativeTagInfo = classElement.nativeTagInfo.slowToString(); + + List<js.Statement> statements = <js.Statement>[]; + potentiallyConvertDartClosuresToJs(statements, member, stubParameters); + + String target; + List<js.Expression> arguments; + + if (!nativeMethods.contains(member)) { + // When calling a method that has a native body, we call it with our + // calling conventions. + target = backend.namer.getName(member); + arguments = argumentsBuffer; + } else { + // When calling a JS method, we call it with the native name, and only the + // arguments up until the last one provided. + target = member.fixedBackendName(); + arguments = argumentsBuffer.getRange( + 0, indexOfLastOptionalArgumentInParameters + 1); + } + statements.add( + new js.Return( + new js.VariableUse('this').dot(target).callWith(arguments))); + + if (!overriddenMethods.contains(member)) { + // Call the method directly. + return statements; + } else { + return <js.Statement>[ + generateMethodBodyWithPrototypeCheck( + invocationName, new js.Block(statements), stubParameters)]; + } + } + + // If a method is overridden, we must check if the prototype of 'this' has the + // method available. Otherwise, we may end up calling the method from the + // super class. If the method is not available, we make a direct call to + // Object.prototype.$methodName. This method will patch the prototype of + // 'this' to the real method. + js.Statement generateMethodBodyWithPrototypeCheck( + String methodName, + js.Statement body, + List<js.Parameter> parameters) { + return js.if_( + js.use('Object').dot('getPrototypeOf') + .callWith([js.use('this')]) + .dot('hasOwnProperty').callWith([js.string(methodName)]), + body, + js.return_( + js.use('Object').dot('prototype').dot(methodName).dot('call') + .callWith( + <js.Expression>[js.use('this')]..addAll( + parameters.map((param) => js.use(param.name)))))); + } + + js.Block generateMethodBodyWithPrototypeCheckForElement( + FunctionElement element, + js.Block body, + List<js.Parameter> parameters) { + ElementKind kind = element.kind; + if (kind != ElementKind.FUNCTION && + kind != ElementKind.GETTER && + kind != ElementKind.SETTER) { + compiler.internalError("unexpected kind: '$kind'", element: element); + } + + String methodName = backend.namer.getName(element); + return new js.Block( + [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]); + } + + + void emitDynamicDispatchMetadata() { + if (classesWithDynamicDispatch.isEmpty) return; + int length = classesWithDynamicDispatch.length; + if (!compiler.enableMinification) { + nativeBuffer.add('// $length dynamic classes.\n'); + } + + // Build a pre-order traversal over all the classes and their subclasses. + Set<ClassElement> seen = new Set<ClassElement>(); + List<ClassElement> classes = <ClassElement>[]; + void visit(ClassElement cls) { + if (seen.contains(cls)) return; + seen.add(cls); + getDirectSubclasses(cls).forEach(visit); + classes.add(cls); + } + classesWithDynamicDispatch.forEach(visit); + + List<ClassElement> preorderDispatchClasses = classes.where( + (cls) => !getDirectSubclasses(cls).isEmpty && + classesWithDynamicDispatch.contains(cls)).toList(); + + if (!compiler.enableMinification) { + nativeBuffer.add('// ${classes.length} classes\n'); + } + Iterable<ClassElement> classesThatHaveSubclasses = classes.where( + (ClassElement t) => !getDirectSubclasses(t).isEmpty); + if (!compiler.enableMinification) { + nativeBuffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); + } + + // Generate code that builds the map from cls tags used in dynamic dispatch + // to the set of cls tags of classes that extend (TODO: or implement) those + // classes. The set is represented as a string of tags joined with '|'. + // This is easily split into an array of tags, or converted into a regexp. + // + // To reduce the size of the sets, subsets are CSE-ed out into variables. + // The sets could be much smaller if we could make assumptions about the + // cls tags of other classes (which are constructor names or part of the + // result of Object.protocls.toString). For example, if objects that are + // Dart objects could be easily excluded, then we might be able to simplify + // the test, replacing dozens of HTMLxxxElement classes with the regexp + // /HTML.*Element/. + + // Temporary variables for common substrings. + List<String> varNames = <String>[]; + // Values of temporary variables. + Map<String, js.Expression> varDefns = new Map<String, js.Expression>(); + + // Expression to compute tags string for a class. The expression will + // initially be a string or expression building a string, but may be + // replaced with a variable reference to the common substring. + Map<ClassElement, js.Expression> tagDefns = + new Map<ClassElement, js.Expression>(); + + js.Expression makeExpression(ClassElement classElement) { + // Expression fragments for this set of cls keys. + List<js.Expression> expressions = <js.Expression>[]; + // TODO: Remove if cls is abstract. + List<String> subtags = [toNativeTag(classElement)]; + void walk(ClassElement cls) { + for (final ClassElement subclass in getDirectSubclasses(cls)) { + ClassElement tag = subclass; + js.Expression existing = tagDefns[tag]; + if (existing == null) { + // [subclass] is still within the subtree between dispatch classes. + subtags.add(toNativeTag(tag)); + walk(subclass); + } else { + // [subclass] is one of the preorderDispatchClasses, so CSE this + // reference with the previous reference. + js.VariableUse use = existing.asVariableUse(); + if (use != null && varDefns.containsKey(use.name)) { + // We end up here if the subclasses have a DAG structure. We + // don't have DAGs yet, but if the dispatch is used for mixins + // that will be a possibility. + // Re-use the previously created temporary variable. + expressions.add(new js.VariableUse(use.name)); + } else { + String varName = 'v${varNames.length}_${tag.name.slowToString()}'; + varNames.add(varName); + varDefns[varName] = existing; + tagDefns[tag] = new js.VariableUse(varName); + expressions.add(new js.VariableUse(varName)); + } + } + } + } + walk(classElement); + + if (!subtags.isEmpty) { + expressions.add(js.string(Strings.join(subtags, '|'))); + } + js.Expression expression; + if (expressions.length == 1) { + expression = expressions[0]; + } else { + js.Expression array = new js.ArrayInitializer.from(expressions); + expression = js.call(array.dot('join'), [js.string('|')]); + } + return expression; + } + + for (final ClassElement classElement in preorderDispatchClasses) { + tagDefns[classElement] = makeExpression(classElement); + } + + // Write out a thunk that builds the metadata. + if (!tagDefns.isEmpty) { + List<js.Statement> statements = <js.Statement>[]; + + List<js.VariableInitialization> initializations = + <js.VariableInitialization>[]; + for (final String varName in varNames) { + initializations.add( + new js.VariableInitialization( + new js.VariableDeclaration(varName), + varDefns[varName])); + } + if (!initializations.isEmpty) { + statements.add( + new js.ExpressionStatement( + new js.VariableDeclarationList(initializations))); + } + + // [table] is a list of lists, each inner list of the form: + // [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag] + // E.g. + // [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...] + js.Expression table = + new js.ArrayInitializer.from( + preorderDispatchClasses.map((cls) => + new js.ArrayInitializer.from([ + js.string(toNativeTag(cls)), + tagDefns[cls]]))); + + // $.dynamicSetMetadata(table); + statements.add( + new js.ExpressionStatement( + new js.Call( + new js.VariableUse(dynamicSetMetadataName), + [table]))); + + // (function(){statements})(); + if (emitter.compiler.enableMinification) nativeBuffer.add(';'); + nativeBuffer.add( + js.prettyPrint( + new js.ExpressionStatement( + new js.Call(new js.Fun([], new js.Block(statements)), [])), + compiler)); + } + } + + bool isSupertypeOfNativeClass(Element element) { + if (element.isTypeVariable()) { + compiler.cancel("Is check for type variable", element: element); + return false; + } + if (element.computeType(compiler).unalias(compiler) is FunctionType) { + // The element type is a function type either directly or through + // typedef(s). + return false; + } + + if (!element.isClass()) { + compiler.cancel("Is check does not handle element", element: element); + return false; + } + + return subtypes[element] != null; + } + + bool requiresNativeIsCheck(Element element) { + if (!element.isClass()) return false; + ClassElement cls = element; + if (cls.isNative()) return true; + return isSupertypeOfNativeClass(element); + } + + void assembleCode(CodeBuffer targetBuffer) { + if (nativeClasses.isEmpty) return; + emitDynamicDispatchMetadata(); + targetBuffer.add('$defineNativeClassName = ' + '$defineNativeClassFunction$N$n'); + + List<js.Property> objectProperties = <js.Property>[]; + + void addProperty(String name, js.Expression value) { + objectProperties.add(new js.Property(js.string(name), value)); + } + + // Because of native classes, we have to generate some is checks + // by calling a method, instead of accessing a property. So we + // attach to the JS Object prototype these methods that return + // false, and will be overridden by subclasses when they have to + // return true. + void emitIsChecks() { + for (ClassElement element in + Elements.sortedByPosition(emitter.checkedClasses)) { + if (!requiresNativeIsCheck(element)) continue; + if (element.isObject(compiler)) continue; + String name = backend.namer.operatorIs(element); + addProperty(name, + js.fun([], js.block1(js.return_(new js.LiteralBool(false))))); + } + } + emitIsChecks(); + + js.Expression makeCallOnThis(String functionName) => + js.fun([], + js.block1( + js.return_( + js.call(js.use(functionName), [js.use('this')])))); + + // In order to have the toString method on every native class, + // we must patch the JS Object prototype with a helper method. + String toStringName = backend.namer.publicInstanceMethodNameByArity( + const SourceString('toString'), 0); + addProperty(toStringName, makeCallOnThis(toStringHelperName)); + + // Same as above, but for hashCode. + String hashCodeName = + backend.namer.publicGetterName(const SourceString('hashCode')); + addProperty(hashCodeName, makeCallOnThis(hashCodeHelperName)); + + // Same as above, but for operator==. + String equalsName = backend.namer.publicInstanceMethodNameByArity( + const SourceString('=='), 1); + addProperty(equalsName, js.fun(['a'], js.block1( + js.return_(js.strictEquals(new js.This(), js.use('a')))))); + + // If the native emitter has been asked to take care of the + // noSuchMethod handlers, we do that now. + if (handleNoSuchMethod) { + emitter.emitNoSuchMethodHandlers(addProperty); + } + + // If we have any properties to add to Object.prototype, we run + // through them and add them using defineProperty. + if (!objectProperties.isEmpty) { + js.Expression init = + js.call( + js.fun(['table'], + js.block1( + new js.ForIn( + new js.VariableDeclarationList( + [new js.VariableInitialization( + new js.VariableDeclaration('key'), + null)]), + js.use('table'), + new js.ExpressionStatement( + js.call( + js.use(defPropName), + [js.use('Object').dot('prototype'), + js.use('key'), + new js.PropertyAccess(js.use('table'), + js.use('key'))]))))), + [new js.ObjectInitializer(objectProperties)]); + + if (emitter.compiler.enableMinification) targetBuffer.add(';'); + targetBuffer.add(js.prettyPrint( + new js.ExpressionStatement(init), compiler)); + targetBuffer.add('\n'); + } + + targetBuffer.add(nativeBuffer); + targetBuffer.add('\n'); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart b/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart new file mode 100644 index 0000000..a3a0339 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart
@@ -0,0 +1,173 @@ +// 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. + +part of js_backend; + +/// For each class, stores the possible class subtype tests that could succeed. +abstract class TypeChecks { + /// Get the set of checks required for class [element]. + Iterable<ClassElement> operator[](ClassElement element); + // Get the iterator for all classes that need type checks. + Iterator<ClassElement> get iterator; +} + +class RuntimeTypeInformation { + final Compiler compiler; + + RuntimeTypeInformation(this.compiler); + + /// Contains the classes of all arguments that have been used in + /// instantiations and checks. + Set<ClassElement> allArguments; + + bool isJsNative(Element element) { + return (element == compiler.intClass || + element == compiler.boolClass || + element == compiler.numClass || + element == compiler.doubleClass || + element == compiler.stringClass || + element == compiler.listClass || + element == compiler.objectClass || + element == compiler.dynamicClass); + } + + TypeChecks computeRequiredChecks() { + Set<ClassElement> instantiatedArguments = new Set<ClassElement>(); + for (DartType type in compiler.codegenWorld.instantiatedTypes) { + addAllInterfaceTypeArguments(type, instantiatedArguments); + } + + Set<ClassElement> checkedArguments = new Set<ClassElement>(); + for (DartType type in compiler.enqueuer.codegen.universe.isChecks) { + addAllInterfaceTypeArguments(type, checkedArguments); + } + + allArguments = new Set<ClassElement>.from(instantiatedArguments) + ..addAll(checkedArguments); + + TypeCheckMapping requiredChecks = new TypeCheckMapping(); + for (ClassElement element in instantiatedArguments) { + if (element == compiler.dynamicClass) continue; + if (checkedArguments.contains(element)) { + requiredChecks.add(element, element); + } + // Find all supertypes of [element] in [checkedArguments] and add checks. + for (DartType supertype in element.allSupertypes) { + ClassElement superelement = supertype.element; + if (checkedArguments.contains(superelement)) { + requiredChecks.add(element, superelement); + } + } + } + + return requiredChecks; + } + + void addAllInterfaceTypeArguments(DartType type, Set<ClassElement> classes) { + if (type is !InterfaceType) return; + for (DartType argument in type.typeArguments) { + forEachInterfaceType(argument, (InterfaceType t) { + ClassElement cls = t.element; + if (cls != compiler.dynamicClass && cls != compiler.objectClass) { + classes.add(cls); + } + }); + } + } + + void forEachInterfaceType(DartType type, f(InterfaceType type)) { + if (type.kind == TypeKind.INTERFACE) { + f(type); + InterfaceType interface = type; + for (DartType argument in interface.typeArguments) { + forEachInterfaceType(argument, f); + } + } + } + + /// Return the unique name for the element as an unquoted string. + String getNameAsString(Element element) { + JavaScriptBackend backend = compiler.backend; + return backend.namer.getName(element); + } + + /// Return the unique JS name for the element, which is a quoted string for + /// native classes and the isolate acccess to the constructor for classes. + String getJsName(Element element) { + JavaScriptBackend backend = compiler.backend; + Namer namer = backend.namer; + return namer.isolateAccess(element); + } + + String getRawTypeRepresentation(DartType type) { + String name = getNameAsString(type.element); + if (!type.element.isClass()) return name; + InterfaceType interface = type; + Link<DartType> variables = interface.element.typeVariables; + if (variables.isEmpty) return name; + List<String> arguments = []; + variables.forEach((_) => arguments.add('dynamic')); + return '$name<${Strings.join(arguments, ', ')}>'; + } + + String getTypeRepresentation(DartType type, void onVariable(variable)) { + StringBuffer builder = new StringBuffer(); + void build(DartType part) { + if (part is TypeVariableType) { + builder.add('#'); + onVariable(part); + } else { + bool hasArguments = part is InterfaceType && !part.isRaw; + Element element = part.element; + if (hasArguments) { + builder.add('['); + } + builder.add(getJsName(element)); + if (!hasArguments) return; + InterfaceType interface = part; + for (DartType argument in interface.typeArguments) { + builder.add(', '); + build(argument); + } + builder.add(']'); + } + } + build(type); + return builder.toString(); + } + + static bool hasTypeArguments(DartType type) { + if (type is InterfaceType) { + InterfaceType interfaceType = type; + return !interfaceType.isRaw; + } + return false; + } + + static int getTypeVariableIndex(TypeVariableType variable) { + ClassElement classElement = variable.element.getEnclosingClass(); + Link<DartType> variables = classElement.typeVariables; + for (int index = 0; !variables.isEmpty; + index++, variables = variables.tail) { + if (variables.head == variable) return index; + } + } +} + +class TypeCheckMapping implements TypeChecks { + final Map<ClassElement, Set<ClassElement>> map = + new Map<ClassElement, Set<ClassElement>>(); + + Iterable<ClassElement> operator[](ClassElement element) { + Set<ClassElement> result = map[element]; + return result != null ? result : const <ClassElement>[]; + } + + void add(ClassElement cls, ClassElement check) { + map.putIfAbsent(cls, () => new Set<ClassElement>()); + map[cls].add(check); + } + + Iterator<ClassElement> get iterator => map.keys.iterator; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart new file mode 100644 index 0000000..0fccce2 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart
@@ -0,0 +1,21 @@ +// 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. + +// Patch file for the dart:async library. + +import 'dart:_isolate_helper' show TimerImpl; + +patch class Timer { + patch factory Timer(int milliseconds, void callback(Timer timer)) { + return new TimerImpl(milliseconds, callback); + } + + /** + * Creates a new repeating timer. The [callback] is invoked every + * [milliseconds] millisecond until cancelled. + */ + patch factory Timer.repeating(int milliseconds, void callback(Timer timer)) { + return new TimerImpl.repeating(milliseconds, callback); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart new file mode 100644 index 0000000..75446c7 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart
@@ -0,0 +1,75 @@ +// 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. + +part of _js_helper; + +// This class has no constructor. This is on purpose since the instantiation +// is shortcut by the compiler. +class ConstantMap<V> implements Map<String, V> { + final int length; + // A constant map is backed by a JavaScript object. + final _jsObject; + final List<String> _keys; + + bool containsValue(V needle) { + return values.any((V value) => value == needle); + } + + bool containsKey(String key) { + if (key == '__proto__') return false; + return jsHasOwnProperty(_jsObject, key); + } + + V operator [](String key) { + if (!containsKey(key)) return null; + return jsPropertyAccess(_jsObject, key); + } + + void forEach(void f(String key, V value)) { + _keys.forEach((String key) => f(key, this[key])); + } + + Iterable<String> get keys { + return new _ConstantMapKeyIterable(this); + } + + Iterable<V> get values { + return _keys.map((String key) => this[key]); + } + + bool get isEmpty => length == 0; + + String toString() => Maps.mapToString(this); + + _throwUnmodifiable() { + throw new UnsupportedError("Cannot modify unmodifiable Map"); + } + void operator []=(String key, V val) => _throwUnmodifiable(); + V putIfAbsent(String key, V ifAbsent()) => _throwUnmodifiable(); + V remove(String key) => _throwUnmodifiable(); + void clear() => _throwUnmodifiable(); +} + +// This class has no constructor. This is on purpose since the instantiation +// is shortcut by the compiler. +class ConstantProtoMap<V> extends ConstantMap<V> { + final V _protoValue; + + bool containsKey(String key) { + if (key == '__proto__') return true; + return super.containsKey(key); + } + + V operator [](String key) { + if (key == '__proto__') return _protoValue; + return super[key]; + } +} + +class _ConstantMapKeyIterable extends Iterable<String> { + ConstantMap _map; + _ConstantMapKeyIterable(this._map); + + Iterator<String> get iterator => _map._keys.iterator; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart new file mode 100644 index 0000000..09e31da --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart
@@ -0,0 +1,250 @@ +// 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. + +// Patch file for dart:core classes. + +import 'dart:_interceptors'; +import 'dart:_js_helper' show checkNull, + getRuntimeTypeString, + isJsArray, + JSSyntaxRegExp, + Primitives, + TypeImpl, + stringJoinUnchecked, + JsStringBuffer; + +patch void print(var object) { + Primitives.printString(object.toString()); +} + +// Patch for Object implementation. +patch class Object { + patch int get hashCode => Primitives.objectHashCode(this); + + patch String toString() => Primitives.objectToString(this); + + patch dynamic noSuchMethod(InvocationMirror invocation) { + throw new NoSuchMethodError(this, + invocation.memberName, + invocation.positionalArguments, + invocation.namedArguments); + } + + patch Type get runtimeType { + String type = getRuntimeTypeString(this); + return new TypeImpl(type); + } +} + +// Patch for Function implementation. +patch class Function { + patch static apply(Function function, + List positionalArguments, + [Map<String,dynamic> namedArguments]) { + return Primitives.applyFunction( + function, positionalArguments, namedArguments); + } +} + +// Patch for Expando implementation. +patch class Expando<T> { + patch Expando([String name]) : this.name = name; + + patch T operator[](Object object) { + var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME); + return (values == null) ? null : Primitives.getProperty(values, _getKey()); + } + + patch void operator[]=(Object object, T value) { + var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME); + if (values == null) { + values = new Object(); + Primitives.setProperty(object, _EXPANDO_PROPERTY_NAME, values); + } + Primitives.setProperty(values, _getKey(), value); + } + + String _getKey() { + String key = Primitives.getProperty(this, _KEY_PROPERTY_NAME); + if (key == null) { + key = "expando\$key\$${_keyCount++}"; + Primitives.setProperty(this, _KEY_PROPERTY_NAME, key); + } + return key; + } + + static const String _KEY_PROPERTY_NAME = 'expando\$key'; + static const String _EXPANDO_PROPERTY_NAME = 'expando\$values'; + static int _keyCount = 0; +} + +patch class int { + patch static int parse(String source, + { int radix, + int onError(String source) }) { + return Primitives.parseInt(source, radix, onError); + } +} + +patch class double { + patch static double parse(String source, [int handleError(String source)]) { + return Primitives.parseDouble(source, handleError); + } +} + +patch class Error { + patch static String _objectToString(Object object) { + return Primitives.objectToString(object); + } +} + + +// Patch for DateTime implementation. +patch class DateTime { + patch DateTime._internal(int year, + int month, + int day, + int hour, + int minute, + int second, + int millisecond, + bool isUtc) + : this.isUtc = checkNull(isUtc), + millisecondsSinceEpoch = Primitives.valueFromDecomposedDate( + year, month, day, hour, minute, second, millisecond, isUtc) { + Primitives.lazyAsJsDate(this); + } + + patch DateTime._now() + : isUtc = false, + millisecondsSinceEpoch = Primitives.dateNow() { + Primitives.lazyAsJsDate(this); + } + + patch static int _brokenDownDateToMillisecondsSinceEpoch( + int year, int month, int day, int hour, int minute, int second, + int millisecond, bool isUtc) { + return Primitives.valueFromDecomposedDate( + year, month, day, hour, minute, second, millisecond, isUtc); + } + + patch String get timeZoneName { + if (isUtc) return "UTC"; + return Primitives.getTimeZoneName(this); + } + + patch Duration get timeZoneOffset { + if (isUtc) return new Duration(); + return new Duration(minutes: Primitives.getTimeZoneOffsetInMinutes(this)); + } + + patch int get year => Primitives.getYear(this); + + patch int get month => Primitives.getMonth(this); + + patch int get day => Primitives.getDay(this); + + patch int get hour => Primitives.getHours(this); + + patch int get minute => Primitives.getMinutes(this); + + patch int get second => Primitives.getSeconds(this); + + patch int get millisecond => Primitives.getMilliseconds(this); + + patch int get weekday => Primitives.getWeekday(this); +} + + +// Patch for Stopwatch implementation. +patch class Stopwatch { + patch static int _frequency() => 1000000; + patch static int _now() => Primitives.numMicroseconds(); +} + + +// Patch for List implementation. +patch class List<E> { + patch factory List([int length = 0]) { + // Explicit type test is necessary to protect Primitives.newGrowableList in + // unchecked mode. + if ((length is !int) || (length < 0)) { + throw new ArgumentError("Length must be a positive integer: $length."); + } + return Primitives.newGrowableList(length); + } + + patch factory List.fixedLength(int length, {E fill: null}) { + // Explicit type test is necessary to protect Primitives.newFixedList in + // unchecked mode. + if ((length is !int) || (length < 0)) { + throw new ArgumentError("Length must be a positive integer: $length."); + } + List result = Primitives.newFixedList(length); + if (length != 0 && fill != null) { + for (int i = 0; i < result.length; i++) { + result[i] = fill; + } + } + return result; + } +} + + +patch class String { + patch factory String.fromCharCodes(List<int> charCodes) { + if (!isJsArray(charCodes)) { + if (charCodes is !List) throw new ArgumentError(charCodes); + charCodes = new List.from(charCodes); + } + return Primitives.stringFromCharCodes(charCodes); + } +} + +// Patch for String implementation. +patch class Strings { + patch static String join(Iterable<String> strings, String separator) { + checkNull(strings); + if (separator is !String) throw new ArgumentError(separator); + return stringJoinUnchecked(_toJsStringArray(strings), separator); + } + + patch static String concatAll(Iterable<String> strings) { + return stringJoinUnchecked(_toJsStringArray(strings), ""); + } + + static List _toJsStringArray(Iterable<String> strings) { + checkNull(strings); + var array; + if (!isJsArray(strings)) { + strings = new List.from(strings); + } + final length = strings.length; + for (int i = 0; i < length; i++) { + final string = strings[i]; + if (string is !String) throw new ArgumentError(string); + } + return strings; + } +} + +patch class RegExp { + patch factory RegExp(String pattern, + {bool multiLine: false, + bool caseSensitive: true}) + => new JSSyntaxRegExp(pattern, + multiLine: multiLine, + caseSensitive: caseSensitive); +} + +// Patch for 'identical' function. +patch bool identical(Object a, Object b) { + return Primitives.identicalImplementation(a, b); +} + +patch class StringBuffer { + patch factory StringBuffer([Object content = ""]) { + return new JsStringBuffer(content); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart new file mode 100644 index 0000000..2ad4a1e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart
@@ -0,0 +1,138 @@ +// 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. + +library _foreign_helper; + +/** + * Emits a JavaScript code fragment parameterized by arguments. + * + * Hash characters `#` in the [codeTemplate] are replaced in left-to-right order + * with expressions that contain the values of, or evaluate to, the arguments. + * The number of hash marks must match the number or arguments. Although + * declared with arguments [arg0] through [arg2], the form actually has no limit + * on the number of arguments. + * + * The [typeDescription] argument is interpreted as a description of the + * behavior of the JavaScript code. Currently it describes the types that may + * be returned by the expression, with the additional behavior that the returned + * values may be fresh instances of the types. The type information must be + * correct as it is trusted by the compiler in optimizations, and it must be + * precise as possible since it is used for native live type analysis to + * tree-shake large parts of the DOM libraries. If poorly written, the + * [typeDescription] will cause unnecessarily bloated programs. (You can check + * for this by compiling with `--verbose`; there is an info message describing + * the number of native (DOM) types that can be removed, which usually should be + * greater than zero.) + * + * The [typeDescription] is a [String] which contains a union of types separated + * by vertical bar `|` symbols, e.g. `"num|String"` describes the union of + * numbers and Strings. There is no type in Dart that is this precise. The + * Dart alternative would be `Object` or `dynamic`, but these types imply that + * the JS-code might also be creating instances of all the DOM types. The + * [typeDescription] has several extensions to help describe the behavior more + * accurately. In addition to the union type already described: + * + * + `=List` is the JavaScript array type. This is more precise than `List`, + * which includes about fifty DOM types that also implement the List + * interface. + * + * + `=Object` is a plain JavaScript object. Some DOM methods return instances + * that have no corresponing Dart type (e.g. cross-frame documents), + * `=Object` can be used to describe these untyped' values. + * + * + `var`. If the entire [typeDescription] is `var` then the type is + * `dynamic` but the code is known to not create any instances. + * + * Examples: + * + * // Create a JavaScript Array. + * List a = JS('=List', 'new Array(#)', length); + * + * // Parent window might be an opaque cross-frame window. + * var thing = JS('=Object|Window', '#.parent', myWindow); + * + * Guidelines: + * + * + Do not use any parameter, local, method or field names in the + * [codeTemplate]. These names are all subject to arbitrary renaming by the + * compiler. Pass the values in via `#` substition, and test with the + * `--minify` dart2js command-line option. + * + * + The substituted expressions are values, not locations. + * + * JS('void', '# += "x"', this.field); + * + * `this.field` might not be a substituted as a reference to the field. The + * generated code might accidentally work as intended, but it also might be + * + * var t1 = this.field; + * t1 += "x"; + * + * or + * + * this.get$field() += "x"; + * + * The remedy in this case is to expand the `+=` operator, leaving all + * references to the Dart field as Dart code: + * + * this.field = JS('String', '# + "x"', this.field); + * + * + * Additional notes. + * + * In the future we may extend [typeDescription] to include other aspects of the + * behavior, for example, separating the returned types from the instantiated + * types, or including effects to allow the compiler to perform more + * optimizations around the code. This might be an extension of [JS] or a new + * function similar to [JS] with additional arguments for the new information. + */ +// Add additional optional arguments if needed. The method is treated internally +// as a variable argument method. +dynamic JS(String typeDescription, String codeTemplate, + [var arg0, var arg1, var arg2, var arg3, var arg4, var arg5, var arg6, + var arg7, var arg8, var arg9, var arg10, var arg11]) {} + +/** + * Returns the isolate in which this code is running. + */ +dynamic JS_CURRENT_ISOLATE() {} + +/** + * Invokes [function] in the context of [isolate]. + */ +dynamic JS_CALL_IN_ISOLATE(var isolate, Function function) {} + +/** + * Converts the Dart closure [function] into a JavaScript closure. + */ +dynamic DART_CLOSURE_TO_JS(Function function) {} + +/** + * Returns a raw reference to the JavaScript function which implements + * [function]. + * + * Warning: this is dangerous, you should probably use + * [DART_CLOSURE_TO_JS] instead. The returned object is not a valid + * Dart closure, does not store the isolate context or arity. + * + * A valid example of where this can be used is as the second argument + * to V8's Error.captureStackTrace. See + * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi. + */ +dynamic RAW_DART_FUNCTION_REF(Function function) {} + +/** + * Sets the current isolate to [isolate]. + */ +void JS_SET_CURRENT_ISOLATE(var isolate) {} + +/** + * Creates an isolate and returns it. + */ +dynamic JS_CREATE_ISOLATE() {} + +/** + * Returns the prefix used for generated is checks on classes. + */ +String JS_OPERATOR_IS_PREFIX() {}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart new file mode 100644 index 0000000..6a32f17 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart
@@ -0,0 +1,90 @@ +// 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. + +library _interceptors; + +import 'dart:collection'; +import 'dart:_collection-dev'; +import 'dart:_js_helper' show allMatchesInStringUnchecked, + Null, + JSSyntaxRegExp, + Primitives, + checkGrowable, + checkMutable, + checkNull, + checkNum, + checkString, + getRuntimeTypeString, + listInsertRange, + regExpGetNative, + stringContainsUnchecked, + stringLastIndexOfUnchecked, + stringReplaceAllFuncUnchecked, + stringReplaceAllUnchecked, + stringReplaceFirstUnchecked, + TypeImpl; +import 'dart:_foreign_helper' show JS; + +part 'js_array.dart'; +part 'js_number.dart'; +part 'js_string.dart'; + +/** + * The interceptor class for all non-primitive objects. All its + * members are synthethized by the compiler's emitter. + */ +class ObjectInterceptor { + const ObjectInterceptor(); +} + +/** + * Get the interceptor for [object]. Called by the compiler when it needs + * to emit a call to an intercepted method, that is a method that is + * defined in an interceptor class. + */ +getInterceptor(object) { + // This is a magic method: the compiler does specialization of it + // depending on the uses of intercepted methods and instantiated + // primitive types. +} + +/** + * The interceptor class for tear-off static methods. Unlike + * tear-off instance methods, tear-off static methods are just the JS + * function, and methods inherited from Object must therefore be + * intercepted. + */ +class JSFunction implements Function { + const JSFunction(); + String toString() => 'Closure'; +} + +/** + * The interceptor class for [bool]. + */ +class JSBool implements bool { + const JSBool(); + + // Note: if you change this, also change the function [S]. + String toString() => JS('String', r'String(#)', this); + + // The values here are SMIs, co-prime and differ about half of the bit + // positions, including the low bit, so they are different mod 2^k. + int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811); + + Type get runtimeType => bool; +} + +/** + * The interceptor class for [Null]. + */ +class JSNull implements Null { + const JSNull(); + + // Note: if you change this, also change the function [S]. + String toString() => 'null'; + + int get hashCode => 0; + Type get runtimeType => Null; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart new file mode 100644 index 0000000..0374de5 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart
@@ -0,0 +1,217 @@ +// 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. + +patch class _BufferUtils { + patch static bool _isBuiltinList(List buffer) { + throw new UnsupportedError("_isBuiltinList"); + } +} + +patch class _Directory { + patch static String _current() { + throw new UnsupportedError("Directory._current"); + } + patch static _createTemp(String template) { + throw new UnsupportedError("Directory._createTemp"); + } + patch static int _exists(String path) { + throw new UnsupportedError("Directory._exists"); + } + patch static _create(String path) { + throw new UnsupportedError("Directory._create"); + } + patch static _delete(String path, bool recursive) { + throw new UnsupportedError("Directory._delete"); + } + patch static _rename(String path, String newPath) { + throw new UnsupportedError("Directory._rename"); + } + patch static List _list(String path, bool recursive) { + throw new UnsupportedError("Directory._list"); + } + patch static SendPort _newServicePort() { + throw new UnsupportedError("Directory._newServicePort"); + } +} + +patch class _EventHandler { + patch static void _start() { + throw new UnsupportedError("EventHandler._start"); + } + + patch static _sendData(Object sender, + ReceivePort receivePort, + int data) { + throw new UnsupportedError("EventHandler._sendData"); + } +} + +patch class _FileUtils { + patch static SendPort _newServicePort() { + throw new UnsupportedError("FileUtils._newServicePort"); + } +} + +patch class _File { + patch static _exists(String name) { + throw new UnsupportedError("File._exists"); + } + patch static _create(String name) { + throw new UnsupportedError("File._create"); + } + patch static _delete(String name) { + throw new UnsupportedError("File._delete"); + } + patch static _directory(String name) { + throw new UnsupportedError("File._directory"); + } + patch static _lengthFromName(String name) { + throw new UnsupportedError("File._lengthFromName"); + } + patch static _lastModified(String name) { + throw new UnsupportedError("File._lastModified"); + } + patch static _open(String name, int mode) { + throw new UnsupportedError("File._open"); + } + patch static int _openStdio(int fd) { + throw new UnsupportedError("File._openStdio"); + } + patch static _fullPath(String name) { + throw new UnsupportedError("File._fullPath"); + } +} + +patch class _RandomAccessFile { + patch static int _close(int id) { + throw new UnsupportedError("RandomAccessFile._close"); + } + patch static _readByte(int id) { + throw new UnsupportedError("RandomAccessFile._readByte"); + } + patch static _read(int id, int bytes) { + throw new UnsupportedError("RandomAccessFile._read"); + } + patch static _readList(int id, List<int> buffer, int offset, int bytes) { + throw new UnsupportedError("RandomAccessFile._readList"); + } + patch static _writeByte(int id, int value) { + throw new UnsupportedError("RandomAccessFile._writeByte"); + } + patch static _writeList(int id, List<int> buffer, int offset, int bytes) { + throw new UnsupportedError("RandomAccessFile._writeList"); + } + patch static _position(int id) { + throw new UnsupportedError("RandomAccessFile._position"); + } + patch static _setPosition(int id, int position) { + throw new UnsupportedError("RandomAccessFile._setPosition"); + } + patch static _truncate(int id, int length) { + throw new UnsupportedError("RandomAccessFile._truncate"); + } + patch static _length(int id) { + throw new UnsupportedError("RandomAccessFile._length"); + } + patch static _flush(int id) { + throw new UnsupportedError("RandomAccessFile._flush"); + } +} + +patch class _HttpSessionManager { + patch static Uint8List _getRandomBytes(int count) { + throw new UnsupportedError("HttpSessionManager._getRandomBytes"); + } +} + +patch class _Platform { + patch static int _numberOfProcessors() { + throw new UnsupportedError("Platform._numberOfProcessors"); + } + patch static String _pathSeparator() { + throw new UnsupportedError("Platform._pathSeparator"); + } + patch static String _operatingSystem() { + throw new UnsupportedError("Platform._operatingSystem"); + } + patch static _localHostname() { + throw new UnsupportedError("Platform._localHostname"); + } + patch static _environment() { + throw new UnsupportedError("Platform._environment"); + } +} + +patch class _ProcessUtils { + patch static _exit(int status) { + throw new UnsupportedError("ProcessUtils._exit"); + } + patch static _setExitCode(int status) { + throw new UnsupportedError("ProcessUtils._setExitCode"); + } +} + +patch class Process { + patch static Future<Process> start(String executable, + List<String> arguments, + [ProcessOptions options]) { + throw new UnsupportedError("Process.start"); + } + + patch static Future<ProcessResult> run(String executable, + List<String> arguments, + [ProcessOptions options]) { + throw new UnsupportedError("Process.run"); + } +} + +patch class ServerSocket { + patch factory ServerSocket(String bindAddress, int port, int backlog) { + throw new UnsupportedError("ServerSocket constructor"); + } +} + +patch class Socket { + patch factory Socket(String host, int port) { + throw new UnsupportedError("Socket constructor"); + } +} + +patch class SecureSocket { + patch static void initialize({String database, + String password, + bool useBuiltinRoots: true}) { + throw new UnsupportedError("SecureSocket.setCertificateDatabase"); + } +} + +patch class _SecureFilter { + patch factory _SecureFilter() { + throw new UnsupportedError("_SecureFilter._SecureFilter"); + } +} + +patch class _StdIOUtils { + patch static InputStream _getStdioInputStream() { + throw new UnsupportedError("StdIOUtils._getStdioInputStream"); + } + patch static OutputStream _getStdioOutputStream(int fd) { + throw new UnsupportedError("StdIOUtils._getStdioOutputStream"); + } + patch static int _socketType(Socket socket) { + throw new UnsupportedError("StdIOUtils._socketType"); + } +} + +patch class _WindowsCodePageDecoder { + patch static String _decodeBytes(List<int> bytes) { + throw new UnsupportedError("_WindowsCodePageDecoder._decodeBytes"); + } +} + +patch class _WindowsCodePageEncoder { + patch static List<int> _encodeString(String string) { + throw new UnsupportedError("_WindowsCodePageEncoder._encodeString"); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart new file mode 100644 index 0000000..82a80fe --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart
@@ -0,0 +1,1329 @@ +// 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. + +library _isolate_helper; + +import 'dart:async'; +import 'dart:collection' show Queue, HashMap; +import 'dart:isolate'; +import 'dart:_js_helper' show convertDartClosureToJS, + Null; +import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, + JS, + JS_CREATE_ISOLATE, + JS_SET_CURRENT_ISOLATE; + +ReceivePort lazyPort; + +/** + * Called by the compiler to support switching + * between isolates when we get a callback from the DOM. + */ +void _callInIsolate(_IsolateContext isolate, Function function) { + isolate.eval(function); + _globalState.topEventLoop.run(); +} + +/** + * Called by the compiler to fetch the current isolate context. + */ +_IsolateContext _currentIsolate() => _globalState.currentContext; + +/** + * Wrapper that takes the dart entry point and runs it within an isolate. The + * dart2js compiler will inject a call of the form + * [: startRootIsolate(main); :] when it determines that this wrapping + * is needed. For single-isolate applications (e.g. hello world), this + * call is not emitted. + */ +void startRootIsolate(entry) { + _globalState = new _Manager(); + + // Don't start the main loop again, if we are in a worker. + if (_globalState.isWorker) return; + final rootContext = new _IsolateContext(); + _globalState.rootContext = rootContext; + + // BUG(5151491): Setting currentContext should not be necessary, but + // because closures passed to the DOM as event handlers do not bind their + // isolate automatically we try to give them a reasonable context to live in + // by having a "default" isolate (the first one created). + _globalState.currentContext = rootContext; + + rootContext.eval(entry); + _globalState.topEventLoop.run(); +} + +/******************************************************** + Inserted from lib/isolate/dart2js/isolateimpl.dart + ********************************************************/ + +/** + * Concepts used here: + * + * "manager" - A manager contains one or more isolates, schedules their + * execution, and performs other plumbing on their behalf. The isolate + * present at the creation of the manager is designated as its "root isolate". + * A manager may, for example, be implemented on a web Worker. + * + * [_Manager] - State present within a manager (exactly once, as a global). + * + * [_ManagerStub] - A handle held within one manager that allows interaction + * with another manager. A target manager may be addressed by zero or more + * [_ManagerStub]s. + * + */ + +/** + * A native object that is shared across isolates. This object is visible to all + * isolates running under the same manager (either UI or background web worker). + * + * This is code that is intended to 'escape' the isolate boundaries in order to + * implement the semantics of isolates in JavaScript. Without this we would have + * been forced to implement more code (including the top-level event loop) in + * JavaScript itself. + */ +// TODO(eub, sigmund): move the "manager" to be entirely in JS. +// Running any Dart code outside the context of an isolate gives it +// the chance to break the isolate abstraction. +_Manager get _globalState => JS("_Manager", r"$globalState"); + +set _globalState(_Manager val) { + JS("void", r"$globalState = #", val); +} + +/** State associated with the current manager. See [globalState]. */ +// TODO(sigmund): split in multiple classes: global, thread, main-worker states? +class _Manager { + + /** Next available isolate id within this [_Manager]. */ + int nextIsolateId = 0; + + /** id assigned to this [_Manager]. */ + int currentManagerId = 0; + + /** + * Next available manager id. Only used by the main manager to assign a unique + * id to each manager created by it. + */ + int nextManagerId = 1; + + /** Context for the currently running [Isolate]. */ + _IsolateContext currentContext = null; + + /** Context for the root [Isolate] that first run in this [_Manager]. */ + _IsolateContext rootContext = null; + + /** The top-level event loop. */ + _EventLoop topEventLoop; + + /** Whether this program is running from the command line. */ + bool fromCommandLine; + + /** Whether this [_Manager] is running as a web worker. */ + bool isWorker; + + /** Whether we support spawning web workers. */ + bool supportsWorkers; + + /** + * Whether to use web workers when implementing isolates. Set to false for + * debugging/testing. + */ + bool get useWorkers => supportsWorkers; + + /** + * Whether to use the web-worker JSON-based message serialization protocol. By + * default this is only used with web workers. For debugging, you can force + * using this protocol by changing this field value to [true]. + */ + bool get needSerialization => useWorkers; + + /** + * Registry of isolates. Isolates must be registered if, and only if, receive + * ports are alive. Normally no open receive-ports means that the isolate is + * dead, but DOM callbacks could resurrect it. + */ + Map<int, _IsolateContext> isolates; + + /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */ + _ManagerStub mainManager; + + /** Registry of active [_ManagerStub]s. Only used in the main [_Manager]. */ + Map<int, _ManagerStub> managers; + + _Manager() { + _nativeDetectEnvironment(); + topEventLoop = new _EventLoop(); + isolates = new Map<int, _IsolateContext>(); + managers = new Map<int, _ManagerStub>(); + if (isWorker) { // "if we are not the main manager ourself" is the intent. + mainManager = new _MainManagerStub(); + _nativeInitWorkerMessageHandler(); + } + } + + void _nativeDetectEnvironment() { + bool isWindowDefined = globalWindow != null; + bool isWorkerDefined = globalWorker != null; + + isWorker = !isWindowDefined && globalPostMessageDefined; + supportsWorkers = isWorker + || (isWorkerDefined && IsolateNatives.thisScript != null); + fromCommandLine = !isWindowDefined && !isWorker; + } + + void _nativeInitWorkerMessageHandler() { + var function = JS('', + "function (e) { #(#, e); }", + DART_CLOSURE_TO_JS(IsolateNatives._processWorkerMessage), + mainManager); + JS("void", r"#.onmessage = #", globalThis, function); + // We define dartPrint so that the implementation of the Dart + // print method knows what to call. + // TODO(ngeoffray): Should we forward to the main isolate? What if + // it exited? + JS('void', r'#.dartPrint = function (object) {}', globalThis); + } + + + /** + * Close the worker running this code if all isolates are done and + * there is no active timer. + */ + void maybeCloseWorker() { + if (isWorker + && isolates.isEmpty + && topEventLoop.activeTimerCount == 0) { + mainManager.postMessage(_serializeMessage({'command': 'close'})); + } + } +} + +/** Context information tracked for each isolate. */ +class _IsolateContext { + /** Current isolate id. */ + int id; + + /** Registry of receive ports currently active on this isolate. */ + Map<int, ReceivePort> ports; + + /** Holds isolate globals (statics and top-level properties). */ + var isolateStatics; // native object containing all globals of an isolate. + + _IsolateContext() { + id = _globalState.nextIsolateId++; + ports = new Map<int, ReceivePort>(); + isolateStatics = JS_CREATE_ISOLATE(); + } + + /** + * Run [code] in the context of the isolate represented by [this]. + */ + dynamic eval(Function code) { + var old = _globalState.currentContext; + _globalState.currentContext = this; + this._setGlobals(); + var result = null; + try { + result = code(); + } finally { + _globalState.currentContext = old; + if (old != null) old._setGlobals(); + } + return result; + } + + void _setGlobals() { + JS_SET_CURRENT_ISOLATE(isolateStatics); + } + + /** Lookup a port registered for this isolate. */ + ReceivePort lookup(int portId) => ports[portId]; + + /** Register a port on this isolate. */ + void register(int portId, ReceivePort port) { + if (ports.containsKey(portId)) { + throw new Exception("Registry: ports must be registered only once."); + } + ports[portId] = port; + _globalState.isolates[id] = this; // indicate this isolate is active + } + + /** Unregister a port on this isolate. */ + void unregister(int portId) { + ports.remove(portId); + if (ports.isEmpty) { + _globalState.isolates.remove(id); // indicate this isolate is not active + } + } +} + +/** Represent the event loop on a javascript thread (DOM or worker). */ +class _EventLoop { + final Queue<_IsolateEvent> events = new Queue<_IsolateEvent>(); + int activeTimerCount = 0; + + _EventLoop(); + + void enqueue(isolate, fn, msg) { + events.addLast(new _IsolateEvent(isolate, fn, msg)); + } + + _IsolateEvent dequeue() { + if (events.isEmpty) return null; + return events.removeFirst(); + } + + void checkOpenReceivePortsFromCommandLine() { + if (_globalState.rootContext != null + && _globalState.isolates.containsKey(_globalState.rootContext.id) + && _globalState.fromCommandLine + && _globalState.rootContext.ports.isEmpty) { + // We want to reach here only on the main [_Manager] and only + // on the command-line. In the browser the isolate might + // still be alive due to DOM callbacks, but the presumption is + // that on the command-line, no future events can be injected + // into the event queue once it's empty. Node has setTimeout + // so this presumption is incorrect there. We think(?) that + // in d8 this assumption is valid. + throw new Exception("Program exited with open ReceivePorts."); + } + } + + /** Process a single event, if any. */ + bool runIteration() { + final event = dequeue(); + if (event == null) { + checkOpenReceivePortsFromCommandLine(); + _globalState.maybeCloseWorker(); + return false; + } + event.process(); + return true; + } + + /** + * Runs multiple iterations of the run-loop. If possible, each iteration is + * run asynchronously. + */ + void _runHelper() { + if (globalWindow != null) { + // Run each iteration from the browser's top event loop. + void next() { + if (!runIteration()) return; + new Timer(0, (_) => next()); + } + next(); + } else { + // Run synchronously until no more iterations are available. + while (runIteration()) {} + } + } + + /** + * Call [_runHelper] but ensure that worker exceptions are propragated. + */ + void run() { + if (!_globalState.isWorker) { + _runHelper(); + } else { + try { + _runHelper(); + } catch (e, trace) { + _globalState.mainManager.postMessage(_serializeMessage( + {'command': 'error', 'msg': '$e\n$trace' })); + } + } + } +} + +/** An event in the top-level event queue. */ +class _IsolateEvent { + _IsolateContext isolate; + Function fn; + String message; + + _IsolateEvent(this.isolate, this.fn, this.message); + + void process() { + isolate.eval(fn); + } +} + +/** An interface for a stub used to interact with a manager. */ +abstract class _ManagerStub { + get id; + void set id(int i); + void set onmessage(Function f); + void postMessage(msg); + void terminate(); +} + +/** A stub for interacting with the main manager. */ +class _MainManagerStub implements _ManagerStub { + get id => 0; + void set id(int i) { throw new UnimplementedError(); } + void set onmessage(f) { + throw new Exception("onmessage should not be set on MainManagerStub"); + } + void postMessage(msg) { + JS("void", r"#.postMessage(#)", globalThis, msg); + } + void terminate() {} // Nothing useful to do here. +} + +/** + * A stub for interacting with a manager built on a web worker. This + * definition uses a 'hidden' type (* prefix on the native name) to + * enforce that the type is defined dynamically only when web workers + * are actually available. + */ +// @Native("*Worker"); +class _WorkerStub implements _ManagerStub { + get id => JS("", "#.id", this); + void set id(i) { JS("void", "#.id = #", this, i); } + void set onmessage(f) { JS("void", "#.onmessage = #", this, f); } + void postMessage(msg) { JS("void", "#.postMessage(#)", this, msg); } + void terminate() { JS("void", "#.terminate()", this); } +} + +const String _SPAWNED_SIGNAL = "spawned"; + +var globalThis = IsolateNatives.computeGlobalThis(); +var globalWindow = JS('', "#.window", globalThis); +var globalWorker = JS('', "#.Worker", globalThis); +bool globalPostMessageDefined = + JS('', "#.postMessage !== (void 0)", globalThis); + +class IsolateNatives { + + static String thisScript = computeThisScript(); + + /** + * The src url for the script tag that loaded this code. Used to create + * JavaScript workers. + */ + static String computeThisScript() { + // TODO(7369): Find a cross-platform non-brittle way of getting the + // currently running script. + var scripts = JS('', r"document.getElementsByTagName('script')"); + // The scripts variable only contains the scripts that have already been + // executed. The last one is the currently running script. + for (int i = 0, len = JS('int', '#.length', scripts); i < len; i++) { + var script = JS('', '#[#]', scripts, i); + var src = JS('String|Null', '# && #.src', script, script); + // Filter out the test controller script, and the Dart + // bootstrap script. + if (src != null + && !src.endsWith('test_controller.js') + && !src.endsWith('dart.js')) { + return src; + } + } + return null; + } + + static computeGlobalThis() => JS('', 'function() { return this; }()'); + + /** Starts a new worker with the given URL. */ + static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url); + + /** + * Assume that [e] is a browser message event and extract its message data. + * We don't import the dom explicitly so, when workers are disabled, this + * library can also run on top of nodejs. + */ + static _getEventData(e) => JS("", "#.data", e); + + /** + * Process messages on a worker, either to control the worker instance or to + * pass messages along to the isolate running in the worker. + */ + static void _processWorkerMessage(sender, e) { + var msg = _deserializeMessage(_getEventData(e)); + switch (msg['command']) { + case 'start': + _globalState.currentManagerId = msg['id']; + Function entryPoint = _getJSFunctionFromName(msg['functionName']); + var replyTo = _deserializeMessage(msg['replyTo']); + var context = new _IsolateContext(); + _globalState.topEventLoop.enqueue(context, function() { + _startIsolate(entryPoint, replyTo); + }, 'worker-start'); + // Make sure we always have a current context in this worker. + // TODO(7907): This is currently needed because we're using + // Timers to implement Futures, and this isolate library + // implementation uses Futures. We should either stop using + // Futures in this library, or re-adapt if Futures get a + // different implementation. + _globalState.currentContext = context; + _globalState.topEventLoop.run(); + break; + case 'spawn-worker': + _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']); + break; + case 'message': + SendPort port = msg['port']; + // If the port has been closed, we ignore the message. + if (port != null) { + msg['port'].send(msg['msg'], msg['replyTo']); + } + _globalState.topEventLoop.run(); + break; + case 'close': + _log("Closing Worker"); + _globalState.managers.remove(sender.id); + sender.terminate(); + _globalState.topEventLoop.run(); + break; + case 'log': + _log(msg['msg']); + break; + case 'print': + if (_globalState.isWorker) { + _globalState.mainManager.postMessage( + _serializeMessage({'command': 'print', 'msg': msg})); + } else { + print(msg['msg']); + } + break; + case 'error': + throw msg['msg']; + } + } + + /** Log a message, forwarding to the main [_Manager] if appropriate. */ + static _log(msg) { + if (_globalState.isWorker) { + _globalState.mainManager.postMessage( + _serializeMessage({'command': 'log', 'msg': msg })); + } else { + try { + _consoleLog(msg); + } catch (e, trace) { + throw new Exception(trace); + } + } + } + + static void _consoleLog(msg) { + JS("void", r"#.console.log(#)", globalThis, msg); + } + + /** Find a constructor given its name. */ + static dynamic _getJSConstructorFromName(String factoryName) { + return JS("", r"$[#]", factoryName); + } + + static dynamic _getJSFunctionFromName(String functionName) { + return JS("", r"$[#]", functionName); + } + + /** + * Get a string name for the function, if possible. The result for + * anonymous functions is browser-dependent -- it may be "" or "anonymous" + * but you should probably not count on this. + */ + static String _getJSFunctionName(Function f) { + return JS("String|Null", r"(#.$name || #)", f, null); + } + + /** Create a new JavaScript object instance given its constructor. */ + static dynamic _allocate(var ctor) { + return JS("", "new #()", ctor); + } + + static SendPort spawnFunction(void topLevelFunction()) { + final name = _getJSFunctionName(topLevelFunction); + if (name == null) { + throw new UnsupportedError( + "only top-level functions can be spawned."); + } + return spawn(name, null, false); + } + + static SendPort spawnDomFunction(void topLevelFunction()) { + final name = _getJSFunctionName(topLevelFunction); + if (name == null) { + throw new UnsupportedError( + "only top-level functions can be spawned."); + } + return spawn(name, null, true); + } + + // TODO(sigmund): clean up above, after we make the new API the default: + + static spawn(String functionName, String uri, bool isLight) { + Completer<SendPort> completer = new Completer<SendPort>(); + ReceivePort port = new ReceivePort(); + port.receive((msg, SendPort replyPort) { + port.close(); + assert(msg == _SPAWNED_SIGNAL); + completer.complete(replyPort); + }); + + SendPort signalReply = port.toSendPort(); + + if (_globalState.useWorkers && !isLight) { + _startWorker(functionName, uri, signalReply); + } else { + _startNonWorker(functionName, uri, signalReply); + } + return new _BufferingSendPort( + _globalState.currentContext.id, completer.future); + } + + static SendPort _startWorker( + String functionName, String uri, SendPort replyPort) { + if (_globalState.isWorker) { + _globalState.mainManager.postMessage(_serializeMessage({ + 'command': 'spawn-worker', + 'functionName': functionName, + 'uri': uri, + 'replyPort': replyPort})); + } else { + _spawnWorker(functionName, uri, replyPort); + } + } + + static SendPort _startNonWorker( + String functionName, String uri, SendPort replyPort) { + // TODO(eub): support IE9 using an iframe -- Dart issue 1702. + if (uri != null) throw new UnsupportedError( + "Currently spawnUri is not supported without web workers."); + _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { + final func = _getJSFunctionFromName(functionName); + _startIsolate(func, replyPort); + }, 'nonworker start'); + } + + static void _startIsolate(Function topLevel, SendPort replyTo) { + lazyPort = new ReceivePort(); + replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); + topLevel(); + } + + /** + * Spawns an isolate in a worker. [factoryName] is the Javascript constructor + * name for the isolate entry point class. + */ + static void _spawnWorker(functionName, uri, replyPort) { + if (functionName == null) functionName = 'main'; + if (uri == null) uri = thisScript; + final worker = _newWorker(uri); + worker.onmessage = JS('', + 'function(e) { #(#, e); }', + DART_CLOSURE_TO_JS(_processWorkerMessage), + worker); + var workerId = _globalState.nextManagerId++; + // We also store the id on the worker itself so that we can unregister it. + worker.id = workerId; + _globalState.managers[workerId] = worker; + worker.postMessage(_serializeMessage({ + 'command': 'start', + 'id': workerId, + // Note: we serialize replyPort twice because the child worker needs to + // first deserialize the worker id, before it can correctly deserialize + // the port (port deserialization is sensitive to what is the current + // workerId). + 'replyTo': _serializeMessage(replyPort), + 'functionName': functionName })); + } +} + +/******************************************************** + Inserted from lib/isolate/dart2js/ports.dart + ********************************************************/ + +/** Common functionality to all send ports. */ +class _BaseSendPort implements SendPort { + /** Id for the destination isolate. */ + final int _isolateId; + + const _BaseSendPort(this._isolateId); + + void _checkReplyTo(SendPort replyTo) { + if (replyTo != null + && replyTo is! _NativeJsSendPort + && replyTo is! _WorkerSendPort + && replyTo is! _BufferingSendPort) { + throw new Exception("SendPort.send: Illegal replyTo port type"); + } + } + + Future call(var message) { + final completer = new Completer(); + final port = new ReceivePortImpl(); + send(message, port.toSendPort()); + port.receive((value, ignoreReplyTo) { + port.close(); + if (value is Exception) { + completer.completeError(value); + } else { + completer.complete(value); + } + }); + return completer.future; + } + + void send(var message, [SendPort replyTo]); + bool operator ==(var other); + int get hashCode; +} + +/** A send port that delivers messages in-memory via native JavaScript calls. */ +class _NativeJsSendPort extends _BaseSendPort implements SendPort { + final ReceivePortImpl _receivePort; + + const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); + + void send(var message, [SendPort replyTo = null]) { + _waitForPendingPorts([message, replyTo], () { + _checkReplyTo(replyTo); + // Check that the isolate still runs and the port is still open + final isolate = _globalState.isolates[_isolateId]; + if (isolate == null) return; + if (_receivePort._callback == null) return; + + // We force serialization/deserialization as a simple way to ensure + // isolate communication restrictions are respected between isolates that + // live in the same worker. [_NativeJsSendPort] delivers both messages + // from the same worker and messages from other workers. In particular, + // messages sent from a worker via a [_WorkerSendPort] are received at + // [_processWorkerMessage] and forwarded to a native port. In such cases, + // here we'll see [_globalState.currentContext == null]. + final shouldSerialize = _globalState.currentContext != null + && _globalState.currentContext.id != _isolateId; + var msg = message; + var reply = replyTo; + if (shouldSerialize) { + msg = _serializeMessage(msg); + reply = _serializeMessage(reply); + } + _globalState.topEventLoop.enqueue(isolate, () { + if (_receivePort._callback != null) { + if (shouldSerialize) { + msg = _deserializeMessage(msg); + reply = _deserializeMessage(reply); + } + _receivePort._callback(msg, reply); + } + }, 'receive $message'); + }); + } + + bool operator ==(var other) => (other is _NativeJsSendPort) && + (_receivePort == other._receivePort); + + int get hashCode => _receivePort._id; +} + +/** A send port that delivers messages via worker.postMessage. */ +// TODO(eub): abstract this for iframes. +class _WorkerSendPort extends _BaseSendPort implements SendPort { + final int _workerId; + final int _receivePortId; + + const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId) + : super(isolateId); + + void send(var message, [SendPort replyTo = null]) { + _waitForPendingPorts([message, replyTo], () { + _checkReplyTo(replyTo); + final workerMessage = _serializeMessage({ + 'command': 'message', + 'port': this, + 'msg': message, + 'replyTo': replyTo}); + + if (_globalState.isWorker) { + // Communication from one worker to another go through the + // main worker. + _globalState.mainManager.postMessage(workerMessage); + } else { + // Deliver the message only if the worker is still alive. + _ManagerStub manager = _globalState.managers[_workerId]; + if (manager != null) { + manager.postMessage(workerMessage); + } + } + }); + } + + bool operator ==(var other) { + return (other is _WorkerSendPort) && + (_workerId == other._workerId) && + (_isolateId == other._isolateId) && + (_receivePortId == other._receivePortId); + } + + int get hashCode { + // TODO(sigmund): use a standard hash when we get one available in corelib. + return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; + } +} + +/** A port that buffers messages until an underlying port gets resolved. */ +class _BufferingSendPort extends _BaseSendPort implements SendPort { + /** Internal counter to assign unique ids to each port. */ + static int _idCount = 0; + + /** For implementing equals and hashcode. */ + final int _id; + + /** Underlying port, when resolved. */ + SendPort _port; + + /** + * Future of the underlying port, so that we can detect when this port can be + * sent on messages. + */ + Future<SendPort> _futurePort; + + /** Pending messages (and reply ports). */ + List pending; + + _BufferingSendPort(isolateId, this._futurePort) + : super(isolateId), _id = _idCount, pending = [] { + _idCount++; + _futurePort.then((p) { + _port = p; + for (final item in pending) { + p.send(item['message'], item['replyTo']); + } + pending = null; + }); + } + + _BufferingSendPort.fromPort(isolateId, this._port) + : super(isolateId), _id = _idCount { + _idCount++; + } + + void send(var message, [SendPort replyTo]) { + if (_port != null) { + _port.send(message, replyTo); + } else { + pending.add({'message': message, 'replyTo': replyTo}); + } + } + + bool operator ==(var other) => + other is _BufferingSendPort && _id == other._id; + int get hashCode => _id; +} + +/** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ +class ReceivePortImpl implements ReceivePort { + int _id; + Function _callback; + static int _nextFreeId = 1; + + ReceivePortImpl() + : _id = _nextFreeId++ { + _globalState.currentContext.register(_id, this); + } + + void receive(void onMessage(var message, SendPort replyTo)) { + _callback = onMessage; + } + + void close() { + _callback = null; + _globalState.currentContext.unregister(_id); + } + + SendPort toSendPort() { + return new _NativeJsSendPort(this, _globalState.currentContext.id); + } +} + +/** Wait until all ports in a message are resolved. */ +_waitForPendingPorts(var message, void callback()) { + final finder = new _PendingSendPortFinder(); + finder.traverse(message); + Future.wait(finder.ports).then((_) => callback()); +} + + +/** Visitor that finds all unresolved [SendPort]s in a message. */ +class _PendingSendPortFinder extends _MessageTraverser { + List<Future<SendPort>> ports; + _PendingSendPortFinder() : super(), ports = [] { + _visited = new _JsVisitedMap(); + } + + visitPrimitive(x) {} + + visitList(List list) { + final seen = _visited[list]; + if (seen != null) return; + _visited[list] = true; + // TODO(sigmund): replace with the following: (bug #1660) + // list.forEach(_dispatch); + list.forEach((e) => _dispatch(e)); + } + + visitMap(Map map) { + final seen = _visited[map]; + if (seen != null) return; + + _visited[map] = true; + // TODO(sigmund): replace with the following: (bug #1660) + // map.values.forEach(_dispatch); + map.values.forEach((e) => _dispatch(e)); + } + + visitSendPort(SendPort port) { + if (port is _BufferingSendPort && port._port == null) { + ports.add(port._futurePort); + } + } +} + +/******************************************************** + Inserted from lib/isolate/dart2js/messages.dart + ********************************************************/ + +// Defines message visitors, serialization, and deserialization. + +/** Serialize [message] (or simulate serialization). */ +_serializeMessage(message) { + if (_globalState.needSerialization) { + return new _JsSerializer().traverse(message); + } else { + return new _JsCopier().traverse(message); + } +} + +/** Deserialize [message] (or simulate deserialization). */ +_deserializeMessage(message) { + if (_globalState.needSerialization) { + return new _JsDeserializer().deserialize(message); + } else { + // Nothing more to do. + return message; + } +} + +class _JsSerializer extends _Serializer { + + _JsSerializer() : super() { _visited = new _JsVisitedMap(); } + + visitSendPort(SendPort x) { + if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); + if (x is _WorkerSendPort) return visitWorkerSendPort(x); + if (x is _BufferingSendPort) return visitBufferingSendPort(x); + throw "Illegal underlying port $x"; + } + + visitNativeJsSendPort(_NativeJsSendPort port) { + return ['sendport', _globalState.currentManagerId, + port._isolateId, port._receivePort._id]; + } + + visitWorkerSendPort(_WorkerSendPort port) { + return ['sendport', port._workerId, port._isolateId, port._receivePortId]; + } + + visitBufferingSendPort(_BufferingSendPort port) { + if (port._port != null) { + return visitSendPort(port._port); + } else { + // TODO(floitsch): Use real exception (which one?). + throw + "internal error: must call _waitForPendingPorts to ensure all" + " ports are resolved at this point."; + } + } + +} + + +class _JsCopier extends _Copier { + + _JsCopier() : super() { _visited = new _JsVisitedMap(); } + + visitSendPort(SendPort x) { + if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); + if (x is _WorkerSendPort) return visitWorkerSendPort(x); + if (x is _BufferingSendPort) return visitBufferingSendPort(x); + throw "Illegal underlying port $p"; + } + + SendPort visitNativeJsSendPort(_NativeJsSendPort port) { + return new _NativeJsSendPort(port._receivePort, port._isolateId); + } + + SendPort visitWorkerSendPort(_WorkerSendPort port) { + return new _WorkerSendPort( + port._workerId, port._isolateId, port._receivePortId); + } + + SendPort visitBufferingSendPort(_BufferingSendPort port) { + if (port._port != null) { + return visitSendPort(port._port); + } else { + // TODO(floitsch): Use real exception (which one?). + throw + "internal error: must call _waitForPendingPorts to ensure all" + " ports are resolved at this point."; + } + } + +} + +class _JsDeserializer extends _Deserializer { + + SendPort deserializeSendPort(List x) { + int managerId = x[1]; + int isolateId = x[2]; + int receivePortId = x[3]; + // If two isolates are in the same manager, we use NativeJsSendPorts to + // deliver messages directly without using postMessage. + if (managerId == _globalState.currentManagerId) { + var isolate = _globalState.isolates[isolateId]; + if (isolate == null) return null; // Isolate has been closed. + var receivePort = isolate.lookup(receivePortId); + if (receivePort == null) return null; // Port has been closed. + return new _NativeJsSendPort(receivePort, isolateId); + } else { + return new _WorkerSendPort(managerId, isolateId, receivePortId); + } + } + +} + +class _JsVisitedMap implements _MessageTraverserVisitedMap { + List tagged; + + /** Retrieves any information stored in the native object [object]. */ + operator[](var object) { + return _getAttachedInfo(object); + } + + /** Injects some information into the native [object]. */ + void operator[]=(var object, var info) { + tagged.add(object); + _setAttachedInfo(object, info); + } + + /** Get ready to rumble. */ + void reset() { + assert(tagged == null); + tagged = new List(); + } + + /** Remove all information injected in the native objects. */ + void cleanup() { + for (int i = 0, length = tagged.length; i < length; i++) { + _clearAttachedInfo(tagged[i]); + } + tagged = null; + } + + void _clearAttachedInfo(var o) { + JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null); + } + + void _setAttachedInfo(var o, var info) { + JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info); + } + + _getAttachedInfo(var o) { + return JS("", "#['__MessageTraverser__attached_info__']", o); + } +} + +// only visible for testing purposes +// TODO(sigmund): remove once we can disable privacy for testing (bug #1882) +class TestingOnly { + static copy(x) { + return new _JsCopier().traverse(x); + } + + // only visible for testing purposes + static serialize(x) { + _Serializer serializer = new _JsSerializer(); + _Deserializer deserializer = new _JsDeserializer(); + return deserializer.deserialize(serializer.traverse(x)); + } +} + +/******************************************************** + Inserted from lib/isolate/serialization.dart + ********************************************************/ + +class _MessageTraverserVisitedMap { + + operator[](var object) => null; + void operator[]=(var object, var info) { } + + void reset() { } + void cleanup() { } + +} + +/** Abstract visitor for dart objects that can be sent as isolate messages. */ +class _MessageTraverser { + + _MessageTraverserVisitedMap _visited; + _MessageTraverser() : _visited = new _MessageTraverserVisitedMap(); + + /** Visitor's entry point. */ + traverse(var x) { + if (isPrimitive(x)) return visitPrimitive(x); + _visited.reset(); + var result; + try { + result = _dispatch(x); + } finally { + _visited.cleanup(); + } + return result; + } + + _dispatch(var x) { + if (isPrimitive(x)) return visitPrimitive(x); + if (x is List) return visitList(x); + if (x is Map) return visitMap(x); + if (x is SendPort) return visitSendPort(x); + if (x is SendPortSync) return visitSendPortSync(x); + + // Overridable fallback. + return visitObject(x); + } + + visitPrimitive(x); + visitList(List x); + visitMap(Map x); + visitSendPort(SendPort x); + visitSendPortSync(SendPortSync x); + + visitObject(Object x) { + // TODO(floitsch): make this a real exception. (which one)? + throw "Message serialization: Illegal value $x passed"; + } + + static bool isPrimitive(x) { + return (x == null) || (x is String) || (x is num) || (x is bool); + } +} + + +/** A visitor that recursively copies a message. */ +class _Copier extends _MessageTraverser { + + visitPrimitive(x) => x; + + List visitList(List list) { + List copy = _visited[list]; + if (copy != null) return copy; + + int len = list.length; + + // TODO(floitsch): we loose the generic type of the List. + copy = new List(len); + _visited[list] = copy; + for (int i = 0; i < len; i++) { + copy[i] = _dispatch(list[i]); + } + return copy; + } + + Map visitMap(Map map) { + Map copy = _visited[map]; + if (copy != null) return copy; + + // TODO(floitsch): we loose the generic type of the map. + copy = new Map(); + _visited[map] = copy; + map.forEach((key, val) { + copy[_dispatch(key)] = _dispatch(val); + }); + return copy; + } + +} + +/** Visitor that serializes a message as a JSON array. */ +class _Serializer extends _MessageTraverser { + int _nextFreeRefId = 0; + + visitPrimitive(x) => x; + + visitList(List list) { + int copyId = _visited[list]; + if (copyId != null) return ['ref', copyId]; + + int id = _nextFreeRefId++; + _visited[list] = id; + var jsArray = _serializeList(list); + // TODO(floitsch): we are losing the generic type. + return ['list', id, jsArray]; + } + + visitMap(Map map) { + int copyId = _visited[map]; + if (copyId != null) return ['ref', copyId]; + + int id = _nextFreeRefId++; + _visited[map] = id; + var keys = _serializeList(map.keys.toList()); + var values = _serializeList(map.values.toList()); + // TODO(floitsch): we are losing the generic type. + return ['map', id, keys, values]; + } + + _serializeList(List list) { + int len = list.length; + var result = new List(len); + for (int i = 0; i < len; i++) { + result[i] = _dispatch(list[i]); + } + return result; + } +} + +/** Deserializes arrays created with [_Serializer]. */ +class _Deserializer { + Map<int, dynamic> _deserialized; + + _Deserializer(); + + static bool isPrimitive(x) { + return (x == null) || (x is String) || (x is num) || (x is bool); + } + + deserialize(x) { + if (isPrimitive(x)) return x; + // TODO(floitsch): this should be new HashMap<int, var|Dynamic>() + _deserialized = new HashMap(); + return _deserializeHelper(x); + } + + _deserializeHelper(x) { + if (isPrimitive(x)) return x; + assert(x is List); + switch (x[0]) { + case 'ref': return _deserializeRef(x); + case 'list': return _deserializeList(x); + case 'map': return _deserializeMap(x); + case 'sendport': return deserializeSendPort(x); + default: return deserializeObject(x); + } + } + + _deserializeRef(List x) { + int id = x[1]; + var result = _deserialized[id]; + assert(result != null); + return result; + } + + List _deserializeList(List x) { + int id = x[1]; + // We rely on the fact that Dart-lists are directly mapped to Js-arrays. + List dartList = x[2]; + _deserialized[id] = dartList; + int len = dartList.length; + for (int i = 0; i < len; i++) { + dartList[i] = _deserializeHelper(dartList[i]); + } + return dartList; + } + + Map _deserializeMap(List x) { + Map result = new Map(); + int id = x[1]; + _deserialized[id] = result; + List keys = x[2]; + List values = x[3]; + int len = keys.length; + assert(len == values.length); + for (int i = 0; i < len; i++) { + var key = _deserializeHelper(keys[i]); + var value = _deserializeHelper(values[i]); + result[key] = value; + } + return result; + } + + deserializeSendPort(List x); + + deserializeObject(List x) { + // TODO(floitsch): Use real exception (which one?). + throw "Unexpected serialized object"; + } +} + +class TimerImpl implements Timer { + final bool _once; + bool _inEventLoop = false; + int _handle; + + TimerImpl(int milliseconds, void callback(Timer timer)) + : _once = true { + if (milliseconds == 0 && (!hasTimer() || _globalState.isWorker)) { + // This makes a dependency between the async library and the + // event loop of the isolate library. The compiler makes sure + // that the event loop is compiled if [Timer] is used. + // TODO(7907): In case of web workers, we need to use the event + // loop instead of setTimeout, to make sure the futures get executed in + // order. + _globalState.topEventLoop.enqueue(_globalState.currentContext, () { + callback(this); + }, 'timer'); + _inEventLoop = true; + } else if (hasTimer()) { + _globalState.topEventLoop.activeTimerCount++; + void internalCallback() { + callback(this); + _handle = null; + _globalState.topEventLoop.activeTimerCount--; + } + _handle = JS('int', '#.setTimeout(#, #)', + globalThis, + convertDartClosureToJS(internalCallback, 0), + milliseconds); + } else { + assert(milliseconds > 0); + throw new UnsupportedError("Timer greater than 0."); + } + } + + TimerImpl.repeating(int milliseconds, void callback(Timer timer)) + : _once = false { + if (hasTimer()) { + _globalState.topEventLoop.activeTimerCount++; + _handle = JS('int', '#.setInterval(#, #)', + globalThis, + convertDartClosureToJS(() { callback(this); }, 0), + milliseconds); + } else { + throw new UnsupportedError("Repeating timer."); + } + } + + void cancel() { + if (hasTimer()) { + if (_inEventLoop) { + throw new UnsupportedError("Timer in event loop cannot be canceled."); + } + if (_handle == null) return; + _globalState.topEventLoop.activeTimerCount--; + if (_once) { + JS('void', '#.clearTimeout(#)', globalThis, _handle); + } else { + JS('void', '#.clearInterval(#)', globalThis, _handle); + } + _handle = null; + } else { + throw new UnsupportedError("Canceling a timer."); + } + } +} + +bool hasTimer() => JS('', '#.setTimeout', globalThis) != null;
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart new file mode 100644 index 0000000..aab847a --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart
@@ -0,0 +1,34 @@ +// 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. + +// Patch file for the dart:isolate library. + +import 'dart:_isolate_helper' show IsolateNatives, + lazyPort, + ReceivePortImpl; + +patch class _Isolate { + patch static ReceivePort get port { + if (lazyPort == null) { + lazyPort = new ReceivePort(); + } + return lazyPort; + } + + patch static SendPort spawnFunction(void topLevelFunction(), + [bool UnhandledExceptionCallback(IsolateUnhandledException e)]) { + return IsolateNatives.spawnFunction(topLevelFunction); + } + + patch static SendPort spawnUri(String uri) { + return IsolateNatives.spawn(null, uri, false); + } +} + +/** Default factory for receive ports. */ +patch class ReceivePort { + patch factory ReceivePort() { + return new ReceivePortImpl(); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart new file mode 100644 index 0000000..e33ae3f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart
@@ -0,0 +1,295 @@ +// 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. + +part of _interceptors; + +/** + * The interceptor class for [List]. The compiler recognizes this + * class as an interceptor, and changes references to [:this:] to + * actually use the receiver of the method, which is generated as an extra + * argument added to each member. + */ +class JSArray<E> implements List<E> { + const JSArray(); + + void add(E value) { + checkGrowable(this, 'add'); + JS('void', r'#.push(#)', this, value); + } + + E removeAt(int index) { + if (index is !int) throw new ArgumentError(index); + if (index < 0 || index >= length) { + throw new RangeError.value(index); + } + checkGrowable(this, 'removeAt'); + return JS('var', r'#.splice(#, 1)[0]', this, index); + } + + E removeLast() { + checkGrowable(this, 'removeLast'); + if (length == 0) throw new RangeError.value(-1); + return JS('var', r'#.pop()', this); + } + + void remove(Object element) { + checkGrowable(this, 'remove'); + for (int i = 0; i < this.length; i++) { + if (this[i] == element) { + JS('var', r'#.splice(#, 1)', this, i); + return; + } + } + } + + void removeAll(Iterable elements) { + IterableMixinWorkaround.removeAllList(this, elements); + } + + void retainAll(Iterable elements) { + IterableMixinWorkaround.retainAll(this, elements); + } + + void removeMatching(bool test(E element)) { + // This could, and should, be optimized. + IterableMixinWorkaround.removeMatchingList(this, test); + } + + void retainMatching(bool test(E element)) { + IterableMixinWorkaround.removeMatchingList(this, + (E element) => !test(element)); + } + + Iterable<E> where(bool f(E element)) { + return IterableMixinWorkaround.where(this, f); + } + + Iterable expand(Iterable f(E element)) { + return IterableMixinWorkaround.expand(this, f); + } + + void addAll(Collection<E> collection) { + for (E e in collection) { + this.add(e); + } + } + + void addLast(E value) { + checkGrowable(this, 'addLast'); + JS('void', r'#.push(#)', this, value); + } + + void clear() { + length = 0; + } + + void forEach(void f(E element)) { + return IterableMixinWorkaround.forEach(this, f); + } + + Iterable map(f(E element)) { + return IterableMixinWorkaround.mapList(this, f); + } + + List mappedBy(f(E element)) { + return IterableMixinWorkaround.mappedByList(this, f); + } + + String join([String separator]) { + if (separator == null) separator = ""; + var list = new List(this.length); + for (int i = 0; i < this.length; i++) { + list[i] = "${this[i]}"; + } + return JS('String', "#.join(#)", list, separator); + } + + Iterable<E> take(int n) { + return IterableMixinWorkaround.takeList(this, n); + } + + Iterable<E> takeWhile(bool test(E value)) { + return IterableMixinWorkaround.takeWhile(this, test); + } + + Iterable<E> skip(int n) { + return IterableMixinWorkaround.skipList(this, n); + } + + Iterable<E> skipWhile(bool test(E value)) { + return IterableMixinWorkaround.skipWhile(this, test); + } + + reduce(initialValue, combine(previousValue, E element)) { + return IterableMixinWorkaround.reduce(this, initialValue, combine); + } + + E firstMatching(bool test(E value), {E orElse()}) { + return IterableMixinWorkaround.firstMatching(this, test, orElse); + } + + E lastMatching(bool test(E value), {E orElse()}) { + return IterableMixinWorkaround.lastMatchingInList(this, test, orElse); + } + + E singleMatching(bool test(E value)) { + return IterableMixinWorkaround.singleMatching(this, test); + } + + E elementAt(int index) { + return this[index]; + } + + List<E> getRange(int start, int length) { + // TODO(ngeoffray): Parameterize the return value. + if (0 == length) return []; + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + if (start < 0) throw new RangeError.value(start); + int end = start + length; + if (end > this.length) { + throw new RangeError.value(length); + } + if (length < 0) throw new ArgumentError(length); + return JS('=List', r'#.slice(#, #)', this, start, end); + } + + void insertRange(int start, int length, [E initialValue]) { + return listInsertRange(this, start, length, initialValue); + } + + E get first { + if (length > 0) return this[0]; + throw new StateError("No elements"); + } + + E get last { + if (length > 0) return this[length - 1]; + throw new StateError("No elements"); + } + + E get single { + if (length == 1) return this[0]; + if (length == 0) throw new StateError("No elements"); + throw new StateError("More than one element"); + } + + E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare); + + E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare); + + void removeRange(int start, int length) { + checkGrowable(this, 'removeRange'); + if (length == 0) { + return; + } + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + var receiverLength = this.length; + if (start < 0 || start >= receiverLength) { + throw new RangeError.value(start); + } + if (start + length > receiverLength) { + throw new RangeError.value(start + length); + } + Arrays.copy(this, + start + length, + this, + start, + receiverLength - length - start); + this.length = receiverLength - length; + } + + void setRange(int start, int length, List<E> from, [int startFrom = 0]) { + checkMutable(this, 'set range'); + if (length == 0) return; + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + checkNull(from); // TODO(ahe): This is not specified but co19 tests it. + checkNull(startFrom); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (startFrom is !int) throw new ArgumentError(startFrom); + if (length < 0) throw new ArgumentError(length); + if (start < 0) throw new RangeError.value(start); + if (start + length > this.length) { + throw new RangeError.value(start + length); + } + + Arrays.copy(from, startFrom, this, start, length); + } + + bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); + + bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); + + List<E> get reversed => IterableMixinWorkaround.reversedList(this); + + void sort([int compare(E a, E b)]) { + checkMutable(this, 'sort'); + IterableMixinWorkaround.sortList(this, compare); + } + + int indexOf(E element, [int start = 0]) { + if (start is !int) throw new ArgumentError(start); + return Arrays.indexOf(this, element, start, length); + } + + int lastIndexOf(E element, [int start]) { + if (start == null) start = this.length - 1; + return Arrays.lastIndexOf(this, element, start); + } + + bool contains(E other) { + for (int i = 0; i < length; i++) { + if (other == this[i]) return true; + } + return false; + } + + bool get isEmpty => length == 0; + + String toString() => Collections.collectionToString(this); + + List<E> toList() => new List<E>.from(this); + + Set<E> toSet() => new Set<E>.from(this); + + Iterator<E> get iterator => new ListIterator<E>(this); + + int get hashCode => Primitives.objectHashCode(this); + + Type get runtimeType { + // Call getRuntimeTypeString to get the name including type arguments. + return new TypeImpl(getRuntimeTypeString(this)); + } + + int get length => JS('int', r'#.length', this); + + void set length(int newLength) { + if (newLength is !int) throw new ArgumentError(newLength); + if (newLength < 0) throw new RangeError.value(newLength); + checkGrowable(this, 'set length'); + JS('void', r'#.length = #', this, newLength); + } + + E operator [](int index) { + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + return JS('var', '#[#]', this, index); + } + + void operator []=(int index, E value) { + checkMutable(this, 'indexed set'); + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + JS('void', r'#[#] = #', this, index, value); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart new file mode 100644 index 0000000..0fbff9c --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart
@@ -0,0 +1,1513 @@ +// 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. + +library _js_helper; + +import 'dart:collection'; +import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, + JS, + JS_CALL_IN_ISOLATE, + JS_CURRENT_ISOLATE, + JS_OPERATOR_IS_PREFIX, + JS_HAS_EQUALS, + RAW_DART_FUNCTION_REF, + UNINTERCEPTED; +import 'dart:_interceptors' show getInterceptor; + +part 'constant_map.dart'; +part 'native_helper.dart'; +part 'regexp_helper.dart'; +part 'string_helper.dart'; + +bool isJsArray(var value) { + return value != null && JS('bool', r'#.constructor === Array', value); +} + +checkMutable(list, reason) { + if (JS('bool', r'!!(#.immutable$list)', list)) { + throw new UnsupportedError(reason); + } +} + +checkGrowable(list, reason) { + if (JS('bool', r'!!(#.fixed$length)', list)) { + throw new UnsupportedError(reason); + } +} + +String S(value) { + if (value is String) return value; + if ((value is num && value != 0) || value is bool) { + return JS('String', r'String(#)', value); + } + if (value == null) return 'null'; + var res = value.toString(); + if (res is !String) throw new ArgumentError(value); + return res; +} + +createInvocationMirror(name, internalName, type, arguments, argumentNames) => + new JSInvocationMirror(name, internalName, type, arguments, argumentNames); + +class JSInvocationMirror implements InvocationMirror { + static const METHOD = 0; + static const GETTER = 1; + static const SETTER = 2; + + final String memberName; + final String _internalName; + final int _kind; + final List _arguments; + final List _namedArgumentNames; + /** Map from argument name to index in _arguments. */ + Map<String,dynamic> _namedIndices = null; + + JSInvocationMirror(this.memberName, + this._internalName, + this._kind, + this._arguments, + this._namedArgumentNames); + + bool get isMethod => _kind == METHOD; + bool get isGetter => _kind == GETTER; + bool get isSetter => _kind == SETTER; + bool get isAccessor => _kind != METHOD; + + List get positionalArguments { + if (isGetter) return null; + var list = []; + var argumentCount = + _arguments.length - _namedArgumentNames.length; + for (var index = 0 ; index < argumentCount ; index++) { + list.add(_arguments[index]); + } + return list; + } + + Map<String,dynamic> get namedArguments { + if (isAccessor) return null; + var map = <String,dynamic>{}; + int namedArgumentCount = _namedArgumentNames.length; + int namedArgumentsStartIndex = _arguments.length - namedArgumentCount; + for (int i = 0; i < namedArgumentCount; i++) { + map[_namedArgumentNames[i]] = _arguments[namedArgumentsStartIndex + i]; + } + return map; + } + + static final _objectInterceptor = getInterceptor(new Object()); + invokeOn(Object object) { + var interceptor = getInterceptor(object); + var receiver = object; + var name = _internalName; + var arguments = _arguments; + if (identical(interceptor, _objectInterceptor)) { + if (!isJsArray(arguments)) arguments = new List.from(arguments); + } else { + arguments = [object]..addAll(arguments); + receiver = interceptor; + } + return JS("var", "#[#].apply(#, #)", receiver, name, receiver, arguments); + } +} + +class Primitives { + static int hashCodeSeed = 0; + + static int objectHashCode(object) { + int hash = JS('var', r'#.$identityHash', object); + if (hash == null) { + // TOOD(ahe): We should probably randomize this somehow. + hash = ++hashCodeSeed; + JS('void', r'#.$identityHash = #', object, hash); + } + return hash; + } + + /** + * This is the low-level method that is used to implement + * [print]. It is possible to override this function from JavaScript + * by defining a function in JavaScript called "dartPrint". + */ + static void printString(String string) { + if (JS('bool', r'typeof dartPrint == "function"')) { + // Support overriding print from JavaScript. + JS('void', r'dartPrint(#)', string); + return; + } + + // Inside browser. + if (JS('bool', r'typeof window == "object"')) { + // On IE, the console is only defined if dev tools is open. + if (JS('bool', r'typeof console == "object"')) { + JS('void', r'console.log(#)', string); + } + return; + } + + // Running in d8, the V8 developer shell, or in Firefox' js-shell. + if (JS('bool', r'typeof print == "function"')) { + JS('void', r'print(#)', string); + return; + } + + // This is somewhat nasty, but we don't want to drag in a bunch of + // dependencies to handle a situation that cannot happen. So we + // avoid using Dart [:throw:] and Dart [toString]. + JS('void', "throw 'Unable to print message: ' + String(#)", string); + } + + static void _throwFormatException(String string) { + throw new FormatException(string); + } + + static int parseInt(String source, + int radix, + int handleError(String source)) { + if (handleError == null) handleError = _throwFormatException; + + checkString(source); + var match = JS('=List|Null', + r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)', + source); + int digitsIndex = 1; + int hexIndex = 2; + int decimalIndex = 3; + int nonDecimalHexIndex = 4; + if (radix == null) { + radix = 10; + if (match != null) { + if (match[hexIndex] != null) { + // Cannot fail because we know that the digits are all hex. + return JS('num', r'parseInt(#, 16)', source); + } + if (match[decimalIndex] != null) { + // Cannot fail because we know that the digits are all decimal. + return JS('num', r'parseInt(#, 10)', source); + } + return handleError(source); + } + } else { + if (radix is! int) throw new ArgumentError("Radix is not an integer"); + if (radix < 2 || radix > 36) { + throw new RangeError("Radix $radix not in range 2..36"); + } + if (match != null) { + if (radix == 10 && match[decimalIndex] != null) { + // Cannot fail because we know that the digits are all decimal. + return JS('num', r'parseInt(#, 10)', source); + } + if (radix < 10 || match[decimalIndex] == null) { + // We know that the characters must be ASCII as otherwise the + // regexp wouldn't have matched. Calling toLowerCase is thus + // guaranteed to be a safe operation. If it wasn't ASCII, then + // "İ" would become "i", and we would accept it for radices greater + // than 18. + int maxCharCode; + if (radix <= 10) { + // Allow all digits less than the radix. For example 0, 1, 2 for + // radix 3. + // "0".charCodeAt(0) + radix - 1; + maxCharCode = 0x30 + radix - 1; + } else { + // Characters are located after the digits in ASCII. Therefore we + // only check for the character code. The regexp above made already + // sure that the string does not contain anything but digits or + // characters. + // "0".charCodeAt(0) + radix - 1; + maxCharCode = 0x61 + radix - 10 - 1; + } + String digitsPart = match[digitsIndex].toLowerCase(); + for (int i = 0; i < digitsPart.length; i++) { + if (digitsPart.charCodeAt(i) > maxCharCode) { + return handleError(source); + } + } + } + } + } + if (match == null) return handleError(source); + return JS('num', r'parseInt(#, #)', source, radix); + } + + static double parseDouble(String source, int handleError(String source)) { + checkString(source); + if (handleError == null) handleError = _throwFormatException; + // Notice that JS parseFloat accepts garbage at the end of the string. + // Accept only: + // - NaN + // - [+/-]Infinity + // - a Dart double literal + // We do not allow leading or trailing whitespace. + if (!JS('bool', + r'/^\s*(?:NaN|[+-]?(?:Infinity|' + r'(?:\.\d+|\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*$/.test(#)', + source)) { + return handleError(source); + } + var result = JS('num', r'parseFloat(#)', source); + if (result.isNaN && source != 'NaN') { + return handleError(source); + } + return result; + } + + /** [: r"$".charCodeAt(0) :] */ + static const int DOLLAR_CHAR_VALUE = 36; + + static String objectTypeName(Object object) { + String name = constructorNameFallback(object); + if (name == 'Object') { + // Try to decompile the constructor by turning it into a string + // and get the name out of that. If the decompiled name is a + // string, we use that instead of the very generic 'Object'. + var decompiled = JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]', + JS('var', r'String(#.constructor)', object)); + if (decompiled is String) name = decompiled; + } + // TODO(kasperl): If the namer gave us a fresh global name, we may + // want to remove the numeric suffix that makes it unique too. + if (identical(name.charCodeAt(0), DOLLAR_CHAR_VALUE)) name = name.substring(1); + return name; + } + + static String objectToString(Object object) { + String name = objectTypeName(object); + return "Instance of '$name'"; + } + + static List newGrowableList(length) { + return JS('=List', r'new Array(#)', length); + } + + static List newFixedList(length) { + var result = JS('=List', r'new Array(#)', length); + JS('void', r'#.fixed$length = #', result, true); + return result; + } + + static num dateNow() => JS('num', r'Date.now()'); + + static num numMicroseconds() { + if (JS('bool', 'typeof window != "undefined" && window !== null')) { + var performance = JS('var', 'window.performance'); + if (performance != null && + JS('bool', 'typeof #.webkitNow == "function"', performance)) { + return (1000 * JS('num', '#.webkitNow()', performance)).floor(); + } + } + return 1000 * dateNow(); + } + + // This is to avoid stack overflows due to very large argument arrays in + // apply(). It fixes http://dartbug.com/6919 + static String _fromCharCodeApply(List<int> array) { + String result = ""; + const kMaxApply = 500; + int end = array.length; + for (var i = 0; i < end; i += kMaxApply) { + var subarray; + if (end <= kMaxApply) { + subarray = array; + } else { + subarray = JS('=List', r'#.slice(#, #)', array, + i, i + kMaxApply < end ? i + kMaxApply : end); + } + result = JS('String', '# + String.fromCharCode.apply(#, #)', + result, null, subarray); + } + return result; + } + + static String stringFromCodePoints(codePoints) { + List<int> a = <int>[]; + for (var i in codePoints) { + if (i is !int) throw new ArgumentError(i); + if (i <= 0xffff) { + a.add(i); + } else if (i <= 0x10ffff) { + a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff))); + a.add(0xdc00 + (i & 0x3ff)); + } else { + throw new ArgumentError(i); + } + } + return _fromCharCodeApply(a); + } + + static String stringFromCharCodes(charCodes) { + for (var i in charCodes) { + if (i is !int) throw new ArgumentError(i); + if (i < 0) throw new ArgumentError(i); + if (i > 0xffff) return stringFromCodePoints(charCodes); + } + return _fromCharCodeApply(charCodes); + } + + static String getTimeZoneName(receiver) { + // When calling toString on a Date it will emit the timezone in parenthesis. + // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)". + // We extract this name using a regexp. + var d = lazyAsJsDate(receiver); + return JS('String', r'/\((.*)\)/.exec(#.toString())[1]', d); + } + + static int getTimeZoneOffsetInMinutes(receiver) { + // Note that JS and Dart disagree on the sign of the offset. + return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver)); + } + + static valueFromDecomposedDate(years, month, day, hours, minutes, seconds, + milliseconds, isUtc) { + final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000; + checkInt(years); + checkInt(month); + checkInt(day); + checkInt(hours); + checkInt(minutes); + checkInt(seconds); + checkInt(milliseconds); + checkBool(isUtc); + var jsMonth = month - 1; + var value; + if (isUtc) { + value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)', + years, jsMonth, day, hours, minutes, seconds, milliseconds); + } else { + value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()', + years, jsMonth, day, hours, minutes, seconds, milliseconds); + } + if (value.isNaN || + value < -MAX_MILLISECONDS_SINCE_EPOCH || + value > MAX_MILLISECONDS_SINCE_EPOCH) { + throw new ArgumentError(); + } + if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc); + return value; + } + + static patchUpY2K(value, years, isUtc) { + var date = JS('', r'new Date(#)', value); + if (isUtc) { + JS('num', r'#.setUTCFullYear(#)', date, years); + } else { + JS('num', r'#.setFullYear(#)', date, years); + } + return JS('num', r'#.valueOf()', date); + } + + // Lazily keep a JS Date stored in the JS object. + static lazyAsJsDate(receiver) { + if (JS('bool', r'#.date === (void 0)', receiver)) { + JS('void', r'#.date = new Date(#)', receiver, + receiver.millisecondsSinceEpoch); + } + return JS('var', r'#.date', receiver); + } + + // The getters for date and time parts below add a positive integer to ensure + // that the result is really an integer, because the JavaScript implementation + // may return -0.0 instead of 0. + + static getYear(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver)); + } + + static getMonth(receiver) { + return (receiver.isUtc) + ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver)) + : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver)); + } + + static getDay(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver)); + } + + static getHours(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver)); + } + + static getMinutes(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver)); + } + + static getSeconds(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver)); + } + + static getMilliseconds(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver)); + } + + static getWeekday(receiver) { + int weekday = (receiver.isUtc) + ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver)) + : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver)); + // Adjust by one because JS weeks start on Sunday. + return (weekday + 6) % 7 + 1; + } + + static valueFromDateString(str) { + if (str is !String) throw new ArgumentError(str); + var value = JS('num', r'Date.parse(#)', str); + if (value.isNaN) throw new ArgumentError(str); + return value; + } + + static getProperty(object, key) { + if (object == null || object is bool || object is num || object is String) { + throw new ArgumentError(object); + } + return JS('var', '#[#]', object, key); + } + + static void setProperty(object, key, value) { + if (object == null || object is bool || object is num || object is String) { + throw new ArgumentError(object); + } + JS('void', '#[#] = #', object, key, value); + } + + static applyFunction(Function function, + List positionalArguments, + Map<String, dynamic> namedArguments) { + int argumentCount = 0; + StringBuffer buffer = new StringBuffer(); + List arguments = []; + + if (positionalArguments != null) { + argumentCount += positionalArguments.length; + arguments.addAll(positionalArguments); + } + + // Sort the named arguments to get the right selector name and + // arguments order. + if (namedArguments != null && !namedArguments.isEmpty) { + // Call new List.from to make sure we get a JavaScript array. + List<String> listOfNamedArguments = + new List<String>.from(namedArguments.keys); + argumentCount += namedArguments.length; + // We're sorting on strings, and the behavior is the same between + // Dart string sort and JS string sort. To avoid needing the Dart + // sort implementation, we use the JavaScript one instead. + JS('void', '#.sort()', listOfNamedArguments); + listOfNamedArguments.forEach((String name) { + buffer.add('\$$name'); + arguments.add(namedArguments[name]); + }); + } + + String selectorName = 'call\$$argumentCount$buffer'; + var jsFunction = JS('var', '#[#]', function, selectorName); + if (jsFunction == null) { + throw new NoSuchMethodError(function, selectorName, arguments, {}); + } + // We bound 'this' to [function] because of how we compile + // closures: escaped local variables are stored and accessed through + // [function]. + return JS('var', '#.apply(#, #)', jsFunction, function, arguments); + } + + static getConstructor(String className) { + // TODO(ahe): How to safely access $? + return JS('var', r'$[#]', className); + } + + static bool identicalImplementation(a, b) { + return JS('bool', '# == null', a) + ? JS('bool', '# == null', b) + : JS('bool', '# === #', a, b); + } +} + +/** + * Called by generated code to throw an illegal-argument exception, + * for example, if a non-integer index is given to an optimized + * indexed access. + */ +iae(argument) { + throw new ArgumentError(argument); +} + +/** + * Called by generated code to throw an index-out-of-range exception, + * for example, if a bounds check fails in an optimized indexed + * access. + */ +ioore(index) { + throw new RangeError.value(index); +} + +listInsertRange(receiver, start, length, initialValue) { + if (length == 0) { + return; + } + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + if (start is !int) throw new ArgumentError(start); + + var receiverLength = JS('num', r'#.length', receiver); + if (start < 0 || start > receiverLength) { + throw new RangeError.value(start); + } + receiver.length = receiverLength + length; + Arrays.copy(receiver, + start, + receiver, + start + length, + receiverLength - start); + if (initialValue != null) { + for (int i = start; i < start + length; i++) { + receiver[i] = initialValue; + } + } + receiver.length = receiverLength + length; +} + +stringLastIndexOfUnchecked(receiver, element, start) + => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start); + + +checkNull(object) { + if (object == null) throw new ArgumentError(null); + return object; +} + +checkNum(value) { + if (value is !num) { + throw new ArgumentError(value); + } + return value; +} + +checkInt(value) { + if (value is !int) { + throw new ArgumentError(value); + } + return value; +} + +checkBool(value) { + if (value is !bool) { + throw new ArgumentError(value); + } + return value; +} + +checkString(value) { + if (value is !String) { + throw new ArgumentError(value); + } + return value; +} + +class MathNatives { + static double sqrt(num value) + => JS('double', r'Math.sqrt(#)', checkNum(value)); + + static double sin(num value) + => JS('double', r'Math.sin(#)', checkNum(value)); + + static double cos(num value) + => JS('double', r'Math.cos(#)', checkNum(value)); + + static double tan(num value) + => JS('double', r'Math.tan(#)', checkNum(value)); + + static double acos(num value) + => JS('double', r'Math.acos(#)', checkNum(value)); + + static double asin(num value) + => JS('double', r'Math.asin(#)', checkNum(value)); + + static double atan(num value) + => JS('double', r'Math.atan(#)', checkNum(value)); + + static double atan2(num a, num b) + => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b)); + + static double exp(num value) + => JS('double', r'Math.exp(#)', checkNum(value)); + + static double log(num value) + => JS('double', r'Math.log(#)', checkNum(value)); + + static num pow(num value, num exponent) { + checkNum(value); + checkNum(exponent); + return JS('num', r'Math.pow(#, #)', value, exponent); + } + + static double random() => JS('double', r'Math.random()'); +} + +/** + * Wrap the given Dart object and record a stack trace. + * + * The code in [unwrapException] deals with getting the original Dart + * object out of the wrapper again. + */ +$throw(ex) { + if (ex == null) ex = const NullThrownError(); + var wrapper = new DartError(ex); + + if (JS('bool', '!!Error.captureStackTrace')) { + // Use V8 API for recording a "fast" stack trace (this installs a + // "stack" property getter on [wrapper]). + JS('void', r'Error.captureStackTrace(#, #)', + wrapper, RAW_DART_FUNCTION_REF($throw)); + } else { + // Otherwise, produce a stack trace and record it in the wrapper. + // This is a slower way to create a stack trace which works on + // some browsers, but may simply evaluate to null. + String stackTrace = JS('', 'new Error().stack'); + JS('void', '#.stack = #', wrapper, stackTrace); + } + return wrapper; +} + +/** + * Wrapper class for throwing exceptions. + */ +class DartError { + /// The Dart object (or primitive JavaScript value) which was thrown is + /// attached to this object as a field named 'dartException'. We do this + /// only in raw JS so that we can use the 'in' operator and so that the + /// minifier does not rename the field. Therefore it is not declared as a + /// real field. + + DartError(var dartException) { + JS('void', '#.dartException = #', this, dartException); + // Install a toString method that the JavaScript system will call + // to format uncaught exceptions. + JS('void', '#.toString = #', this, DART_CLOSURE_TO_JS(toStringWrapper)); + } + + /** + * V8/Chrome installs a property getter, "stack", when calling + * Error.captureStackTrace (see [$throw]). In [$throw], we make sure + * that this property is always set. + */ + String get stack => JS('', '#.stack', this); + + /** + * This method can be invoked by calling toString from + * JavaScript. See the constructor of this class. + * + * We only expect this method to be called (indirectly) by the + * browser when an uncaught exception occurs. Instance of this class + * should never escape into Dart code (except for [$throw] above). + */ + String toString() { + // If Error.captureStackTrace is available, accessing stack from + // this method would cause recursion because the stack property + // (on this object) is actually a getter which calls toString on + // this object (via the wrapper installed in this class' + // constructor). Fortunately, both Chrome and d8 prints the stack + // trace and Chrome even applies source maps to the stack + // trace. Remeber, this method is only ever invoked by the browser + // when an uncaught exception occurs. + var dartException = JS('var', r'#.dartException', this); + if (JS('bool', '!!Error.captureStackTrace') || (stack == null)) { + return dartException.toString(); + } else { + return '$dartException\n$stack'; + } + } + + /** + * This method is installed as JavaScript toString method on + * [DartError]. So JavaScript 'this' binds to an instance of + * DartError. + */ + static toStringWrapper() => JS('', r'this').toString(); +} + +makeLiteralListConst(list) { + JS('bool', r'#.immutable$list = #', list, true); + JS('bool', r'#.fixed$length = #', list, true); + return list; +} + +throwRuntimeError(message) { + throw new RuntimeError(message); +} + +/** + * The SSA builder generates a call to this method when a malformed type is used + * in a subtype test. + */ +throwMalformedSubtypeError(value, type, reasons) { + throw new TypeErrorImplementation.malformedSubtype(value, type, reasons); +} + +throwAbstractClassInstantiationError(className) { + throw new AbstractClassInstantiationError(className); +} + +/** + * Called from catch blocks in generated code to extract the Dart + * exception from the thrown value. The thrown value may have been + * created by [$throw] or it may be a 'native' JS exception. + * + * Some native exceptions are mapped to new Dart instances, others are + * returned unmodified. + */ +unwrapException(ex) { + // Note that we are checking if the object has the property. If it + // has, it could be set to null if the thrown value is null. + if (JS('bool', r'"dartException" in #', ex)) { + return JS('', r'#.dartException', ex); + } + + // Grab hold of the exception message. This field is available on + // all supported browsers. + var message = JS('var', r'#.message', ex); + + if (JS('bool', r'# instanceof TypeError', ex)) { + // The type and arguments fields are Chrome specific but they + // allow us to get very detailed information about what kind of + // exception occurred. + var type = JS('var', r'#.type', ex); + var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex); + if (contains(message, 'JSNull') || + type == 'property_not_function' || + type == 'called_non_callable' || + type == 'non_object_property_call' || + type == 'non_object_property_load') { + return new NoSuchMethodError(null, name, [], {}); + } else if (type == 'undefined_method') { + return new NoSuchMethodError('', name, [], {}); + } + + var ieErrorCode = JS('int', '#.number & 0xffff', ex); + var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex); + // If we cannot use [type] to determine what kind of exception + // we're dealing with we fall back on looking at the exception + // message if it is available and a string. + if (message is String) { + if (message.endsWith('is null') || + message.endsWith('is undefined') || + message.endsWith('is null or undefined') || + message.endsWith('of undefined') || + message.endsWith('of null')) { + return new NoSuchMethodError(null, '<unknown>', [], {}); + } else if (contains(message, ' has no method ') || + contains(message, ' is not a function') || + (ieErrorCode == 438 && ieFacilityNumber == 10)) { + // Examples: + // x.foo is not a function + // 'undefined' is not a function (evaluating 'x.foo(1,2,3)') + // Object doesn't support property or method 'foo' which sets the error + // code 438 in IE. + // TODO(kasperl): Compute the right name if possible. + return new NoSuchMethodError('', '<unknown>', [], {}); + } + } + + // If we cannot determine what kind of error this is, we fall back + // to reporting this as a generic exception. It's probably better + // than nothing. + return new Exception(message is String ? message : ''); + } + + if (JS('bool', r'# instanceof RangeError', ex)) { + if (message is String && contains(message, 'call stack')) { + return new StackOverflowError(); + } + + // In general, a RangeError is thrown when trying to pass a number + // as an argument to a function that does not allow a range that + // includes that number. + return new ArgumentError(); + } + + // Check for the Firefox specific stack overflow signal. + if (JS('bool', + r"typeof InternalError == 'function' && # instanceof InternalError", + ex)) { + if (message is String && message == 'too much recursion') { + return new StackOverflowError(); + } + } + + // Just return the exception. We should not wrap it because in case + // the exception comes from the DOM, it is a JavaScript + // object backed by a native Dart class. + return ex; +} + +/** + * Called by generated code to fetch the stack trace from an + * exception. + */ +StackTrace getTraceFromException(exception) { + return new StackTrace(JS("var", r"#.stack", exception)); +} + +class StackTrace { + var stack; + StackTrace(this.stack); + String toString() => stack != null ? stack : ''; +} + + +/** + * Called by generated code to build a map literal. [keyValuePairs] is + * a list of key, value, key, value, ..., etc. + */ +makeLiteralMap(List keyValuePairs) { + Iterator iterator = keyValuePairs.iterator; + Map result = new LinkedHashMap(); + while (iterator.moveNext()) { + String key = iterator.current; + iterator.moveNext(); + var value = iterator.current; + result[key] = value; + } + return result; +} + +invokeClosure(Function closure, + var isolate, + int numberOfArguments, + var arg1, + var arg2) { + if (numberOfArguments == 0) { + return JS_CALL_IN_ISOLATE(isolate, () => closure()); + } else if (numberOfArguments == 1) { + return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1)); + } else if (numberOfArguments == 2) { + return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2)); + } else { + throw new Exception( + 'Unsupported number of arguments for wrapped closure'); + } +} + +/** + * Called by generated code to convert a Dart closure to a JS + * closure when the Dart closure is passed to the DOM. + */ +convertDartClosureToJS(closure, int arity) { + if (closure == null) return null; + var function = JS('var', r'#.$identity', closure); + if (JS('bool', r'!!#', function)) return function; + // By fetching the current isolate before creating the JavaScript + // function, we prevent the compiler from inlining its use in + // the JavaScript function below (the compiler generates code for + // fetching the isolate before creating the JavaScript function). + // If it was inlined, the JavaScript function would not get the + // current isolate, but the one that is active when the callback + // executes. + var currentIsolate = JS_CURRENT_ISOLATE(); + + // We use $0 and $1 to not clash with variable names used by the + // compiler and/or minifier. + function = JS("var", + r"""function($0, $1) { return #(#, #, #, $0, $1); }""", + DART_CLOSURE_TO_JS(invokeClosure), + closure, + JS_CURRENT_ISOLATE(), + arity); + + JS('void', r'#.$identity = #', closure, function); + return function; +} + +/** + * Super class for Dart closures. + */ +class Closure implements Function { + String toString() => "Closure"; +} + +bool jsHasOwnProperty(var jsObject, String property) { + return JS('bool', r'#.hasOwnProperty(#)', jsObject, property); +} + +jsPropertyAccess(var jsObject, String property) { + return JS('var', r'#[#]', jsObject, property); +} + +/** + * Called at the end of unaborted switch cases to get the singleton + * FallThroughError exception that will be thrown. + */ +getFallThroughError() => const FallThroughErrorImplementation(); + +/** + * Represents the type Dynamic. The compiler treats this specially. + */ +abstract class Dynamic_ { +} + +/** + * A metadata annotation describing the types instantiated by a native element. + * + * The annotation is valid on a native method and a field of a native class. + * + * By default, a field of a native class is seen as an instantiation point for + * all native classes that are a subtype of the field's type, and a native + * method is seen as an instantiation point fo all native classes that are a + * subtype of the method's return type, or the argument types of the declared + * type of the method's callback parameter. + * + * An @[Creates] annotation overrides the default set of instantiated types. If + * one or more @[Creates] annotations are present, the type of the native + * element is ignored, and the union of @[Creates] annotations is used instead. + * The names in the strings are resolved and the program will fail to compile + * with dart2js if they do not name types. + * + * The argument to [Creates] is a string. The string is parsed as the names of + * one or more types, separated by vertical bars `|`. There are some special + * names: + * + * * `=List`. This means 'exactly List', which is the JavaScript Array + * implementation of [List] and no other implementation. + * + * * `=Object`. This means 'exactly Object', which is a plain JavaScript object + * with properties and none of the subtypes of Object. + * + * Example: we may know that a method always returns a specific implementation: + * + * @Creates('_NodeList') + * List<Node> getElementsByTagName(String tag) native; + * + * Useful trick: A method can be marked as not instantiating any native classes + * with the annotation `@Creates('Null')`. This is useful for fields on native + * classes that are used only in Dart code. + * + * @Creates('Null') + * var _cachedFoo; + */ +class Creates { + final String types; + const Creates(this.types); +} + +/** + * A metadata annotation describing the types returned or yielded by a native + * element. + * + * The annotation is valid on a native method and a field of a native class. + * + * By default, a native method or field is seen as returning or yielding all + * subtypes if the method return type or field type. This annotation allows a + * more precise set of types to be specified. + * + * See [Creates] for the syntax of the argument. + * + * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys. + * + * @Returns('String|num|=List') + * dynamic key; + * + * // Equivalent: + * @Returns('String') @Returns('num') @Returns('=List') + * dynamic key; + */ +class Returns { + final String types; + const Returns(this.types); +} + +/** + * A metadata annotation placed on native methods and fields of native classes + * to specify the JavaScript name. + * + * This example declares a Dart field + getter + setter called `$dom_title` that + * corresponds to the JavaScript property `title`. + * + * class Docmument native "*Foo" { + * @JSName('title') + * String $dom_title; + * } + */ +class JSName { + final String name; + const JSName(this.name); +} + +/** + * Represents the type of Null. The compiler treats this specially. + * TODO(lrn): Null should be defined in core. It's a class, like int. + * It just happens to act differently in assignability tests and, + * like int, can't be extended or implemented. + */ +class Null { + factory Null() { + throw new UnsupportedError('new Null()'); + } +} + +setRuntimeTypeInfo(target, typeInfo) { + assert(typeInfo == null || isJsArray(typeInfo)); + // We have to check for null because factories may return null. + if (target != null) JS('var', r'#.$builtinTypeInfo = #', target, typeInfo); +} + +getRuntimeTypeInfo(target) { + if (target == null) return null; + var res = JS('var', r'#.$builtinTypeInfo', target); + // If the object does not have runtime type information, return an + // empty literal, to avoid null checks. + // TODO(ngeoffray): Make the object a top-level field to avoid + // allocating a new object every single time. + return (res == null) ? JS('var', '{}') : res; +} + +/** + * The following methods are called by the runtime to implement + * checked mode and casts. We specialize each primitive type (eg int, bool), and + * use the compiler's convention to do is-checks on regular objects. + */ +boolConversionCheck(value) { + boolTypeCheck(value); + assert(value != null); + return value; +} + +stringTypeCheck(value) { + if (value == null) return value; + if (value is String) return value; + throw new TypeErrorImplementation(value, 'String'); +} + +stringTypeCast(value) { + if (value is String || value == null) return value; + // TODO(lrn): When reified types are available, pass value.class and String. + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'String'); +} + +doubleTypeCheck(value) { + if (value == null) return value; + if (value is double) return value; + throw new TypeErrorImplementation(value, 'double'); +} + +doubleTypeCast(value) { + if (value is double || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'double'); +} + +numTypeCheck(value) { + if (value == null) return value; + if (value is num) return value; + throw new TypeErrorImplementation(value, 'num'); +} + +numTypeCast(value) { + if (value is num || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'num'); +} + +boolTypeCheck(value) { + if (value == null) return value; + if (value is bool) return value; + throw new TypeErrorImplementation(value, 'bool'); +} + +boolTypeCast(value) { + if (value is bool || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'bool'); +} + +functionTypeCheck(value) { + if (value == null) return value; + if (value is Function) return value; + throw new TypeErrorImplementation(value, 'Function'); +} + +functionTypeCast(value) { + if (value is Function || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'Function'); +} + +intTypeCheck(value) { + if (value == null) return value; + if (value is int) return value; + throw new TypeErrorImplementation(value, 'int'); +} + +intTypeCast(value) { + if (value is int || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'int'); +} + +void propertyTypeError(value, property) { + // Cuts the property name to the class name. + String name = property.substring(3, property.length); + throw new TypeErrorImplementation(value, name); +} + +void propertyTypeCastError(value, property) { + // Cuts the property name to the class name. + String actualType = Primitives.objectTypeName(value); + String expectedType = property.substring(3, property.length); + throw new CastErrorImplementation(actualType, expectedType); +} + +/** + * For types that are not supertypes of native (eg DOM) types, + * we emit a simple property check to check that an object implements + * that type. + */ +propertyTypeCheck(value, property) { + if (value == null) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +/** + * For types that are not supertypes of native (eg DOM) types, + * we emit a simple property check to check that an object implements + * that type. + */ +propertyTypeCast(value, property) { + if (value == null || JS('bool', '!!#[#]', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * For types that are supertypes of native (eg DOM) types, we emit a + * call because we cannot add a JS property to their prototype at load + * time. + */ +callTypeCheck(value, property) { + if (value == null) return value; + if ((identical(JS('String', 'typeof #', value), 'object')) + && JS('bool', '#[#]()', value, property)) { + return value; + } + propertyTypeError(value, property); +} + +/** + * For types that are supertypes of native (eg DOM) types, we emit a + * call because we cannot add a JS property to their prototype at load + * time. + */ +callTypeCast(value, property) { + if (value == null + || ((JS('bool', 'typeof # === "object"', value)) + && JS('bool', '#[#]()', value, property))) { + return value; + } + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for num and String and their + * supertype since [value] can be a JS primitive. + */ +numberOrStringSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +numberOrStringSuperTypeCast(value, property) { + if (value is String) return value; + if (value is num) return value; + return propertyTypeCast(value, property); +} + +numberOrStringSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +numberOrStringSuperNativeTypeCast(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for String and its supertype + * since [value] can be a JS primitive. + */ +stringSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +stringSuperTypeCast(value, property) { + if (value is String) return value; + return propertyTypeCast(value, property); +} + +stringSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +stringSuperNativeTypeCast(value, property) { + if (value is String || value == null) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for List and its supertypes, + * since [value] can be a JS array. + */ +listTypeCheck(value) { + if (value == null) return value; + if (value is List) return value; + throw new TypeErrorImplementation(value, 'List'); +} + +listTypeCast(value) { + if (value is List || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'List'); +} + +listSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is List) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +listSuperTypeCast(value, property) { + if (value is List) return value; + return propertyTypeCast(value, property); +} + +listSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is List) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +listSuperNativeTypeCast(value, property) { + if (value is List || value == null) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +voidTypeCheck(value) { + if (value == null) return value; + throw new TypeErrorImplementation(value, 'void'); +} + +malformedTypeCheck(value, type, reasons) { + if (value == null) return value; + throwMalformedSubtypeError(value, type, reasons); +} + +/** + * Special interface recognized by the compiler and implemented by DOM + * objects that support integer indexing. This interface is not + * visible to anyone, and is only injected into special libraries. + */ +abstract class JavaScriptIndexingBehavior { +} + +// TODO(lrn): These exceptions should be implemented in core. +// When they are, remove the 'Implementation' here. + +/** Thrown by type assertions that fail. */ +class TypeErrorImplementation implements TypeError { + final String message; + + /** + * Normal type error caused by a failed subtype test. + */ + TypeErrorImplementation(Object value, String type) + : message = "type '${Primitives.objectTypeName(value)}' is not a subtype " + "of type '$type'"; + + /** + * Type error caused by a subtype test on a malformed type. + */ + TypeErrorImplementation.malformedSubtype(Object value, + String type, String reasons) + : message = "type '${Primitives.objectTypeName(value)}' is not a subtype " + "of type '$type' because '$type' is malformed: $reasons."; + + String toString() => message; +} + +/** Thrown by the 'as' operator if the cast isn't valid. */ +class CastErrorImplementation implements CastError { + // TODO(lrn): Rename to CastError (and move implementation into core). + // TODO(lrn): Change actualType and expectedType to "Type" when reified + // types are available. + final Object actualType; + final Object expectedType; + + CastErrorImplementation(this.actualType, this.expectedType); + + String toString() { + return "CastError: Casting value of type $actualType to" + " incompatible type $expectedType"; + } +} + +class FallThroughErrorImplementation implements FallThroughError { + const FallThroughErrorImplementation(); + String toString() => "Switch case fall-through."; +} + +/** + * Helper function for implementing asserts. The compiler treats this specially. + */ +void assertHelper(condition) { + if (condition is Function) condition = condition(); + if (condition is !bool) { + throw new TypeErrorImplementation(condition, 'bool'); + } + // Compare to true to avoid boolean conversion check in checked + // mode. + if (!identical(condition, true)) throw new AssertionError(); +} + +/** + * Called by generated code when a method that must be statically + * resolved cannot be found. + */ +void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) { + throw new NoSuchMethodError(obj, name, arguments, const {}, + expectedArgumentNames); +} + +/** + * Called by generated code when a static field's initializer references the + * field that is currently being initialized. + */ +void throwCyclicInit(String staticName) { + throw new RuntimeError("Cyclic initialization for static $staticName"); +} + +class TypeImpl implements Type { + final String typeName; + TypeImpl(this.typeName); + toString() => typeName; + int get hashCode => typeName.hashCode; + bool operator ==(other) { + if (other is !TypeImpl) return false; + return typeName == other.typeName; + } +} + +String getClassName(var object) { + return JS('String', r'#.constructor.builtin$cls', object); +} + +String getTypeArgumentAsString(List runtimeType) { + String className = getConstructorName(runtimeType[0]); + if (runtimeType.length == 1) return className; + return '$className<${joinArguments(runtimeType, 1)}>'; +} + +String getConstructorName(type) => JS('String', r'#.builtin$cls', type); + +String runtimeTypeToString(type) { + if (type == null) { + return 'dynamic'; + } else if (isJsArray(type)) { + // A list representing a type with arguments. + return getTypeArgumentAsString(type); + } else { + // A reference to the constructor. + return getConstructorName(type); + } +} + +String joinArguments(var types, int startIndex) { + bool firstArgument = true; + StringBuffer buffer = new StringBuffer(); + for (int index = startIndex; index < types.length; index++) { + if (firstArgument) { + firstArgument = false; + } else { + buffer. add(', '); + } + var argument = types[index]; + buffer.add(runtimeTypeToString(argument)); + } + return buffer.toString(); +} + +String getRuntimeTypeString(var object) { + String className = isJsArray(object) ? 'List' : getClassName(object); + var typeInfo = JS('var', r'#.$builtinTypeInfo', object); + if (typeInfo == null) return className; + return "$className<${joinArguments(typeInfo, 0)}>"; +} + +/** + * Check whether the type represented by [s] is a subtype of the type + * represented by [t]. + * + * Type representations can be: + * 1) a JavaScript constructor for a class C: the represented type is the raw + * type C. + * 2) a JavaScript object: this represents a class for which there is no + * JavaScript constructor, because it is only used in type arguments or it + * is native. The represented type is the raw type of this class. + * 3) a JavaScript array: the first entry is of type 1 or 2 and identifies the + * class of the type and the rest of the array are the type arguments. + * 4) [:null:]: the dynamic type. + */ +bool isSubtype(var s, var t) { + // If either type is dynamic, [s] is a subtype of [t]. + if (JS('bool', '# == null', s) || JS('bool', '# == null', t)) return true; + // Subtyping is reflexive. + if (JS('bool', '# === #', s, t)) return true; + // Get the object describing the class and check for the subtyping flag + // constructed from the type of [t]. + var typeOfS = isJsArray(s) ? s[0] : s; + var typeOfT = isJsArray(t) ? t[0] : t; + var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}'; + if (JS('var', r'#[#]', typeOfS, test) == null) return false; + // The class of [s] is a subclass of the class of [t]. If either of the types + // is raw, [s] is a subtype of [t]. + if (!isJsArray(s) || !isJsArray(t)) return true; + // Recursively check the type arguments. + int len = s.length; + if (len != t.length) return false; + for (int i = 1; i < len; i++) { + if (!isSubtype(s[i], t[i])) { + return false; + } + } + return true; +} + +createRuntimeType(String name) => new TypeImpl(name);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart new file mode 100644 index 0000000..fa5677f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart
@@ -0,0 +1,272 @@ +// 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. + +part of _interceptors; + +/** + * The super interceptor class for [JSInt] and [JSDouble]. The compiler + * recognizes this class as an interceptor, and changes references to + * [:this:] to actually use the receiver of the method, which is + * generated as an extra argument added to each member. + */ +class JSNumber implements num { + const JSNumber(); + + int compareTo(num b) { + if (b is! num) throw new ArgumentError(b); + if (this < b) { + return -1; + } else if (this > b) { + return 1; + } else if (this == b) { + if (this == 0) { + bool bIsNegative = b.isNegative; + if (isNegative == bIsNegative) return 0; + if (isNegative) return -1; + return 1; + } + return 0; + } else if (isNaN) { + if (b.isNaN) { + return 0; + } + return 1; + } else { + return -1; + } + } + + bool get isNegative => (this == 0) ? (1 / this) < 0 : this < 0; + + bool get isNaN => JS('bool', r'isNaN(#)', this); + + num remainder(num b) { + checkNull(b); // TODO(ngeoffray): This is not specified but co19 tests it. + if (b is! num) throw new ArgumentError(b); + return JS('num', r'# % #', this, b); + } + + num abs() => JS('num', r'Math.abs(#)', this); + + int toInt() { + if (isNaN) throw new UnsupportedError('NaN'); + if (isInfinite) throw new UnsupportedError('Infinity'); + num truncated = truncate(); + return JS('bool', r'# == -0.0', truncated) ? 0 : truncated; + } + + num ceil() => JS('num', r'Math.ceil(#)', this); + + num floor() => JS('num', r'Math.floor(#)', this); + + bool get isInfinite { + return JS('bool', r'# == Infinity', this) + || JS('bool', r'# == -Infinity', this); + } + + num round() { + if (this < 0) { + return JS('num', r'-Math.round(-#)', this); + } else { + return JS('num', r'Math.round(#)', this); + } + } + + num clamp(lowerLimit, upperLimit) { + if (lowerLimit is! num) throw new ArgumentError(lowerLimit); + if (upperLimit is! num) throw new ArgumentError(upperLimit); + if (lowerLimit.compareTo(upperLimit) > 0) { + throw new ArgumentError(lowerLimit); + } + if (this.compareTo(lowerLimit) < 0) return lowerLimit; + if (this.compareTo(upperLimit) > 0) return upperLimit; + return this; + } + + double toDouble() => this; + + num truncate() => this < 0 ? ceil() : floor(); + + String toStringAsFixed(int fractionDigits) { + checkNum(fractionDigits); + // TODO(floitsch): fractionDigits must be an integer. + if (fractionDigits < 0 || fractionDigits > 20) { + throw new RangeError(fractionDigits); + } + String result = JS('String', r'#.toFixed(#)', this, fractionDigits); + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toStringAsExponential([int fractionDigits]) { + String result; + if (fractionDigits != null) { + // TODO(floitsch): fractionDigits must be an integer. + checkNum(fractionDigits); + if (fractionDigits < 0 || fractionDigits > 20) { + throw new RangeError(fractionDigits); + } + result = JS('String', r'#.toExponential(#)', this, fractionDigits); + } else { + result = JS('String', r'#.toExponential()', this); + } + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toStringAsPrecision(int precision) { + // TODO(floitsch): precision must be an integer. + checkNum(precision); + if (precision < 1 || precision > 21) { + throw new RangeError(precision); + } + String result = JS('String', r'#.toPrecision(#)', + this, precision); + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toRadixString(int radix) { + checkNum(radix); + if (radix < 2 || radix > 36) throw new RangeError(radix); + return JS('String', r'#.toString(#)', this, radix); + } + + // Note: if you change this, also change the function [S]. + String toString() { + if (this == 0 && JS('bool', '(1 / #) < 0', this)) { + return '-0.0'; + } else { + return JS('String', r'String(#)', this); + } + } + + int get hashCode => JS('int', '# & 0x1FFFFFFF', this); + + num operator -() => JS('num', r'-#', this); + + num operator +(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# + #', this, other); + } + + num operator -(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# - #', this, other); + } + + num operator /(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# / #', this, other); + } + + num operator *(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# * #', this, other); + } + + num operator %(num other) { + if (other is !num) throw new ArgumentError(other); + // Euclidean Modulo. + num result = JS('num', r'# % #', this, other); + if (result == 0) return 0; // Make sure we don't return -0.0. + if (result > 0) return result; + if (JS('num', '#', other) < 0) { + return result - JS('num', '#', other); + } else { + return result + JS('num', '#', other); + } + } + + num operator ~/(num other) { + if (other is !num) throw new ArgumentError(other); + return (JS('num', r'# / #', this, other)).truncate(); + } + + // TODO(ngeoffray): Move the bit operations below to [JSInt] and + // make them take an int. Because this will make operations slower, + // we define these methods on number for now but we need to decide + // the grain at which we do the type checks. + + num operator <<(num other) { + if (other is !num) throw new ArgumentError(other); + if (JS('num', '#', other) < 0) throw new ArgumentError(other); + // JavaScript only looks at the last 5 bits of the shift-amount. Shifting + // by 33 is hence equivalent to a shift by 1. + if (JS('bool', r'# > 31', other)) return 0; + return JS('num', r'(# << #) >>> 0', this, other); + } + + num operator >>(num other) { + if (other is !num) throw new ArgumentError(other); + if (JS('num', '#', other) < 0) throw new ArgumentError(other); + if (JS('num', '#', this) > 0) { + // JavaScript only looks at the last 5 bits of the shift-amount. In JS + // shifting by 33 is hence equivalent to a shift by 1. Shortcut the + // computation when that happens. + if (JS('bool', r'# > 31', other)) return 0; + // Given that 'a' is positive we must not use '>>'. Otherwise a number + // that has the 31st bit set would be treated as negative and shift in + // ones. + return JS('num', r'# >>> #', this, other); + } + // For negative numbers we just clamp the shift-by amount. 'a' could be + // negative but not have its 31st bit set. The ">>" would then shift in + // 0s instead of 1s. Therefore we cannot simply return 0xFFFFFFFF. + if (JS('num', '#', other) > 31) other = 31; + return JS('num', r'(# >> #) >>> 0', this, other); + } + + num operator &(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# & #) >>> 0', this, other); + } + + num operator |(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# | #) >>> 0', this, other); + } + + num operator ^(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# ^ #) >>> 0', this, other); + } + + bool operator <(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# < #', this, other); + } + + bool operator >(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# > #', this, other); + } + + bool operator <=(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# <= #', this, other); + } + + bool operator >=(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# >= #', this, other); + } +} + +class JSInt extends JSNumber implements int { + const JSInt(); + + bool get isEven => (this & 1) == 0; + + bool get isOdd => (this & 1) == 1; + + Type get runtimeType => int; + + int operator ~() => JS('int', r'(~#) >>> 0', this); +} + +class JSDouble extends JSNumber implements double { + const JSDouble(); + Type get runtimeType => double; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart new file mode 100644 index 0000000..381a690 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart
@@ -0,0 +1,229 @@ +// 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. + +part of _interceptors; + +/** + * The interceptor class for [String]. The compiler recognizes this + * class as an interceptor, and changes references to [:this:] to + * actually use the receiver of the method, which is generated as an extra + * argument added to each member. + */ +class JSString implements String { + const JSString(); + + int charCodeAt(index) => codeUnitAt(index); + + int codeUnitAt(int index) { + if (index is !num) throw new ArgumentError(index); + if (index < 0) throw new RangeError.value(index); + if (index >= length) throw new RangeError.value(index); + return JS('int', r'#.charCodeAt(#)', this, index); + } + + Iterable<Match> allMatches(String str) { + checkString(str); + return allMatchesInStringUnchecked(this, str); + } + + String concat(String other) { + if (other is !String) throw new ArgumentError(other); + return JS('String', r'# + #', this, other); + } + + bool endsWith(String other) { + checkString(other); + int otherLength = other.length; + if (otherLength > length) return false; + return other == substring(length - otherLength); + } + + String replaceAll(Pattern from, String to) { + checkString(to); + return stringReplaceAllUnchecked(this, from, to); + } + + String replaceAllMapped(Pattern from, String convert(Match match)) { + return this.splitMapJoin(from, onMatch: convert); + } + + String splitMapJoin(Pattern from, + {String onMatch(Match match), + String onNonMatch(String nonMatch)}) { + return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch); + } + + String replaceFirst(Pattern from, String to) { + checkString(to); + return stringReplaceFirstUnchecked(this, from, to); + } + + List<String> split(Pattern pattern) { + checkNull(pattern); + if (pattern is String) { + return JS('=List', r'#.split(#)', this, pattern); + } else if (pattern is JSSyntaxRegExp) { + var re = regExpGetNative(pattern); + return JS('=List', r'#.split(#)', this, re); + } else { + throw "String.split(Pattern) UNIMPLEMENTED"; + } + } + + List<String> splitChars() { + return JS('=List', r'#.split("")', this); + } + + bool startsWith(String other) { + checkString(other); + int otherLength = other.length; + if (otherLength > length) return false; + return JS('bool', r'# == #', other, + JS('String', r'#.substring(0, #)', this, otherLength)); + } + + String substring(int startIndex, [int endIndex]) { + checkNum(startIndex); + if (endIndex == null) endIndex = length; + checkNum(endIndex); + if (startIndex < 0 ) throw new RangeError.value(startIndex); + if (startIndex > endIndex) throw new RangeError.value(startIndex); + if (endIndex > length) throw new RangeError.value(endIndex); + return JS('String', r'#.substring(#, #)', this, startIndex, endIndex); + } + + String slice([int startIndex, int endIndex]) { + int start, end; + if (startIndex == null) { + start = 0; + } else if (startIndex is! int) { + throw new ArgumentError("startIndex is not int"); + } else if (startIndex >= 0) { + start = startIndex; + } else { + start = this.length + startIndex; + } + if (start < 0 || start > this.length) { + throw new RangeError( + "startIndex out of range: $startIndex (length: $length)"); + } + if (endIndex == null) { + end = this.length; + } else if (endIndex is! int) { + throw new ArgumentError("endIndex is not int"); + } else if (endIndex >= 0) { + end = endIndex; + } else { + end = this.length + endIndex; + } + if (end < 0 || end > this.length) { + throw new RangeError( + "endIndex out of range: $endIndex (length: $length)"); + } + if (end < start) { + throw new ArgumentError( + "End before start: $endIndex < $startIndex (length: $length)"); + } + return JS('String', '#.substring(#, #)', this, start, end); + } + + + String toLowerCase() { + return JS('String', r'#.toLowerCase()', this); + } + + String toUpperCase() { + return JS('String', r'#.toUpperCase()', this); + } + + String trim() { + return JS('String', r'#.trim()', this); + } + + List<int> get charCodes { + List<int> result = new List<int>.fixedLength(length); + for (int i = 0; i < length; i++) { + result[i] = JS('int', '#.charCodeAt(#)', this, i); + } + return result; + } + + Iterable<int> get codeUnits { + throw new UnimplementedError("String.codeUnits"); + } + + Iterable<int> get runes { + throw new UnimplementedError("String.runes"); + } + + int indexOf(String other, [int start = 0]) { + checkNull(other); + if (start is !int) throw new ArgumentError(start); + if (other is !String) throw new ArgumentError(other); + if (start < 0) return -1; + return JS('int', r'#.indexOf(#, #)', this, other, start); + } + + int lastIndexOf(String other, [int start]) { + checkNull(other); + if (other is !String) throw new ArgumentError(other); + if (start != null) { + if (start is !num) throw new ArgumentError(start); + if (start < 0) return -1; + if (start >= length) { + if (other == "") return length; + start = length - 1; + } + } else { + start = length - 1; + } + return stringLastIndexOfUnchecked(this, other, start); + } + + bool contains(String other, [int startIndex = 0]) { + checkNull(other); + return stringContainsUnchecked(this, other, startIndex); + } + + bool get isEmpty => length == 0; + + int compareTo(String other) { + if (other is !String) throw new ArgumentError(other); + return this == other ? 0 + : JS('bool', r'# < #', this, other) ? -1 : 1; + } + + // Note: if you change this, also change the function [S]. + String toString() => this; + + /** + * This is the [Jenkins hash function][1] but using masking to keep + * values in SMI range. + * + * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function + */ + int get hashCode { + // TODO(ahe): This method shouldn't have to use JS. Update when our + // optimizations are smarter. + int hash = 0; + for (int i = 0; i < length; i++) { + hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i)); + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + hash = JS('int', '# ^ (# >> 6)', hash, hash); + } + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = JS('int', '# ^ (# >> 11)', hash, hash); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } + + Type get runtimeType => String; + + int get length => JS('int', r'#.length', this); + + String operator [](int index) { + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + return JS('String', '#[#]', this, index); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart new file mode 100644 index 0000000..3bccb2c --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart
@@ -0,0 +1,68 @@ +// 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. + +// Patch file for dart:math library. +import 'dart:_foreign_helper' show JS; + +patch double sqrt(num x) + => JS('double', r'Math.sqrt(#)', checkNum(x)); + +patch double sin(num x) + => JS('double', r'Math.sin(#)', checkNum(x)); + +patch double cos(num x) + => JS('double', r'Math.cos(#)', checkNum(x)); + +patch double tan(num x) + => JS('double', r'Math.tan(#)', checkNum(x)); + +patch double acos(num x) + => JS('double', r'Math.acos(#)', checkNum(x)); + +patch double asin(num x) + => JS('double', r'Math.asin(#)', checkNum(x)); + +patch double atan(num x) + => JS('double', r'Math.atan(#)', checkNum(x)); + +patch double atan2(num a, num b) + => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b)); + +patch double exp(num x) + => JS('double', r'Math.exp(#)', checkNum(x)); + +patch double log(num x) + => JS('double', r'Math.log(#)', checkNum(x)); + +patch num pow(num x, num exponent) { + checkNum(x); + checkNum(exponent); + return JS('num', r'Math.pow(#, #)', x, exponent); +} + +patch class Random { + patch factory Random([int seed]) => const _Random(); +} + +class _Random implements Random { + // The Dart2JS implementation of Random doesn't use a seed. + const _Random(); + + int nextInt(int max) { + if (max < 0) throw new ArgumentError("negative max: $max"); + if (max > 0xFFFFFFFF) max = 0xFFFFFFFF; + return JS("int", "(Math.random() * #) >>> 0", max); + } + + /** + * Generates a positive random floating point value uniformly distributed on + * the range from 0.0, inclusive, to 1.0, exclusive. + */ + double nextDouble() => JS("double", "Math.random()"); + + /** + * Generates a random boolean value. + */ + bool nextBool() => JS("bool", "Math.random() < 0.5"); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart new file mode 100644 index 0000000..1236507 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart
@@ -0,0 +1,114 @@ +// 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. + +// Patch library for dart:mirrors. + +import 'dart:_foreign_helper' show JS; + +// Yeah, seriously: mirrors in dart2js are experimental... +const String _MIRROR_OPT_IN_MESSAGE = """ + +This program is using an experimental feature called \"mirrors\". As +currently implemented, mirrors do not work with minification, and will +cause spurious errors depending on how code was optimized. + +The authors of this program are aware of these problems and have +decided the thrill of using an experimental feature is outweighing the +risks. Furthermore, the authors of this program understand that +long-term, to fix the problems mentioned above, mirrors may have +negative impact on size and performance of Dart programs compiled to +JavaScript. +"""; + +bool _mirrorsEnabled = false; + +/** + * Stub class for the mirror system. + */ +patch MirrorSystem currentMirrorSystem() { + _ensureEnabled(); + throw new UnsupportedError("MirrorSystem not implemented"); +} + +patch Future<MirrorSystem> mirrorSystemOf(SendPort port) { + _ensureEnabled(); + throw new UnsupportedError("MirrorSystem not implemented"); +} + +patch InstanceMirror reflect(Object reflectee) { + if (!_mirrorsEnabled && (_MIRROR_OPT_IN_MESSAGE == reflectee)) { + // Turn on mirrors and warn that it is an experimental feature. + _mirrorsEnabled = true; + print(reflectee); + } + _ensureEnabled(); + return new _InstanceMirror(reflectee); +} + +class _InstanceMirror extends InstanceMirror { + static final Expando<ClassMirror> classMirrors = new Expando<ClassMirror>(); + + final reflectee; + + _InstanceMirror(this.reflectee) { + _ensureEnabled(); + } + + bool get hasReflectee => true; + + ClassMirror get type { + String className = Primitives.objectTypeName(reflectee); + var constructor = Primitives.getConstructor(className); + var mirror = classMirrors[constructor]; + if (mirror == null) { + mirror = new _ClassMirror(className, constructor); + classMirrors[constructor] = mirror; + } + return mirror; + } + + Future<InstanceMirror> invoke(String memberName, + List<Object> positionalArguments, + [Map<String,Object> namedArguments]) { + if (namedArguments != null && !namedArguments.isEmpty) { + throw new UnsupportedError('Named arguments are not implemented'); + } + // Copy the list to ensure that it can safely be passed to + // JavaScript. + var jsList = new List.from(positionalArguments); + var mangledName = '${memberName}\$${positionalArguments.length}'; + var method = JS('var', '#[#]', reflectee, mangledName); + var completer = new Completer<InstanceMirror>(); + // TODO(ahe): [Completer] or [Future] should have API to create a + // delayed action. Simulating with a [Timer]. + new Timer(0, (timer) { + if (JS('String', 'typeof #', method) == 'function') { + var result = + JS('var', '#.apply(#, #)', method, reflectee, jsList); + completer.complete(new _InstanceMirror(result)); + } else { + completer.completeError('not a method $memberName'); + } + }); + return completer.future; + } + + String toString() => 'InstanceMirror($reflectee)'; +} + +class _ClassMirror extends ClassMirror { + final String _name; + final _jsConstructor; + + _ClassMirror(this._name, this._jsConstructor) { + _ensureEnabled(); + } + + String toString() => 'ClassMirror($_name)'; +} + +_ensureEnabled() { + if (_mirrorsEnabled) return; + throw new UnsupportedError('dart:mirrors is an experimental feature'); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart new file mode 100644 index 0000000..c75e554 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart
@@ -0,0 +1,431 @@ +// 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. + +part of _js_helper; + +String typeNameInChrome(obj) { + String name = JS('String', "#.constructor.name", obj); + return typeNameInWebKitCommon(name); +} + +String typeNameInSafari(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + // Safari is very similar to Chrome. + return typeNameInWebKitCommon(name); +} + +String typeNameInWebKitCommon(tag) { + String name = JS('String', '#', tag); + if (name == 'Window') return 'DOMWindow'; + if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; + if (name == 'WebKitMutationObserver') return 'MutationObserver'; + if (name == 'AudioChannelMerger') return 'ChannelMergerNode'; + if (name == 'AudioChannelSplitter') return 'ChannelSplitterNode'; + if (name == 'AudioGainNode') return 'GainNode'; + if (name == 'AudioPannerNode') return 'PannerNode'; + if (name == 'JavaScriptAudioNode') return 'ScriptProcessorNode'; + if (name == 'Oscillator') return 'OscillatorNode'; + if (name == 'RealtimeAnalyserNode') return 'AnalyserNode'; + if (name == 'IDBVersionChangeRequest') return 'IDBOpenDBRequest'; + return name; +} + +String typeNameInOpera(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'ApplicationCache') return 'DOMApplicationCache'; + return name; +} + +String typeNameInFirefox(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'CSS2Properties') return 'CSSStyleDeclaration'; + if (name == 'DataTransfer') return 'Clipboard'; + if (name == 'DragEvent') return 'MouseEvent'; + if (name == 'GeoGeolocation') return 'Geolocation'; + if (name == 'MouseScrollEvent') return 'WheelEvent'; + if (name == 'OfflineResourceList') return 'DOMApplicationCache'; + if (name == 'WorkerMessageEvent') return 'MessageEvent'; + if (name == 'XMLDocument') return 'Document'; + return name; +} + +String typeNameInIE(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'Document') { + // IE calls both HTML and XML documents 'Document', so we check for the + // xmlVersion property, which is the empty string on HTML documents. + if (JS('bool', '!!#.xmlVersion', obj)) return 'Document'; + return 'HTMLDocument'; + } + if (name == 'ApplicationCache') return 'DOMApplicationCache'; + if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; + if (name == 'DataTransfer') return 'Clipboard'; + if (name == 'DragEvent') return 'MouseEvent'; + if (name == 'HTMLDDElement') return 'HTMLElement'; + if (name == 'HTMLDTElement') return 'HTMLElement'; + if (name == 'HTMLTableDataCellElement') return 'HTMLTableCellElement'; + if (name == 'HTMLTableHeaderCellElement') return 'HTMLTableCellElement'; + if (name == 'HTMLPhraseElement') return 'HTMLElement'; + if (name == 'MSStyleCSSProperties') return 'CSSStyleDeclaration'; + if (name == 'MouseWheelEvent') return 'WheelEvent'; + if (name == 'Position') return 'Geoposition'; + + // Patches for types which report themselves as Objects. + if (name == 'Object') { + if (JS('bool', 'window.DataView && (# instanceof window.DataView)', obj)) { + return 'DataView'; + } + } + return name; +} + +String constructorNameFallback(object) { + if (object == null) return 'Null'; + var constructor = JS('var', "#.constructor", object); + if (identical(JS('String', "typeof(#)", constructor), 'function')) { + // The constructor isn't null or undefined at this point. Try + // to grab hold of its name. + var name = JS('var', '#.name', constructor); + // If the name is a non-empty string, we use that as the type + // name of this object. On Firefox, we often get 'Object' as + // the constructor name even for more specialized objects so + // we have to fall through to the toString() based implementation + // below in that case. + if (name is String + && !identical(name, '') + && !identical(name, 'Object') + && !identical(name, 'Function.prototype')) { // Can happen in Opera. + return name; + } + } + String string = JS('String', 'Object.prototype.toString.call(#)', object); + return JS('String', '#.substring(8, # - 1)', string, string.length); +} + +/** + * If a lookup on an object [object] that has [tag] fails, this function is + * called to provide an alternate tag. This allows us to fail gracefully if we + * can make a good guess, for example, when browsers add novel kinds of + * HTMLElement that we have never heard of. + */ +String alternateTag(object, String tag) { + // Does it smell like some kind of HTML element? + if (JS('bool', r'!!/^HTML[A-Z].*Element$/.test(#)', tag)) { + // Check that it is not a simple JavaScript object. + String string = JS('String', 'Object.prototype.toString.call(#)', object); + if (string == '[object Object]') return null; + return 'HTMLElement'; + } + return null; +} + +// TODO(ngeoffray): stop using this method once our optimizers can +// change str1.contains(str2) into str1.indexOf(str2) != -1. +bool contains(String userAgent, String name) { + return JS('int', '#.indexOf(#)', userAgent, name) != -1; +} + +int arrayLength(List array) { + return JS('int', '#.length', array); +} + +arrayGet(List array, int index) { + return JS('var', '#[#]', array, index); +} + +void arraySet(List array, int index, var value) { + JS('var', '#[#] = #', array, index, value); +} + +propertyGet(var object, String property) { + return JS('var', '#[#]', object, property); +} + +bool callHasOwnProperty(var function, var object, String property) { + return JS('bool', '#.call(#, #)', function, object, property); +} + +void propertySet(var object, String property, var value) { + JS('var', '#[#] = #', object, property, value); +} + +getPropertyFromPrototype(var object, String name) { + return JS('var', 'Object.getPrototypeOf(#)[#]', object, name); +} + +newJsObject() { + return JS('var', '{}'); +} + +/** + * Returns the function to use to get the type name of an object. + */ +Function getFunctionForTypeNameOf() { + // If we're not in the browser, we're almost certainly running on v8. + if (!identical(JS('String', 'typeof(navigator)'), 'object')) return typeNameInChrome; + + String userAgent = JS('String', "navigator.userAgent"); + if (contains(userAgent, 'Chrome') || contains(userAgent, 'DumpRenderTree')) { + return typeNameInChrome; + } else if (contains(userAgent, 'Firefox')) { + return typeNameInFirefox; + } else if (contains(userAgent, 'MSIE')) { + return typeNameInIE; + } else if (contains(userAgent, 'Opera')) { + return typeNameInOpera; + } else if (contains(userAgent, 'AppleWebKit')) { + // Chrome matches 'AppleWebKit' too, but we test for Chrome first, so this + // is not a problem. + // Note: Just testing for "Safari" doesn't work when the page is embedded + // in a UIWebView on iOS 6. + return typeNameInSafari; + } else { + return constructorNameFallback; + } +} + + +/** + * Cached value for the function to use to get the type name of an + * object. + */ +Function _getTypeNameOf; + +/** + * Returns the type name of [obj]. + */ +String getTypeNameOf(var obj) { + if (_getTypeNameOf == null) _getTypeNameOf = getFunctionForTypeNameOf(); + return _getTypeNameOf(obj); +} + +String toStringForNativeObject(var obj) { + String name = JS('String', '#', getTypeNameOf(obj)); + return 'Instance of $name'; +} + +int hashCodeForNativeObject(object) => Primitives.objectHashCode(object); + +/** + * Sets a JavaScript property on an object. + */ +void defineProperty(var obj, String property, var value) { + JS('void', + 'Object.defineProperty(#, #, ' + '{value: #, enumerable: false, writable: true, configurable: true})', + obj, + property, + value); +} + +/** + * This method looks up the type name of [obj] in [methods]. [methods] + * is a Javascript object. If it cannot find it, it looks into the + * [_dynamicMetadata] array. If the method can still not be found, it + * creates a method that will throw a [NoSuchMethodError]. + * + * Once it has a method, the prototype of [obj] is patched with that + * method, on the property [name]. The method is then invoked. + * + * This method returns the result of invoking the found method. + */ +dynamicBind(var obj, + String name, + var methods, + List arguments) { + // The tag is related to the class name. E.g. the dart:html class + // '_ButtonElement' has the tag 'HTMLButtonElement'. TODO(erikcorry): rename + // getTypeNameOf to getTypeTag. + String tag = getTypeNameOf(obj); + var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty'); + + var method = dynamicBindLookup(hasOwnPropertyFunction, tag, methods); + if (method == null) { + String secondTag = alternateTag(obj, tag); + if (secondTag != null) { + method = dynamicBindLookup(hasOwnPropertyFunction, secondTag, methods); + } + } + + // If we didn't find the method then look up in the Dart Object class, using + // getTypeNameOf in case the minifier has renamed Object. + if (method == null) { + String nameOfObjectClass = getTypeNameOf(const Object()); + method = + lookupDynamicClass(hasOwnPropertyFunction, methods, nameOfObjectClass); + } + + var proto = JS('var', 'Object.getPrototypeOf(#)', obj); + if (method == null) { + // If the method cannot be found, we use a trampoline method that + // will throw a [NoSuchMethodError] if the object is of the + // exact prototype, or will call [dynamicBind] again if the object + // is a subclass. + method = JS('var', + 'function () {' + 'if (Object.getPrototypeOf(this) === #) {' + 'throw new TypeError(# + " is not a function");' + '} else {' + 'return Object.prototype[#].apply(this, arguments);' + '}' + '}', + proto, name, name); + } + + if (!callHasOwnProperty(hasOwnPropertyFunction, proto, name)) { + defineProperty(proto, name, method); + } + + return JS('var', '#.apply(#, #)', method, obj, arguments); +} + +dynamicBindLookup(var hasOwnPropertyFunction, String tag, var methods) { + var method = lookupDynamicClass(hasOwnPropertyFunction, methods, tag); + // Look at the inheritance data, getting the class tags and using them + // to check the methods table for this method name. + if (method == null && _dynamicMetadata != null) { + for (int i = 0; i < arrayLength(_dynamicMetadata); i++) { + MetaInfo entry = arrayGet(_dynamicMetadata, i); + if (callHasOwnProperty(hasOwnPropertyFunction, entry._set, tag)) { + method = + lookupDynamicClass(hasOwnPropertyFunction, methods, entry._tag); + // Stop if we found it in the methods array. + if (method != null) break; + } + } + } + return method; +} + +// For each method name and class inheritance subtree, we use an ordinary JS +// object as a hash map to store the method for each class. Entries are added +// in native_emitter.dart (see dynamicName). In order to avoid the class names +// clashing with the method names on Object.prototype (needed for native +// objects) we must always use hasOwnProperty. +var lookupDynamicClass(var hasOwnPropertyFunction, + var methods, + String className) { + return callHasOwnProperty(hasOwnPropertyFunction, methods, className) ? + propertyGet(methods, className) : + null; +} + +/** + * Code for doing the dynamic dispatch on JavaScript prototypes that are not + * available at compile-time. Each property of a native Dart class + * is registered through this function, which is called with the + * following pattern: + * + * dynamicFunction('propertyName').prototypeName = // JS code + * + * What this function does is: + * - Creates a map of { prototypeName: JS code }. + * - Attaches 'propertyName' to the JS Object prototype that will + * intercept at runtime all calls to propertyName. + * - Sets the value of 'propertyName' to the returned method from + * [dynamicBind]. + * + */ +dynamicFunction(name) { + var f = JS('var', 'Object.prototype[#]', name); + if (f != null && JS('bool', '!!#.methods', f)) { + return JS('var', '#.methods', f); + } + + // TODO(ngeoffray): We could make this a map if the code we + // generate plays well with a Dart map. + var methods = JS('var', '{}'); + // If there is a method attached to the Dart Object class, use it as + // the method to call in case no method is registered for that type. + var dartMethod = getPropertyFromPrototype(const Object(), name); + // Take the method from the Dart Object class if we didn't find it yet and it + // is there. + if (dartMethod != null) propertySet(methods, 'Object', dartMethod); + + var bind = JS('var', + 'function() {' + 'return #(this, #, #, Array.prototype.slice.call(arguments));' + '}', + DART_CLOSURE_TO_JS(dynamicBind), name, methods); + + JS('void', '#.methods = #', bind, methods); + defineProperty(JS('var', 'Object.prototype'), name, bind); + return methods; +} + +/** + * This class encodes the class hierarchy when we need it for dynamic + * dispatch. + */ +class MetaInfo { + /** + * The type name this [MetaInfo] relates to. + */ + String _tag; + + /** + * A string containing the names of subtypes of [tag], separated by + * '|'. + */ + String _tags; + + /** + * A list of names of subtypes of [tag]. + */ + Object _set; + + MetaInfo(this._tag, this._tags, this._set); +} + +List<MetaInfo> get _dynamicMetadata { + // Because [dynamicMetadata] has to be shared with multiple isolates + // that access native classes (eg multiple DOM isolates), + // [_dynamicMetadata] cannot be a field, otherwise all non-main + // isolates would not have any value for it. + if (identical(JS('var', 'typeof(\$dynamicMetadata)'), 'undefined')) { + _dynamicMetadata = <MetaInfo>[]; + } + return JS('var', '\$dynamicMetadata'); +} + +void set _dynamicMetadata(List<MetaInfo> table) { + JS('void', '\$dynamicMetadata = #', table); +} + +/** + * Builds the metadata used for encoding the class hierarchy of native + * classes. The following example: + * + * class A native "*A" {} + * class B extends A native "*B" {} + * + * Will generate: + * ['A', 'A|B'] + * + * This method returns a list of [MetaInfo] objects. + */ +List <MetaInfo> buildDynamicMetadata(List<List<String>> inputTable) { + List<MetaInfo> result = <MetaInfo>[]; + for (int i = 0; i < arrayLength(inputTable); i++) { + String tag = JS('String', '#', arrayGet(arrayGet(inputTable, i), 0)); + String tags = JS('String', '#', arrayGet(arrayGet(inputTable, i), 1)); + var set = newJsObject(); + List<String> tagNames = tags.split('|'); + for (int j = 0; j < arrayLength(tagNames); j++) { + propertySet(set, arrayGet(tagNames, j), true); + } + result.add(new MetaInfo(tag, tags, set)); + } + return result; +} + +/** + * Called by the compiler to setup [_dynamicMetadata]. + */ +void dynamicSetMetadata(List<List<String>> inputTable) { + _dynamicMetadata = buildDynamicMetadata(inputTable); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart new file mode 100644 index 0000000..b62b0b5 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart
@@ -0,0 +1,151 @@ +// 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. + +part of _js_helper; + +List regExpExec(JSSyntaxRegExp regExp, String str) { + var nativeRegExp = regExpGetNative(regExp); + var result = JS('=List', r'#.exec(#)', nativeRegExp, str); + if (JS('bool', r'# == null', result)) return null; + return result; +} + +bool regExpTest(JSSyntaxRegExp regExp, String str) { + var nativeRegExp = regExpGetNative(regExp); + return JS('bool', r'#.test(#)', nativeRegExp, str); +} + +regExpGetNative(JSSyntaxRegExp regExp) { + var r = JS('var', r'#._re', regExp); + if (r == null) { + r = JS('var', r'#._re = #', regExp, regExpMakeNative(regExp)); + } + return r; +} + +regExpAttachGlobalNative(JSSyntaxRegExp regExp) { + JS('void', r'#._re = #', regExp, regExpMakeNative(regExp, global: true)); +} + +regExpMakeNative(JSSyntaxRegExp regExp, {bool global: false}) { + String pattern = regExp.pattern; + bool isMultiLine = regExp.isMultiLine; + bool isCaseSensitive = regExp.isCaseSensitive; + checkString(pattern); + StringBuffer sb = new StringBuffer(); + if (isMultiLine) sb.add('m'); + if (!isCaseSensitive) sb.add('i'); + if (global) sb.add('g'); + try { + return JS('var', r'new RegExp(#, #)', pattern, sb.toString()); + } catch (e) { + throw new IllegalJSRegExpException(pattern, + JS('String', r'String(#)', e)); + } +} + +int regExpMatchStart(m) => JS('int', r'#.index', m); + +class JSSyntaxRegExp implements RegExp { + final String _pattern; + final bool _isMultiLine; + final bool _isCaseSensitive; + + const JSSyntaxRegExp(String pattern, + {bool multiLine: false, + bool caseSensitive: true}) + : _pattern = pattern, + _isMultiLine = multiLine, + _isCaseSensitive = caseSensitive; + + Match firstMatch(String str) { + List<String> m = regExpExec(this, checkString(str)); + if (m == null) return null; + var matchStart = regExpMatchStart(m); + // m.lastIndex only works with flag 'g'. + var matchEnd = matchStart + m[0].length; + return new _MatchImplementation(pattern, str, matchStart, matchEnd, m); + } + + bool hasMatch(String str) => regExpTest(this, checkString(str)); + + String stringMatch(String str) { + var match = firstMatch(str); + return match == null ? null : match.group(0); + } + + Iterable<Match> allMatches(String str) { + checkString(str); + return new _AllMatchesIterable(this, str); + } + + String get pattern => _pattern; + bool get isMultiLine => _isMultiLine; + bool get isCaseSensitive => _isCaseSensitive; + + static JSSyntaxRegExp _globalVersionOf(JSSyntaxRegExp other) { + JSSyntaxRegExp re = + new JSSyntaxRegExp(other.pattern, + multiLine: other.isMultiLine, + caseSensitive: other.isCaseSensitive); + regExpAttachGlobalNative(re); + return re; + } + + _getNative() => regExpGetNative(this); +} + +class _MatchImplementation implements Match { + final String pattern; + final String str; + final int start; + final int end; + final List<String> _groups; + + const _MatchImplementation( + String this.pattern, + String this.str, + int this.start, + int this.end, + List<String> this._groups); + + String group(int index) => _groups[index]; + String operator [](int index) => group(index); + int get groupCount => _groups.length - 1; + + List<String> groups(List<int> groups) { + List<String> out = []; + for (int i in groups) { + out.add(group(i)); + } + return out; + } +} + +class _AllMatchesIterable extends Iterable<Match> { + final JSSyntaxRegExp _re; + final String _str; + + const _AllMatchesIterable(this._re, this._str); + + Iterator<Match> get iterator => new _AllMatchesIterator(_re, _str); +} + +class _AllMatchesIterator implements Iterator<Match> { + final RegExp _re; + final String _str; + Match _current; + + _AllMatchesIterator(JSSyntaxRegExp re, String this._str) + : _re = JSSyntaxRegExp._globalVersionOf(re); + + Match get current => _current; + + bool moveNext() { + // firstMatch actually acts as nextMatch because of + // hidden global flag. + _current = _re.firstMatch(_str); + return _current != null; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart new file mode 100644 index 0000000..326b892 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart
@@ -0,0 +1,130 @@ +// 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. + +// This is an empty dummy patch file for the VM dart:scalarlist library. +// This is needed in order to be able to generate documentation for the +// scalarlist library. + +patch class Int8List { + patch factory Int8List(int length) { + throw new UnsupportedError('Int8List'); + } + + patch factory Int8List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int8List.view'); + } +} + + +patch class Uint8List { + patch factory Uint8List(int length) { + throw new UnsupportedError('Uint8List'); + } + + patch factory Uint8List.view(ByteArray array, + [int start = 0, int length]) { + throw new UnsupportedError('Uint8List.view'); + } +} + + +patch class Uint8ClampedList { + patch factory Uint8ClampedList(int length) { + throw new UnsupportedError('Uint8ClampedList'); + } + + patch factory Uint8ClampedList.view(ByteArray array, + [int start = 0, int length]) { + throw new UnsupportedError('Uint8ClampedList.view'); + } +} + + +patch class Int16List { + patch factory Int16List(int length) { + throw new UnsupportedError('Int16List'); + + } + + patch factory Int16List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int16List.view'); + } +} + + +patch class Uint16List { + patch factory Uint16List(int length) { + throw new UnsupportedError('Uint16List'); + } + + patch factory Uint16List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint16List.view'); + } +} + + +patch class Int32List { + patch factory Int32List(int length) { + throw new UnsupportedError('Int32List'); + } + + patch factory Int32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int32List.view'); + } +} + + +patch class Uint32List { + patch factory Uint32List(int length) { + throw new UnsupportedError('Uint32List'); + } + + patch factory Uint32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint32List.view'); + } +} + + +patch class Int64List { + patch factory Int64List(int length) { + throw new UnsupportedError('Int64List'); + } + + patch factory Int64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int64List.view'); + } +} + + +patch class Uint64List { + patch factory Uint64List(int length) { + throw new UnsupportedError('Uint64List'); + } + + patch factory Uint64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint64List.view'); + } +} + + +patch class Float32List { + patch factory Float32List(int length) { + throw new UnsupportedError('Float32List'); + } + + patch factory Float32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Float32List.view'); + } +} + + +patch class Float64List { + patch factory Float64List(int length) { + throw new UnsupportedError('Float64List'); + } + + patch factory Float64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Float64List.view'); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart b/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart new file mode 100644 index 0000000..8ac1fa4 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart
@@ -0,0 +1,234 @@ +// 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. + +part of _js_helper; + +class StringMatch implements Match { + const StringMatch(int this.start, + String this.str, + String this.pattern); + + int get end => start + pattern.length; + String operator[](int g) => group(g); + int get groupCount => 0; + + String group(int group_) { + if (group_ != 0) { + throw new RangeError.value(group_); + } + return pattern; + } + + List<String> groups(List<int> groups_) { + List<String> result = new List<String>(); + for (int g in groups_) { + result.add(group(g)); + } + return result; + } + + final int start; + final String str; + final String pattern; +} + +List<Match> allMatchesInStringUnchecked(String needle, String haystack) { + // Copied from StringBase.allMatches in + // /runtime/lib/string_base.dart + List<Match> result = new List<Match>(); + int length = haystack.length; + int patternLength = needle.length; + int startIndex = 0; + while (true) { + int position = haystack.indexOf(needle, startIndex); + if (position == -1) { + break; + } + result.add(new StringMatch(position, haystack, needle)); + int endIndex = position + patternLength; + if (endIndex == length) { + break; + } else if (position == endIndex) { + ++startIndex; // empty match, advance and restart + } else { + startIndex = endIndex; + } + } + return result; +} + +stringContainsUnchecked(receiver, other, startIndex) { + if (other is String) { + return receiver.indexOf(other, startIndex) != -1; + } else if (other is JSSyntaxRegExp) { + return other.hasMatch(receiver.substring(startIndex)); + } else { + var substr = receiver.substring(startIndex); + return other.allMatches(substr).iterator.moveNext(); + } +} + +stringReplaceJS(receiver, replacer, to) { + // The JavaScript String.replace method recognizes replacement + // patterns in the replacement string. Dart does not have that + // behavior. + to = JS('String', r"#.replace('$', '$$$$')", to); + return JS('String', r'#.replace(#, #)', receiver, replacer, to); +} + +final RegExp quoteRegExp = new JSSyntaxRegExp(r'[-[\]{}()*+?.,\\^$|#\s]'); + +stringReplaceAllUnchecked(receiver, from, to) { + checkString(to); + if (from is String) { + if (from == "") { + if (receiver == "") { + return to; + } else { + StringBuffer result = new StringBuffer(); + int length = receiver.length; + result.add(to); + for (int i = 0; i < length; i++) { + result.add(receiver[i]); + result.add(to); + } + return result.toString(); + } + } else { + var quoter = regExpMakeNative(quoteRegExp, global: true); + var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter); + RegExp replaceRegExp = new JSSyntaxRegExp(quoted); + var replacer = regExpMakeNative(replaceRegExp, global: true); + return stringReplaceJS(receiver, replacer, to); + } + } else if (from is JSSyntaxRegExp) { + var re = regExpMakeNative(from, global: true); + return stringReplaceJS(receiver, re, to); + } else { + checkNull(from); + // TODO(floitsch): implement generic String.replace (with patterns). + throw "String.replaceAll(Pattern) UNIMPLEMENTED"; + } +} + +String _matchString(Match match) => match[0]; +String _stringIdentity(String string) => string; + +stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { + if (pattern is! Pattern) { + throw new ArgumentError("${pattern} is not a Pattern"); + } + if (onMatch == null) onMatch = _matchString; + if (onNonMatch == null) onNonMatch = _stringIdentity; + if (pattern is String) { + return stringReplaceAllStringFuncUnchecked(receiver, pattern, + onMatch, onNonMatch); + } + StringBuffer buffer = new StringBuffer(); + int startIndex = 0; + for (Match match in pattern.allMatches(receiver)) { + buffer.add(onNonMatch(receiver.substring(startIndex, match.start))); + buffer.add(onMatch(match)); + startIndex = match.end; + } + buffer.add(onNonMatch(receiver.substring(startIndex))); + return buffer.toString(); +} + +stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { + // Pattern is the empty string. + StringBuffer buffer = new StringBuffer(); + int length = receiver.length; + int i = 0; + buffer.add(onNonMatch("")); + while (i < length) { + buffer.add(onMatch(new StringMatch(i, receiver, ""))); + // Special case to avoid splitting a surrogate pair. + int code = receiver.charCodeAt(i); + if ((code & ~0x3FF) == 0xD800 && length > i + 1) { + // Leading surrogate; + code = receiver.charCodeAt(i + 1); + if ((code & ~0x3FF) == 0xDC00) { + // Matching trailing surrogate. + buffer.add(onNonMatch(receiver.substring(i, i + 2))); + i += 2; + continue; + } + } + buffer.add(onNonMatch(receiver[i])); + i++; + } + buffer.add(onMatch(new StringMatch(i, receiver, ""))); + buffer.add(onNonMatch("")); + return buffer.toString(); +} + +stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { + int patternLength = pattern.length; + if (patternLength == 0) { + return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); + } + int length = receiver.length; + StringBuffer buffer = new StringBuffer(); + int startIndex = 0; + while (startIndex < length) { + int position = receiver.indexOf(pattern, startIndex); + if (position == -1) { + break; + } + buffer.add(onNonMatch(receiver.substring(startIndex, position))); + buffer.add(onMatch(new StringMatch(position, receiver, pattern))); + startIndex = position + patternLength; + } + buffer.add(onNonMatch(receiver.substring(startIndex))); + return buffer.toString(); +} + + +stringReplaceFirstUnchecked(receiver, from, to) { + if (from is String) { + return stringReplaceJS(receiver, from, to); + } else if (from is JSSyntaxRegExp) { + var re = regExpGetNative(from); + return stringReplaceJS(receiver, re, to); + } else { + checkNull(from); + // TODO(floitsch): implement generic String.replace (with patterns). + throw "String.replace(Pattern) UNIMPLEMENTED"; + } +} + +stringJoinUnchecked(array, separator) { + return JS('String', r'#.join(#)', array, separator); +} + +class JsStringBuffer implements StringBuffer { + String _contents; + + JsStringBuffer(content) + : _contents = (content is String) ? content : '$content'; + + int get length => _contents.length; + + bool get isEmpty => length == 0; + + void add(Object obj) { + _contents = JS('String', '# + #', _contents, + (obj is String) ? obj : '$obj'); + } + + void addAll(Iterable objects) { + for (Object obj in objects) add(obj); + } + + void addCharCode(int charCode) { + add(new String.fromCharCodes([charCode])); + } + + void clear() { + _contents = ""; + } + + String toString() => _contents; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart b/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart new file mode 100644 index 0000000..074cece --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/library_loader.dart
@@ -0,0 +1,837 @@ +// 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. + +part of dart2js; + +/** + * [CompilerTask] for loading libraries and setting up the import/export scopes. + * + * The library loader uses four different kinds of URIs in different parts of + * the loading process. + * + * ## User URI ## + * + * A 'user URI' is a URI provided by the user in code and as the main entry URI + * at the command line. These generally come in 3 versions: + * + * * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'. + * + * * A dart URI such as 'dart:core' and 'dart:_js_helper'. + * + * * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'. + * + * A user URI can also be absolute, like 'file:///foo.dart' or + * 'http://example.com/bar.dart', but such URIs cannot necessarily be used for + * locating source files, since the scheme must be supported by the input + * provider. The standard input provider for dart2js only supports the 'file' + * scheme. + * + * ## Resolved URI ## + * + * A 'resolved URI' is a (user) URI that has been resolved to an absolute URI + * based on the readable URI (see below) from which it was loaded. A URI with an + * explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved. + * A relative URI like for instance '../foo/bar.dart' is translated into an + * resolved URI in one of three ways: + * + * * If provided as the main entry URI at the command line, the URI is resolved + * relative to the current working directory, say + * 'file:///current/working/dir/', and the resolved URI is therefore + * 'file:///current/working/foo/bar.dart'. + * + * * If the relative URI is provided in an import, export or part tag, and the + * readable URI of the enclosing compilation unit is a file URI, + * 'file://some/path/baz.dart', then the resolved URI is + * 'file://some/foo/bar.dart'. + * + * * If the relative URI is provided in an import, export or part tag, and the + * readable URI of the enclosing compilation unit is a package URI, + * 'package:some/path/baz.dart', then the resolved URI is + * 'package:some/foo/bar.dart'. + * + * The resolved URI thus preserves the scheme through resolution: A readable + * file URI results in an resolved file URI and a readable package URI results + * in an resolved package URI. Note that since a dart URI is not a readable URI, + * import, export or part tags within platform libraries are not interpreted as + * dart URIs but instead relative to the library source file location. + * + * The resolved URI of a library is also used as the canonical URI + * ([LibraryElement.canonicalUri]) by which we identify which libraries are + * identical. This means that libraries loaded through the 'package' scheme will + * resolve to the same library when loaded from within using relative URIs (see + * for instance the test 'standalone/package/package1_test.dart'). But loading a + * platform library using a relative URI will _not_ result in the same library + * as when loaded through the dart URI. + * + * ## Readable URI ## + * + * A 'readable URI' is an absolute URI whose scheme is either 'package' or + * something supported by the input provider, normally 'file'. Dart URIs such as + * 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead + * resolved into a readable URI using the library root URI provided from the + * command line and the list of platform libraries found in + * 'sdk/lib/_internal/libraries.dart'. This is done through the + * [Compiler.translateResolvedUri] method which checks whether a library by that + * name exists and in case of internal libraries whether access is granted. + * + * ## Resource URI ## + * + * A 'resource URI' is an absolute URI with a scheme supported by the input + * provider. For the standard implementation this means a URI with the 'file' + * scheme. Readable URIs are converted into resource URIs as part of the + * [Compiler.readScript] method. In the standard implementation the package URIs + * are converted to file URIs using the package root URI provided on the + * command line as base. If the package root URI is + * 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart' + * will be resolved to the resource URI + * 'file:///current/working/dir/foo/bar.dart'. + * + * The distinction between readable URI and resource URI is necessary to ensure + * that these imports + * + * import 'package:foo.dart' as a; + * import 'packages/foo.dart' as b; + * + * do _not_ resolve to the same library when the package root URI happens to + * point to the 'packages' folder. + * + */ +abstract class LibraryLoader extends CompilerTask { + LibraryLoader(Compiler compiler) : super(compiler); + + /** + * Loads the library specified by the [resolvedUri] and returns its + * [LibraryElement]. + * + * If the library is not already loaded, the method creates the + * [LibraryElement] for the library and computes the import/export scope, + * loading and computing the import/export scopes of all required libraries in + * the process. The method handles cyclic dependency between libraries. + * + * This is the main entry point for [LibraryLoader]. + */ + // TODO(johnniwinther): Remove [canonicalUri] together with + // [Compiler.scanBuiltinLibrary]. + LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri); + + // TODO(johnniwinther): Remove this when patches don't need special parsing. + void registerLibraryFromTag(LibraryDependencyHandler handler, + LibraryElement library, + LibraryDependency tag); + + /** + * Adds the elements in the export scope of [importedLibrary] to the import + * scope of [importingLibrary]. + */ + // TODO(johnniwinther): Move handling of 'js_helper' to the library loader + // to remove this method from the [LibraryLoader] interface. + void importLibrary(LibraryElement importingLibrary, + LibraryElement importedLibrary, + Import tag); +} + +/** + * [CombinatorFilter] is a succinct representation of a list of combinators from + * a library dependency tag. + */ +class CombinatorFilter { + const CombinatorFilter(); + + /** + * Returns [:true:] if [element] is excluded by this filter. + */ + bool exclude(Element element) => false; + + /** + * Creates a filter based on the combinators of [tag]. + */ + factory CombinatorFilter.fromTag(LibraryDependency tag) { + if (tag == null || tag.combinators == null) { + return const CombinatorFilter(); + } + + // If the list of combinators contain at least one [:show:] we can create + // a positive list of elements to include, otherwise we create a negative + // list of elements to exclude. + bool show = false; + Set<SourceString> nameSet; + for (Combinator combinator in tag.combinators) { + if (combinator.isShow) { + show = true; + var set = new Set<SourceString>(); + for (Identifier identifier in combinator.identifiers) { + set.add(identifier.source); + } + if (nameSet == null) { + nameSet = set; + } else { + nameSet = nameSet.intersection(set); + } + } + } + if (nameSet == null) { + nameSet = new Set<SourceString>(); + } + for (Combinator combinator in tag.combinators) { + if (combinator.isHide) { + for (Identifier identifier in combinator.identifiers) { + if (show) { + // We have a positive list => Remove hidden elements. + nameSet.remove(identifier.source); + } else { + // We have no positive list => Accumulate hidden elements. + nameSet.add(identifier.source); + } + } + } + } + return show ? new ShowFilter(nameSet) : new HideFilter(nameSet); + } +} + +/** + * A list of combinators represented as a list of element names to include. + */ +class ShowFilter extends CombinatorFilter { + final Set<SourceString> includedNames; + + ShowFilter(this.includedNames); + + bool exclude(Element element) => !includedNames.contains(element.name); +} + +/** + * A list of combinators represented as a list of element names to exclude. + */ +class HideFilter extends CombinatorFilter { + final Set<SourceString> excludedNames; + + HideFilter(this.excludedNames); + + bool exclude(Element element) => excludedNames.contains(element.name); +} + +/** + * Implementation class for [LibraryLoader]. The distinction between + * [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from + * the [LibraryLoader] interface. + */ +class LibraryLoaderTask extends LibraryLoader { + LibraryLoaderTask(Compiler compiler) : super(compiler); + String get name => 'LibraryLoader'; + + final Map<String, LibraryElement> libraryNames = + new LinkedHashMap<String, LibraryElement>(); + + LibraryDependencyHandler currentHandler; + + LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri) { + return measure(() { + assert(currentHandler == null); + currentHandler = new LibraryDependencyHandler(compiler); + LibraryElement library = + createLibrary(currentHandler, null, resolvedUri, node, canonicalUri); + currentHandler.computeExports(); + currentHandler = null; + return library; + }); + } + + /** + * Processes the library tags in [library]. + * + * The imported/exported libraries are loaded and processed recursively but + * the import/export scopes are not set up. + */ + void processLibraryTags(LibraryDependencyHandler handler, + LibraryElement library) { + int tagState = TagState.NO_TAG_SEEN; + + /** + * If [value] is less than [tagState] complain and return + * [tagState]. Otherwise return the new value for [tagState] + * (transition function for state machine). + */ + int checkTag(int value, LibraryTag tag) { + if (tagState > value) { + compiler.reportError(tag, 'out of order'); + return tagState; + } + return TagState.NEXT[value]; + } + + bool importsDartCore = false; + var libraryDependencies = new LinkBuilder<LibraryDependency>(); + Uri base = library.entryCompilationUnit.script.uri; + for (LibraryTag tag in library.tags.reverse()) { + if (tag.isImport) { + Import import = tag; + tagState = checkTag(TagState.IMPORT_OR_EXPORT, import); + if (import.uri.dartString.slowToString() == 'dart:core') { + importsDartCore = true; + } + libraryDependencies.addLast(import); + } else if (tag.isExport) { + tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag); + libraryDependencies.addLast(tag); + } else if (tag.isLibraryName) { + tagState = checkTag(TagState.LIBRARY, tag); + if (library.libraryTag != null) { + compiler.cancel("duplicated library declaration", node: tag); + } else { + library.libraryTag = tag; + } + checkDuplicatedLibraryName(library); + } else if (tag.isPart) { + Part part = tag; + StringNode uri = part.uri; + Uri resolvedUri = base.resolve(uri.dartString.slowToString()); + tagState = checkTag(TagState.SOURCE, part); + scanPart(part, resolvedUri, library); + } else { + compiler.internalError("Unhandled library tag.", node: tag); + } + } + + // Apply patch, if any. + if (library.isPlatformLibrary) { + patchDartLibrary(handler, library, library.canonicalUri.path); + } + + // Import dart:core if not already imported. + if (!importsDartCore && !isDartCore(library.canonicalUri)) { + handler.registerDependency(library, null, loadCoreLibrary(handler)); + } + + for (LibraryDependency tag in libraryDependencies.toLink()) { + registerLibraryFromTag(handler, library, tag); + } + } + + void checkDuplicatedLibraryName(LibraryElement library) { + LibraryName tag = library.libraryTag; + if (tag != null) { + String name = library.getLibraryOrScriptName(); + LibraryElement existing = + libraryNames.putIfAbsent(name, () => library); + if (!identical(existing, library)) { + Uri uri = library.entryCompilationUnit.script.uri; + compiler.reportMessage( + compiler.spanFromSpannable(tag.name, uri), + MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}), + api.Diagnostic.WARNING); + Uri existingUri = existing.entryCompilationUnit.script.uri; + compiler.reportMessage( + compiler.spanFromSpannable(existing.libraryTag.name, existingUri), + MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}), + api.Diagnostic.WARNING); + } + } + } + + bool isDartCore(Uri uri) => uri.scheme == "dart" && uri.path == "core"; + + /** + * Lazily loads and returns the [LibraryElement] for the dart:core library. + */ + LibraryElement loadCoreLibrary(LibraryDependencyHandler handler) { + if (compiler.coreLibrary == null) { + Uri coreUri = new Uri.fromComponents(scheme: 'dart', path: 'core'); + compiler.coreLibrary + = createLibrary(handler, null, coreUri, null, coreUri); + } + return compiler.coreLibrary; + } + + void patchDartLibrary(LibraryDependencyHandler handler, + LibraryElement library, String dartLibraryPath) { + if (library.isPatched) return; + Uri patchUri = compiler.resolvePatchUri(dartLibraryPath); + if (patchUri != null) { + compiler.patchParser.patchLibrary(handler, patchUri, library); + } + } + + /** + * Handle a part tag in the scope of [library]. The [resolvedUri] given is + * used as is, any URI resolution should be done beforehand. + */ + void scanPart(Part part, Uri resolvedUri, LibraryElement library) { + if (!resolvedUri.isAbsolute()) throw new ArgumentError(resolvedUri); + Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part); + Script sourceScript = compiler.readScript(readableUri, part); + CompilationUnitElement unit = + new CompilationUnitElementX(sourceScript, library); + compiler.withCurrentElement(unit, () { + compiler.scanner.scan(unit); + if (unit.partTag == null) { + bool wasDiagnosticEmitted = false; + compiler.withCurrentElement(library, () { + wasDiagnosticEmitted = + compiler.onDeprecatedFeature(part, 'missing part-of tag'); + }); + if (wasDiagnosticEmitted) { + compiler.reportMessage( + compiler.spanFromElement(unit), + MessageKind.MISSING_PART_OF_TAG.error(), + api.Diagnostic.INFO); + } + } + }); + } + + /** + * Handle an import/export tag by loading the referenced library and + * registering its dependency in [handler] for the computation of the import/ + * export scope. + */ + void registerLibraryFromTag(LibraryDependencyHandler handler, + LibraryElement library, + LibraryDependency tag) { + Uri base = library.entryCompilationUnit.script.uri; + Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString()); + LibraryElement loadedLibrary = + createLibrary(handler, library, resolvedUri, tag.uri, resolvedUri); + handler.registerDependency(library, tag, loadedLibrary); + + if (!loadedLibrary.hasLibraryName()) { + compiler.withCurrentElement(library, () { + compiler.reportError(tag == null ? null : tag.uri, + 'no library name found in ${loadedLibrary.canonicalUri}'); + }); + } + } + + /** + * Create (or reuse) a library element for the library specified by the + * [resolvedUri]. + * + * If a new library is created, the [handler] is notified. + */ + // TODO(johnniwinther): Remove [canonicalUri] and make [resolvedUri] the + // canonical uri when [Compiler.scanBuiltinLibrary] is removed. + LibraryElement createLibrary(LibraryDependencyHandler handler, + LibraryElement importingLibrary, + Uri resolvedUri, Node node, Uri canonicalUri) { + bool newLibrary = false; + Uri readableUri = + compiler.translateResolvedUri(importingLibrary, resolvedUri, node); + if (readableUri == null) return null; + LibraryElement createLibrary() { + newLibrary = true; + Script script = compiler.readScript(readableUri, node); + LibraryElement element = new LibraryElementX(script, canonicalUri); + handler.registerNewLibrary(element); + native.maybeEnableNative(compiler, element); + return element; + } + LibraryElement library; + if (canonicalUri == null) { + library = createLibrary(); + } else { + library = compiler.libraries.putIfAbsent(canonicalUri.toString(), + createLibrary); + } + if (newLibrary) { + compiler.withCurrentElement(library, () { + compiler.scanner.scanLibrary(library); + processLibraryTags(handler, library); + handler.registerLibraryExports(library); + compiler.onLibraryScanned(library, resolvedUri); + }); + } + return library; + } + + // TODO(johnniwinther): Remove this method when 'js_helper' is handled by + // [LibraryLoaderTask]. + void importLibrary(LibraryElement importingLibrary, + LibraryElement importedLibrary, + Import tag) { + new ImportLink(tag, importedLibrary).importLibrary(compiler, + importingLibrary); + } +} + + +/** + * The fields of this class models a state machine for checking script + * tags come in the correct order. + */ +class TagState { + static const int NO_TAG_SEEN = 0; + static const int LIBRARY = 1; + static const int IMPORT_OR_EXPORT = 2; + static const int SOURCE = 3; + static const int RESOURCE = 4; + + /** Next state. */ + static const List<int> NEXT = + const <int>[NO_TAG_SEEN, + IMPORT_OR_EXPORT, // Only one library tag is allowed. + IMPORT_OR_EXPORT, + SOURCE, + RESOURCE]; +} + +/** + * An [import] tag and the [importedLibrary] imported through [import]. + */ +class ImportLink { + final Import import; + final LibraryElement importedLibrary; + + ImportLink(this.import, this.importedLibrary); + + /** + * Imports the library into the [importingLibrary]. + */ + void importLibrary(Compiler compiler, LibraryElement importingLibrary) { + assert(invariant(importingLibrary, + importedLibrary.exportsHandled, + message: 'Exports not handled on $importedLibrary')); + var combinatorFilter = new CombinatorFilter.fromTag(import); + if (import != null && import.prefix != null) { + SourceString prefix = import.prefix.source; + Element e = importingLibrary.find(prefix); + if (e == null) { + e = new PrefixElementX(prefix, importingLibrary.entryCompilationUnit, + import.getBeginToken()); + importingLibrary.addToScope(e, compiler); + } + if (!identical(e.kind, ElementKind.PREFIX)) { + compiler.withCurrentElement(e, () { + compiler.reportWarning(new Identifier(e.position()), + 'duplicated definition'); + }); + compiler.reportError(import.prefix, 'duplicate definition'); + } + PrefixElement prefixElement = e; + importedLibrary.forEachExport((Element element) { + if (combinatorFilter.exclude(element)) return; + // TODO(johnniwinther): Clean-up like [checkDuplicateLibraryName]. + Element existing = + prefixElement.imported.putIfAbsent(element.name, () => element); + if (!identical(existing, element)) { + compiler.withCurrentElement(existing, () { + compiler.reportWarning(new Identifier(existing.position()), + 'duplicated import'); + }); + compiler.withCurrentElement(element, () { + compiler.reportError(new Identifier(element.position()), + 'duplicated import'); + }); + } + }); + } else { + importedLibrary.forEachExport((Element element) { + compiler.withCurrentElement(element, () { + if (combinatorFilter.exclude(element)) return; + importingLibrary.addImport(element, compiler); + }); + }); + } + } +} + +/** + * The combinator filter computed from an export tag and the library dependency + * node for the library that declared the export tag. This represents an edge in + * the library dependency graph. + */ +class ExportLink { + final CombinatorFilter combinatorFilter; + final LibraryDependencyNode exportNode; + + ExportLink(Export export, LibraryDependencyNode this.exportNode) + : this.combinatorFilter = new CombinatorFilter.fromTag(export); + + /** + * Exports [element] to the dependent library unless [element] is filtered by + * the export combinators. Returns [:true:] if the set pending exports of the + * dependent library was modified. + */ + bool exportElement(Element element) { + if (combinatorFilter.exclude(element)) return false; + return exportNode.addElementToPendingExports(element); + } +} + +/** + * A node in the library dependency graph. + * + * This class is used to collect the library dependencies expressed through + * import and export tags, and as the work-list entry in computations of library + * exports performed in [LibraryDependencyHandler.computeExports]. + */ +class LibraryDependencyNode { + final LibraryElement library; + + // TODO(ahe): Remove [hashCodeCounter] and [hashCode] when + // VM implementation of Object.hashCode is not slow. + final int hashCode = ++hashCodeCounter; + static int hashCodeCounter = 0; + + + /** + * A linked list of the import tags that import [library] mapped to the + * corresponding libraries. This is used to propagate exports into imports + * after the export scopes have been computed. + */ + Link<ImportLink> imports = const Link<ImportLink>(); + + /** + * A linked list of the export tags the dependent upon this node library. + * This is used to propagate exports during the computation of export scopes. + */ + Link<ExportLink> dependencies = const Link<ExportLink>(); + + /** + * The export scope for [library] which is gradually computed by the work-list + * computation in [LibraryDependencyHandler.computeExports]. + */ + Map<SourceString, Element> exportScope = + new LinkedHashMap<SourceString, Element>(); + + /** + * The set of exported elements that need to be propageted to dependent + * libraries as part of the work-list computation performed in + * [LibraryDependencyHandler.computeExports]. + */ + Set<Element> pendingExportSet = new Set<Element>(); + + LibraryDependencyNode(LibraryElement this.library); + + /** + * Registers that the library of this node imports [importLibrary] through the + * [import] tag. + */ + void registerImportDependency(Import import, + LibraryElement importedLibrary) { + imports = imports.prepend(new ImportLink(import, importedLibrary)); + } + + /** + * Registers that the library of this node is exported by + * [exportingLibraryNode] through the [export] tag. + */ + void registerExportDependency(Export export, + LibraryDependencyNode exportingLibraryNode) { + dependencies = + dependencies.prepend(new ExportLink(export, exportingLibraryNode)); + } + + /** + * Registers all non-private locally declared members of the library of this + * node to be exported. This forms the basis for the work-list computation of + * the export scopes performed in [LibraryDependencyHandler.computeExports]. + */ + void registerInitialExports() { + pendingExportSet.addAll(library.getNonPrivateElementsInScope()); + } + + void registerHandledExports(LibraryElement exportedLibraryElement, + CombinatorFilter filter) { + assert(invariant(library, exportedLibraryElement.exportsHandled)); + for (Element exportedElement in exportedLibraryElement.exports) { + if (!filter.exclude(exportedElement)) { + pendingExportSet.add(exportedElement); + } + } + } + + /** + * Registers the compute export scope with the node library. + */ + void registerExports() { + library.setExports(exportScope.values.toList()); + } + + /** + * Registers the imports of the node library. + */ + void registerImports(Compiler compiler) { + for (ImportLink link in imports) { + link.importLibrary(compiler, library); + } + } + + /** + * Copies and clears pending export set for this node. + */ + List<Element> pullPendingExports() { + List<Element> pendingExports = new List.from(pendingExportSet); + pendingExportSet.clear(); + return pendingExports; + } + + /** + * Adds [element] to the export scope for this node. If the [element] name + * is a duplicate, an error element is inserted into the export scope. + */ + Element addElementToExportScope(Compiler compiler, Element element) { + SourceString name = element.name; + Element existingElement = exportScope[name]; + if (existingElement != null) { + if (existingElement.isErroneous()) { + compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + element = existingElement; + } else if (existingElement.getLibrary() != library) { + // Declared elements hide exported elements. + compiler.reportErrorCode(existingElement, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + element = exportScope[name] = new ErroneousElementX( + MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library); + } + } else { + exportScope[name] = element; + } + return element; + } + + /** + * Propagates the exported [element] to all library nodes that depend upon + * this node. If the propagation updated any pending exports, [:true:] is + * returned. + */ + bool propagateElement(Element element) { + bool change = false; + for (ExportLink link in dependencies) { + if (link.exportElement(element)) { + change = true; + } + } + return change; + } + + /** + * Adds [element] to the pending exports of this node and returns [:true:] if + * the pending export set was modified. The combinators of [export] are used + * to filter the element. + */ + bool addElementToPendingExports(Element element) { + if (!identical(exportScope[element.name], element)) { + if (!pendingExportSet.contains(element)) { + pendingExportSet.add(element); + return true; + } + } + return false; + } +} + +/** + * Helper class used for computing the possibly cyclic import/export scopes of + * a set of libraries. + * + * This class is used by [ScannerTask.loadLibrary] to collect all newly loaded + * libraries and to compute their import/export scopes through a fixed-point + * algorithm. + */ +class LibraryDependencyHandler { + final Compiler compiler; + + /** + * Newly loaded libraries and their corresponding node in the library + * dependency graph. Libraries that have already been fully loaded are not + * part of the dependency graph of this handler since their export scopes have + * already been computed. + */ + Map<LibraryElement,LibraryDependencyNode> nodeMap = + new LinkedHashMap<LibraryElement,LibraryDependencyNode>(); + + LibraryDependencyHandler(Compiler this.compiler); + + /** + * Performs a fixed-point computation on the export scopes of all registered + * libraries and creates the import/export of the libraries based on the + * fixed-point. + */ + void computeExports() { + bool changed = true; + while (changed) { + changed = false; + Map<LibraryDependencyNode, List<Element>> tasks = + new LinkedHashMap<LibraryDependencyNode, List<Element>>(); + + // Locally defined elements take precedence over exported + // elements. So we must propagate local elements first. We + // ensure this by pulling the pending exports before + // propagating. This enforces that we handle exports + // breadth-first, with locally defined elements being level 0. + nodeMap.forEach((_, LibraryDependencyNode node) { + List<Element> pendingExports = node.pullPendingExports(); + tasks[node] = pendingExports; + }); + tasks.forEach((LibraryDependencyNode node, List<Element> pendingExports) { + pendingExports.forEach((Element element) { + element = node.addElementToExportScope(compiler, element); + if (node.propagateElement(element)) { + changed = true; + } + }); + }); + } + + // Setup export scopes. These have to be set before computing the import + // scopes to avoid accessing uncomputed export scopes during handling of + // imports. + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { + node.registerExports(); + }); + + // Setup import scopes. + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { + node.registerImports(compiler); + }); + } + + /** + * Registers that [library] depends on [loadedLibrary] through [tag]. + */ + void registerDependency(LibraryElement library, + LibraryDependency tag, + LibraryElement loadedLibrary) { + if (tag is Export) { + // [loadedLibrary] is exported by [library]. + LibraryDependencyNode exportingNode = nodeMap[library]; + if (loadedLibrary.exportsHandled) { + // Export scope already computed on [loadedLibrary]. + var combinatorFilter = new CombinatorFilter.fromTag(tag); + exportingNode.registerHandledExports(loadedLibrary, combinatorFilter); + return; + } + LibraryDependencyNode exportedNode = nodeMap[loadedLibrary]; + assert(invariant(loadedLibrary, exportedNode != null, + message: "$loadedLibrary has not been registered")); + assert(invariant(library, exportingNode != null, + message: "$library has not been registered")); + exportedNode.registerExportDependency(tag, exportingNode); + } else if (tag == null || tag is Import) { + // [loadedLibrary] is imported by [library]. + LibraryDependencyNode importingNode = nodeMap[library]; + assert(invariant(library, importingNode != null, + message: "$library has not been registered")); + importingNode.registerImportDependency(tag, loadedLibrary); + } + } + + /** + * Registers [library] for the processing of its import/export scope. + */ + void registerNewLibrary(LibraryElement library) { + nodeMap[library] = new LibraryDependencyNode(library); + } + + /** + * Registers all top-level entities of [library] as starting point for the + * fixed-point computation of the import/export scopes. + */ + void registerLibraryExports(LibraryElement library) { + nodeMap[library].registerInitialExports(); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart new file mode 100644 index 0000000..13a15f8 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
@@ -0,0 +1,1742 @@ +// 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. + +library mirrors_dart2js; + +import 'dart:async'; +import 'dart:collection' show LinkedHashMap; +import 'dart:io'; +import 'dart:uri'; + +import '../../compiler.dart' as diagnostics; +import '../elements/elements.dart'; +import '../resolution/resolution.dart' show ResolverTask, ResolverVisitor; +import '../apiimpl.dart' show Compiler; +import '../scanner/scannerlib.dart' hide SourceString; +import '../ssa/ssa.dart'; +import '../dart2jslib.dart' hide Compiler; +import '../dart_types.dart'; +import '../filenames.dart'; +import '../source_file.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../util/uri_extras.dart'; +import '../dart2js.dart'; +import '../util/characters.dart'; +import '../source_file_provider.dart'; + +import 'mirrors.dart'; +import 'mirrors_util.dart'; +import 'util.dart'; + +//------------------------------------------------------------------------------ +// Utility types and functions for the dart2js mirror system +//------------------------------------------------------------------------------ + +bool _isPrivate(String name) { + return name.startsWith('_'); +} + +List<ParameterMirror> _parametersFromFunctionSignature( + Dart2JsMirrorSystem system, + Dart2JsMethodMirror method, + FunctionSignature signature) { + var parameters = <ParameterMirror>[]; + Link<Element> link = signature.requiredParameters; + while (!link.isEmpty) { + parameters.add(new Dart2JsParameterMirror( + system, method, link.head, false, false)); + link = link.tail; + } + link = signature.optionalParameters; + bool isNamed = signature.optionalParametersAreNamed; + while (!link.isEmpty) { + parameters.add(new Dart2JsParameterMirror( + system, method, link.head, true, isNamed)); + link = link.tail; + } + return parameters; +} + +Dart2JsTypeMirror _convertTypeToTypeMirror( + Dart2JsMirrorSystem system, + DartType type, + InterfaceType defaultType, + [FunctionSignature functionSignature]) { + if (type == null) { + return new Dart2JsInterfaceTypeMirror(system, defaultType); + } else if (type is InterfaceType) { + if (type == system.compiler.types.dynamicType) { + return new Dart2JsDynamicMirror(system, type); + } else { + return new Dart2JsInterfaceTypeMirror(system, type); + } + } else if (type is TypeVariableType) { + return new Dart2JsTypeVariableMirror(system, type); + } else if (type is FunctionType) { + return new Dart2JsFunctionTypeMirror(system, type, functionSignature); + } else if (type is VoidType) { + return new Dart2JsVoidMirror(system, type); + } else if (type is TypedefType) { + return new Dart2JsTypedefMirror(system, type); + } else if (type is MalformedType) { + // TODO(johnniwinther): We need a mirror on malformed types. + return system.dynamicType; + } + _diagnosticListener.internalError( + "Unexpected type $type of kind ${type.kind}"); + system.compiler.internalError("Unexpected type $type of kind ${type.kind}"); +} + +Collection<Dart2JsMemberMirror> _convertElementMemberToMemberMirrors( + Dart2JsContainerMirror library, Element element) { + if (element.isSynthesized) { + return const <Dart2JsMemberMirror>[]; + } else if (element is VariableElement) { + return <Dart2JsMemberMirror>[new Dart2JsFieldMirror(library, element)]; + } else if (element is FunctionElement) { + return <Dart2JsMemberMirror>[new Dart2JsMethodMirror(library, element)]; + } else if (element is AbstractFieldElement) { + var members = <Dart2JsMemberMirror>[]; + if (element.getter != null) { + members.add(new Dart2JsMethodMirror(library, element.getter)); + } + if (element.setter != null) { + members.add(new Dart2JsMethodMirror(library, element.setter)); + } + return members; + } + library.mirrors.compiler.internalError( + "Unexpected member type $element ${element.kind}"); +} + +MethodMirror _convertElementMethodToMethodMirror(Dart2JsContainerMirror library, + Element element) { + if (element is FunctionElement) { + return new Dart2JsMethodMirror(library, element); + } else { + return null; + } +} + +InstanceMirror _convertConstantToInstanceMirror(Dart2JsMirrorSystem mirrors, + Constant constant) { + if (constant is BoolConstant) { + return new Dart2JsBoolConstantMirror(mirrors, constant); + } else if (constant is NumConstant) { + return new Dart2JsNumConstantMirror(mirrors, constant); + } else if (constant is StringConstant) { + return new Dart2JsStringConstantMirror(mirrors, constant); + } else if (constant is ListConstant) { + return new Dart2JsListConstantMirror(mirrors, constant); + } else if (constant is MapConstant) { + return new Dart2JsMapConstantMirror(mirrors, constant); + } else if (constant is TypeConstant) { + return new Dart2JsTypeConstantMirror(mirrors, constant); + } else if (constant is FunctionConstant) { + return new Dart2JsConstantMirror(mirrors, constant); + } else if (constant is NullConstant) { + return new Dart2JsNullConstantMirror(mirrors, constant); + } else if (constant is ConstructedConstant) { + return new Dart2JsConstructedConstantMirror(mirrors, constant); + } + mirrors.compiler.internalError("Unexpected constant $constant"); +} + +class Dart2JsMethodKind { + static const Dart2JsMethodKind REGULAR = const Dart2JsMethodKind("regular"); + static const Dart2JsMethodKind GENERATIVE = + const Dart2JsMethodKind("generative"); + static const Dart2JsMethodKind REDIRECTING = + const Dart2JsMethodKind("redirecting"); + static const Dart2JsMethodKind CONST = const Dart2JsMethodKind("const"); + static const Dart2JsMethodKind FACTORY = const Dart2JsMethodKind("factory"); + static const Dart2JsMethodKind GETTER = const Dart2JsMethodKind("getter"); + static const Dart2JsMethodKind SETTER = const Dart2JsMethodKind("setter"); + static const Dart2JsMethodKind OPERATOR = const Dart2JsMethodKind("operator"); + + final String text; + + const Dart2JsMethodKind(this.text); + + String toString() => text; +} + + +String _getOperatorFromOperatorName(String name) { + Map<String, String> mapping = const { + 'eq': '==', + 'not': '~', + 'index': '[]', + 'indexSet': '[]=', + 'mul': '*', + 'div': '/', + 'mod': '%', + 'tdiv': '~/', + 'add': '+', + 'sub': '-', + 'shl': '<<', + 'shr': '>>', + 'ge': '>=', + 'gt': '>', + 'le': '<=', + 'lt': '<', + 'and': '&', + 'xor': '^', + 'or': '|', + }; + String newName = mapping[name]; + if (newName == null) { + throw new Exception('Unhandled operator name: $name'); + } + return newName; +} + +DiagnosticListener get _diagnosticListener { + return const Dart2JsDiagnosticListener(); +} + +class Dart2JsDiagnosticListener implements DiagnosticListener { + const Dart2JsDiagnosticListener(); + + void cancel(String reason, {node, token, instruction, element}) { + print(reason); + } + + 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); + } + + SourceSpan spanFromSpannable(Node node, [Uri uri]) { + // TODO(johnniwinther): implement this. + throw 'unimplemented'; + } + + void reportMessage(SourceSpan span, Diagnostic message, + diagnostics.Diagnostic kind) { + // TODO(johnniwinther): implement this. + throw 'unimplemented'; + } + + bool onDeprecatedFeature(Spannable span, String feature) { + // TODO(johnniwinther): implement this? + throw 'unimplemented'; + } +} + +//------------------------------------------------------------------------------ +// Compilation implementation +//------------------------------------------------------------------------------ + +// TODO(johnniwinther): Support client configurable handlers/providers. +class Dart2JsCompilation implements Compilation { + Compiler _compiler; + final Uri cwd; + final SourceFileProvider provider; + + Dart2JsCompilation(Path script, Path libraryRoot, + [Path packageRoot, List<String> opts = const <String>[]]) + : cwd = getCurrentDirectory(), + provider = new SourceFileProvider() { + var handler = new FormattingDiagnosticHandler(provider); + var libraryUri = cwd.resolve(libraryRoot.toString()); + var packageUri; + if (packageRoot != null) { + packageUri = cwd.resolve(packageRoot.toString()); + } else { + packageUri = libraryUri; + } + _compiler = new Compiler(provider.readStringFromUri, + null, + handler.diagnosticHandler, + libraryUri, packageUri, opts); + var scriptUri = cwd.resolve(script.toString()); + // TODO(johnniwinther): Detect file not found + _compiler.run(scriptUri); + } + + Dart2JsCompilation.library(List<Path> libraries, Path libraryRoot, + [Path packageRoot, List<String> opts = const <String>[]]) + : cwd = getCurrentDirectory(), + provider = new SourceFileProvider() { + var libraryUri = cwd.resolve(libraryRoot.toString()); + var packageUri; + if (packageRoot != null) { + packageUri = cwd.resolve(packageRoot.toString()); + } else { + packageUri = libraryUri; + } + opts = new List<String>.from(opts); + opts.add('--analyze-only'); + opts.add('--analyze-all'); + _compiler = new Compiler(provider.readStringFromUri, + null, + silentDiagnosticHandler, + libraryUri, packageUri, opts); + var librariesUri = <Uri>[]; + for (Path library in libraries) { + librariesUri.add(cwd.resolve(library.toString())); + // TODO(johnniwinther): Detect file not found + } + _compiler.librariesToAnalyzeWhenRun = librariesUri; + _compiler.run(null); + } + + MirrorSystem get mirrors => new Dart2JsMirrorSystem(_compiler); + + Future<String> compileToJavaScript() => + new Future<String>.immediate(_compiler.assembledCode); +} + + +//------------------------------------------------------------------------------ +// Dart2Js specific extensions of mirror interfaces +//------------------------------------------------------------------------------ + +abstract class Dart2JsMirror implements Mirror { + Dart2JsMirrorSystem get mirrors; +} + +abstract class Dart2JsDeclarationMirror extends Dart2JsMirror + implements DeclarationMirror { + + bool get isTopLevel => owner != null && owner is LibraryMirror; + + bool get isPrivate => _isPrivate(simpleName); + + /** + * Returns the first token for the source of this declaration, not including + * metadata annotations. + */ + Token getBeginToken(); + + /** + * Returns the last token for the source of this declaration. + */ + Token getEndToken(); + + /** + * Returns the script for the source of this declaration. + */ + Script getScript(); +} + +abstract class Dart2JsTypeMirror extends Dart2JsDeclarationMirror + implements TypeMirror { +} + +abstract class Dart2JsElementMirror extends Dart2JsDeclarationMirror { + final Dart2JsMirrorSystem mirrors; + final Element _element; + List<InstanceMirror> _metadata; + + Dart2JsElementMirror(this.mirrors, this._element) { + assert (mirrors != null); + assert (_element != null); + } + + String get simpleName => _element.name.slowToString(); + + String get displayName => simpleName; + + /** + * Computes the first token for this declaration using the begin token of the + * element node or element position as indicator. + */ + Token getBeginToken() { + // TODO(johnniwinther): Avoid calling [parseNode]. + Node node = _element.parseNode(mirrors.compiler); + if (node == null) { + return _element.position(); + } + return node.getBeginToken(); + } + + /** + * Computes the last token for this declaration using the end token of the + * element node or element position as indicator. + */ + Token getEndToken() { + // TODO(johnniwinther): Avoid calling [parseNode]. + Node node = _element.parseNode(mirrors.compiler); + if (node == null) { + return _element.position(); + } + return node.getEndToken(); + } + + /** + * Returns the first token for the source of this declaration, including + * metadata annotations. + */ + Token getFirstToken() { + if (!_element.metadata.isEmpty) { + for (MetadataAnnotation metadata in _element.metadata) { + if (metadata.beginToken != null) { + return metadata.beginToken; + } + } + } + return getBeginToken(); + } + + Script getScript() => _element.getCompilationUnit().script; + + SourceLocation get location { + Token beginToken = getFirstToken(); + Script script = getScript(); + SourceSpan span; + if (beginToken == null) { + span = new SourceSpan(script.uri, 0, 0); + } else { + Token endToken = getEndToken(); + span = mirrors.compiler.spanFromTokens(beginToken, endToken, script.uri); + } + return new Dart2JsSourceLocation(script, span); + } + + String toString() => _element.toString(); + + int get hashCode => qualifiedName.hashCode; + + void _appendCommentTokens(Token commentToken) { + while (commentToken != null && commentToken.kind == COMMENT_TOKEN) { + _metadata.add(new Dart2JsCommentInstanceMirror( + mirrors, commentToken.slowToString())); + commentToken = commentToken.next; + } + } + + List<InstanceMirror> get metadata { + if (_metadata == null) { + _metadata = <InstanceMirror>[]; + for (MetadataAnnotation metadata in _element.metadata) { + _appendCommentTokens(mirrors.compiler.commentMap[metadata.beginToken]); + metadata.ensureResolved(mirrors.compiler); + _metadata.add( + _convertConstantToInstanceMirror(mirrors, metadata.value)); + } + _appendCommentTokens(mirrors.compiler.commentMap[getBeginToken()]); + } + // TODO(johnniwinther): Return an unmodifiable list instead. + return new List<InstanceMirror>.from(_metadata); + } +} + +abstract class Dart2JsMemberMirror extends Dart2JsElementMirror + implements MemberMirror { + + Dart2JsMemberMirror(Dart2JsMirrorSystem system, Element element) + : super(system, element); + + bool get isConstructor => false; + + bool get isVariable => false; + + bool get isMethod => false; + + bool get isStatic => false; + + bool get isParameter => false; +} + +//------------------------------------------------------------------------------ +// Mirror system implementation. +//------------------------------------------------------------------------------ + +class Dart2JsMirrorSystem implements MirrorSystem { + final Compiler compiler; + Map<String, Dart2JsLibraryMirror> _libraries; + Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap; + + Dart2JsMirrorSystem(this.compiler) + : _libraryMap = new Map<LibraryElement, Dart2JsLibraryMirror>(); + + void _ensureLibraries() { + if (_libraries == null) { + _libraries = <String, Dart2JsLibraryMirror>{}; + compiler.libraries.forEach((_, LibraryElement v) { + var mirror = new Dart2JsLibraryMirror(mirrors, v); + _libraries[mirror.simpleName] = mirror; + _libraryMap[v] = mirror; + }); + } + } + + Map<String, LibraryMirror> get libraries { + _ensureLibraries(); + return new ImmutableMapWrapper<String, LibraryMirror>(_libraries); + } + + Dart2JsLibraryMirror _getLibrary(LibraryElement element) => + _libraryMap[element]; + + Dart2JsMirrorSystem get mirrors => this; + + TypeMirror get dynamicType => + _convertTypeToTypeMirror(this, compiler.types.dynamicType, null); + + TypeMirror get voidType => + _convertTypeToTypeMirror(this, compiler.types.voidType, null); +} + +abstract class Dart2JsContainerMirror extends Dart2JsElementMirror + implements ContainerMirror { + Map<String, MemberMirror> _members; + + Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element) + : super(system, element); + + void _ensureMembers(); + + Map<String, MemberMirror> get members { + _ensureMembers(); + return new ImmutableMapWrapper<String, MemberMirror>(_members); + } + + Map<String, MethodMirror> get functions { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => member is MethodMirror ? member : null); + } + + Map<String, MethodMirror> get getters { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isGetter ? + member : null); + } + + Map<String, MethodMirror> get setters { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isSetter ? + member : null); + } + + Map<String, VariableMirror> get variables { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, VariableMirror>( + _members, + (MemberMirror member) => member is VariableMirror ? member : null); + } +} + +class Dart2JsLibraryMirror extends Dart2JsContainerMirror + implements LibraryMirror { + Map<String, ClassMirror> _classes; + + Dart2JsLibraryMirror(Dart2JsMirrorSystem system, LibraryElement library) + : super(system, library); + + LibraryElement get _library => _element; + + Uri get uri => _library.canonicalUri; + + DeclarationMirror get owner => null; + + bool get isPrivate => false; + + LibraryMirror library() => this; + + /** + * Returns the library name (for libraries with a #library tag) or the script + * file name (for scripts without a #library tag). The latter case is used to + * provide a 'library name' for scripts, to use for instance in dartdoc. + */ + String get simpleName { + if (_library.libraryTag != null) { + // TODO(ahe): Remove StringNode check when old syntax is removed. + StringNode name = _library.libraryTag.name.asStringNode(); + if (name != null) { + return name.dartString.slowToString(); + } else { + return _library.libraryTag.name.toString(); + } + } else { + // Use the file name as script name. + String path = _library.canonicalUri.path; + return path.substring(path.lastIndexOf('/') + 1); + } + } + + String get qualifiedName => simpleName; + + void _ensureClasses() { + if (_classes == null) { + _classes = <String, ClassMirror>{}; + _library.forEachLocalMember((Element e) { + if (e.isClass()) { + ClassElement classElement = e; + classElement.ensureResolved(mirrors.compiler); + var type = new Dart2JsClassMirror.fromLibrary(this, classElement); + assert(invariant(_library, !_classes.containsKey(type.simpleName), + message: "Type name '${type.simpleName}' " + "is not unique in $_library.")); + _classes[type.simpleName] = type; + } else if (e.isTypedef()) { + var type = new Dart2JsTypedefMirror.fromLibrary(this, + e.computeType(mirrors.compiler)); + assert(invariant(_library, !_classes.containsKey(type.simpleName), + message: "Type name '${type.simpleName}' " + "is not unique in $_library.")); + _classes[type.simpleName] = type; + } + }); + } + } + + void _ensureMembers() { + if (_members == null) { + _members = <String, MemberMirror>{}; + _library.forEachLocalMember((Element e) { + if (!e.isClass() && !e.isTypedef()) { + for (var member in _convertElementMemberToMemberMirrors(this, e)) { + assert(!_members.containsKey(member.simpleName)); + _members[member.simpleName] = member; + } + } + }); + } + } + + Map<String, ClassMirror> get classes { + _ensureClasses(); + return new ImmutableMapWrapper<String, ClassMirror>(_classes); + } + + /** + * Computes the first token of this library using the first library tag as + * indicator. + */ + Token getBeginToken() { + if (_library.libraryTag != null) { + return _library.libraryTag.getBeginToken(); + } else if (!_library.tags.isEmpty) { + return _library.tags.reverse().head.getBeginToken(); + } + return null; + } + + /** + * Computes the first token of this library using the last library tag as + * indicator. + */ + Token getEndToken() { + if (!_library.tags.isEmpty) { + return _library.tags.head.getEndToken(); + } + return null; + } +} + +class Dart2JsSourceLocation implements SourceLocation { + final Script _script; + final SourceSpan _span; + int _line; + int _column; + + Dart2JsSourceLocation(this._script, this._span); + + int _computeLine() { + var sourceFile = _script.file as SourceFile; + if (sourceFile != null) { + return sourceFile.getLine(offset) + 1; + } + var index = 0; + var lineNumber = 0; + while (index <= offset && index < sourceText.length) { + index = sourceText.indexOf('\n', index) + 1; + if (index <= 0) break; + lineNumber++; + } + return lineNumber; + } + + int get line { + if (_line == null) { + _line = _computeLine(); + } + return _line; + } + + int _computeColumn() { + if (length == 0) return 0; + + var sourceFile = _script.file as SourceFile; + if (sourceFile != null) { + return sourceFile.getColumn(sourceFile.getLine(offset), offset) + 1; + } + int index = offset - 1; + var columnNumber = 0; + while (0 <= index && index < sourceText.length) { + columnNumber++; + var charCode = sourceText.charCodeAt(index); + if (charCode == $CR || charCode == $LF) { + break; + } + index--; + } + return columnNumber; + } + + int get column { + if (_column == null) { + _column = _computeColumn(); + } + return _column; + } + + int get offset => _span.begin; + + int get length => _span.end - _span.begin; + + String get text => _script.text.substring(_span.begin, _span.end); + + Uri get sourceUri => _script.uri; + + String get sourceText => _script.text; +} + +class Dart2JsParameterMirror extends Dart2JsMemberMirror + implements ParameterMirror { + final MethodMirror _method; + final bool isOptional; + final bool isNamed; + + factory Dart2JsParameterMirror(Dart2JsMirrorSystem system, + MethodMirror method, + VariableElement element, + bool isOptional, + bool isNamed) { + if (element is FieldParameterElement) { + return new Dart2JsFieldParameterMirror(system, + method, element, isOptional, isNamed); + } + return new Dart2JsParameterMirror._normal(system, + method, element, isOptional, isNamed); + } + + Dart2JsParameterMirror._normal(Dart2JsMirrorSystem system, + this._method, + VariableElement element, + this.isOptional, + this.isNamed) + : super(system, element); + + DeclarationMirror get owner => _method; + + VariableElement get _variableElement => _element; + + String get qualifiedName => '${_method.qualifiedName}#${simpleName}'; + + TypeMirror get type => _convertTypeToTypeMirror(mirrors, + _variableElement.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType, + _variableElement.variables.functionSignature); + + + bool get isFinal => false; + + bool get isConst => false; + + String get defaultValue { + if (hasDefaultValue) { + SendSet expression = _variableElement.cachedNode.asSendSet(); + return unparse(expression.arguments.head); + } + return null; + } + + bool get hasDefaultValue { + return _variableElement.cachedNode != null && + _variableElement.cachedNode is SendSet; + } + + bool get isInitializingFormal => false; + + VariableMirror get initializedField => null; +} + +class Dart2JsFieldParameterMirror extends Dart2JsParameterMirror { + + Dart2JsFieldParameterMirror(Dart2JsMirrorSystem system, + MethodMirror method, + FieldParameterElement element, + bool isOptional, + bool isNamed) + : super._normal(system, method, element, isOptional, isNamed); + + FieldParameterElement get _fieldParameterElement => _element; + + TypeMirror get type { + if (_fieldParameterElement.variables.cachedNode.type != null) { + return super.type; + } + return _convertTypeToTypeMirror(mirrors, + _fieldParameterElement.fieldElement.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType, + _variableElement.variables.functionSignature); + } + + bool get isInitializingFormal => true; + + VariableMirror get initializedField => new Dart2JsFieldMirror( + _method.owner, _fieldParameterElement.fieldElement); +} + +//------------------------------------------------------------------------------ +// Declarations +//------------------------------------------------------------------------------ +class Dart2JsClassMirror extends Dart2JsContainerMirror + implements Dart2JsTypeMirror, ClassMirror { + final Dart2JsLibraryMirror library; + List<TypeVariableMirror> _typeVariables; + + Dart2JsClassMirror(Dart2JsMirrorSystem system, ClassElement _class) + : this.library = system._getLibrary(_class.getLibrary()), + super(system, _class); + + ClassElement get _class => _element; + + Dart2JsClassMirror.fromLibrary(Dart2JsLibraryMirror library, + ClassElement _class) + : this.library = library, + super(library.mirrors, _class); + + DeclarationMirror get owner => library; + + String get qualifiedName => '${library.qualifiedName}.${simpleName}'; + + void _ensureMembers() { + if (_members == null) { + _members = <String, Dart2JsMemberMirror>{}; + _class.forEachMember((_, e) { + for (var member in _convertElementMemberToMemberMirrors(this, e)) { + assert(!_members.containsKey(member.simpleName)); + _members[member.simpleName] = member; + } + }); + } + } + + Map<String, MethodMirror> get methods => functions; + + Map<String, MethodMirror> get constructors { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, (m) => m.isConstructor ? m : null); + } + + bool get isObject => _class == mirrors.compiler.objectClass; + + bool get isDynamic => false; + + bool get isVoid => false; + + bool get isTypeVariable => false; + + bool get isTypedef => false; + + bool get isFunction => false; + + ClassMirror get originalDeclaration => this; + + ClassMirror get superclass { + if (_class.supertype != null) { + return new Dart2JsInterfaceTypeMirror(mirrors, _class.supertype); + } + return null; + } + + List<ClassMirror> get superinterfaces { + var list = <ClassMirror>[]; + Link<DartType> link = _class.interfaces; + while (!link.isEmpty) { + var type = _convertTypeToTypeMirror(mirrors, link.head, + mirrors.compiler.types.dynamicType); + list.add(type); + link = link.tail; + } + return list; + } + + bool get isClass => !_class.isInterface(); + + bool get isInterface => _class.isInterface(); + + bool get isAbstract => _class.modifiers.isAbstract(); + + bool get isOriginalDeclaration => true; + + List<TypeMirror> get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List<TypeVariableMirror> get typeVariables { + if (_typeVariables == null) { + _typeVariables = <TypeVariableMirror>[]; + _class.ensureResolved(mirrors.compiler); + for (TypeVariableType typeVariable in _class.typeVariables) { + _typeVariables.add( + new Dart2JsTypeVariableMirror(mirrors, typeVariable)); + } + } + return _typeVariables; + } + + /** + * Returns the default type for this interface. + */ + ClassMirror get defaultFactory { + if (_class.defaultClass != null) { + return new Dart2JsInterfaceTypeMirror(mirrors, _class.defaultClass); + } + return null; + } + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! ClassMirror) { + return false; + } + if (library != other.library) { + return false; + } + if (!identical(isOriginalDeclaration, other.isOriginalDeclaration)) { + return false; + } + return qualifiedName == other.qualifiedName; + } +} + +class Dart2JsTypedefMirror extends Dart2JsTypeElementMirror + implements Dart2JsTypeMirror, TypedefMirror { + final Dart2JsLibraryMirror _library; + List<TypeVariableMirror> _typeVariables; + TypeMirror _definition; + + Dart2JsTypedefMirror(Dart2JsMirrorSystem system, TypedefType _typedef) + : this._library = system._getLibrary(_typedef.element.getLibrary()), + super(system, _typedef); + + Dart2JsTypedefMirror.fromLibrary(Dart2JsLibraryMirror library, + TypedefType _typedef) + : this._library = library, + super(library.mirrors, _typedef); + + TypedefType get _typedef => _type; + + String get qualifiedName => '${library.qualifiedName}.${simpleName}'; + + LibraryMirror get library => _library; + + bool get isTypedef => true; + + List<TypeMirror> get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List<TypeVariableMirror> get typeVariables { + if (_typeVariables == null) { + _typeVariables = <TypeVariableMirror>[]; + for (TypeVariableType typeVariable in _typedef.typeArguments) { + _typeVariables.add( + new Dart2JsTypeVariableMirror(mirrors, typeVariable)); + } + } + return _typeVariables; + } + + TypeMirror get value { + if (_definition == null) { + // TODO(johnniwinther): Should be [ensureResolved]. + mirrors.compiler.resolveTypedef(_typedef.element); + _definition = _convertTypeToTypeMirror( + mirrors, + _typedef.element.alias, + mirrors.compiler.types.dynamicType, + _typedef.element.functionSignature); + } + return _definition; + } + + ClassMirror get originalDeclaration => this; + + // TODO(johnniwinther): How should a typedef respond to these? + ClassMirror get superclass => null; + + List<ClassMirror> get superinterfaces => const <ClassMirror>[]; + + bool get isClass => false; + + bool get isInterface => false; + + bool get isOriginalDeclaration => true; + + bool get isAbstract => false; +} + +class Dart2JsTypeVariableMirror extends Dart2JsTypeElementMirror + implements TypeVariableMirror { + final TypeVariableType _typeVariableType; + ClassMirror _declarer; + + Dart2JsTypeVariableMirror(Dart2JsMirrorSystem system, + TypeVariableType typeVariableType) + : this._typeVariableType = typeVariableType, + super(system, typeVariableType) { + assert(_typeVariableType != null); + } + + + String get qualifiedName => '${declarer.qualifiedName}.${simpleName}'; + + ClassMirror get declarer { + if (_declarer == null) { + if (_typeVariableType.element.enclosingElement.isClass()) { + _declarer = new Dart2JsClassMirror(mirrors, + _typeVariableType.element.enclosingElement); + } else if (_typeVariableType.element.enclosingElement.isTypedef()) { + _declarer = new Dart2JsTypedefMirror(mirrors, + _typeVariableType.element.enclosingElement.computeType( + mirrors.compiler)); + } + } + return _declarer; + } + + LibraryMirror get library => declarer.library; + + DeclarationMirror get owner => declarer; + + bool get isTypeVariable => true; + + TypeMirror get upperBound => _convertTypeToTypeMirror( + mirrors, + _typeVariableType.element.bound, + mirrors.compiler.objectClass.computeType(mirrors.compiler)); + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeVariableMirror) { + return false; + } + if (declarer != other.declarer) { + return false; + } + return qualifiedName == other.qualifiedName; + } +} + + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +abstract class Dart2JsTypeElementMirror extends Dart2JsElementMirror + implements Dart2JsTypeMirror { + final DartType _type; + + Dart2JsTypeElementMirror(Dart2JsMirrorSystem system, DartType type) + : super(system, type.element), + this._type = type; + + String get simpleName => _type.name.slowToString(); + + DeclarationMirror get owner => library; + + LibraryMirror get library { + return mirrors._getLibrary(_type.element.getLibrary()); + } + + bool get isObject => false; + + bool get isVoid => false; + + bool get isDynamic => false; + + bool get isTypeVariable => false; + + bool get isTypedef => false; + + bool get isFunction => false; + + String toString() => _type.toString(); + + Map<String, MemberMirror> get members => const <String, MemberMirror>{}; + + Map<String, MethodMirror> get constructors => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get methods => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get getters => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get setters => const <String, MethodMirror>{}; + + Map<String, VariableMirror> get variables => const <String, VariableMirror>{}; + + ClassMirror get defaultFactory => null; +} + +class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror + implements ClassMirror { + List<TypeMirror> _typeArguments; + + Dart2JsInterfaceTypeMirror(Dart2JsMirrorSystem system, + InterfaceType interfaceType) + : super(system, interfaceType); + + InterfaceType get _interfaceType => _type; + + String get qualifiedName => originalDeclaration.qualifiedName; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MemberMirror> get members => originalDeclaration.members; + + bool get isObject => mirrors.compiler.objectClass == _type.element; + + bool get isDynamic => mirrors.compiler.dynamicClass == _type.element; + + ClassMirror get originalDeclaration + => new Dart2JsClassMirror(mirrors, _type.element); + + // TODO(johnniwinther): Substitute type arguments for type variables. + ClassMirror get superclass => originalDeclaration.superclass; + + // TODO(johnniwinther): Substitute type arguments for type variables. + List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces; + + bool get isClass => originalDeclaration.isClass; + + bool get isInterface => originalDeclaration.isInterface; + + bool get isAbstract => originalDeclaration.isAbstract; + + bool get isPrivate => originalDeclaration.isPrivate; + + bool get isOriginalDeclaration => false; + + List<TypeMirror> get typeArguments { + if (_typeArguments == null) { + _typeArguments = <TypeMirror>[]; + if (!_interfaceType.isRaw) { + Link<DartType> type = _interfaceType.typeArguments; + while (type != null && type.head != null) { + _typeArguments.add(_convertTypeToTypeMirror(mirrors, type.head, + mirrors.compiler.types.dynamicType)); + type = type.tail; + } + } + } + return _typeArguments; + } + + List<TypeVariableMirror> get typeVariables => + originalDeclaration.typeVariables; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get constructors => + originalDeclaration.constructors; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get methods => originalDeclaration.methods; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get setters => originalDeclaration.setters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get getters => originalDeclaration.getters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, VariableMirror> get variables => originalDeclaration.variables; + + // TODO(johnniwinther): Substitute type arguments for type variables? + ClassMirror get defaultFactory => originalDeclaration.defaultFactory; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! ClassMirror) { + return false; + } + if (other.isOriginalDeclaration) { + return false; + } + if (originalDeclaration != other.originalDeclaration) { + return false; + } + var thisTypeArguments = typeArguments.iterator; + var otherTypeArguments = other.typeArguments.iterator; + while (thisTypeArguments.moveNext()) { + if (!otherTypeArguments.moveNext()) return false; + if (thisTypeArguments.current != otherTypeArguments.current) { + return false; + } + } + return !otherTypeArguments.moveNext(); + } +} + + +class Dart2JsFunctionTypeMirror extends Dart2JsTypeElementMirror + implements FunctionTypeMirror { + final FunctionSignature _functionSignature; + List<ParameterMirror> _parameters; + + Dart2JsFunctionTypeMirror(Dart2JsMirrorSystem system, + FunctionType functionType, this._functionSignature) + : super(system, functionType) { + assert (_functionSignature != null); + } + + FunctionType get _functionType => _type; + + // TODO(johnniwinther): Is this the qualified name of a function type? + String get qualifiedName => originalDeclaration.qualifiedName; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MemberMirror> get members { + var method = callMethod; + if (method != null) { + var map = new Map<String, MemberMirror>.from( + originalDeclaration.members); + var name = method.qualifiedName; + assert(!map.containsKey(name)); + map[name] = method; + return new ImmutableMapWrapper<String, MemberMirror>(map); + } + return originalDeclaration.members; + } + + bool get isFunction => true; + + MethodMirror get callMethod => _convertElementMethodToMethodMirror( + mirrors._getLibrary(_functionType.element.getLibrary()), + _functionType.element); + + ClassMirror get originalDeclaration + => new Dart2JsClassMirror(mirrors, mirrors.compiler.functionClass); + + // TODO(johnniwinther): Substitute type arguments for type variables. + ClassMirror get superclass => originalDeclaration.superclass; + + // TODO(johnniwinther): Substitute type arguments for type variables. + List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces; + + bool get isClass => originalDeclaration.isClass; + + bool get isInterface => originalDeclaration.isInterface; + + bool get isPrivate => originalDeclaration.isPrivate; + + bool get isOriginalDeclaration => false; + + bool get isAbstract => false; + + List<TypeMirror> get typeArguments => const <TypeMirror>[]; + + List<TypeVariableMirror> get typeVariables => + originalDeclaration.typeVariables; + + TypeMirror get returnType { + return _convertTypeToTypeMirror(mirrors, _functionType.returnType, + mirrors.compiler.types.dynamicType); + } + + List<ParameterMirror> get parameters { + if (_parameters == null) { + _parameters = _parametersFromFunctionSignature(mirrors, callMethod, + _functionSignature); + } + return _parameters; + } +} + +class Dart2JsVoidMirror extends Dart2JsTypeElementMirror { + + Dart2JsVoidMirror(Dart2JsMirrorSystem system, VoidType voidType) + : super(system, voidType); + + VoidType get _voidType => _type; + + String get qualifiedName => simpleName; + + /** + * The void type has no location. + */ + SourceLocation get location => null; + + /** + * The void type has no library. + */ + LibraryMirror get library => null; + + bool get isVoid => true; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeMirror) { + return false; + } + return other.isVoid; + } +} + + +class Dart2JsDynamicMirror extends Dart2JsTypeElementMirror { + Dart2JsDynamicMirror(Dart2JsMirrorSystem system, InterfaceType voidType) + : super(system, voidType); + + InterfaceType get _dynamicType => _type; + + String get qualifiedName => simpleName; + + /** + * The dynamic type has no location. + */ + SourceLocation get location => null; + + /** + * The dynamic type has no library. + */ + LibraryMirror get library => null; + + bool get isDynamic => true; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeMirror) { + return false; + } + return other.isDynamic; + } +} + +//------------------------------------------------------------------------------ +// Member mirrors implementation. +//------------------------------------------------------------------------------ + +class Dart2JsMethodMirror extends Dart2JsMemberMirror + implements MethodMirror { + final Dart2JsContainerMirror _objectMirror; + final String simpleName; + final String displayName; + final String constructorName; + final String operatorName; + final Dart2JsMethodKind _kind; + + Dart2JsMethodMirror._internal(Dart2JsContainerMirror objectMirror, + FunctionElement function, + String this.simpleName, + String this.displayName, + String this.constructorName, + String this.operatorName, + Dart2JsMethodKind this._kind) + : this._objectMirror = objectMirror, + super(objectMirror.mirrors, function); + + factory Dart2JsMethodMirror(Dart2JsContainerMirror objectMirror, + FunctionElement function) { + String realName = function.name.slowToString(); + // TODO(ahe): This method should not be calling + // Elements.operatorNameToIdentifier. + String simpleName = + Elements.operatorNameToIdentifier(function.name).slowToString(); + String displayName; + String constructorName = null; + String operatorName = null; + Dart2JsMethodKind kind; + if (function.kind == ElementKind.GETTER) { + kind = Dart2JsMethodKind.GETTER; + displayName = simpleName; + } else if (function.kind == ElementKind.SETTER) { + kind = Dart2JsMethodKind.SETTER; + displayName = simpleName; + simpleName = '$simpleName='; + } else if (function.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { + // TODO(johnniwinther): Support detection of redirecting constructors. + constructorName = ''; + int dollarPos = simpleName.indexOf('\$'); + if (dollarPos != -1) { + constructorName = simpleName.substring(dollarPos + 1); + simpleName = simpleName.substring(0, dollarPos); + // Simple name is TypeName.constructorName. + simpleName = '$simpleName.$constructorName'; + } else { + // Simple name is TypeName. + } + if (function.modifiers.isConst()) { + kind = Dart2JsMethodKind.CONST; + } else { + kind = Dart2JsMethodKind.GENERATIVE; + } + displayName = simpleName; + } else if (function.modifiers.isFactory()) { + kind = Dart2JsMethodKind.FACTORY; + constructorName = ''; + int dollarPos = simpleName.indexOf('\$'); + if (dollarPos != -1) { + constructorName = simpleName.substring(dollarPos+1); + simpleName = simpleName.substring(0, dollarPos); + simpleName = '$simpleName.$constructorName'; + } + // Simple name is TypeName.constructorName. + displayName = simpleName; + } else if (realName == 'unary-') { + kind = Dart2JsMethodKind.OPERATOR; + operatorName = '-'; + // Simple name is 'unary-'. + simpleName = Mirror.UNARY_MINUS; + // Display name is 'operator operatorName'. + displayName = 'operator -'; + } else if (simpleName.startsWith('operator\$')) { + String str = simpleName.substring(9); + simpleName = 'operator'; + kind = Dart2JsMethodKind.OPERATOR; + operatorName = _getOperatorFromOperatorName(str); + // Simple name is 'operator operatorName'. + simpleName = operatorName; + // Display name is 'operator operatorName'. + displayName = 'operator $operatorName'; + } else { + kind = Dart2JsMethodKind.REGULAR; + displayName = simpleName; + } + return new Dart2JsMethodMirror._internal(objectMirror, function, + simpleName, displayName, constructorName, operatorName, kind); + } + + FunctionElement get _function => _element; + + String get qualifiedName + => '${owner.qualifiedName}.$simpleName'; + + DeclarationMirror get owner => _objectMirror; + + bool get isTopLevel => _objectMirror is LibraryMirror; + + bool get isConstructor + => isGenerativeConstructor || isConstConstructor || + isFactoryConstructor || isRedirectingConstructor; + + bool get isMethod => !isConstructor; + + bool get isPrivate => + isConstructor ? _isPrivate(constructorName) : _isPrivate(simpleName); + + bool get isStatic => _function.modifiers.isStatic(); + + List<ParameterMirror> get parameters { + return _parametersFromFunctionSignature(mirrors, this, + _function.computeSignature(mirrors.compiler)); + } + + TypeMirror get returnType => _convertTypeToTypeMirror( + mirrors, _function.computeSignature(mirrors.compiler).returnType, + mirrors.compiler.types.dynamicType); + + bool get isAbstract => _function.isAbstract(mirrors.compiler); + + bool get isRegularMethod => !(isGetter || isSetter || isConstructor); + + bool get isConstConstructor => _kind == Dart2JsMethodKind.CONST; + + bool get isGenerativeConstructor => _kind == Dart2JsMethodKind.GENERATIVE; + + bool get isRedirectingConstructor => _kind == Dart2JsMethodKind.REDIRECTING; + + bool get isFactoryConstructor => _kind == Dart2JsMethodKind.FACTORY; + + bool get isGetter => _kind == Dart2JsMethodKind.GETTER; + + bool get isSetter => _kind == Dart2JsMethodKind.SETTER; + + bool get isOperator => _kind == Dart2JsMethodKind.OPERATOR; +} + +class Dart2JsFieldMirror extends Dart2JsMemberMirror implements VariableMirror { + Dart2JsContainerMirror _objectMirror; + VariableElement _variable; + + Dart2JsFieldMirror(Dart2JsContainerMirror objectMirror, + VariableElement variable) + : this._objectMirror = objectMirror, + this._variable = variable, + super(objectMirror.mirrors, variable); + + String get qualifiedName + => '${owner.qualifiedName}.$simpleName'; + + DeclarationMirror get owner => _objectMirror; + + bool get isTopLevel => _objectMirror is LibraryMirror; + + bool get isVariable => true; + + bool get isStatic => _variable.modifiers.isStatic(); + + bool get isFinal => _variable.modifiers.isFinal(); + + bool get isConst => _variable.modifiers.isConst(); + + TypeMirror get type => _convertTypeToTypeMirror(mirrors, + _variable.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType); +} + +//////////////////////////////////////////////////////////////////////////////// +// Mirrors on constant values used for metadata. +//////////////////////////////////////////////////////////////////////////////// + +class Dart2JsConstantMirror extends InstanceMirror { + final Dart2JsMirrorSystem mirrors; + final Constant _constant; + + Dart2JsConstantMirror(this.mirrors, this._constant); + + ClassMirror get type { + return new Dart2JsClassMirror(mirrors, + _constant.computeType(mirrors.compiler).element); + } + + bool get hasReflectee => false; + + get reflectee { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } + + Future<InstanceMirror> getField(String fieldName) { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } +} + +class Dart2JsNullConstantMirror extends Dart2JsConstantMirror { + Dart2JsNullConstantMirror(Dart2JsMirrorSystem mirrors, NullConstant constant) + : super(mirrors, constant); + + NullConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => null; +} + +class Dart2JsBoolConstantMirror extends Dart2JsConstantMirror { + Dart2JsBoolConstantMirror(Dart2JsMirrorSystem mirrors, BoolConstant constant) + : super(mirrors, constant); + + Dart2JsBoolConstantMirror.fromBool(Dart2JsMirrorSystem mirrors, bool value) + : super(mirrors, value ? new TrueConstant() : new FalseConstant()); + + BoolConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant is TrueConstant; +} + +class Dart2JsStringConstantMirror extends Dart2JsConstantMirror { + Dart2JsStringConstantMirror(Dart2JsMirrorSystem mirrors, + StringConstant constant) + : super(mirrors, constant); + + Dart2JsStringConstantMirror.fromString(Dart2JsMirrorSystem mirrors, + String text) + : super(mirrors, + new StringConstant(new DartString.literal(text), null)); + + StringConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant.value.slowToString(); +} + +class Dart2JsNumConstantMirror extends Dart2JsConstantMirror { + Dart2JsNumConstantMirror(Dart2JsMirrorSystem mirrors, + NumConstant constant) + : super(mirrors, constant); + + NumConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant.value; +} + +class Dart2JsListConstantMirror extends Dart2JsConstantMirror + implements ListInstanceMirror { + Dart2JsListConstantMirror(Dart2JsMirrorSystem mirrors, + ListConstant constant) + : super(mirrors, constant); + + ListConstant get _constant => super._constant; + + int get length => _constant.length; + + Future<InstanceMirror> operator[](int index) { + if (index < 0) throw new RangeError('Negative index'); + if (index >= _constant.length) throw new RangeError('Index out of bounds'); + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, _constant.entries[index])); + } +} + +class Dart2JsMapConstantMirror extends Dart2JsConstantMirror + implements MapInstanceMirror { + List<String> _listCache; + + Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors, + MapConstant constant) + : super(mirrors, constant); + + MapConstant get _constant => super._constant; + + List<String> get _list { + if (_listCache == null) { + _listCache = new List<String>(_constant.keys.entries.length); + int index = 0; + for (StringConstant keyConstant in _constant.keys.entries) { + _listCache[index] = keyConstant.value.slowToString(); + index++; + } + } + return _listCache; + } + + int get length => _constant.length; + + Collection<String> get keys { + // TODO(johnniwinther): Return an unmodifiable list instead. + return new List<String>.from(_list); + } + + Future<InstanceMirror> operator[](String key) { + int index = _list.indexOf(key); + if (index == -1) return null; + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, _constant.values[index])); + } +} + +class Dart2JsTypeConstantMirror extends Dart2JsConstantMirror + implements TypeInstanceMirror { + + Dart2JsTypeConstantMirror(Dart2JsMirrorSystem mirrors, + TypeConstant constant) + : super(mirrors, constant); + + TypeConstant get _constant => super._constant; + + TypeMirror get representedType => _convertTypeToTypeMirror( + mirrors, _constant.representedType, mirrors.compiler.types.dynamicType); +} + +class Dart2JsConstructedConstantMirror extends Dart2JsConstantMirror { + Map<String,Constant> _fieldMapCache; + + Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors, + ConstructedConstant constant) + : super(mirrors, constant); + + ConstructedConstant get _constant => super._constant; + + Map<String,Constant> get _fieldMap { + if (_fieldMapCache == null) { + _fieldMapCache = new LinkedHashMap<String,Constant>(); + if (identical(_constant.type.element.kind, ElementKind.CLASS)) { + var index = 0; + ClassElement element = _constant.type.element; + element.forEachInstanceField((_, Element field) { + String fieldName = field.name.slowToString(); + _fieldMapCache.putIfAbsent(fieldName, () => _constant.fields[index]); + index++; + }, includeBackendMembers: true, includeSuperMembers: true); + } + } + return _fieldMapCache; + } + + Future<InstanceMirror> getField(String fieldName) { + Constant fieldConstant = _fieldMap[fieldName]; + if (fieldConstant != null) { + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, fieldConstant)); + } + return super.getField(fieldName); + } +} + +class Dart2JsCommentInstanceMirror implements CommentInstanceMirror { + final Dart2JsMirrorSystem mirrors; + final String text; + String _trimmedText; + + Dart2JsCommentInstanceMirror(this.mirrors, this.text); + + ClassMirror get type { + return new Dart2JsClassMirror(mirrors, mirrors.compiler.documentClass); + } + + bool get isDocComment => text.startsWith('/**') || text.startsWith('///'); + + String get trimmedText { + if (_trimmedText == null) { + _trimmedText = stripComment(text); + } + return _trimmedText; + } + + bool get hasReflectee => false; + + get reflectee { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } + + Future<InstanceMirror> getField(String fieldName) { + if (fieldName == 'isDocComment') { + return new Future.immediate( + new Dart2JsBoolConstantMirror.fromBool(mirrors, isDocComment)); + } else if (fieldName == 'text') { + return new Future.immediate( + new Dart2JsStringConstantMirror.fromString(mirrors, text)); + } else if (fieldName == 'trimmedText') { + return new Future.immediate( + new Dart2JsStringConstantMirror.fromString(mirrors, trimmedText)); + } + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart new file mode 100644 index 0000000..6f5189e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart
@@ -0,0 +1,738 @@ +// 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. + +library mirrors; + +import 'dart:async'; +import 'dart:io'; +import 'dart:uri'; + +// TODO(rnystrom): Use "package:" URL (#4968). +import 'dart2js_mirror.dart'; + +/** + * [Compilation] encapsulates the compilation of a program. + */ +abstract class Compilation { + /** + * Creates a new compilation which has [script] as its entry point. + */ + factory Compilation(Path script, + Path libraryRoot, + [Path packageRoot, + List<String> opts = const <String>[]]) { + return new Dart2JsCompilation(script, libraryRoot, packageRoot, opts); + } + + /** + * Creates a new compilation which consists of a set of libraries, but which + * has no entry point. This compilation cannot generate output but can only + * be used for static inspection of the source code. + */ + factory Compilation.library(List<Path> libraries, + Path libraryRoot, + [Path packageRoot, + List<String> opts = const []]) { + return new Dart2JsCompilation.library(libraries, libraryRoot, + packageRoot, opts); + } + + /** + * Returns the mirror system for this compilation. + */ + final MirrorSystem mirrors; + + /** + * Returns a future for the compiled JavaScript code. + */ + Future<String> compileToJavaScript(); +} + +/** + * The main interface for the whole mirror system. + */ +abstract class MirrorSystem { + /** + * Returns an unmodifiable map of all libraries in this mirror system. + */ + Map<String, LibraryMirror> get libraries; + + /** + * A mirror on the [:dynamic:] type. + */ + TypeMirror get dynamicType; + + /** + * A mirror on the [:void:] type. + */ + TypeMirror get voidType; +} + + +/** + * An entity in the mirror system. + */ +abstract class Mirror { + static const String UNARY_MINUS = 'unary-'; + + // TODO(johnniwinther): Do we need this on all mirrors? + /** + * Returns the mirror system which contains this mirror. + */ + MirrorSystem get mirrors; +} + +abstract class DeclarationMirror implements Mirror { + /** + * The simple name of the entity. The simple name is unique within the + * scope of the entity declaration. + * + * The simple name is in most cases the declared single identifier name of + * the entity, such as 'method' for a method [:void method() {...}:]. For an + * unnamed constructor for [:class Foo:] the simple name is 'Foo'. For a + * constructor for [:class Foo:] named 'named' the simple name is 'Foo.named'. + * For a property [:foo:] the simple name of the getter method is 'foo' and + * the simple name of the setter is 'foo='. For operators the simple name is + * the operator itself, for example '+' for [:operator +:]. + * + * The simple name for the unary minus operator is [UNARY_MINUS]. + */ + String get simpleName; + + /** + * The display name is the normal representation of the entity name. In most + * cases the display name is the simple name, but for a setter 'foo=' the + * display name is simply 'foo' and for the unary minus operator the display + * name is 'operator -'. The display name is not unique. + */ + String get displayName; + + /** + * Returns the name of this entity qualified by is enclosing context. For + * instance, the qualified name of a method 'method' in class 'Class' in + * library 'library' is 'library.Class.method'. + */ + String get qualifiedName; + + /** + * The source location of this Dart language entity. + */ + SourceLocation get location; + + /** + * A mirror on the owner of this function. This is the declaration immediately + * surrounding the reflectee. + * + * Note that for libraries, the owner will be [:null:]. + */ + DeclarationMirror get owner; + + /** + * Is this declaration private? + * + * Note that for libraries, this will be [:false:]. + */ + bool get isPrivate; + + /** + * Is this declaration top-level? + * + * This is defined to be equivalent to: + * [:mirror.owner != null && mirror.owner is LibraryMirror:] + */ + bool get isTopLevel; + + /** + * A list of the metadata associated with this declaration. + */ + List<InstanceMirror> get metadata; +} + +abstract class ObjectMirror implements Mirror { + /** + * Invokes a getter and returns a mirror on the result. The getter + * can be the implicit getter for a field or a user-defined getter + * method. + */ + Future<InstanceMirror> getField(String fieldName); +} + +/** + * An [InstanceMirror] reflects an instance of a Dart language object. + */ +abstract class InstanceMirror implements ObjectMirror { + /** + * A mirror on the type of the reflectee. + */ + ClassMirror get type; + + /** + * Does [reflectee] contain the instance reflected by this mirror? + * This will always be true in the local case (reflecting instances + * in the same isolate), but only true in the remote case if this + * mirror reflects a simple value. + * + * A value is simple if one of the following holds: + * - the value is null + * - the value is of type [num] + * - the value is of type [bool] + * - the value is of type [String] + */ + bool get hasReflectee; + + /** + * If the [InstanceMirror] reflects an instance it is meaningful to + * have a local reference to, we provide access to the actual + * instance here. + * + * If you access [reflectee] when [hasReflectee] is false, an + * exception is thrown. + */ + get reflectee; +} + +/** + * Specialized [InstanceMirror] used for reflection on constant lists. + */ +abstract class ListInstanceMirror implements InstanceMirror { + Future<InstanceMirror> operator[](int index); + int get length; +} + +/** + * Specialized [InstanceMirror] used for reflection on constant maps. + */ +abstract class MapInstanceMirror implements InstanceMirror { + /** + * Returns a collection containing all the keys in the map. + */ + Collection<String> get keys; + + /** + * Returns a future on the instance mirror of the value for the given key or + * null if key is not in the map. + */ + Future<InstanceMirror> operator[](String key); + + /** + * The number of {key, value} pairs in the map. + */ + int get length; +} + +/** + * Specialized [InstanceMirror] used for reflection on type constants. + */ +abstract class TypeInstanceMirror implements InstanceMirror { + /** + * Returns the type mirror for the type represented by the reflected type + * constant. + */ + TypeMirror get representedType; +} + +/** + * Specialized [InstanceMirror] used for reflection on comments as metadata. + */ +abstract class CommentInstanceMirror implements InstanceMirror { + /** + * The comment text as written in the source text. + */ + String get text; + + /** + * The comment text without the start, end, and padding text. + * + * For example, if [text] is [: /** Comment text. */ :] then the [trimmedText] + * is [: Comment text. :]. + */ + String get trimmedText; + + /** + * Is [:true:] if this comment is a documentation comment. + * + * That is, that the comment is either enclosed in [: /** ... */ :] or starts + * with [: /// :]. + */ + bool get isDocComment; +} + +/** + * Common interface for classes and libraries. + */ +abstract class ContainerMirror implements Mirror { + + /** + * An immutable map from from names to mirrors for all members in this + * container. + */ + Map<String, MemberMirror> get members; +} + +/** + * A library. + */ +abstract class LibraryMirror implements ContainerMirror, DeclarationMirror { + /** + * An immutable map from from names to mirrors for all members in this + * library. + * + * The members of a library are its top-level classes, functions, variables, + * getters, and setters. + */ + Map<String, MemberMirror> get members; + + /** + * An immutable map from names to mirrors for all class + * declarations in this library. + */ + Map<String, ClassMirror> get classes; + + /** + * An immutable map from names to mirrors for all function, getter, + * and setter declarations in this library. + */ + Map<String, MethodMirror> get functions; + + /** + * An immutable map from names to mirrors for all getter + * declarations in this library. + */ + Map<String, MethodMirror> get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations in this library. + */ + Map<String, MethodMirror> get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations in this library. + */ + Map<String, VariableMirror> get variables; + + /** + * Returns the canonical URI for this library. + */ + Uri get uri; +} + +/** + * Common interface for classes, interfaces, typedefs and type variables. + */ +abstract class TypeMirror implements DeclarationMirror { + /** + * Returns the library in which this member resides. + */ + LibraryMirror get library; + + /** + * Is [:true:] iff this type is the [:Object:] type. + */ + bool get isObject; + + /** + * Is [:true:] iff this type is the [:dynamic:] type. + */ + bool get isDynamic; + + /** + * Is [:true:] iff this type is the void type. + */ + bool get isVoid; + + /** + * Is [:true:] iff this type is a type variable. + */ + bool get isTypeVariable; + + /** + * Is [:true:] iff this type is a typedef. + */ + bool get isTypedef; + + /** + * Is [:true:] iff this type is a function type. + */ + bool get isFunction; +} + +/** + * A class or interface type. + */ +abstract class ClassMirror implements TypeMirror, ContainerMirror { + /** + * A mirror on the original declaration of this type. + * + * For most classes, they are their own original declaration. For + * generic classes, however, there is a distinction between the + * original class declaration, which has unbound type variables, and + * the instantiations of generic classes, which have bound type + * variables. + */ + ClassMirror get originalDeclaration; + + /** + * Returns the super class of this type, or null if this type is [Object] or a + * typedef. + */ + ClassMirror get superclass; + + /** + * Returns a list of the interfaces directly implemented by this type. + */ + List<ClassMirror> get superinterfaces; + + /** + * Is [:true:] iff this type is a class. + */ + bool get isClass; + + /** + * Is [:true:] iff this type is an interface. + */ + bool get isInterface; + + /** + * Is this the original declaration of this type? + * + * For most classes, they are their own original declaration. For + * generic classes, however, there is a distinction between the + * original class declaration, which has unbound type variables, and + * the instantiations of generic classes, which have bound type + * variables. + */ + bool get isOriginalDeclaration; + + /** + * Is [:true:] if this class is declared abstract. + */ + bool get isAbstract; + + /** + * Returns a list of the type arguments for this type. + */ + List<TypeMirror> get typeArguments; + + /** + * Returns the list of type variables for this type. + */ + List<TypeVariableMirror> get typeVariables; + + /** + * An immutable map from from names to mirrors for all members of + * this type. + * + * The members of a type are its methods, fields, getters, and + * setters. Note that constructors and type variables are not + * considered to be members of a type. + * + * This does not include inherited members. + */ + Map<String, MemberMirror> get members; + + /** + * An immutable map from names to mirrors for all method, + * declarations for this type. This does not include getters and + * setters. + */ + Map<String, MethodMirror> get methods; + + /** + * An immutable map from names to mirrors for all getter + * declarations for this type. + */ + Map<String, MethodMirror> get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations for this type. + */ + Map<String, MethodMirror> get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations for this type. + */ + Map<String, VariableMirror> get variables; + + /** + * An immutable map from names to mirrors for all constructor + * declarations for this type. + */ + Map<String, MethodMirror> get constructors; + + /** + * Returns the default type for this interface. + */ + ClassMirror get defaultFactory; +} + +/** + * A type parameter as declared on a generic type. + */ +abstract class TypeVariableMirror implements TypeMirror { + /** + * Returns the bound of the type parameter. + */ + TypeMirror get upperBound; +} + +/** + * A function type. + */ +abstract class FunctionTypeMirror implements ClassMirror { + /** + * Returns the return type of this function type. + */ + TypeMirror get returnType; + + /** + * Returns the parameters for this function type. + */ + List<ParameterMirror> get parameters; + + /** + * Returns the call method for this function type. + */ + MethodMirror get callMethod; +} + +/** + * A typedef. + */ +abstract class TypedefMirror implements ClassMirror { + /** + * The defining type for this typedef. + * + * For instance [:void f(int):] for a [:typedef void f(int):]. + */ + TypeMirror get value; +} + +/** + * A member of a type, i.e. a field, method or constructor. + */ +abstract class MemberMirror implements DeclarationMirror { + /** + * Is this member a constructor? + */ + bool get isConstructor; + + /** + * Is this member a variable? + * + * This is [:false:] for locals. + */ + bool get isVariable; + + /** + * Is this member a method?. + * + * This is [:false:] for constructors. + */ + bool get isMethod; + + /** + * Is this member declared static? + */ + bool get isStatic; + + /** + * Is this member a parameter? + */ + bool get isParameter; +} + +/** + * A field. + */ +abstract class VariableMirror implements MemberMirror { + + /** + * Returns true if this field is final. + */ + bool get isFinal; + + /** + * Returns true if this field is const. + */ + bool get isConst; + + /** + * Returns the type of this field. + */ + TypeMirror get type; +} + +/** + * Common interface constructors and methods, including factories, getters and + * setters. + */ +abstract class MethodMirror implements MemberMirror { + /** + * Returns the list of parameters for this method. + */ + List<ParameterMirror> get parameters; + + /** + * Returns the return type of this method. + */ + TypeMirror get returnType; + + /** + * Is the reflectee abstract? + */ + bool get isAbstract; + + /** + * Is the reflectee a regular function or method? + * + * A function or method is regular if it is not a getter, setter, or + * constructor. Note that operators, by this definition, are + * regular methods. + */ + bool get isRegularMethod; + + /** + * Is the reflectee a const constructor? + */ + bool get isConstConstructor; + + /** + * Is the reflectee a generative constructor? + */ + bool get isGenerativeConstructor; + + /** + * Is the reflectee a redirecting constructor? + */ + bool get isRedirectingConstructor; + + /** + * Is the reflectee a factory constructor? + */ + bool get isFactoryConstructor; + + /** + * Returns the constructor name for named constructors and factory methods, + * e.g. [:'bar':] for constructor [:Foo.bar:] of type [:Foo:]. + */ + String get constructorName; + + /** + * Is [:true:] if this method is a getter method. + */ + bool get isGetter; + + /** + * Is [:true:] if this method is a setter method. + */ + bool get isSetter; + + /** + * Is [:true:] if this method is an operator method. + */ + bool get isOperator; + + /** + * Returns the operator name for operator methods, e.g. [:'<':] for + * [:operator <:] + */ + String get operatorName; +} + +/** + * A formal parameter. + */ +abstract class ParameterMirror implements VariableMirror { + /** + * Returns the type of this parameter. + */ + TypeMirror get type; + + /** + * Returns the default value for this parameter. + */ + String get defaultValue; + + /** + * Does this parameter have a default value? + */ + bool get hasDefaultValue; + + /** + * Is this parameter optional? + */ + bool get isOptional; + + /** + * Is this parameter named? + */ + bool get isNamed; + + /** + * Returns [:true:] iff this parameter is an initializing formal of a + * constructor. That is, if it is of the form [:this.x:] where [:x:] is a + * field. + */ + bool get isInitializingFormal; + + /** + * Returns the initialized field, if this parameter is an initializing formal. + */ + VariableMirror get initializedField; +} + +/** + * A [SourceLocation] describes the span of an entity in Dart source code. + * A [SourceLocation] with a non-zero [length] should be the minimum span that + * encloses the declaration of the mirrored entity. + */ +abstract class SourceLocation { + /** + * The 1-based line number for this source location. + * + * A value of 0 means that the line number is unknown. + */ + int get line; + + /** + * The 1-based column number for this source location. + * + * A value of 0 means that the column number is unknown. + */ + int get column; + + /** + * The 0-based character offset into the [sourceText] where this source + * location begins. + * + * A value of -1 means that the offset is unknown. + */ + int get offset; + + /** + * The number of characters in this source location. + * + * A value of 0 means that the [offset] is approximate. + */ + int get length; + + /** + * The text of the location span. + */ + String get text; + + /** + * Returns the URI where the source originated. + */ + Uri get sourceUri; + + /** + * Returns the text of this source. + */ + String get sourceText; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart new file mode 100644 index 0000000..d904089 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart
@@ -0,0 +1,161 @@ +// 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. + +library mirrors_util; + +import 'dart:collection' show Queue; + +// TODO(rnystrom): Use "package:" URL (#4968). +import 'mirrors.dart'; + +//------------------------------------------------------------------------------ +// Utility functions for using the Mirror API +//------------------------------------------------------------------------------ + +/** + * Returns an iterable over the type declarations directly inheriting from + * the declaration of this type. + */ +Iterable<ClassMirror> computeSubdeclarations(ClassMirror type) { + type = type.originalDeclaration; + var subtypes = <ClassMirror>[]; + type.mirrors.libraries.forEach((_, library) { + for (ClassMirror otherType in library.classes.values) { + var superClass = otherType.superclass; + if (superClass != null) { + superClass = superClass.originalDeclaration; + if (type.library == superClass.library) { + if (superClass == type) { + subtypes.add(otherType); + } + } + } + final superInterfaces = otherType.superinterfaces; + for (ClassMirror superInterface in superInterfaces) { + superInterface = superInterface.originalDeclaration; + if (type.library == superInterface.library) { + if (superInterface == type) { + subtypes.add(otherType); + } + } + } + } + }); + return subtypes; +} + +LibraryMirror findLibrary(MemberMirror member) { + DeclarationMirror owner = member.owner; + if (owner is LibraryMirror) { + return owner; + } else if (owner is TypeMirror) { + return owner.library; + } + throw new Exception('Unexpected owner: ${owner}'); +} + +class HierarchyIterable extends Iterable<ClassMirror> { + final bool includeType; + final ClassMirror type; + + HierarchyIterable(this.type, {bool includeType}) + : this.includeType = includeType; + + Iterator<ClassMirror> get iterator => + new HierarchyIterator(type, includeType: includeType); +} + +/** + * [HierarchyIterator] iterates through the class hierarchy of the provided + * type. + * + * First the superclass relation is traversed, skipping [Object], next the + * superinterface relation and finally is [Object] visited. The supertypes are + * visited in breadth first order and a superinterface is visited more than once + * if implemented through multiple supertypes. + */ +class HierarchyIterator implements Iterator<ClassMirror> { + final Queue<ClassMirror> queue = new Queue<ClassMirror>(); + ClassMirror object; + ClassMirror _current; + + HierarchyIterator(ClassMirror type, {bool includeType}) { + if (includeType) { + queue.add(type); + } else { + push(type); + } + } + + ClassMirror push(ClassMirror type) { + if (type.superclass != null) { + if (type.superclass.isObject) { + object = type.superclass; + } else { + queue.addFirst(type.superclass); + } + } + queue.addAll(type.superinterfaces); + return type; + } + + ClassMirror get current => _current; + + bool moveNext() { + _current = null; + if (queue.isEmpty) { + if (object == null) return false; + _current = object; + object = null; + return true; + } else { + _current = push(queue.removeFirst()); + return true; + } + } +} + +final RegExp _singleLineCommentStart = new RegExp(r'^///? ?(.*)'); +final RegExp _multiLineCommentStartEnd = + new RegExp(r'^/\*\*? ?([\s\S]*)\*/$', multiLine: true); +final RegExp _multiLineCommentLineStart = new RegExp(r'^[ \t]*\* ?(.*)'); + +/** + * Pulls the raw text out of a comment (i.e. removes the comment + * characters). + */ +String stripComment(String comment) { + Match match = _singleLineCommentStart.firstMatch(comment); + if (match != null) { + return match[1]; + } + match = _multiLineCommentStartEnd.firstMatch(comment); + if (match != null) { + comment = match[1]; + var sb = new StringBuffer(); + List<String> lines = comment.split('\n'); + for (int index = 0 ; index < lines.length ; index++) { + String line = lines[index]; + if (index == 0) { + sb.add(line); // Add the first line unprocessed. + continue; + } + sb.add('\n'); + match = _multiLineCommentLineStart.firstMatch(line); + if (match != null) { + sb.add(match[1]); + } else if (index < lines.length-1 || !line.trim().isEmpty) { + // Do not add the last line if it only contains white space. + // This interprets cases like + // /* + // * Foo + // */ + // as "\nFoo\n" and not as "\nFoo\n ". + sb.add(line); + } + } + return sb.toString(); + } + throw new ArgumentError('Invalid comment $comment'); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart b/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart new file mode 100644 index 0000000..3470211 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart
@@ -0,0 +1,173 @@ +// 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. + +library util; + +/** + * An abstract map implementation. This class can be used as a superclass for + * implementing maps, requiring only the further implementation of the + * [:operator []:], [:forEach:] and [:length:] methods to provide a fully + * implemented immutable map. + */ +abstract class AbstractMap<K,V> implements Map<K,V> { + AbstractMap(); + + AbstractMap.from(Map<K,V> other) { + other.forEach((k,v) => this[k] = v); + } + + void operator []=(K key, value) { + throw new UnsupportedError('[]= is not supported'); + } + + void clear() { + throw new UnsupportedError('clear() is not supported'); + } + + bool containsKey(K key) { + var found = false; + forEach((k,_) { + if (k == key) { + found = true; + } + }); + return found; + } + + bool containsValue(V value) { + var found = false; + forEach((_,v) { + if (v == value) { + found = true; + } + }); + return found; + } + + Collection<K> get keys { + var keys = <K>[]; + forEach((k,_) => keys.add(k)); + return keys; + } + + Collection<V> get values { + var values = <V>[]; + forEach((_,v) => values.add(v)); + return values; + } + + bool get isEmpty => length == 0; + V putIfAbsent(K key, V ifAbsent()) { + if (!containsKey(key)) { + V value = this[key]; + this[key] = ifAbsent(); + return value; + } + return null; + } + + V remove(K key) { + throw new UnsupportedError('V remove(K key) is not supported'); + } +} + +/** + * [ImmutableMapWrapper] wraps a (mutable) map as an immutable map where all + * mutating operations throw [UnsupportedError] upon invocation. + */ +class ImmutableMapWrapper<K,V> extends AbstractMap<K,V> { + final Map<K,V> _map; + + ImmutableMapWrapper(this._map); + + int get length => _map.length; + + V operator [](K key) { + if (key is K) { + return _map[key]; + } + return null; + } + + void forEach(void f(K key, V value)) { + _map.forEach(f); + } +} + +/** + * A [Filter] function returns [:true:] iff [value] should be included. + */ +typedef bool Filter<V>(V value); + +/** + * An immutable map wrapper capable of filtering the input map. + */ +class FilteredImmutableMap<K,V> extends ImmutableMapWrapper<K,V> { + final Filter<V> _filter; + + FilteredImmutableMap(Map<K,V> map, this._filter) : super(map); + + int get length { + var count = 0; + forEach((k,v) { + count++; + }); + return count; + } + + void forEach(void f(K key, V value)) { + _map.forEach((K k, V v) { + if (_filter(v)) { + f(k, v); + } + }); + } +} + +/** + * An [AsFilter] takes a [value] of type [V1] and returns [value] iff it is of + * type [V2] or [:null:] otherwise. An [AsFilter] therefore behaves like the + * [:as:] expression. + */ +typedef V2 AsFilter<V1, V2>(V1 value); + +/** + * An immutable map wrapper capable of filtering the input map based on types. + * It takes an [AsFilter] function which converts the original values of type + * [Vin] into values of type [Vout], or returns [:null:] if the value should + * not be included in the filtered map. + */ +class AsFilteredImmutableMap<K, Vin, Vout> extends AbstractMap<K, Vout> { + final Map<K, Vin> _map; + final AsFilter<Vin, Vout> _filter; + + AsFilteredImmutableMap(this._map, this._filter); + + int get length { + var count = 0; + forEach((k,v) { + count++; + }); + return count; + } + + Vout operator [](K key) { + if (key is K) { + Vin value = _map[key]; + if (value != null) { + return _filter(value); + } + } + return null; + } + + void forEach(void f(K key, Vout value)) { + _map.forEach((K k, Vin v) { + var value = _filter(v); + if (value != null) { + f(k, value); + } + }); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart b/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart new file mode 100644 index 0000000..373f16e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/native_handler.dart
@@ -0,0 +1,902 @@ +// 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. + +library native; + +import 'dart:collection' show Queue; +import 'dart:uri'; +import 'dart2jslib.dart' hide SourceString; +import 'dart_types.dart'; +import 'elements/elements.dart'; +import 'js_backend/js_backend.dart'; +import 'resolution/resolution.dart' show ResolverVisitor; +import 'scanner/scannerlib.dart'; +import 'ssa/ssa.dart'; +import 'tree/tree.dart'; +import 'util/util.dart'; + + +/// This class is a temporary work-around until we get a more powerful DartType. +class SpecialType { + final String name; + const SpecialType._(this.name); + + /// The type Object, but no subtypes: + static const JsObject = const SpecialType._('=Object'); + + /// The specific implementation of List that is JavaScript Array: + static const JsArray = const SpecialType._('=List'); +} + + +/** + * This could be an abstract class but we use it as a stub for the dart_backend. + */ +class NativeEnqueuer { + /// Initial entry point to native enqueuer. + void processNativeClasses(Iterable<LibraryElement> libraries) {} + + /// Notification of a main Enqueuer worklist element. For methods, adds + /// information from metadata attributes, and computes types instantiated due + /// to calling the method. + void registerElement(Element element) {} + + /// Notification of native field. Adds information from metadata attributes. + void handleFieldAnnotations(Element field) {} + + /// Computes types instantiated due to getting a native field. + void registerFieldLoad(Element field) {} + + /// Computes types instantiated due to setting a native field. + void registerFieldStore(Element field) {} + + NativeBehavior getNativeBehaviorOf(Send node) => null; + + /** + * Handles JS-calls, which can be an instantiation point for types. + * + * For example, the following code instantiates and returns native classes + * that are `_DOMWindowImpl` or a subtype. + * + * JS('_DOMWindowImpl', 'window') + * + */ + // TODO(sra): The entry from codegen will not have a resolver. + void registerJsCall(Send node, ResolverVisitor resolver) {} + + /// Emits a summary information using the [log] function. + void logSummary(log(message)) {} +} + + +abstract class NativeEnqueuerBase implements NativeEnqueuer { + + /** + * The set of all native classes. Each native class is in [nativeClasses] and + * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses]. + */ + final Set<ClassElement> nativeClasses = new Set<ClassElement>(); + + final Set<ClassElement> registeredClasses = new Set<ClassElement>(); + final Set<ClassElement> pendingClasses = new Set<ClassElement>(); + final Set<ClassElement> unusedClasses = new Set<ClassElement>(); + + /** + * Records matched constraints ([SpecialType] or [DartType]). Once a type + * constraint has been matched, there is no need to match it again. + */ + final Set matchedTypeConstraints = new Set(); + + /// Pending actions. Classes in [pendingClasses] have action thunks in + /// [queue] to register the class. + final queue = new Queue(); + bool flushing = false; + + /// Maps JS foreign calls to their computed native behavior. + final Map<Node, NativeBehavior> nativeBehaviors = + new Map<Node, NativeBehavior>(); + + final Enqueuer world; + final Compiler compiler; + final bool enableLiveTypeAnalysis; + + ClassElement _annotationCreatesClass; + ClassElement _annotationReturnsClass; + ClassElement _annotationJsNameClass; + + /// Subclasses of [NativeEnqueuerBase] are constructed by the backend. + NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis); + + void processNativeClasses(Iterable<LibraryElement> libraries) { + libraries.forEach(processNativeClassesInLibrary); + processNativeClassesInLibrary(compiler.isolateHelperLibrary); + if (!enableLiveTypeAnalysis) { + nativeClasses.forEach((c) => enqueueClass(c, 'forced')); + flushQueue(); + } + } + + void processNativeClassesInLibrary(LibraryElement library) { + // Use implementation to ensure the inclusion of injected members. + library.implementation.forEachLocalMember((Element element) { + if (element.isClass() && element.isNative()) { + processNativeClass(element); + } + }); + } + + void processNativeClass(ClassElement classElement) { + nativeClasses.add(classElement); + unusedClasses.add(classElement); + // Resolve class to ensure the class has valid inheritance info. + classElement.ensureResolved(compiler); + } + + ClassElement get annotationCreatesClass { + findAnnotationClasses(); + return _annotationCreatesClass; + } + + ClassElement get annotationReturnsClass { + findAnnotationClasses(); + return _annotationReturnsClass; + } + + ClassElement get annotationJsNameClass { + findAnnotationClasses(); + return _annotationJsNameClass; + } + + void findAnnotationClasses() { + if (_annotationCreatesClass != null) return; + ClassElement find(name) { + Element e = compiler.findHelper(name); + if (e == null || e is! ClassElement) { + compiler.cancel("Could not find implementation class '${name}'"); + } + return e; + } + _annotationCreatesClass = find(const SourceString('Creates')); + _annotationReturnsClass = find(const SourceString('Returns')); + _annotationJsNameClass = find(const SourceString('JSName')); + } + + /// Returns the JSName annotation string or `null` if no JSName annotation is + /// present. + String findJsNameFromAnnotation(Element element) { + String name = null; + ClassElement annotationClass = annotationJsNameClass; + for (Link<MetadataAnnotation> link = element.metadata; + !link.isEmpty; + link = link.tail) { + MetadataAnnotation annotation = link.head.ensureResolved(compiler); + var value = annotation.value; + if (value is! ConstructedConstant) continue; + if (value.type is! InterfaceType) continue; + if (!identical(value.type.element, annotationClass)) continue; + + var fields = value.fields; + // TODO(sra): Better validation of the constant. + if (fields.length != 1 || fields[0] is! StringConstant) { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Annotations needs one string: ${partial.parseNode(compiler)}'); + } + String specString = fields[0].toDartString().slowToString(); + if (name == null) { + name = specString; + } else { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Too many JSName annotations: ${partial.parseNode(compiler)}'); + } + } + return name; + } + + enqueueClass(ClassElement classElement, cause) { + assert(unusedClasses.contains(classElement)); + unusedClasses.remove(classElement); + pendingClasses.add(classElement); + queue.add(() { processClass(classElement, cause); }); + } + + void flushQueue() { + if (flushing) return; + flushing = true; + while (!queue.isEmpty) { + (queue.removeFirst())(); + } + flushing = false; + } + + processClass(ClassElement classElement, cause) { + assert(!registeredClasses.contains(classElement)); + + bool firstTime = registeredClasses.isEmpty; + pendingClasses.remove(classElement); + registeredClasses.add(classElement); + + world.registerInstantiatedClass(classElement); + + // Also parse the node to know all its methods because otherwise it will + // only be parsed if there is a call to one of its constructors. + classElement.parseNode(compiler); + + if (firstTime) { + queue.add(onFirstNativeClass); + } + } + + registerElement(Element element) { + compiler.withCurrentElement(element, () { + if (element.isFunction() || element.isGetter() || element.isSetter()) { + handleMethodAnnotations(element); + if (element.isNative()) { + registerMethodUsed(element); + } + } else if (element.isField()) { + handleFieldAnnotations(element); + if (element.isNative()) { + registerFieldLoad(element); + registerFieldStore(element); + } + } + }); + } + + handleFieldAnnotations(Element element) { + if (element.enclosingElement.isNative()) { + // Exclude non-instance (static) fields - they not really native and are + // compiled as isolate globals. Access of a property of a constructor + // function or a non-method property in the prototype chain, must be coded + // using a JS-call. + if (element.isInstanceMember()) { + setNativeName(element); + } + } + } + + handleMethodAnnotations(Element method) { + if (isNativeMethod(method)) { + setNativeName(method); + } + } + + /// Sets the native name of [element], either from an annotation, or + /// defaulting to the Dart name. + void setNativeName(Element element) { + String name = findJsNameFromAnnotation(element); + if (name == null) name = element.name.slowToString(); + element.setNative(name); + } + + bool isNativeMethod(Element element) { + if (!element.getLibrary().canUseNative) return false; + // Native method? + return compiler.withCurrentElement(element, () { + Node node = element.parseNode(compiler); + if (node is! FunctionExpression) return false; + node = node.body; + Token token = node.getBeginToken(); + if (identical(token.stringValue, 'native')) return true; + return false; + }); + } + + void registerMethodUsed(Element method) { + processNativeBehavior( + NativeBehavior.ofMethod(method, compiler), + method); + flushQueue(); + } + + void registerFieldLoad(Element field) { + processNativeBehavior( + NativeBehavior.ofFieldLoad(field, compiler), + field); + flushQueue(); + } + + void registerFieldStore(Element field) { + processNativeBehavior( + NativeBehavior.ofFieldStore(field, compiler), + field); + flushQueue(); + } + + void registerJsCall(Send node, ResolverVisitor resolver) { + NativeBehavior behavior = NativeBehavior.ofJsCall(node, compiler, resolver); + processNativeBehavior(behavior, node); + nativeBehaviors[node] = behavior; + flushQueue(); + } + + NativeBehavior getNativeBehaviorOf(Send node) => nativeBehaviors[node]; + + processNativeBehavior(NativeBehavior behavior, cause) { + bool allUsedBefore = unusedClasses.isEmpty; + for (var type in behavior.typesInstantiated) { + if (matchedTypeConstraints.contains(type)) continue; + matchedTypeConstraints.add(type); + if (type is SpecialType) { + if (type == SpecialType.JsArray) { + world.registerInstantiatedClass(compiler.listClass); + } else if (type == SpecialType.JsObject) { + world.registerInstantiatedClass(compiler.objectClass); + } + continue; + } + if (type is InterfaceType) { + if (type.element == compiler.intClass) { + world.registerInstantiatedClass(compiler.intClass); + } else if (type.element == compiler.doubleClass) { + world.registerInstantiatedClass(compiler.doubleClass); + } else if (type.element == compiler.numClass) { + world.registerInstantiatedClass(compiler.doubleClass); + world.registerInstantiatedClass(compiler.intClass); + } else if (type.element == compiler.stringClass) { + world.registerInstantiatedClass(compiler.stringClass); + } else if (type.element == compiler.nullClass) { + world.registerInstantiatedClass(compiler.nullClass); + } else if (type.element == compiler.boolClass) { + world.registerInstantiatedClass(compiler.boolClass); + } + } + assert(type is DartType); + enqueueUnusedClassesMatching( + (nativeClass) => compiler.types.isSubtype(nativeClass.thisType, type), + cause, + 'subtypeof($type)'); + } + + // Give an info so that library developers can compile with -v to find why + // all the native classes are included. + if (unusedClasses.isEmpty && !allUsedBefore) { + compiler.log('All native types marked as used due to $cause.'); + } + } + + enqueueUnusedClassesMatching(bool predicate(classElement), + cause, + [String reason]) { + Iterable matches = unusedClasses.where(predicate); + matches.forEach((c) => enqueueClass(c, cause)); + } + + onFirstNativeClass() { + staticUse(name) => world.registerStaticUse(compiler.findHelper(name)); + + staticUse(const SourceString('dynamicFunction')); + staticUse(const SourceString('dynamicSetMetadata')); + staticUse(const SourceString('defineProperty')); + staticUse(const SourceString('toStringForNativeObject')); + staticUse(const SourceString('hashCodeForNativeObject')); + + addNativeExceptions(); + } + + addNativeExceptions() { + enqueueUnusedClassesMatching((classElement) { + // TODO(sra): Annotate exception classes in dart:html. + String name = classElement.name.slowToString(); + if (name.contains('Exception')) return true; + if (name.contains('Error')) return true; + return false; + }, + 'native exception'); + } +} + + +class NativeResolutionEnqueuer extends NativeEnqueuerBase { + + NativeResolutionEnqueuer(Enqueuer world, Compiler compiler) + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); + + void logSummary(log(message)) { + log('Resolved ${registeredClasses.length} native elements used, ' + '${unusedClasses.length} native elements dead.'); + } +} + + +class NativeCodegenEnqueuer extends NativeEnqueuerBase { + + final CodeEmitterTask emitter; + + final Set<ClassElement> doneAddSubtypes = new Set<ClassElement>(); + + NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter) + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); + + void processNativeClasses(Iterable<LibraryElement> libraries) { + super.processNativeClasses(libraries); + + // HACK HACK - add all the resolved classes. + NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; + for (final classElement in enqueuer.registeredClasses) { + if (unusedClasses.contains(classElement)) { + enqueueClass(classElement, 'was resolved'); + } + } + flushQueue(); + } + + processClass(ClassElement classElement, cause) { + super.processClass(classElement, cause); + // Add the information that this class is a subtype of its supertypes. The + // code emitter and the ssa builder use that information. + addSubtypes(classElement, emitter.nativeEmitter); + } + + void addSubtypes(ClassElement cls, NativeEmitter emitter) { + if (!cls.isNative()) return; + if (doneAddSubtypes.contains(cls)) return; + doneAddSubtypes.add(cls); + + // Walk the superclass chain since classes on the superclass chain might not + // be instantiated (abstract or simply unused). + addSubtypes(cls.superclass, emitter); + + for (DartType type in cls.allSupertypes) { + List<Element> subtypes = emitter.subtypes.putIfAbsent( + type.element, + () => <ClassElement>[]); + subtypes.add(cls); + } + + // Skip through all the mixin applications in the super class + // chain. That way, the direct subtypes set only contain the + // natives classes. + ClassElement superclass = cls.superclass; + while (superclass != null && superclass.isMixinApplication) { + assert(!superclass.isNative()); + superclass = superclass.superclass; + } + + List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent( + superclass, + () => <ClassElement>[]); + directSubtypes.add(cls); + } + + void logSummary(log(message)) { + log('Compiled ${registeredClasses.length} native classes, ' + '${unusedClasses.length} native classes omitted.'); + } +} + +void maybeEnableNative(Compiler compiler, + LibraryElement library) { + String libraryName = library.canonicalUri.toString(); + if (library.entryCompilationUnit.script.name.contains( + 'dart/tests/compiler/dart2js_native') + || libraryName == 'dart:async' + || libraryName == 'dart:html' + || libraryName == 'dart:html_common' + || libraryName == 'dart:indexed_db' + || libraryName == 'dart:svg' + || libraryName == 'dart:web_audio') { + library.canUseNative = true; + } +} + +/** + * A summary of the behavior of a native element. + * + * Native code can return values of one type and cause native subtypes of + * another type to be instantiated. By default, we compute both from the + * declared type. + * + * A field might yield any native type that 'is' the field type. + * + * A method might create and return instances of native subclasses of its + * declared return type, and a callback argument may be called with instances of + * the callback parameter type (e.g. Event). + * + * If there is one or more @Creates annotations, the union of the named types + * replaces the inferred instantiated type, and the return type is ignored for + * the purpose of inferring instantiated types. + * + * @Creates(IDBCursor) // Created asynchronously. + * @Creates(IDBRequest) // Created synchronously (for return value). + * IDBRequest request = objectStore.openCursor(); + * + * If there is one or more @Returns annotations, the union of the named types + * replaces the declared return type. + * + * @Returns(IDBRequest) + * IDBRequest request = objectStore.openCursor(); + */ +class NativeBehavior { + + /// [DartType]s or [SpecialType]s returned or yielded by the native element. + final List typesReturned = []; + + /// [DartType]s or [SpecialType]s instantiated by the native element. + final List typesInstantiated = []; + + static final NativeBehavior NONE = new NativeBehavior(); + + //NativeBehavior(); + + static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { + // The first argument of a JS-call is a string encoding various attributes + // of the code. + // + // 'Type1|Type2'. A union type. + // '=Object'. A JavaScript Object, no subtype. + // '=List'. A JavaScript Array, no subtype. + + var argNodes = jsCall.arguments; + if (argNodes.isEmpty) { + compiler.cancel("JS expression has no type", node: jsCall); + } + + var firstArg = argNodes.head; + LiteralString specLiteral = firstArg.asLiteralString(); + if (specLiteral != null) { + String specString = specLiteral.dartString.slowToString(); + // Various things that are not in fact types. + if (specString == 'void') return NativeBehavior.NONE; + if (specString == '' || specString == 'var') { + var behavior = new NativeBehavior(); + behavior.typesReturned.add(compiler.objectClass.computeType(compiler)); + return behavior; + } + var behavior = new NativeBehavior(); + for (final typeString in specString.split('|')) { + var type = _parseType(typeString, compiler, + (name) => resolver.resolveTypeFromString(name), + jsCall); + behavior.typesInstantiated.add(type); + behavior.typesReturned.add(type); + } + return behavior; + } + + // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It + // is not very satisfactory because it does not work for void, dynamic. + + compiler.cancel("Unexpected JS first argument", node: firstArg); + } + + static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) { + FunctionType type = method.computeType(compiler); + var behavior = new NativeBehavior(); + behavior.typesReturned.add(type.returnType); + behavior._capture(type, compiler); + + // TODO(sra): Optional arguments are currently missing from the + // DartType. This should be fixed so the following work-around can be + // removed. + method.computeSignature(compiler).forEachOptionalParameter( + (Element parameter) { + behavior._escape(parameter.computeType(compiler), compiler); + }); + + behavior._overrideWithAnnotations(method, compiler); + return behavior; + } + + static NativeBehavior ofFieldLoad(Element field, Compiler compiler) { + DartType type = field.computeType(compiler); + var behavior = new NativeBehavior(); + behavior.typesReturned.add(type); + behavior._capture(type, compiler); + behavior._overrideWithAnnotations(field, compiler); + return behavior; + } + + static NativeBehavior ofFieldStore(Element field, Compiler compiler) { + DartType type = field.computeType(compiler); + var behavior = new NativeBehavior(); + behavior._escape(type, compiler); + // We don't override the default behaviour - the annotations apply to + // loading the field. + return behavior; + } + + void _overrideWithAnnotations(Element element, Compiler compiler) { + if (element.metadata.isEmpty) return; + + DartType lookup(String name) { + Element e = element.buildScope().lookup(new SourceString(name)); + if (e == null) return null; + if (e is! ClassElement) return null; + e.ensureResolved(compiler); + return e.computeType(compiler); + } + + NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; + var creates = _collect(element, compiler, enqueuer.annotationCreatesClass, + lookup); + var returns = _collect(element, compiler, enqueuer.annotationReturnsClass, + lookup); + + if (creates != null) { + typesInstantiated..clear()..addAll(creates); + } + if (returns != null) { + typesReturned..clear()..addAll(returns); + } + } + + /** + * Returns a list of type constraints from the annotations of + * [annotationClass]. + * Returns `null` if no constraints. + */ + static _collect(Element element, Compiler compiler, Element annotationClass, + lookup(str)) { + var types = null; + for (Link<MetadataAnnotation> link = element.metadata; + !link.isEmpty; + link = link.tail) { + MetadataAnnotation annotation = link.head.ensureResolved(compiler); + var value = annotation.value; + if (value is! ConstructedConstant) continue; + if (value.type is! InterfaceType) continue; + if (!identical(value.type.element, annotationClass)) continue; + + var fields = value.fields; + // TODO(sra): Better validation of the constant. + if (fields.length != 1 || fields[0] is! StringConstant) { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Annotations needs one string: ${partial.parseNode(compiler)}'); + } + String specString = fields[0].toDartString().slowToString(); + for (final typeString in specString.split('|')) { + var type = _parseType(typeString, compiler, lookup, annotation); + if (types == null) types = []; + types.add(type); + } + } + return types; + } + + /// Models the behavior of having intances of [type] escape from Dart code + /// into native code. + void _escape(DartType type, Compiler compiler) { + type = type.unalias(compiler); + if (type is FunctionType) { + // A function might be called from native code, passing us novel + // parameters. + _escape(type.returnType, compiler); + for (Link<DartType> parameters = type.parameterTypes; + !parameters.isEmpty; + parameters = parameters.tail) { + _capture(parameters.head, compiler); + } + } + } + + /// Models the behavior of Dart code receiving instances and methods of [type] + /// from native code. We usually start the analysis by capturing a native + /// method that has been used. + void _capture(DartType type, Compiler compiler) { + type = type.unalias(compiler); + if (type is FunctionType) { + _capture(type.returnType, compiler); + for (Link<DartType> parameters = type.parameterTypes; + !parameters.isEmpty; + parameters = parameters.tail) { + _escape(parameters.head, compiler); + } + } else { + typesInstantiated.add(type); + } + } + + static _parseType(String typeString, Compiler compiler, + lookup(name), locationNodeOrElement) { + if (typeString == '=Object') return SpecialType.JsObject; + if (typeString == '=List') return SpecialType.JsArray; + if (typeString == 'dynamic') { + return compiler.dynamicClass.computeType(compiler); + } + DartType type = lookup(typeString); + if (type != null) return type; + + int index = typeString.indexOf('<'); + if (index < 1) { + compiler.cancel("Type '$typeString' not found", + node: _errorNode(locationNodeOrElement, compiler)); + } + type = lookup(typeString.substring(0, index)); + if (type != null) { + // TODO(sra): Parse type parameters. + return type; + } + compiler.cancel("Type '$typeString' not found", + node: _errorNode(locationNodeOrElement, compiler)); + } + + static _errorNode(locationNodeOrElement, compiler) { + if (locationNodeOrElement is Node) return locationNodeOrElement; + return locationNodeOrElement.parseNode(compiler); + } +} + +void checkAllowedLibrary(ElementListener listener, Token token) { + LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); + if (!currentLibrary.canUseNative) { + listener.recoverableError("Unexpected token", token: token); + } +} + +Token handleNativeBlockToSkip(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + token = token.next; + if (identical(token.kind, STRING_TOKEN)) { + token = token.next; + } + if (identical(token.stringValue, '{')) { + BeginGroupToken beginGroupToken = token; + token = beginGroupToken.endGroup; + } + return token; +} + +Token handleNativeClassBodyToSkip(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + listener.handleIdentifier(token); + token = token.next; + if (!identical(token.kind, STRING_TOKEN)) { + return listener.unexpected(token); + } + token = token.next; + if (!identical(token.stringValue, '{')) { + return listener.unexpected(token); + } + BeginGroupToken beginGroupToken = token; + token = beginGroupToken.endGroup; + return token; +} + +Token handleNativeClassBody(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + token = token.next; + if (!identical(token.kind, STRING_TOKEN)) { + listener.unexpected(token); + } else { + token = token.next; + } + return token; +} + +Token handleNativeFunctionBody(ElementListener listener, Token token) { + checkAllowedLibrary(listener, token); + Token begin = token; + listener.beginReturnStatement(token); + token = token.next; + bool hasExpression = false; + if (identical(token.kind, STRING_TOKEN)) { + hasExpression = true; + listener.beginLiteralString(token); + listener.endLiteralString(0); + token = token.next; + } + listener.endReturnStatement(hasExpression, begin, token); + // TODO(ngeoffray): expect a ';'. + // Currently there are method with both native marker and Dart body. + return token.next; +} + +SourceString checkForNativeClass(ElementListener listener) { + SourceString nativeTagInfo; + Node node = listener.nodes.head; + if (node != null + && node.asIdentifier() != null + && node.asIdentifier().source.stringValue == 'native') { + nativeTagInfo = node.asIdentifier().token.next.value; + listener.popNode(); + } + return nativeTagInfo; +} + +bool isOverriddenMethod(FunctionElement element, + ClassElement cls, + NativeEmitter nativeEmitter) { + List<ClassElement> subtypes = nativeEmitter.subtypes[cls]; + if (subtypes == null) return false; + for (ClassElement subtype in subtypes) { + if (subtype.lookupLocalMember(element.name) != null) return true; + } + return false; +} + +final RegExp nativeRedirectionRegExp = new RegExp(r'^[a-zA-Z][a-zA-Z_$0-9]*$'); + +void handleSsaNative(SsaBuilder builder, Expression nativeBody) { + Compiler compiler = builder.compiler; + FunctionElement element = builder.work.element; + NativeEmitter nativeEmitter = builder.emitter.nativeEmitter; + + HInstruction convertDartClosure(Element parameter, FunctionType type) { + HInstruction local = builder.localsHandler.readLocal(parameter); + Constant arityConstant = + builder.constantSystem.createInt(type.computeArity()); + HInstruction arity = builder.graph.addConstant(arityConstant); + // TODO(ngeoffray): For static methods, we could pass a method with a + // defined arity. + Element helper = builder.backend.getClosureConverter(); + builder.pushInvokeHelper2(helper, local, arity, HType.UNKNOWN); + HInstruction closure = builder.pop(); + return closure; + } + + // Check which pattern this native method follows: + // 1) foo() native; + // hasBody = false + // 2) foo() native "bar"; + // No longer supported, this is now done with @JSName('foo') and case 1. + // 3) foo() native "return 42"; + // hasBody = true + bool hasBody = false; + assert(element.isNative()); + String nativeMethodName = element.fixedBackendName(); + if (nativeBody != null) { + LiteralString jsCode = nativeBody.asLiteralString(); + String str = jsCode.dartString.slowToString(); + if (nativeRedirectionRegExp.hasMatch(str)) { + compiler.cancel("Deprecated syntax, use @JSName('name') instead.", + node: nativeBody); + } + hasBody = true; + } + + if (!hasBody) { + nativeEmitter.nativeMethods.add(element); + } + + FunctionSignature parameters = element.computeSignature(builder.compiler); + if (!hasBody) { + List<String> arguments = <String>[]; + List<HInstruction> inputs = <HInstruction>[]; + String receiver = ''; + if (element.isInstanceMember()) { + receiver = '#.'; + inputs.add(builder.localsHandler.readThis()); + } + parameters.forEachParameter((Element parameter) { + DartType type = parameter.computeType(compiler).unalias(compiler); + HInstruction input = builder.localsHandler.readLocal(parameter); + if (type is FunctionType) { + // The parameter type is a function type either directly or through + // typedef(s). + input = convertDartClosure(parameter, type); + } + inputs.add(input); + arguments.add('#'); + }); + + String foreignParameters = Strings.join(arguments, ','); + String nativeMethodCall; + if (element.kind == ElementKind.FUNCTION) { + nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)'; + } else if (element.kind == ElementKind.GETTER) { + nativeMethodCall = '$receiver$nativeMethodName'; + } else if (element.kind == ElementKind.SETTER) { + nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters'; + } else { + builder.compiler.internalError('unexpected kind: "${element.kind}"', + element: element); + } + + DartString jsCode = new DartString.literal(nativeMethodCall); + builder.push(new HForeign(jsCode, HType.UNKNOWN, inputs)); + builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit); + } else { + if (parameters.parameterCount != 0) { + compiler.cancel( + 'native "..." syntax is restricted to functions with zero parameters', + node: nativeBody); + } + LiteralString jsCode = nativeBody.asLiteralString(); + builder.push(new HForeign.statement(jsCode.dartString, <HInstruction>[])); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart new file mode 100644 index 0000000..5a0da01 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart
@@ -0,0 +1,596 @@ +// 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. + +/** + * This library contains the infrastructure to parse and integrate patch files. + * + * Three types of elements can be patched: [LibraryElement], [ClassElement], + * [FunctionElement]. Patches are introduced in patch libraries which are loaded + * together with the corresponding origin library. Which libraries that are + * patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found + * in [:lib/_internal/libraries.dart:]. + * + * Patch libraries are parsed like regular library and thus provided with their + * own elements. These elements which are distinct from the elements from the + * patched library and the relation between patched and patch elements is + * established through the [:patch:] and [:origin:] fields found on + * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields + * are set on the patched elements to point to their corresponding patch + * element, and the [:origin:] elements are set on the patch elements to point + * their corresponding patched elements. + * + * The fields [Element.isPatched] and [Element.isPatch] can be used to determine + * whether the [:patch:] or [:origin:] field, respectively, has been set on an + * element, regardless of whether the element is one of the three patchable + * element types or not. + * + * ## Variants of Classes and Functions ## + * + * With patches there are four variants of classes and function: + * + * Regular: A class or function which is not declared in a patch library and + * which has no corresponding patch. + * Origin: A class or function which is not declared in a patch library and + * which has a corresponding patch. Origin functions must use the [:external:] + * modifier and can have no body. Origin classes and functions are also + * called 'patched'. + * Patch: A class or function which is declared in a patch library and which + * has a corresponding origin. Both patch classes and patch functions must use + * the [:patch:] modifier. + * Injected: A class or function (or even field) which is declared in a + * patch library and which has no corresponding origin. An injected element + * cannot use the [:patch:] modifier. Injected elements are never visible from + * outside the patch library in which they have been declared. For this + * reason, injected elements are often declared private and therefore called + * also called 'patch private'. + * + * Examples of the variants is shown in the code below: + * + * // In the origin library: + * class RegularClass { // A regular class. + * void regularMethod() {} // A regular method. + * } + * class PatchedClass { // An origin class. + * int regularField; // A regular field. + * void regularMethod() {} // A regular method. + * external void patchedMethod(); // An origin method. + * } + * + * // In the patch library: + * class _InjectedClass { // An injected class. + * void _injectedMethod() {} // An injected method. + * } + * patch class PatchedClass { // A patch class. + * int _injectedField; { // An injected field. + * patch void patchedMethod() {} // A patch method. + * } + * + * + * ## Declaration and Implementation ## + * + * With patches we have two views on elements: as the 'declaration' which + * introduces the entity and defines its interface, and as the 'implementation' + * which defines the actual implementation of the entity. + * + * Every element has a 'declaration' and an 'implementation' element. For + * regular and injected elements these are the same. For origin elements the + * declaration is the element itself and the implementation is the patch element + * found through its [:patch:] field. For patch elements the implementation is + * the element itself and the declaration is the origin element found through + * its [:origin:] field. The declaration and implementation of any element is + * conveniently available through the [Element.declaration] and + * [Element.implementation] getters. + * + * Most patch-related invariants enforced through-out the compiler are defined + * in terms of 'declaration' and 'implementation', and tested through the + * predicate getters [Element.isDeclaration] and [Element.isImplementation]. + * Patch invariants are stated both in comments and as assertions. + * + * + * ## General invariant guidelines ## + * + * For [LibraryElement] we always use declarations. This means the + * [Element.getLibrary] method will only return library declarations. Patch + * library implementations are only accessed through calls to + * [Element.getImplementationLibrary] which is used to setup the correct + * [Element.enclosingElement] relation between patch/injected elements and the + * patch library. + * + * For [ClassElement] and [FunctionElement] we use declarations for determining + * identity and implementations for work based on the AST nodes, such as + * resolution, type-checking, type inference, building SSA graphs, etc. + * - Worklist only contain declaration elements. + * - Most maps and sets use declarations exclusively, and their individual + * invariants are stated in the field comments. + * - [TreeElements] only map to patch elements from inside a patch library. + * TODO(johnniwinther): Simplify this invariant to use only declarations in + * [TreeElements]. + * - Builders shift between declaration and implementation depending on usages. + * - Compile-time constants use constructor implementation exclusively. + * - Work on function parameters is performed on the declaration of the function + * element. + */ + +library patchparser; + +import "dart:uri"; +import "tree/tree.dart" as tree; +import "dart2jslib.dart" as leg; // CompilerTask, Compiler. +import "apiimpl.dart"; +import "../compiler.dart" as api; +import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners +import "elements/elements.dart"; +import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX; +import 'util/util.dart'; + +class PatchParserTask extends leg.CompilerTask { + PatchParserTask(leg.Compiler compiler): super(compiler); + final String name = "Patching Parser"; + + /** + * Scans a library patch file, applies the method patches and + * injections to the library, and returns a list of class + * patches. + */ + void patchLibrary(leg.LibraryDependencyHandler handler, + Uri patchUri, LibraryElement originLibrary) { + + leg.Script script = compiler.readScript(patchUri, null); + var patchLibrary = new LibraryElementX(script, null, originLibrary); + compiler.withCurrentElement(patchLibrary, () { + handler.registerNewLibrary(patchLibrary); + LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>(); + compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () { + // This patches the elements of the patch library into [library]. + // Injected elements are added directly under the compilation unit. + // Patch elements are stored on the patched functions or classes. + scanLibraryElements(patchLibrary.entryCompilationUnit, imports); + }); + // After scanning declarations, we handle the import tags in the patch. + // TODO(lrn): These imports end up in the original library and are in + // scope for the original methods too. This should be fixed. + compiler.importHelperLibrary(originLibrary); + for (tree.LibraryTag tag in imports.toLink()) { + compiler.libraryLoader.registerLibraryFromTag( + handler, patchLibrary, tag); + } + }); + } + + void scanLibraryElements( + CompilationUnitElement compilationUnit, + LinkBuilder<tree.LibraryTag> imports) { + measure(() { + // TODO(lrn): Possibly recursively handle #source directives in patch. + leg.Script script = compilationUnit.script; + Token tokens = new StringScanner(script.text).tokenize(); + Function idGenerator = compiler.getNextFreeClassId; + PatchListener patchListener = + new PatchElementListener(compiler, + compilationUnit, + idGenerator, + imports); + new PatchParser(patchListener).parseUnit(tokens); + }); + } + + void parsePatchClassNode(PartialClassElement element) { + // Parse [PartialClassElement] using a "patch"-aware parser instead + // of calling its [parseNode] method. + if (element.cachedNode != null) return; + + return measure(() => compiler.withCurrentElement(element, () { + PatchMemberListener listener = new PatchMemberListener(compiler, element); + Parser parser = new PatchClassElementParser(listener); + Token token = parser.parseTopLevelDeclaration(element.beginToken); + assert(identical(token, element.endToken.next)); + element.cachedNode = listener.popNode(); + assert(listener.nodes.isEmpty); + + Link<Element> patches = element.localMembers; + applyContainerPatch(element.origin, patches); + })); + } + + void applyContainerPatch(ClassElement originClass, + Link<Element> patches) { + for (Element patch in patches) { + if (!isPatchElement(patch)) continue; + + Element origin = originClass.localLookup(patch.name); + patchElement(compiler, origin, patch); + } + } +} + +/** + * Extension of the [Listener] interface to handle the extra "patch" pseudo- + * keyword in patch files. + * Patch files shouldn't have a type named "patch". + */ +abstract class PatchListener extends Listener { + void beginPatch(Token patch); + void endPatch(Token patch); +} + +/** + * Partial parser that extends the top-level and class grammars to allow the + * word "patch" in front of some declarations. + */ +class PatchParser extends PartialParser { + PatchParser(PatchListener listener) : super(listener); + + PatchListener get patchListener => listener; + + bool isPatch(Token token) { + return token.stringValue == null && + token.slowToString() == "patch"; + } + + /** + * Parse top-level declarations, and allow "patch" in front of functions + * and classes. + */ + Token parseTopLevelDeclaration(Token token) { + if (!isPatch(token)) { + return super.parseTopLevelDeclaration(token); + } + Token patch = token; + token = token.next; + String value = token.stringValue; + if (identical(value, 'interface') + || identical(value, 'typedef') + || identical(value, '#') + || identical(value, 'abstract')) { + // At the top level, you can only patch functions and classes. + // Patch classes and functions can't be marked abstract. + return listener.unexpected(patch); + } + patchListener.beginPatch(patch); + token = super.parseTopLevelDeclaration(token); + patchListener.endPatch(patch); + return token; + } + + /** + * Parse a class member. + * If the member starts with "patch", it's a member override. + * Only methods can be overridden, including constructors, getters and + * setters, but not fields. If "patch" occurs in front of a field, the error + * is caught elsewhere. + */ + Token parseMember(Token token) { + if (!isPatch(token)) { + return super.parseMember(token); + } + Token patch = token; + patchListener.beginPatch(patch); + token = super.parseMember(token.next); + patchListener.endPatch(patch); + return token; + } +} + +/** + * Partial parser for patch files that also handles the members of class + * declarations. + */ +class PatchClassElementParser extends PatchParser { + PatchClassElementParser(PatchListener listener) : super(listener); + + Token parseClassBody(Token token) => fullParseClassBody(token); +} + +/** + * Extension of [ElementListener] for parsing patch files. + */ +class PatchElementListener extends ElementListener implements PatchListener { + final LinkBuilder<tree.LibraryTag> imports; + bool isMemberPatch = false; + bool isClassPatch = false; + + PatchElementListener(leg.DiagnosticListener listener, + CompilationUnitElement patchElement, + int idGenerator(), + this.imports) + : super(listener, patchElement, idGenerator); + + MetadataAnnotation popMetadataHack() { + // TODO(ahe): Remove this method. + popNode(); // Discard null. + return new PatchMetadataAnnotation(); + } + + void beginPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = true; + } else { + isMemberPatch = true; + } + handleIdentifier(token); + } + + void endPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = false; + } else { + isMemberPatch = false; + } + } + + /** + * Allow script tags (import only, the parser rejects the rest for now) in + * patch files. The import tags will be added to the library. + */ + bool allowLibraryTags() => true; + + void addLibraryTag(tree.LibraryTag tag) { + super.addLibraryTag(tag); + imports.addLast(tag); + } + + void pushElement(Element patch) { + if (isMemberPatch || (isClassPatch && patch is ClassElement)) { + // Apply patch. + patch.addMetadata(popMetadataHack()); + LibraryElement originLibrary = compilationUnitElement.getLibrary(); + assert(originLibrary.isPatched); + Element origin = originLibrary.localLookup(patch.name); + patchElement(listener, origin, patch); + } + super.pushElement(patch); + } +} + +/** + * Extension of [MemberListener] for parsing patch class bodies. + */ +class PatchMemberListener extends MemberListener implements PatchListener { + bool isMemberPatch = false; + bool isClassPatch = false; + PatchMemberListener(leg.DiagnosticListener listener, + Element enclosingElement) + : super(listener, enclosingElement); + + MetadataAnnotation popMetadataHack() { + // TODO(ahe): Remove this method. + popNode(); // Discard null. + return new PatchMetadataAnnotation(); + } + + void beginPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = true; + } else { + isMemberPatch = true; + } + handleIdentifier(token); + } + + void endPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = false; + } else { + isMemberPatch = false; + } + } + + void addMember(Element element) { + if (isMemberPatch || (isClassPatch && element is ClassElement)) { + element.addMetadata(popMetadataHack()); + } + super.addMember(element); + } +} + +// TODO(ahe): Get rid of this class. +class PatchMetadataAnnotation extends MetadataAnnotationX { + final leg.Constant value = null; + + PatchMetadataAnnotation() : super(STATE_DONE); + + Token get beginToken => null; + Token get endToken => null; +} + +void patchElement(leg.DiagnosticListener listener, + Element origin, + Element patch) { + if (origin == null) { + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_NON_EXISTING.error({'name': patch.name}), + api.Diagnostic.ERROR); + return; + } + if (!(origin.isClass() || + origin.isConstructor() || + origin.isFunction() || + origin.isAbstractField())) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NONPATCHABLE.error(), + api.Diagnostic.ERROR); + return; + } + if (patch.isClass()) { + tryPatchClass(listener, origin, patch); + } else if (patch.isGetter()) { + tryPatchGetter(listener, origin, patch); + } else if (patch.isSetter()) { + tryPatchSetter(listener, origin, patch); + } else if (patch.isConstructor()) { + tryPatchConstructor(listener, origin, patch); + } else if(patch.isFunction()) { + tryPatchFunction(listener, origin, patch); + } else { + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_NONPATCHABLE.error(), + api.Diagnostic.ERROR); + } +} + +void tryPatchClass(leg.DiagnosticListener listener, + Element origin, + ClassElement patch) { + if (!origin.isClass()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_CLASS.error({'className': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_CLASS.error({'className': patch.name}), + api.Diagnostic.INFO); + return; + } + patchClass(listener, origin, patch); +} + +void patchClass(leg.DiagnosticListener listener, + ClassElement origin, + ClassElement patch) { + if (origin.isPatched) { + listener.internalErrorOnElement( + origin, "Patching the same class more than once."); + } + // TODO(johnniwinther): Change to functions on the ElementX class. + origin.patch = patch; + patch.origin = origin; +} + +void tryPatchGetter(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isAbstractField()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_GETTER.error({'name': origin.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.INFO); + return; + } + AbstractFieldElement originField = origin; + if (originField.getter == null) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, originField.getter, patch); +} + +void tryPatchSetter(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isAbstractField()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_SETTER.error({'name': origin.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.INFO); + return; + } + AbstractFieldElement originField = origin; + if (originField.setter == null) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, originField.setter, patch); +} + +void tryPatchConstructor(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isConstructor()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_CONSTRUCTOR.error( + {'constructorName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error( + {'constructorName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, origin, patch); +} + +void tryPatchFunction(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isFunction()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_FUNCTION.error({'functionName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( + {'functionName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, origin, patch); +} + +void patchFunction(leg.DiagnosticListener listener, + FunctionElement origin, + FunctionElement patch) { + if (!origin.modifiers.isExternal()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_EXTERNAL.error(), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( + {'functionName': patch.name}), + api.Diagnostic.INFO); + return; + } + if (origin.isPatched) { + listener.internalErrorOnElement(origin, + "Trying to patch a function more than once."); + } + if (origin.cachedNode != null) { + listener.internalErrorOnElement(origin, + "Trying to patch an already compiled function."); + } + // Don't just assign the patch field. This also updates the cachedNode. + // TODO(johnniwinther): Change to functions on the ElementX class. + origin.setPatch(patch); + patch.origin = origin; +} + +// TODO(johnniwinther): Add unittest when patch is (real) metadata. +bool isPatchElement(Element element) { + // TODO(lrn): More checks needed if we introduce metadata for real. + // In that case, it must have the identifier "native" as metadata. + for (Link link = element.metadata; !link.isEmpty; link = link.tail) { + if (link.head is PatchMetadataAnnotation) return true; + } + return false; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart new file mode 100644 index 0000000..e62aa70 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart
@@ -0,0 +1,3663 @@ +// 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. + +part of resolution; + +abstract class TreeElements { + Element operator[](Node node); + Selector getSelector(Send send); + DartType getType(Node node); + bool isParameterChecked(Element element); + Set<Node> get superUses; +} + +class TreeElementMapping implements TreeElements { + final Element currentElement; + final Map<Node, Selector> selectors = new LinkedHashMap<Node, Selector>(); + final Map<Node, DartType> types = new LinkedHashMap<Node, DartType>(); + final Set<Element> checkedParameters = new Set<Element>(); + final Set<Node> superUses = new Set<Node>(); + + TreeElementMapping(this.currentElement); + + operator []=(Node node, Element element) { + assert(invariant(node, () { + if (node is FunctionExpression) { + return !node.modifiers.isExternal(); + } + return true; + })); + // TODO(johnniwinther): Simplify this invariant to use only declarations in + // [TreeElements]. + assert(invariant(node, () { + if (!element.isErroneous() && currentElement != null && element.isPatch) { + return currentElement.getImplementationLibrary().isPatch; + } + return true; + })); + // TODO(ahe): Investigate why the invariant below doesn't hold. + // assert(invariant(node, + // getTreeElement(node) == element || + // getTreeElement(node) == null, + // message: '${getTreeElement(node)}; $element')); + + setTreeElement(node, element); + } + + operator [](Node node) => getTreeElement(node); + + void remove(Node node) { + setTreeElement(node, null); + } + + void setType(Node node, DartType type) { + types[node] = type; + } + + DartType getType(Node node) => types[node]; + + void setSelector(Node node, Selector selector) { + selectors[node] = selector; + } + + Selector getSelector(Node node) => selectors[node]; + + bool isParameterChecked(Element element) { + return checkedParameters.contains(element); + } +} + +class ResolverTask extends CompilerTask { + ResolverTask(Compiler compiler) : super(compiler); + + String get name => 'Resolver'; + + TreeElements resolve(Element element) { + return measure(() { + if (Elements.isErroneousElement(element)) return null; + + for (MetadataAnnotation metadata in element.metadata) { + metadata.ensureResolved(compiler); + } + + ElementKind kind = element.kind; + if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR) || + identical(kind, ElementKind.FUNCTION) || + identical(kind, ElementKind.GETTER) || + identical(kind, ElementKind.SETTER)) { + return resolveMethodElement(element); + } + + if (identical(kind, ElementKind.FIELD)) return resolveField(element); + + if (identical(kind, ElementKind.PARAMETER) || + identical(kind, ElementKind.FIELD_PARAMETER)) { + return resolveParameter(element); + } + if (element.isClass()) { + ClassElement cls = element; + cls.ensureResolved(compiler); + return null; + } else if (element.isTypedef()) { + TypedefElement typdef = element; + resolveTypedef(typdef); + return null; + } else if (element.isTypeVariable()) { + element.computeType(compiler); + return null; + } + + compiler.unimplemented("resolve($element)", + node: element.parseNode(compiler)); + }); + } + + String constructorNameForDiagnostics(SourceString className, + SourceString constructorName) { + String classNameString = className.slowToString(); + String constructorNameString = constructorName.slowToString(); + return (constructorName == const SourceString('')) + ? classNameString + : "$classNameString.$constructorNameString"; + } + + void resolveRedirectingConstructor(InitializerResolver resolver, + Node node, + FunctionElement constructor, + FunctionElement redirection) { + Set<FunctionElement> seen = new Set<FunctionElement>(); + seen.add(constructor); + while (redirection != null) { + if (seen.contains(redirection)) { + resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE); + return; + } + seen.add(redirection); + + if (redirection.isPatched) { + checkMatchingPatchSignatures(constructor, redirection.patch); + redirection = redirection.patch; + } + redirection = resolver.visitor.resolveConstructorRedirection(redirection); + } + } + + void checkMatchingPatchParameters(FunctionElement origin, + Link<Element> originParameters, + Link<Element> patchParameters) { + while (!originParameters.isEmpty) { + Element originParameter = originParameters.head; + Element patchParameter = patchParameters.head; + // Hack: Use unparser to test parameter equality. This only works because + // we are restricting patch uses and the approach cannot be used + // elsewhere. + String originParameterText = + originParameter.parseNode(compiler).toString(); + String patchParameterText = + patchParameter.parseNode(compiler).toString(); + if (originParameterText != patchParameterText) { + error(originParameter.parseNode(compiler), + MessageKind.PATCH_PARAMETER_MISMATCH, + {'methodName': origin.name, + 'originParameter': originParameterText, + 'patchParameter': patchParameterText}); + } + + originParameters = originParameters.tail; + patchParameters = patchParameters.tail; + } + } + + void checkMatchingPatchSignatures(FunctionElement origin, + FunctionElement patch) { + // TODO(johnniwinther): Show both origin and patch locations on errors. + FunctionExpression originTree = compiler.withCurrentElement(origin, () { + return origin.parseNode(compiler); + }); + FunctionSignature originSignature = compiler.withCurrentElement(origin, () { + return origin.computeSignature(compiler); + }); + FunctionExpression patchTree = compiler.withCurrentElement(patch, () { + return patch.parseNode(compiler); + }); + FunctionSignature patchSignature = compiler.withCurrentElement(patch, () { + return patch.computeSignature(compiler); + }); + + if (originSignature.returnType != patchSignature.returnType) { + compiler.withCurrentElement(patch, () { + Node errorNode = + patchTree.returnType != null ? patchTree.returnType : patchTree; + error(errorNode, MessageKind.PATCH_RETURN_TYPE_MISMATCH, + {'methodName': origin.name, + 'originReturnType': originSignature.returnType, + 'patchReturnType': patchSignature.returnType}); + }); + } + if (originSignature.requiredParameterCount != + patchSignature.requiredParameterCount) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH, + {'methodName': origin.name, + 'originParameterCount': originSignature.requiredParameterCount, + 'patchParameterCount': patchSignature.requiredParameterCount}); + }); + } else { + checkMatchingPatchParameters(origin, + originSignature.requiredParameters, + patchSignature.requiredParameters); + } + if (originSignature.optionalParameterCount != 0 && + patchSignature.optionalParameterCount != 0) { + if (originSignature.optionalParametersAreNamed != + patchSignature.optionalParametersAreNamed) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH, + {'methodName': origin.name}); + }); + } + } + if (originSignature.optionalParameterCount != + patchSignature.optionalParameterCount) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH, + {'methodName': origin.name, + 'originParameterCount': originSignature.optionalParameterCount, + 'patchParameterCount': patchSignature.optionalParameterCount}); + }); + } else { + checkMatchingPatchParameters(origin, + originSignature.optionalParameters, + patchSignature.optionalParameters); + } + } + + TreeElements resolveMethodElement(FunctionElement element) { + assert(invariant(element, element.isDeclaration)); + return compiler.withCurrentElement(element, () { + bool isConstructor = + identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR); + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(element); + if (elements != null) { + assert(isConstructor); + return elements; + } + if (element.isPatched) { + checkMatchingPatchSignatures(element, element.patch); + element = element.patch; + } + return compiler.withCurrentElement(element, () { + FunctionExpression tree = element.parseNode(compiler); + if (tree.modifiers.isExternal()) { + error(tree, MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION); + return; + } + if (isConstructor) { + if (tree.returnType != null) { + error(tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE); + } + resolveConstructorImplementation(element, tree); + } + ResolverVisitor visitor = visitorFor(element); + visitor.useElement(tree, element); + visitor.setupFunction(tree, element); + + if (isConstructor) { + // Even if there is no initializer list we still have to do the + // resolution in case there is an implicit super constructor call. + InitializerResolver resolver = new InitializerResolver(visitor); + FunctionElement redirection = + resolver.resolveInitializers(element, tree); + if (redirection != null) { + resolveRedirectingConstructor(resolver, tree, element, redirection); + } + } else if (tree.initializers != null) { + error(tree, MessageKind.FUNCTION_WITH_INITIALIZER); + } + visitBody(visitor, tree.body); + + // Get the resolution tree and check that the resolved + // function doesn't use 'super' if it is mixed into another + // class. This is the part of the 'super' mixin check that + // happens when a function is resolved after the mixin + // application has been performed. + TreeElements resolutionTree = visitor.mapping; + ClassElement enclosingClass = element.getEnclosingClass(); + if (enclosingClass != null) { + Set<MixinApplicationElement> mixinUses = + compiler.world.mixinUses[enclosingClass]; + if (mixinUses != null) { + ClassElement mixin = enclosingClass; + for (MixinApplicationElement mixinApplication in mixinUses) { + checkMixinSuperUses(resolutionTree, mixinApplication, mixin); + } + } + } + return resolutionTree; + }); + }); + } + + /// This method should only be used by this library (or tests of + /// this library). + ResolverVisitor visitorFor(Element element) { + var mapping = new TreeElementMapping(element); + return new ResolverVisitor(compiler, element, mapping); + } + + void visitBody(ResolverVisitor visitor, Statement body) { + visitor.visit(body); + } + + void resolveConstructorImplementation(FunctionElement constructor, + FunctionExpression node) { + if (!identical(constructor.defaultImplementation, constructor)) return; + ClassElement intrface = constructor.getEnclosingClass(); + if (!intrface.isInterface()) return; + DartType defaultType = intrface.defaultClass; + if (defaultType == null) { + error(node, MessageKind.NO_DEFAULT_CLASS, + {'interfaceName': intrface.name}); + } + ClassElement defaultClass = defaultType.element; + defaultClass.ensureResolved(compiler); + assert(defaultClass.resolutionState == STATE_DONE); + assert(defaultClass.supertypeLoadState == STATE_DONE); + if (defaultClass.isInterface()) { + error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': defaultClass.name}); + } + // We have now established the following: + // [intrface] is an interface, let's say "MyInterface". + // [defaultClass] is a class, let's say "MyClass". + + Selector selector; + // If the default class implements the interface then we must use the + // default class' name. Otherwise we look for a factory with the name + // of the interface. + if (defaultClass.implementsInterface(intrface)) { + var constructorNameString = constructor.name.slowToString(); + // Create selector based on constructor.name but where interface + // is replaced with default class name. + // TODO(ahe): Don't use string manipulations here. + int classNameSeparatorIndex = constructorNameString.indexOf('\$'); + if (classNameSeparatorIndex < 0) { + selector = new Selector.callDefaultConstructor( + defaultClass.getLibrary()); + } else { + selector = new Selector.callConstructor( + new SourceString( + constructorNameString.substring(classNameSeparatorIndex + 1)), + defaultClass.getLibrary()); + } + constructor.defaultImplementation = + defaultClass.lookupConstructor(selector); + } else { + selector = + new Selector.callConstructor(constructor.name, + defaultClass.getLibrary()); + constructor.defaultImplementation = + defaultClass.lookupFactoryConstructor(selector); + } + if (constructor.defaultImplementation == null) { + // We failed to find a constructor named either + // "MyInterface.name" or "MyClass.name". + // TODO(aprelev@gmail.com): Use constructorNameForDiagnostics in + // the error message below. + error(node, + MessageKind.CANNOT_FIND_CONSTRUCTOR2, + {'constructorName': selector.name, 'className': defaultClass.name}); + } + } + + TreeElements resolveField(VariableElement element) { + Node tree = element.parseNode(compiler); + if(element.modifiers.isStatic() && element.variables.isTopLevel()) { + error(element.modifiers.getStatic(), + MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC); + } + ResolverVisitor visitor = visitorFor(element); + initializerDo(tree, visitor.visit); + + // Perform various checks as side effect of "computing" the type. + element.computeType(compiler); + + return visitor.mapping; + } + + TreeElements resolveParameter(Element element) { + Node tree = element.parseNode(compiler); + ResolverVisitor visitor = visitorFor(element.enclosingElement); + initializerDo(tree, visitor.visit); + return visitor.mapping; + } + + DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) { + DartType type = resolveReturnType(element, annotation); + if (type == compiler.types.voidType) { + error(annotation, MessageKind.VOID_NOT_ALLOWED); + } + return type; + } + + DartType resolveReturnType(Element element, TypeAnnotation annotation) { + if (annotation == null) return compiler.types.dynamicType; + DartType result = visitorFor(element).resolveTypeAnnotation(annotation); + if (result == null) { + // TODO(karklose): warning. + return compiler.types.dynamicType; + } + return result; + } + + /** + * Load and resolve the supertypes of [cls]. + * + * Warning: do not call this method directly. It should only be + * called by [resolveClass] and [ClassSupertypeResolver]. + */ + void loadSupertypes(ClassElement cls, Spannable from) { + compiler.withCurrentElement(cls, () => measure(() { + if (cls.supertypeLoadState == STATE_DONE) return; + if (cls.supertypeLoadState == STATE_STARTED) { + compiler.reportErrorCode(from, MessageKind.CYCLIC_CLASS_HIERARCHY, + {'className': cls.name}); + cls.supertypeLoadState = STATE_DONE; + cls.allSupertypes = const Link<DartType>().prepend( + compiler.objectClass.computeType(compiler)); + // TODO(ahe): We should also set cls.supertype here to avoid + // creating a malformed class hierarchy. + return; + } + cls.supertypeLoadState = STATE_STARTED; + compiler.withCurrentElement(cls, () { + // TODO(ahe): Cache the node in cls. + cls.parseNode(compiler).accept( + new ClassSupertypeResolver(compiler, cls)); + if (cls.supertypeLoadState != STATE_DONE) { + cls.supertypeLoadState = STATE_DONE; + } + }); + })); + } + + // TODO(johnniwinther): Remove this queue when resolution has been split into + // syntax and semantic resolution. + ClassElement currentlyResolvedClass; + Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>(); + + /** + * Resolve the class [element]. + * + * Before calling this method, [element] was constructed by the + * scanner and most fields are null or empty. This method fills in + * these fields and also ensure that the supertypes of [element] are + * resolved. + * + * Warning: Do not call this method directly. Instead use + * [:element.ensureResolved(compiler):]. + */ + void resolveClass(ClassElement element) { + ClassElement previousResolvedClass = currentlyResolvedClass; + currentlyResolvedClass = element; + resolveClassInternal(element); + if (previousResolvedClass == null) { + while (!pendingClassesToBeResolved.isEmpty) { + pendingClassesToBeResolved.removeFirst().ensureResolved(compiler); + } + } + currentlyResolvedClass = previousResolvedClass; + } + + void _ensureClassWillBeResolved(ClassElement element) { + if (currentlyResolvedClass == null) { + element.ensureResolved(compiler); + } else { + pendingClassesToBeResolved.add(element); + } + } + + void resolveClassInternal(ClassElement element) { + if (!element.isPatch) { + compiler.withCurrentElement(element, () => measure(() { + assert(element.resolutionState == STATE_NOT_STARTED); + element.resolutionState = STATE_STARTED; + Node tree = element.parseNode(compiler); + loadSupertypes(element, tree); + + ClassResolverVisitor visitor = + new ClassResolverVisitor(compiler, element); + visitor.visit(tree); + element.resolutionState = STATE_DONE; + })); + if (element.isPatched) { + // Ensure handling patch after origin. + element.patch.ensureResolved(compiler); + } + } else { // Handle patch classes: + element.resolutionState = STATE_STARTED; + // Ensure handling origin before patch. + element.origin.ensureResolved(compiler); + // Ensure that the type is computed. + element.computeType(compiler); + // Copy class hiearchy from origin. + element.supertype = element.origin.supertype; + element.defaultClass = element.origin.defaultClass; + element.interfaces = element.origin.interfaces; + element.allSupertypes = element.origin.allSupertypes; + // Stepwise assignment to ensure invariant. + element.supertypeLoadState = STATE_STARTED; + element.supertypeLoadState = STATE_DONE; + element.resolutionState = STATE_DONE; + // TODO(johnniwinther): Check matching type variables and + // empty extends/implements clauses. + } + for (MetadataAnnotation metadata in element.metadata) { + metadata.ensureResolved(compiler); + } + } + + void checkClass(ClassElement element) { + if (element.isMixinApplication) { + checkMixinApplication(element); + } else { + checkClassMembers(element); + } + } + + void checkMixinApplication(MixinApplicationElement mixinApplication) { + Modifiers modifiers = mixinApplication.modifiers; + int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT; + if (illegalFlags != 0) { + Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags); + compiler.reportErrorCode( + modifiers, + MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS, + {'modifiers': illegalModifiers}); + } + + // In case of cyclic mixin applications, the mixin chain will have + // been cut. If so, we have already reported the error to the + // user so we just return from here. + ClassElement mixin = mixinApplication.mixin; + if (mixin == null) return; + + // Check that the mixed in class has Object as its superclass. + if (!mixin.superclass.isObject(compiler)) { + compiler.reportErrorCode(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS); + } + + // Check that the mixed in class doesn't have any constructors and + // make sure we aren't mixing in methods that use 'super'. + mixin.forEachLocalMember((Element member) { + if (member.isGenerativeConstructor() && !member.isSynthesized) { + compiler.reportErrorCode(member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR); + } else { + // Get the resolution tree and check that the resolved member + // doesn't use 'super'. This is the part of the 'super' mixin + // check that happens when a function is resolved before the + // mixin application has been performed. + checkMixinSuperUses( + compiler.enqueuer.resolution.resolvedElements[member], + mixinApplication, + mixin); + } + }); + } + + void checkMixinSuperUses(TreeElements resolutionTree, + MixinApplicationElement mixinApplication, + ClassElement mixin) { + if (resolutionTree == null) return; + Set<Node> superUses = resolutionTree.superUses; + if (superUses.isEmpty) return; + compiler.reportErrorCode(mixinApplication, + MessageKind.ILLEGAL_MIXIN_WITH_SUPER, + {'className': mixin.name}); + // Show the user the problematic uses of 'super' in the mixin. + for (Node use in superUses) { + CompilationError error = MessageKind.ILLEGAL_MIXIN_SUPER_USE.error(); + compiler.reportMessage(compiler.spanFromNode(use), + error, Diagnostic.INFO); + } + } + + void checkClassMembers(ClassElement cls) { + assert(invariant(cls, cls.isDeclaration)); + if (cls.isObject(compiler)) return; + // TODO(johnniwinther): Should this be done on the implementation element as + // well? + cls.forEachMember((holder, member) { + compiler.withCurrentElement(member, () { + // Perform various checks as side effect of "computing" the type. + member.computeType(compiler); + + // Check modifiers. + if (member.isFunction() && member.modifiers.isFinal()) { + compiler.reportErrorCode( + member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER); + } + if (member.isConstructor()) { + final mismatchedFlagsBits = + member.modifiers.flags & + (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT); + if (mismatchedFlagsBits != 0) { + final mismatchedFlags = + new Modifiers.withFlags(null, mismatchedFlagsBits); + compiler.reportErrorCode( + member, + MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS, + {'modifiers': mismatchedFlags}); + } + checkConstructorNameHack(holder, member); + } + checkAbstractField(member); + checkValidOverride(member, cls.lookupSuperMember(member.name)); + checkUserDefinableOperator(member); + }); + }); + } + + // TODO(ahe): Remove this method. It is only needed while we store + // constructor names as ClassName$id. Once we start storing + // constructors as just id, this will be caught by the general + // mechanism for duplicate members. + /// Check that a constructor name does not conflict with a member. + void checkConstructorNameHack(ClassElement holder, FunctionElement member) { + // If the name of the constructor is the same as the name of the + // class, there cannot be a problem. + if (member.name == holder.name) return; + + SourceString name = + Elements.deconstructConstructorName(member.name, holder); + + // If the name could not be deconstructed, this is is from a + // factory method from a deprecated interface implementation. + if (name == null) return; + + Element otherMember = holder.lookupLocalMember(name); + if (otherMember != null) { + if (compiler.onDeprecatedFeature(member, 'conflicting constructor')) { + compiler.reportMessage( + compiler.spanFromElement(otherMember), + MessageKind.GENERIC.error({'text': 'This member conflicts with a' + ' constructor.'}), + Diagnostic.INFO); + } + } + } + + void checkAbstractField(Element member) { + // Only check for getters. The test can only fail if there is both a setter + // and a getter with the same name, and we only need to check each abstract + // field once, so we just ignore setters. + if (!member.isGetter()) return; + + // Find the associated abstract field. + ClassElement classElement = member.getEnclosingClass(); + Element lookupElement = classElement.lookupLocalMember(member.name); + if (lookupElement == null) { + compiler.internalErrorOnElement(member, + "No abstract field for accessor"); + } else if (!identical(lookupElement.kind, ElementKind.ABSTRACT_FIELD)) { + compiler.internalErrorOnElement( + member, "Inaccessible abstract field for accessor"); + } + AbstractFieldElement field = lookupElement; + + if (field.getter == null) return; + if (field.setter == null) return; + int getterFlags = field.getter.modifiers.flags | Modifiers.FLAG_ABSTRACT; + int setterFlags = field.setter.modifiers.flags | Modifiers.FLAG_ABSTRACT; + if (!identical(getterFlags, setterFlags)) { + final mismatchedFlags = + new Modifiers.withFlags(null, getterFlags ^ setterFlags); + compiler.reportErrorCode( + field.getter, + MessageKind.GETTER_MISMATCH, + {'modifiers': mismatchedFlags}); + compiler.reportErrorCode( + field.setter, + MessageKind.SETTER_MISMATCH, + {'modifiers': mismatchedFlags}); + } + } + + void checkUserDefinableOperator(Element member) { + FunctionElement function = member.asFunctionElement(); + if (function == null) return; + String value = member.name.stringValue; + if (value == null) return; + if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return; + + bool isMinus = false; + int requiredParameterCount; + MessageKind messageKind; + FunctionSignature signature = function.computeSignature(compiler); + if (identical(value, 'unary-')) { + isMinus = true; + messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; + requiredParameterCount = 0; + } else if (isMinusOperator(value)) { + isMinus = true; + messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; + requiredParameterCount = 1; + } else if (isUnaryOperator(value)) { + messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 0; + } else if (isBinaryOperator(value)) { + messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 1; + } else if (isTernaryOperator(value)) { + messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 2; + } else { + compiler.internalErrorOnElement(function, + 'Unexpected user defined operator $value'); + } + checkArity(function, requiredParameterCount, messageKind, isMinus); + } + + void checkArity(FunctionElement function, + int requiredParameterCount, MessageKind messageKind, + bool isMinus) { + FunctionExpression node = function.parseNode(compiler); + FunctionSignature signature = function.computeSignature(compiler); + if (signature.requiredParameterCount != requiredParameterCount) { + Node errorNode = node; + if (node.parameters != null) { + if (isMinus || + signature.requiredParameterCount < requiredParameterCount) { + // If there are too few parameters, point to the whole parameter list. + // For instance + // + // int operator +() {} + // ^^ + // + // int operator []=(value) {} + // ^^^^^^^ + // + // For operator -, always point the whole parameter list, like + // + // int operator -(a, b) {} + // ^^^^^^ + // + // instead of + // + // int operator -(a, b) {} + // ^ + // + // since the correction might not be to remove 'b' but instead to + // remove 'a, b'. + errorNode = node.parameters; + } else { + errorNode = node.parameters.nodes.skip(requiredParameterCount).head; + } + } + compiler.reportErrorCode( + errorNode, messageKind, {'operatorName': function.name}); + } + if (signature.optionalParameterCount != 0) { + Node errorNode = + node.parameters.nodes.skip(signature.requiredParameterCount).head; + if (signature.optionalParametersAreNamed) { + compiler.reportErrorCode( + errorNode, + MessageKind.OPERATOR_NAMED_PARAMETERS, + {'operatorName': function.name}); + } else { + compiler.reportErrorCode( + errorNode, + MessageKind.OPERATOR_OPTIONAL_PARAMETERS, + {'operatorName': function.name}); + } + } + } + + reportErrorWithContext(Element errorneousElement, + MessageKind errorMessage, + Element contextElement, + MessageKind contextMessage) { + compiler.reportErrorCode( + errorneousElement, + errorMessage, + {'memberName': contextElement.name, + 'className': contextElement.getEnclosingClass().name}); + compiler.reportMessage( + compiler.spanFromElement(contextElement), + contextMessage.error(), + Diagnostic.INFO); + } + + void checkValidOverride(Element member, Element superMember) { + if (superMember == null) return; + if (member.modifiers.isStatic()) { + reportErrorWithContext( + member, MessageKind.NO_STATIC_OVERRIDE, + superMember, MessageKind.NO_STATIC_OVERRIDE_CONT); + } else { + FunctionElement superFunction = superMember.asFunctionElement(); + FunctionElement function = member.asFunctionElement(); + if (superFunction == null || superFunction.isAccessor()) { + // Field or accessor in super. + if (function != null && !function.isAccessor()) { + // But a plain method in this class. + reportErrorWithContext( + member, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD, + superMember, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT); + } + } else { + // Instance method in super. + if (function == null || function.isAccessor()) { + // But a field (or accessor) in this class. + reportErrorWithContext( + member, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD, + superMember, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT); + } else { + // Both are plain instance methods. + if (superFunction.requiredParameterCount(compiler) != + function.requiredParameterCount(compiler)) { + reportErrorWithContext( + member, + MessageKind.BAD_ARITY_OVERRIDE, + superMember, + MessageKind.BAD_ARITY_OVERRIDE_CONT); + } + // TODO(ahe): Check optional parameters. + } + } + } + } + + FunctionSignature resolveSignature(FunctionElement element) { + return compiler.withCurrentElement(element, () { + FunctionExpression node = + compiler.parser.measure(() => element.parseNode(compiler)); + return measure(() => SignatureResolver.analyze( + compiler, node.parameters, node.returnType, element)); + }); + } + + FunctionSignature resolveFunctionExpression(Element element, + FunctionExpression node) { + return measure(() => SignatureResolver.analyze( + compiler, node.parameters, node.returnType, element)); + } + + void resolveTypedef(TypedefElement element) { + if (element.isResolved || element.isBeingResolved) return; + element.isBeingResolved = true; + return compiler.withCurrentElement(element, () { + measure(() { + Typedef node = + compiler.parser.measure(() => element.parseNode(compiler)); + TypedefResolverVisitor visitor = + new TypedefResolverVisitor(compiler, element); + visitor.visit(node); + + element.isBeingResolved = false; + element.isResolved = true; + }); + }); + } + + FunctionType computeFunctionType(Element element, + FunctionSignature signature) { + var parameterTypes = new LinkBuilder<DartType>(); + for (Element parameter in signature.requiredParameters) { + parameterTypes.addLast(parameter.computeType(compiler)); + } + var optionalParameterTypes = const Link<DartType>(); + var namedParameters = const Link<SourceString>(); + var namedParameterTypes = const Link<DartType>(); + if (signature.optionalParametersAreNamed) { + var namedParametersBuilder = new LinkBuilder<SourceString>(); + var namedParameterTypesBuilder = new LinkBuilder<DartType>(); + for (Element parameter in signature.orderedOptionalParameters) { + namedParametersBuilder.addLast(parameter.name); + namedParameterTypesBuilder.addLast(parameter.computeType(compiler)); + } + namedParameters = namedParametersBuilder.toLink(); + namedParameterTypes = namedParameterTypesBuilder.toLink(); + } else { + var optionalParameterTypesBuilder = new LinkBuilder<DartType>(); + for (Element parameter in signature.optionalParameters) { + optionalParameterTypesBuilder.addLast(parameter.computeType(compiler)); + } + optionalParameterTypes = optionalParameterTypesBuilder.toLink(); + } + return new FunctionType(element, + signature.returnType, + parameterTypes.toLink(), + optionalParameterTypes, + namedParameters, + namedParameterTypes); + } + + void resolveMetadataAnnotation(PartialMetadataAnnotation annotation) { + compiler.withCurrentElement(annotation.annotatedElement, () => measure(() { + assert(annotation.resolutionState == STATE_NOT_STARTED); + annotation.resolutionState = STATE_STARTED; + + Node node = annotation.parseNode(compiler); + ResolverVisitor visitor = + visitorFor(annotation.annotatedElement.enclosingElement); + node.accept(visitor); + annotation.value = compiler.metadataHandler.compileNodeWithDefinitions( + node, visitor.mapping, isConst: true); + + annotation.resolutionState = STATE_DONE; + })); + } + + error(Node node, MessageKind kind, [arguments = const {}]) { + ResolutionError message = new ResolutionError(kind, arguments); + compiler.reportError(node, message); + } +} + +class InitializerResolver { + final ResolverVisitor visitor; + final Map<SourceString, Node> initialized; + Link<Node> initializers; + bool hasSuper; + + InitializerResolver(this.visitor) + : initialized = new Map<SourceString, Node>(), hasSuper = false; + + error(Node node, MessageKind kind, [arguments = const {}]) { + visitor.error(node, kind, arguments); + } + + warning(Node node, MessageKind kind, [arguments = const {}]) { + visitor.warning(node, kind, arguments); + } + + bool isFieldInitializer(SendSet node) { + if (node.selector.asIdentifier() == null) return false; + if (node.receiver == null) return true; + if (node.receiver.asIdentifier() == null) return false; + return node.receiver.asIdentifier().isThis(); + } + + void checkForDuplicateInitializers(SourceString name, Node init) { + if (initialized.containsKey(name)) { + error(init, MessageKind.DUPLICATE_INITIALIZER, {'fieldName': name}); + warning(initialized[name], MessageKind.ALREADY_INITIALIZED, + {'fieldName': name}); + } + initialized[name] = init; + } + + void resolveFieldInitializer(FunctionElement constructor, SendSet init) { + // init is of the form [this.]field = value. + final Node selector = init.selector; + final SourceString name = selector.asIdentifier().source; + // Lookup target field. + Element target; + if (isFieldInitializer(init)) { + target = constructor.getEnclosingClass().lookupLocalMember(name); + if (target == null) { + error(selector, MessageKind.CANNOT_RESOLVE, {'name': name}); + } else if (target.kind != ElementKind.FIELD) { + error(selector, MessageKind.NOT_A_FIELD, {'fieldName': name}); + } else if (!target.isInstanceMember()) { + error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name}); + } + } else { + error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); + } + visitor.useElement(init, target); + visitor.world.registerStaticUse(target); + checkForDuplicateInitializers(name, init); + // Resolve initializing value. + visitor.visitInStaticContext(init.arguments.head); + } + + ClassElement getSuperOrThisLookupTarget(FunctionElement constructor, + bool isSuperCall, + Node diagnosticNode) { + ClassElement lookupTarget = constructor.getEnclosingClass(); + if (isSuperCall) { + // Calculate correct lookup target and constructor name. + if (identical(lookupTarget, visitor.compiler.objectClass)) { + error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT); + } else { + return lookupTarget.supertype.element; + } + } + return lookupTarget; + } + + Element resolveSuperOrThisForSend(FunctionElement constructor, + FunctionExpression functionNode, + Send call) { + // Resolve the selector and the arguments. + ResolverTask resolver = visitor.compiler.resolver; + visitor.inStaticContext(() { + visitor.resolveSelector(call); + visitor.resolveArguments(call.argumentsNode); + }); + Selector selector = visitor.mapping.getSelector(call); + bool isSuperCall = Initializers.isSuperConstructorCall(call); + + ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, + isSuperCall, + call); + Selector constructorSelector = + visitor.getRedirectingThisOrSuperConstructorSelector(call); + FunctionElement calledConstructor = + lookupTarget.lookupConstructor(constructorSelector); + + final bool isImplicitSuperCall = false; + final SourceString className = lookupTarget.name; + verifyThatConstructorMatchesCall(calledConstructor, + selector, + isImplicitSuperCall, + call, + className, + constructorSelector); + + visitor.useElement(call, calledConstructor); + visitor.world.registerStaticUse(calledConstructor); + return calledConstructor; + } + + void resolveImplicitSuperConstructorSend(FunctionElement constructor, + FunctionExpression functionNode) { + // If the class has a super resolve the implicit super call. + ClassElement classElement = constructor.getEnclosingClass(); + ClassElement superClass = classElement.superclass; + if (classElement != visitor.compiler.objectClass) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + SourceString constructorName = const SourceString(''); + Selector callToMatch = new Selector.call( + constructorName, + classElement.getLibrary(), + 0); + + final bool isSuperCall = true; + ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, + isSuperCall, + functionNode); + Selector constructorSelector = new Selector.callDefaultConstructor( + visitor.enclosingElement.getLibrary()); + Element calledConstructor = lookupTarget.lookupConstructor( + constructorSelector); + + final SourceString className = lookupTarget.name; + final bool isImplicitSuperCall = true; + verifyThatConstructorMatchesCall(calledConstructor, + callToMatch, + isImplicitSuperCall, + functionNode, + className, + constructorSelector); + + visitor.world.registerStaticUse(calledConstructor); + } + } + + void verifyThatConstructorMatchesCall( + FunctionElement lookedupConstructor, + Selector call, + bool isImplicitSuperCall, + Node diagnosticNode, + SourceString className, + Selector constructorSelector) { + if (lookedupConstructor == null + || !lookedupConstructor.isGenerativeConstructor()) { + var fullConstructorName = + visitor.compiler.resolver.constructorNameForDiagnostics( + className, + constructorSelector.name); + MessageKind kind = isImplicitSuperCall + ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT + : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR; + error(diagnosticNode, kind, {'constructorName': fullConstructorName}); + } else { + if (!call.applies(lookedupConstructor, visitor.compiler)) { + MessageKind kind = isImplicitSuperCall + ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT + : MessageKind.NO_MATCHING_CONSTRUCTOR; + error(diagnosticNode, kind); + } + } + } + + FunctionElement resolveRedirection(FunctionElement constructor, + FunctionExpression functionNode) { + if (functionNode.initializers == null) return null; + Link<Node> link = functionNode.initializers.nodes; + if (!link.isEmpty && Initializers.isConstructorRedirect(link.head)) { + return resolveSuperOrThisForSend(constructor, functionNode, link.head); + } + return null; + } + + /** + * Resolve all initializers of this constructor. In the case of a redirecting + * constructor, the resolved constructor's function element is returned. + */ + FunctionElement resolveInitializers(FunctionElement constructor, + FunctionExpression functionNode) { + // Keep track of all "this.param" parameters specified for constructor so + // that we can ensure that fields are initialized only once. + FunctionSignature functionParameters = + constructor.computeSignature(visitor.compiler); + functionParameters.forEachParameter((Element element) { + if (identical(element.kind, ElementKind.FIELD_PARAMETER)) { + checkForDuplicateInitializers(element.name, + element.parseNode(visitor.compiler)); + } + }); + + if (functionNode.initializers == null) { + initializers = const Link<Node>(); + } else { + initializers = functionNode.initializers.nodes; + } + FunctionElement result; + bool resolvedSuper = false; + for (Link<Node> link = initializers; + !link.isEmpty; + link = link.tail) { + if (link.head.asSendSet() != null) { + final SendSet init = link.head.asSendSet(); + resolveFieldInitializer(constructor, init); + } else if (link.head.asSend() != null) { + final Send call = link.head.asSend(); + if (Initializers.isSuperConstructorCall(call)) { + if (resolvedSuper) { + error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER); + } + resolveSuperOrThisForSend(constructor, functionNode, call); + resolvedSuper = true; + } else if (Initializers.isConstructorRedirect(call)) { + // Check that there is no body (Language specification 7.5.1). + if (functionNode.hasBody()) { + error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY); + } + // Check that there are no other initializers. + if (!initializers.tail.isEmpty) { + error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER); + } + return resolveSuperOrThisForSend(constructor, functionNode, call); + } else { + visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED); + return null; + } + } else { + error(link.head, MessageKind.INVALID_INITIALIZER); + } + } + if (!resolvedSuper) { + resolveImplicitSuperConstructorSend(constructor, functionNode); + } + return null; // If there was no redirection always return null. + } +} + +class CommonResolverVisitor<R> extends Visitor<R> { + final Compiler compiler; + + CommonResolverVisitor(Compiler this.compiler); + + R visitNode(Node node) { + cancel(node, + 'internal error: Unhandled node: ${node.getObjectDescription()}'); + } + + R visitEmptyStatement(Node node) => null; + + /** Convenience method for visiting nodes that may be null. */ + R visit(Node node) => (node == null) ? null : node.accept(this); + + void error(Node node, MessageKind kind, [Map arguments = const {}]) { + ResolutionError message = new ResolutionError(kind, arguments); + compiler.reportError(node, message); + } + + void warning(Node node, MessageKind kind, [Map arguments = const {}]) { + ResolutionWarning message = new ResolutionWarning(kind, arguments); + compiler.reportWarning(node, message); + } + + void cancel(Node node, String message) { + compiler.cancel(message, node: node); + } + + void internalError(Node node, String message) { + compiler.internalError(message, node: node); + } + + void unimplemented(Node node, String message) { + compiler.unimplemented(message, node: node); + } +} + +abstract class LabelScope { + LabelScope get outer; + LabelElement lookup(String label); +} + +class LabeledStatementLabelScope implements LabelScope { + final LabelScope outer; + final Map<String, LabelElement> labels; + LabeledStatementLabelScope(this.outer, this.labels); + LabelElement lookup(String labelName) { + LabelElement label = labels[labelName]; + if (label != null) return label; + return outer.lookup(labelName); + } +} + +class SwitchLabelScope implements LabelScope { + final LabelScope outer; + final Map<String, LabelElement> caseLabels; + + SwitchLabelScope(this.outer, this.caseLabels); + + LabelElement lookup(String labelName) { + LabelElement result = caseLabels[labelName]; + if (result != null) return result; + return outer.lookup(labelName); + } +} + +class EmptyLabelScope implements LabelScope { + const EmptyLabelScope(); + LabelElement lookup(String label) => null; + LabelScope get outer { + throw 'internal error: empty label scope has no outer'; + } +} + +class StatementScope { + LabelScope labels; + Link<TargetElement> breakTargetStack; + Link<TargetElement> continueTargetStack; + // Used to provide different numbers to statements if one is inside the other. + // Can be used to make otherwise duplicate labels unique. + int nestingLevel = 0; + + StatementScope() + : labels = const EmptyLabelScope(), + breakTargetStack = const Link<TargetElement>(), + continueTargetStack = const Link<TargetElement>(); + + LabelElement lookupLabel(String label) { + return labels.lookup(label); + } + + TargetElement currentBreakTarget() => + breakTargetStack.isEmpty ? null : breakTargetStack.head; + + TargetElement currentContinueTarget() => + continueTargetStack.isEmpty ? null : continueTargetStack.head; + + void enterLabelScope(Map<String, LabelElement> elements) { + labels = new LabeledStatementLabelScope(labels, elements); + nestingLevel++; + } + + void exitLabelScope() { + nestingLevel--; + labels = labels.outer; + } + + void enterLoop(TargetElement element) { + breakTargetStack = breakTargetStack.prepend(element); + continueTargetStack = continueTargetStack.prepend(element); + nestingLevel++; + } + + void exitLoop() { + nestingLevel--; + breakTargetStack = breakTargetStack.tail; + continueTargetStack = continueTargetStack.tail; + } + + void enterSwitch(TargetElement breakElement, + Map<String, LabelElement> continueElements) { + breakTargetStack = breakTargetStack.prepend(breakElement); + labels = new SwitchLabelScope(labels, continueElements); + nestingLevel++; + } + + void exitSwitch() { + nestingLevel--; + breakTargetStack = breakTargetStack.tail; + labels = labels.outer; + } +} + +class TypeResolver { + final Compiler compiler; + + TypeResolver(this.compiler); + + Element resolveTypeName(Scope scope, + SourceString prefixName, + Identifier typeName) { + if (prefixName != null) { + Element e = scope.lookup(prefixName); + if (e != null) { + if (identical(e.kind, ElementKind.PREFIX)) { + // The receiver is a prefix. Lookup in the imported members. + PrefixElement prefix = e; + return prefix.lookupLocalMember(typeName.source); + } else if (identical(e.kind, ElementKind.CLASS)) { + // TODO(johnniwinther): Remove this case. + // The receiver is the class part of a named constructor. + return e; + } + } else { + // The caller creates the ErroneousElement for the MalformedType. + return null; + } + } else { + String stringValue = typeName.source.stringValue; + if (identical(stringValue, 'void')) { + return compiler.types.voidType.element; + } else if (identical(stringValue, 'Dynamic')) { + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + compiler.onDeprecatedFeature(typeName, 'Dynamic'); + return compiler.dynamicClass; + } else if (identical(stringValue, 'dynamic')) { + return compiler.dynamicClass; + } else { + return scope.lookup(typeName.source); + } + } + } + + // TODO(johnniwinther): Change [onFailure] and [whenResolved] to use boolean + // flags instead of closures. + DartType resolveTypeAnnotation( + TypeAnnotation node, + Scope scope, + Element enclosingElement, + {onFailure(Node node, MessageKind kind, [Map arguments]), + whenResolved(Node node, DartType type)}) { + if (onFailure == null) { + onFailure = (n, k, [arguments]) {}; + } + if (whenResolved == null) { + whenResolved = (n, t) {}; + } + if (scope == null) { + compiler.internalError('resolveTypeAnnotation: no scope specified'); + } + return resolveTypeAnnotationInContext(scope, node, enclosingElement, + onFailure, whenResolved); + } + + DartType resolveTypeAnnotationInContext(Scope scope, TypeAnnotation node, + Element enclosingElement, + onFailure, whenResolved) { + Identifier typeName; + SourceString prefixName; + Send send = node.typeName.asSend(); + if (send != null) { + // The type name is of the form [: prefix . identifier :]. + prefixName = send.receiver.asIdentifier().source; + typeName = send.selector.asIdentifier(); + } else { + typeName = node.typeName.asIdentifier(); + } + + Element element = resolveTypeName(scope, prefixName, typeName); + DartType type; + + DartType reportFailureAndCreateType(MessageKind messageKind, + Map messageArguments) { + onFailure(node, messageKind, messageArguments); + var erroneousElement = new ErroneousElementX( + messageKind, messageArguments, typeName.source, enclosingElement); + var arguments = new LinkBuilder<DartType>(); + resolveTypeArguments( + node, null, enclosingElement, + scope, onFailure, whenResolved, arguments); + return new MalformedType(erroneousElement, null, arguments.toLink()); + } + + DartType checkNoTypeArguments(DartType type) { + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, const Link<DartType>(), enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + type, arguments.toLink()); + } + return type; + } + + if (element == null) { + type = reportFailureAndCreateType( + MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node.typeName}); + } else if (element.isAmbiguous()) { + AmbiguousElement ambiguous = element; + type = reportFailureAndCreateType( + ambiguous.messageKind, ambiguous.messageArguments); + } else if (!element.impliesType()) { + type = reportFailureAndCreateType( + MessageKind.NOT_A_TYPE, {'node': node.typeName}); + } else { + if (identical(element, compiler.types.voidType.element) || + identical(element, compiler.types.dynamicType.element)) { + type = checkNoTypeArguments(element.computeType(compiler)); + } else if (element.isClass()) { + ClassElement cls = element; + compiler.resolver._ensureClassWillBeResolved(cls); + element.computeType(compiler); + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, cls.typeVariables, enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + new InterfaceType(cls.declaration, arguments.toLink())); + } else { + if (arguments.isEmpty) { + type = cls.rawType; + } else { + type = new InterfaceType(cls.declaration, arguments.toLink()); + } + } + } else if (element.isTypedef()) { + TypedefElement typdef = element; + // TODO(ahe): Should be [ensureResolved]. + compiler.resolveTypedef(typdef); + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, typdef.typeVariables, enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + new TypedefType(typdef, arguments.toLink())); + } else { + if (arguments.isEmpty) { + type = typdef.rawType; + } else { + type = new TypedefType(typdef, arguments.toLink()); + } + } + } else if (element.isTypeVariable()) { + if (enclosingElement.isInStaticMember()) { + compiler.reportWarning(node, + MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER.message( + {'typeVariableName': node})); + type = new MalformedType( + new ErroneousElementX( + MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, + {'typeVariableName': node}, + typeName.source, enclosingElement), + element.computeType(compiler)); + } else { + type = element.computeType(compiler); + } + type = checkNoTypeArguments(type); + } else { + compiler.cancel("unexpected element kind ${element.kind}", + node: node); + } + } + whenResolved(node, type); + return type; + } + + /** + * Resolves the type arguments of [node] and adds these to [arguments]. + * + * Returns [: true :] if the number of type arguments did not match the + * number of type variables. + */ + bool resolveTypeArguments( + TypeAnnotation node, + Link<DartType> typeVariables, + Element enclosingElement, + Scope scope, + onFailure, whenResolved, + LinkBuilder<DartType> arguments) { + if (node.typeArguments == null) { + return false; + } + bool typeArgumentCountMismatch = false; + for (Link<Node> typeArguments = node.typeArguments.nodes; + !typeArguments.isEmpty; + typeArguments = typeArguments.tail) { + if (typeVariables != null && typeVariables.isEmpty) { + onFailure(typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); + typeArgumentCountMismatch = true; + } + DartType argType = resolveTypeAnnotationInContext(scope, + typeArguments.head, + enclosingElement, + onFailure, + whenResolved); + arguments.addLast(argType); + if (typeVariables != null && !typeVariables.isEmpty) { + typeVariables = typeVariables.tail; + } + } + if (typeVariables != null && !typeVariables.isEmpty) { + onFailure(node.typeArguments, MessageKind.MISSING_TYPE_ARGUMENT); + typeArgumentCountMismatch = true; + } + return typeArgumentCountMismatch; + } +} + +/** + * Core implementation of resolution. + * + * Do not subclass or instantiate this class outside this library + * except for testing. + */ +class ResolverVisitor extends CommonResolverVisitor<Element> { + final TreeElementMapping mapping; + Element enclosingElement; + final TypeResolver typeResolver; + bool inInstanceContext; + bool inCheckContext; + bool inCatchBlock; + Scope scope; + ClassElement currentClass; + ExpressionStatement currentExpressionStatement; + bool typeRequired = false; + StatementScope statementScope; + int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION + | ElementCategory.IMPLIES_TYPE; + + ResolverVisitor(Compiler compiler, Element element, this.mapping) + : this.enclosingElement = element, + // When the element is a field, we are actually resolving its + // initial value, which should not have access to instance + // fields. + inInstanceContext = (element.isInstanceMember() && !element.isField()) + || element.isGenerativeConstructor(), + this.currentClass = element.isMember() ? element.getEnclosingClass() + : null, + this.statementScope = new StatementScope(), + typeResolver = new TypeResolver(compiler), + scope = element.buildScope(), + inCheckContext = compiler.enableTypeAssertions, + inCatchBlock = false, + super(compiler); + + ResolutionEnqueuer get world => compiler.enqueuer.resolution; + + Element lookup(Node node, SourceString name) { + Element result = scope.lookup(name); + if (!Elements.isUnresolved(result)) { + if (!inInstanceContext && result.isInstanceMember()) { + compiler.reportErrorCode( + node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); + return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE, + {'name': name}, + name, enclosingElement); + } else if (result.isAmbiguous()) { + AmbiguousElement ambiguous = result; + compiler.reportErrorCode( + node, ambiguous.messageKind, ambiguous.messageArguments); + return new ErroneousElementX(ambiguous.messageKind, + ambiguous.messageArguments, + name, enclosingElement); + } + } + return result; + } + + // Create, or reuse an already created, statement element for a statement. + TargetElement getOrCreateTargetElement(Node statement) { + TargetElement element = mapping[statement]; + if (element == null) { + element = new TargetElementX(statement, + statementScope.nestingLevel, + enclosingElement); + mapping[statement] = element; + } + return element; + } + + doInCheckContext(action()) { + bool wasInCheckContext = inCheckContext; + inCheckContext = true; + var result = action(); + inCheckContext = wasInCheckContext; + return result; + } + + inStaticContext(action()) { + bool wasInstanceContext = inInstanceContext; + inInstanceContext = false; + var result = action(); + inInstanceContext = wasInstanceContext; + return result; + } + + visitInStaticContext(Node node) { + inStaticContext(() => visit(node)); + } + + ErroneousElement warnAndCreateErroneousElement(Node node, + SourceString name, + MessageKind kind, + [Map arguments = const {}]) { + ResolutionWarning warning = new ResolutionWarning(kind, arguments); + compiler.reportWarning(node, warning); + return new ErroneousElementX(kind, arguments, name, enclosingElement); + } + + Element visitIdentifier(Identifier node) { + if (node.isThis()) { + if (!inInstanceContext) { + error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node}); + } + return null; + } else if (node.isSuper()) { + if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC); + if ((ElementCategory.SUPER & allowedCategory) == 0) { + error(node, MessageKind.INVALID_USE_OF_SUPER); + } + return null; + } else { + Element element = lookup(node, node.source); + if (element == null) { + if (!inInstanceContext) { + element = warnAndCreateErroneousElement(node, node.source, + MessageKind.CANNOT_RESOLVE, + {'name': node}); + } + } else if (element.isErroneous()) { + // Use the erroneous element. + } else { + if ((element.kind.category & allowedCategory) == 0) { + // TODO(ahe): Improve error message. Need UX input. + error(node, MessageKind.GENERIC, + {'text': "is not an expression $element"}); + } + } + if (!Elements.isUnresolved(element) + && element.kind == ElementKind.CLASS) { + ClassElement classElement = element; + classElement.ensureResolved(compiler); + } + return useElement(node, element); + } + } + + Element visitTypeAnnotation(TypeAnnotation node) { + DartType type = resolveTypeAnnotation(node); + if (type != null) { + if (inCheckContext) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + return type.element; + } + return null; + } + + Element defineElement(Node node, Element element, + {bool doAddToScope: true}) { + compiler.ensure(element != null); + mapping[node] = element; + if (doAddToScope) { + Element existing = scope.add(element); + if (existing != element) { + error(node, MessageKind.DUPLICATE_DEFINITION, {'name': node}); + } + } + return element; + } + + Element useElement(Node node, Element element) { + if (element == null) return null; + return mapping[node] = element; + } + + DartType useType(TypeAnnotation annotation, DartType type) { + if (type != null) { + mapping.setType(annotation, type); + useElement(annotation, type.element); + } + return type; + } + + bool isNamedConstructor(Send node) => node.receiver != null; + + Selector getRedirectingThisOrSuperConstructorSelector(Send node) { + if (isNamedConstructor(node)) { + SourceString constructorName = node.selector.asIdentifier().source; + return new Selector.callConstructor( + constructorName, + enclosingElement.getLibrary()); + } else { + return new Selector.callDefaultConstructor( + enclosingElement.getLibrary()); + } + } + + FunctionElement resolveConstructorRedirection(FunctionElement constructor) { + FunctionExpression node = constructor.parseNode(compiler); + + // A synthetic constructor does not have a node. + if (node == null) return null; + if (node.initializers == null) return null; + Link<Node> initializers = node.initializers.nodes; + if (!initializers.isEmpty && + Initializers.isConstructorRedirect(initializers.head)) { + Selector selector = + getRedirectingThisOrSuperConstructorSelector(initializers.head); + final ClassElement classElement = constructor.getEnclosingClass(); + return classElement.lookupConstructor(selector); + } + return null; + } + + void setupFunction(FunctionExpression node, FunctionElement function) { + scope = new MethodScope(scope, function); + + // Put the parameters in scope. + FunctionSignature functionParameters = + function.computeSignature(compiler); + Link<Node> parameterNodes = (node.parameters == null) + ? const Link<Node>() : node.parameters.nodes; + functionParameters.forEachParameter((Element element) { + if (element == functionParameters.optionalParameters.head) { + NodeList nodes = parameterNodes.head; + parameterNodes = nodes.nodes; + } + VariableDefinitions variableDefinitions = parameterNodes.head; + Node parameterNode = variableDefinitions.definitions.nodes.head; + initializerDo(parameterNode, (n) => n.accept(this)); + // Field parameters (this.x) are not visible inside the constructor. The + // fields they reference are visible, but must be resolved independently. + if (element.kind == ElementKind.FIELD_PARAMETER) { + useElement(parameterNode, element); + } else { + defineElement(variableDefinitions.definitions.nodes.head, element); + } + parameterNodes = parameterNodes.tail; + }); + } + + visitCascade(Cascade node) { + visit(node.expression); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + } + + Element visitClassNode(ClassNode node) { + cancel(node, "shouldn't be called"); + } + + visitIn(Node node, Scope nestedScope) { + Scope oldScope = scope; + scope = nestedScope; + Element element = visit(node); + scope = oldScope; + return element; + } + + /** + * Introduces new default targets for break and continue + * before visiting the body of the loop + */ + visitLoopBodyIn(Node loop, Node body, Scope bodyScope) { + TargetElement element = getOrCreateTargetElement(loop); + statementScope.enterLoop(element); + visitIn(body, bodyScope); + statementScope.exitLoop(); + if (!element.isTarget) { + mapping.remove(loop); + } + } + + visitBlock(Block node) { + visitIn(node.statements, new BlockScope(scope)); + } + + visitDoWhile(DoWhile node) { + visitLoopBodyIn(node, node.body, new BlockScope(scope)); + visit(node.condition); + } + + visitEmptyStatement(EmptyStatement node) { } + + visitExpressionStatement(ExpressionStatement node) { + ExpressionStatement oldExpressionStatement = currentExpressionStatement; + currentExpressionStatement = node; + visit(node.expression); + currentExpressionStatement = oldExpressionStatement; + } + + visitFor(For node) { + Scope blockScope = new BlockScope(scope); + visitIn(node.initializer, blockScope); + visitIn(node.condition, blockScope); + visitIn(node.update, blockScope); + visitLoopBodyIn(node, node.body, blockScope); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + assert(node.function.name != null); + visit(node.function); + FunctionElement functionElement = mapping[node.function]; + // TODO(floitsch): this might lead to two errors complaining about + // shadowing. + defineElement(node, functionElement); + } + + visitFunctionExpression(FunctionExpression node) { + visit(node.returnType); + SourceString name; + if (node.name == null) { + name = const SourceString(""); + } else { + name = node.name.asIdentifier().source; + } + + FunctionElement function = new FunctionElementX.node( + name, node, ElementKind.FUNCTION, Modifiers.EMPTY, + enclosingElement); + Scope oldScope = scope; // The scope is modified by [setupFunction]. + setupFunction(node, function); + defineElement(node, function, doAddToScope: node.name != null); + + Element previousEnclosingElement = enclosingElement; + enclosingElement = function; + // Run the body in a fresh statement scope. + StatementScope oldStatementScope = statementScope; + statementScope = new StatementScope(); + visit(node.body); + statementScope = oldStatementScope; + + scope = oldScope; + enclosingElement = previousEnclosingElement; + + world.registerInstantiatedClass(compiler.functionClass); + } + + visitIf(If node) { + visit(node.condition); + visit(node.thenPart); + visit(node.elsePart); + } + + static bool isLogicalOperator(Identifier op) { + String str = op.source.stringValue; + return (identical(str, '&&') || str == '||' || str == '!'); + } + + Element resolveSend(Send node) { + Selector selector = resolveSelector(node); + if (node.isSuperCall) mapping.superUses.add(node); + + if (node.receiver == null) { + // If this send is of the form "assert(expr);", then + // this is an assertion. + if (selector.isAssert()) { + if (selector.argumentCount != 1) { + error(node.selector, + MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT, + {'argumentCount': selector.argumentCount}); + } else if (selector.namedArgumentCount != 0) { + error(node.selector, + MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS, + {'argumentCount': selector.namedArgumentCount}); + } + return compiler.assertMethod; + } + + return node.selector.accept(this); + } + + var oldCategory = allowedCategory; + allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER; + Element resolvedReceiver = visit(node.receiver); + allowedCategory = oldCategory; + + Element target; + SourceString name = node.selector.asIdentifier().source; + if (identical(name.stringValue, 'this')) { + error(node.selector, MessageKind.GENERIC, + {'text': "expected an identifier"}); + } else if (node.isSuperCall) { + if (node.isOperator) { + if (isUserDefinableOperator(name.stringValue)) { + name = selector.name; + } else { + error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name}); + } + } + if (!inInstanceContext) { + error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); + return null; + } + if (currentClass.supertype == null) { + // This is just to guard against internal errors, so no need + // for a real error message. + error(node.receiver, MessageKind.GENERIC, + {'text': "Object has no superclass"}); + } + // TODO(johnniwinther): Ensure correct behavior if currentClass is a + // patch. + target = currentClass.lookupSuperMember(name); + // [target] may be null which means invoking noSuchMethod on + // super. + } else if (Elements.isUnresolved(resolvedReceiver)) { + return null; + } else if (identical(resolvedReceiver.kind, ElementKind.CLASS)) { + ClassElement receiverClass = resolvedReceiver; + receiverClass.ensureResolved(compiler); + if (node.isOperator) { + // When the resolved receiver is a class, we can have two cases: + // 1) a static send: C.foo, or + // 2) an operator send, where the receiver is a class literal: 'C + 1'. + // The following code that looks up the selector on the resolved + // receiver will treat the second as the invocation of a static operator + // if the resolved receiver is not null. + return null; + } + target = receiverClass.lookupLocalMember(name); + if (target == null) { + // TODO(johnniwinther): With the simplified [TreeElements] invariant, + // try to resolve injected elements if [currentClass] is in the patch + // library of [receiverClass]. + + // TODO(karlklose): this should be reported by the caller of + // [resolveSend] to select better warning messages for getters and + // setters. + return warnAndCreateErroneousElement(node, name, + MessageKind.METHOD_NOT_FOUND, + {'className': receiverClass.name, + 'methodName': name}); + } else if (target.isInstanceMember()) { + error(node, MessageKind.MEMBER_NOT_STATIC, + {'className': receiverClass.name, + 'memberName': name}); + } + } else if (identical(resolvedReceiver.kind, ElementKind.PREFIX)) { + PrefixElement prefix = resolvedReceiver; + target = prefix.lookupLocalMember(name); + if (Elements.isUnresolved(target)) { + return warnAndCreateErroneousElement( + node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER, + {'libraryName': prefix.name, 'memberName': name}); + } else if (target.kind == ElementKind.CLASS) { + ClassElement classElement = target; + classElement.ensureResolved(compiler); + } + } + return target; + } + + DartType resolveTypeTest(Node argument) { + TypeAnnotation node = argument.asTypeAnnotation(); + if (node == null) { + // node is of the form !Type. + node = argument.asSend().receiver.asTypeAnnotation(); + if (node == null) compiler.cancel("malformed send"); + } + return resolveTypeRequired(node); + } + + static Selector computeSendSelector(Send node, LibraryElement library) { + // First determine if this is part of an assignment. + bool isSet = node.asSendSet() != null; + + if (node.isIndex) { + return isSet ? new Selector.indexSet() : new Selector.index(); + } + + if (node.isOperator) { + SourceString source = node.selector.asOperator().source; + String string = source.stringValue; + if (identical(string, '!') || + identical(string, '&&') || identical(string, '||') || + identical(string, 'is') || identical(string, 'as') || + identical(string, '===') || identical(string, '!==') || + identical(string, '?') || + identical(string, '>>>')) { + return null; + } + if (!isUserDefinableOperator(source.stringValue)) { + source = Elements.mapToUserOperator(source); + } + return node.arguments.isEmpty + ? new Selector.unaryOperator(source) + : new Selector.binaryOperator(source); + } + + Identifier identifier = node.selector.asIdentifier(); + if (node.isPropertyAccess) { + assert(!isSet); + return new Selector.getter(identifier.source, library); + } else if (isSet) { + return new Selector.setter(identifier.source, library); + } + + // Compute the arity and the list of named arguments. + int arity = 0; + List<SourceString> named = <SourceString>[]; + for (Link<Node> link = node.argumentsNode.nodes; + !link.isEmpty; + link = link.tail) { + Expression argument = link.head; + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + named.add(namedArgument.name.source); + } + arity++; + } + + // If we're invoking a closure, we do not have an identifier. + return (identifier == null) + ? new Selector.callClosure(arity, named) + : new Selector.call(identifier.source, library, arity, named); + } + + Selector resolveSelector(Send node) { + LibraryElement library = enclosingElement.getLibrary(); + Selector selector = computeSendSelector(node, library); + if (selector != null) mapping.setSelector(node, selector); + return selector; + } + + void resolveArguments(NodeList list) { + if (list == null) return; + List<SourceString> seenNamedArguments = <SourceString>[]; + for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) { + Expression argument = link.head; + visit(argument); + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + SourceString source = namedArgument.name.source; + if (seenNamedArguments.contains(source)) { + error(argument, MessageKind.DUPLICATE_DEFINITION, + {'name': source}); + } + seenNamedArguments.add(source); + } else if (!seenNamedArguments.isEmpty) { + error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); + } + } + } + + visitSend(Send node) { + Element target = resolveSend(node); + if (!Elements.isUnresolved(target) + && target.kind == ElementKind.ABSTRACT_FIELD) { + AbstractFieldElement field = target; + target = field.getter; + if (target == null && !inInstanceContext) { + target = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_GETTER); + } + } + + bool resolvedArguments = false; + if (node.isOperator) { + String operatorString = node.selector.asOperator().source.stringValue; + if (identical(operatorString, 'is') || identical(operatorString, 'as')) { + assert(node.arguments.tail.isEmpty); + DartType type = resolveTypeTest(node.arguments.head); + if (type != null) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + resolvedArguments = true; + } else if (identical(operatorString, '?')) { + Element parameter = mapping[node.receiver]; + if (parameter == null + || !identical(parameter.kind, ElementKind.PARAMETER)) { + error(node.receiver, MessageKind.PARAMETER_NAME_EXPECTED); + } else { + mapping.checkedParameters.add(parameter); + } + } + } + + if (!resolvedArguments) { + resolveArguments(node.argumentsNode); + } + + // If the selector is null, it means that we will not be generating + // code for this as a send. + Selector selector = mapping.getSelector(node); + if (selector == null) return; + + if (node.isCall) { + if (Elements.isUnresolved(target) || + target.isGetter() || + Elements.isClosureSend(node, target)) { + // If we don't know what we're calling or if we are calling a getter, + // we need to register that fact that we may be calling a closure + // with the same arguments. + Selector call = new Selector.callClosureFrom(selector); + world.registerDynamicInvocation(call.name, call); + } else if (target.impliesType()) { + // We call 'call()' on a Type instance returned from the reference to a + // class or typedef literal. We do not need to register this call as a + // dynamic invocation, because we statically know what the target is. + } else if (!selector.applies(target, compiler)) { + warnArgumentMismatch(node, target); + } + + if (target != null && + target.isForeign(compiler) && + selector.name == const SourceString('JS')) { + world.registerJsCall(node, this); + } + } + + // TODO(ngeoffray): Warn if target is null and the send is + // unqualified. + useElement(node, target); + registerSend(selector, target); + if (node.isPropertyAccess) { + // It might be the closurization of a method. + world.registerInstantiatedClass(compiler.functionClass); + } + return node.isPropertyAccess ? target : null; + } + + void warnArgumentMismatch(Send node, Element target) { + // TODO(karlklose): we can be more precise about the reason of the + // mismatch. + warning(node.argumentsNode, MessageKind.INVALID_ARGUMENTS, + {'methodName': target.name}); + } + + /// Callback for native enqueuer to parse a type. Returns [:null:] on error. + DartType resolveTypeFromString(String typeName) { + Element element = scope.lookup(new SourceString(typeName)); + if (element == null) return null; + if (element is! ClassElement) return null; + element.ensureResolved(compiler); + return element.computeType(compiler); + } + + visitSendSet(SendSet node) { + Element target = resolveSend(node); + Element setter = target; + Element getter = target; + SourceString operatorName = node.assignmentOperator.source; + String source = operatorName.stringValue; + bool isComplex = !identical(source, '='); + if (!Elements.isUnresolved(target) + && target.kind == ElementKind.ABSTRACT_FIELD) { + AbstractFieldElement field = target; + setter = field.setter; + getter = field.getter; + if (setter == null && !inInstanceContext) { + setter = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_SETTER); + } + if (isComplex && getter == null && !inInstanceContext) { + getter = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_GETTER); + } + } + + visit(node.argumentsNode); + + // TODO(ngeoffray): Check if the target can be assigned. + // TODO(ngeoffray): Warn if target is null and the send is + // unqualified. + + Selector selector = mapping.getSelector(node); + if (isComplex) { + if (selector.isSetter()) { + // TODO(kasperl): We're registering the getter selector for + // compound assignments on the AST selector node. In the code + // generator, we then fetch it from there when generating the + // getter for a SendSet node. + Selector getterSelector = new Selector.getterFrom(selector); + registerSend(getterSelector, getter); + mapping.setSelector(node.selector, getterSelector); + useElement(node.selector, getter); + } else { + // TODO(kasperl): If [getter] is resolved, it will actually + // refer to the []= operator which isn't the one we want to + // register here. We should consider using some notion of + // abstract indexable element that we can resolve to so we can + // distinguish the two. + assert(selector.isIndexSet()); + registerSend(new Selector.index(), null); + } + + // Make sure we include the + and - operators if we are using + // the ++ and -- ones. Also, if op= form is used, include op itself. + void registerBinaryOperator(SourceString name) { + Selector binop = new Selector.binaryOperator(name); + world.registerDynamicInvocation(binop.name, binop); + } + if (identical(source, '++')) registerBinaryOperator(const SourceString('+')); + if (identical(source, '--')) registerBinaryOperator(const SourceString('-')); + if (source.endsWith('=')) { + registerBinaryOperator(Elements.mapToUserOperator(operatorName)); + } + } + + registerSend(selector, setter); + return useElement(node, setter); + } + + void registerSend(Selector selector, Element target) { + if (target == null || target.isInstanceMember()) { + if (selector.isGetter()) { + world.registerDynamicGetter(selector.name, selector); + } else if (selector.isSetter()) { + world.registerDynamicSetter(selector.name, selector); + } else { + world.registerDynamicInvocation(selector.name, selector); + } + } else if (Elements.isStaticOrTopLevel(target)) { + // TODO(kasperl): It seems like we're not supposed to register + // the use of classes. Wouldn't it be simpler if we just did? + if (!target.isClass()) { + // [target] might be the implementation element and only declaration + // elements may be registered. + world.registerStaticUse(target.declaration); + } + } + } + + visitLiteralInt(LiteralInt node) { + world.registerInstantiatedClass(compiler.intClass); + } + + visitLiteralDouble(LiteralDouble node) { + world.registerInstantiatedClass(compiler.doubleClass); + } + + visitLiteralBool(LiteralBool node) { + world.registerInstantiatedClass(compiler.boolClass); + } + + visitLiteralString(LiteralString node) { + world.registerInstantiatedClass(compiler.stringClass); + } + + visitLiteralNull(LiteralNull node) { + world.registerInstantiatedClass(compiler.nullClass); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + world.registerInstantiatedClass(compiler.stringClass); + node.visitChildren(this); + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + visit(link.head); + } + } + + visitOperator(Operator node) { + unimplemented(node, 'operator'); + } + + visitReturn(Return node) { + if (node.isRedirectingFactoryBody) { + handleRedirectingFactoryBody(node); + } else { + visit(node.expression); + } + } + + void handleRedirectingFactoryBody(Return node) { + if (!enclosingElement.isFactoryConstructor()) { + compiler.reportErrorCode( + node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY); + compiler.reportErrorCode( + enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD); + } + Element redirectionTarget = resolveRedirectingFactory(node); + var type = mapping.getType(node.expression); + if (type is InterfaceType && !type.isRaw) { + unimplemented(node.expression, 'type arguments on redirecting factory'); + } + useElement(node.expression, redirectionTarget); + FunctionElement constructor = enclosingElement; + if (constructor.modifiers.isConst() && + !redirectionTarget.modifiers.isConst()) { + error(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST); + } + constructor.defaultImplementation = redirectionTarget; + if (Elements.isUnresolved(redirectionTarget)) return; + + // TODO(ahe): Check that this doesn't lead to a cycle. For now, + // just make sure that the redirection target isn't itself a + // redirecting factory. + { // This entire block is temporary code per the above TODO. + FunctionElement targetImplementation = redirectionTarget.implementation; + FunctionExpression function = targetImplementation.parseNode(compiler); + if (function.body != null && function.body.asReturn() != null + && function.body.asReturn().isRedirectingFactoryBody) { + unimplemented(node.expression, 'redirecing to redirecting factory'); + } + } + world.registerStaticUse(redirectionTarget); + world.registerInstantiatedClass( + redirectionTarget.enclosingElement.declaration); + } + + visitThrow(Throw node) { + if (!inCatchBlock && node.expression == null) { + error(node, MessageKind.THROW_WITHOUT_EXPRESSION); + } + visit(node.expression); + } + + visitVariableDefinitions(VariableDefinitions node) { + VariableDefinitionsVisitor visitor = + new VariableDefinitionsVisitor(compiler, node, this, + ElementKind.VARIABLE); + // Ensure that we set the type of the [VariableListElement] since it depends + // on the current scope. If the current scope is a [MethodScope] or + // [BlockScope] it will not be available for the + // [VariableListElement.computeType] method. + if (node.type != null) { + visitor.variables.type = resolveTypeAnnotation(node.type); + } else { + visitor.variables.type = compiler.types.dynamicType; + } + visitor.visit(node.definitions); + } + + visitWhile(While node) { + visit(node.condition); + visitLoopBodyIn(node, node.body, new BlockScope(scope)); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + visit(node.expression); + } + + visitNewExpression(NewExpression node) { + Node selector = node.send.selector; + FunctionElement constructor = resolveConstructor(node); + resolveSelector(node.send); + resolveArguments(node.send.argumentsNode); + useElement(node.send, constructor); + if (Elements.isUnresolved(constructor)) return constructor; + // TODO(karlklose): handle optional arguments. + if (node.send.argumentCount() != constructor.parameterCount(compiler)) { + // TODO(ngeoffray): resolution error with wrong number of + // parameters. We cannot do this rigth now because of the + // List constructor. + } + // [constructor] might be the implementation element and only declaration + // elements may be registered. + world.registerStaticUse(constructor.declaration); + compiler.withCurrentElement(constructor, () { + FunctionExpression tree = constructor.parseNode(compiler); + compiler.resolver.resolveConstructorImplementation(constructor, tree); + }); + // [constructor.defaultImplementation] might be the implementation element + // and only declaration elements may be registered. + world.registerStaticUse(constructor.defaultImplementation.declaration); + ClassElement cls = constructor.defaultImplementation.getEnclosingClass(); + // [cls] might be the implementation element and only declaration elements + // may be registered. + world.registerInstantiatedClass(cls.declaration); + // [cls] might be the declaration element and we want to include injected + // members. + cls.implementation.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + world.addToWorkList(member); + }, + includeBackendMembers: false, + includeSuperMembers: true); + return null; + } + + /** + * Try to resolve the constructor that is referred to by [node]. + * Note: this function may return an ErroneousFunctionElement instead of + * [null], if there is no corresponding constructor, class or library. + */ + FunctionElement resolveConstructor(NewExpression node) { + return node.accept(new ConstructorResolver(compiler, this)); + } + + FunctionElement resolveRedirectingFactory(Return node) { + return node.accept(new ConstructorResolver(compiler, this)); + } + + DartType resolveTypeRequired(TypeAnnotation node) { + bool old = typeRequired; + typeRequired = true; + DartType result = resolveTypeAnnotation(node); + typeRequired = old; + return result; + } + + void analyzeTypeArgument(DartType annotation, DartType argument) { + if (argument == null) return; + if (argument.element.isTypeVariable()) { + // Register a dependency between the class where the type + // variable is, and the annotation. If the annotation requires + // runtime type information, then the class of the type variable + // does too. + compiler.world.registerRtiDependency( + annotation.element, + argument.element.enclosingElement); + } else if (argument is InterfaceType) { + InterfaceType type = argument; + type.typeArguments.forEach((DartType argument) { + analyzeTypeArgument(type, argument); + }); + } + } + + DartType resolveTypeAnnotation(TypeAnnotation node) { + Function report = typeRequired ? error : warning; + DartType type = typeResolver.resolveTypeAnnotation( + node, scope, enclosingElement, + onFailure: report, whenResolved: useType); + if (type == null) return null; + if (inCheckContext) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + if (typeRequired || inCheckContext) { + if (type is InterfaceType) { + InterfaceType itf = type; + itf.typeArguments.forEach((DartType argument) { + analyzeTypeArgument(type, argument); + }); + } + // TODO(ngeoffray): Also handle cases like: + // 1) a is T + // 2) T a (in checked mode). + } + return type; + } + + visitModifiers(Modifiers node) { + // TODO(ngeoffray): Implement this. + unimplemented(node, 'modifiers'); + } + + visitLiteralList(LiteralList node) { + world.registerInstantiatedClass(compiler.listClass); + NodeList arguments = node.typeArguments; + if (arguments != null) { + Link<Node> nodes = arguments.nodes; + if (nodes.isEmpty) { + error(arguments, MessageKind.MISSING_TYPE_ARGUMENT); + } else { + resolveTypeRequired(nodes.head); + for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { + error(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); + resolveTypeRequired(nodes.head); + } + } + } + visit(node.elements); + } + + visitConditional(Conditional node) { + node.visitChildren(this); + } + + visitStringInterpolation(StringInterpolation node) { + world.registerInstantiatedClass(compiler.stringClass); + node.visitChildren(this); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + registerImplicitInvocation(const SourceString('toString'), 0); + node.visitChildren(this); + } + + visitBreakStatement(BreakStatement node) { + TargetElement target; + if (node.target == null) { + target = statementScope.currentBreakTarget(); + if (target == null) { + error(node, MessageKind.NO_BREAK_TARGET); + return; + } + target.isBreakTarget = true; + } else { + String labelName = node.target.source.slowToString(); + LabelElement label = statementScope.lookupLabel(labelName); + if (label == null) { + error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); + return; + } + target = label.target; + if (!target.statement.isValidBreakTarget()) { + error(node.target, MessageKind.INVALID_BREAK); + return; + } + label.setBreakTarget(); + mapping[node.target] = label; + } + if (mapping[node] != null) { + // TODO(ahe): I'm not sure why this node already has an element + // that is different from target. I will talk to Lasse and + // figure out what is going on. + mapping.remove(node); + } + mapping[node] = target; + } + + visitContinueStatement(ContinueStatement node) { + TargetElement target; + if (node.target == null) { + target = statementScope.currentContinueTarget(); + if (target == null) { + error(node, MessageKind.NO_CONTINUE_TARGET); + return; + } + target.isContinueTarget = true; + } else { + String labelName = node.target.source.slowToString(); + LabelElement label = statementScope.lookupLabel(labelName); + if (label == null) { + error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); + return; + } + target = label.target; + if (!target.statement.isValidContinueTarget()) { + error(node.target, MessageKind.INVALID_CONTINUE); + } + // TODO(lrn): Handle continues to switch cases. + if (target.statement is SwitchCase) { + unimplemented(node, "continue to switch case"); + } + label.setContinueTarget(); + mapping[node.target] = label; + } + mapping[node] = target; + } + + registerImplicitInvocation(SourceString name, int arity) { + Selector selector = new Selector.call(name, null, arity); + world.registerDynamicInvocation(name, selector); + } + + registerImplicitFieldGet(SourceString name) { + Selector selector = new Selector.getter(name, null); + world.registerDynamicGetter(name, selector); + } + + visitForIn(ForIn node) { + for (final name in const [ + const SourceString('iterator'), + const SourceString('current')]) { + registerImplicitFieldGet(name); + } + registerImplicitInvocation(const SourceString('moveNext'), 0); + visit(node.expression); + Scope blockScope = new BlockScope(scope); + Node declaration = node.declaredIdentifier; + visitIn(declaration, blockScope); + visitLoopBodyIn(node, node.body, blockScope); + + // TODO(lrn): Also allow a single identifier. + if ((declaration is !Send || declaration.asSend().selector is !Identifier + || declaration.asSend().receiver != null) + && (declaration is !VariableDefinitions || + !declaration.asVariableDefinitions().definitions.nodes.tail.isEmpty)) + { + // The variable declaration is either not an identifier, not a + // declaration, or it's declaring more than one variable. + error(node.declaredIdentifier, MessageKind.INVALID_FOR_IN); + } + } + + visitLabel(Label node) { + // Labels are handled by their containing statements/cases. + } + + visitLabeledStatement(LabeledStatement node) { + Statement body = node.statement; + TargetElement targetElement = getOrCreateTargetElement(body); + Map<String, LabelElement> labelElements = <String, LabelElement>{}; + for (Label label in node.labels) { + String labelName = label.slowToString(); + if (labelElements.containsKey(labelName)) continue; + LabelElement element = targetElement.addLabel(label, labelName); + labelElements[labelName] = element; + } + statementScope.enterLabelScope(labelElements); + visit(node.statement); + statementScope.exitLabelScope(); + labelElements.forEach((String labelName, LabelElement element) { + if (element.isTarget) { + mapping[element.label] = element; + } else { + warning(element.label, MessageKind.UNUSED_LABEL, + {'labelName': labelName}); + } + }); + if (!targetElement.isTarget && identical(mapping[body], targetElement)) { + // If the body is itself a break or continue for another target, it + // might have updated its mapping to the target it actually does target. + mapping.remove(body); + } + } + + visitLiteralMap(LiteralMap node) { + world.registerInstantiatedClass(compiler.mapClass); + node.visitChildren(this); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + node.visitChildren(this); + } + + visitNamedArgument(NamedArgument node) { + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + node.expression.accept(this); + + TargetElement breakElement = getOrCreateTargetElement(node); + Map<String, LabelElement> continueLabels = <String, LabelElement>{}; + Link<Node> cases = node.cases.nodes; + while (!cases.isEmpty) { + SwitchCase switchCase = cases.head; + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is! Label) continue; + Label label = labelOrCase; + String labelName = label.slowToString(); + + LabelElement existingElement = continueLabels[labelName]; + if (existingElement != null) { + // It's an error if the same label occurs twice in the same switch. + warning(label, MessageKind.DUPLICATE_LABEL, {'labelName': labelName}); + error(existingElement.label, MessageKind.EXISTING_LABEL, + {'labelName': labelName}); + } else { + // It's only a warning if it shadows another label. + existingElement = statementScope.lookupLabel(labelName); + if (existingElement != null) { + warning(label, MessageKind.DUPLICATE_LABEL, + {'labelName': labelName}); + warning(existingElement.label, + MessageKind.EXISTING_LABEL, {'labelName': labelName}); + } + } + + TargetElement targetElement = + new TargetElementX(switchCase, + statementScope.nestingLevel, + enclosingElement); + if (mapping[switchCase] != null) { + // TODO(ahe): Talk to Lasse about this. + mapping.remove(switchCase); + } + mapping[switchCase] = targetElement; + + LabelElement labelElement = + new LabelElementX(label, labelName, + targetElement, enclosingElement); + mapping[label] = labelElement; + continueLabels[labelName] = labelElement; + } + cases = cases.tail; + // Test that only the last case, if any, is a default case. + if (switchCase.defaultKeyword != null && !cases.isEmpty) { + error(switchCase, MessageKind.INVALID_CASE_DEFAULT); + } + } + + statementScope.enterSwitch(breakElement, continueLabels); + node.cases.accept(this); + statementScope.exitSwitch(); + + // Clean-up unused labels. + continueLabels.forEach((String key, LabelElement label) { + if (!label.isContinueTarget) { + TargetElement targetElement = label.target; + SwitchCase switchCase = targetElement.statement; + mapping.remove(switchCase); + mapping.remove(label.label); + } + }); + } + + visitSwitchCase(SwitchCase node) { + node.labelsAndCases.accept(this); + visitIn(node.statements, new BlockScope(scope)); + } + + visitCaseMatch(CaseMatch node) { + visit(node.expression); + } + + visitTryStatement(TryStatement node) { + visit(node.tryBlock); + if (node.catchBlocks.isEmpty && node.finallyBlock == null) { + // TODO(ngeoffray): The precise location is + // node.getEndtoken.next. Adjust when issue #1581 is fixed. + error(node, MessageKind.NO_CATCH_NOR_FINALLY); + } + visit(node.catchBlocks); + visit(node.finallyBlock); + } + + visitCatchBlock(CatchBlock node) { + // Check that if catch part is present, then + // it has one or two formal parameters. + if (node.formals != null) { + if (node.formals.isEmpty) { + error(node, MessageKind.EMPTY_CATCH_DECLARATION); + } + if (!node.formals.nodes.tail.isEmpty && + !node.formals.nodes.tail.tail.isEmpty) { + for (Node extra in node.formals.nodes.tail.tail) { + error(extra, MessageKind.EXTRA_CATCH_DECLARATION); + } + } + + // Check that the formals aren't optional and that they have no + // modifiers or type. + for (Link<Node> link = node.formals.nodes; + !link.isEmpty; + link = link.tail) { + // If the formal parameter is a node list, it means that it is a + // sequence of optional parameters. + NodeList nodeList = link.head.asNodeList(); + if (nodeList != null) { + error(nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH); + } else { + VariableDefinitions declaration = link.head; + for (Node modifier in declaration.modifiers.nodes) { + error(modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH); + } + TypeAnnotation type = declaration.type; + if (type != null) { + error(type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH); + } + } + } + } + + Scope blockScope = new BlockScope(scope); + var wasTypeRequired = typeRequired; + typeRequired = true; + doInCheckContext(() => visitIn(node.type, blockScope)); + typeRequired = wasTypeRequired; + visitIn(node.formals, blockScope); + var oldInCatchBlock = inCatchBlock; + inCatchBlock = true; + visitIn(node.block, blockScope); + inCatchBlock = oldInCatchBlock; + } + + visitTypedef(Typedef node) { + unimplemented(node, 'typedef'); + } +} + +class TypeDefinitionVisitor extends CommonResolverVisitor<DartType> { + Scope scope; + TypeDeclarationElement element; + TypeResolver typeResolver; + + TypeDefinitionVisitor(Compiler compiler, TypeDeclarationElement element) + : this.element = element, + scope = Scope.buildEnclosingScope(element), + typeResolver = new TypeResolver(compiler), + super(compiler); + + void resolveTypeVariableBounds(NodeList node) { + if (node == null) return; + + var nameSet = new Set<SourceString>(); + // Resolve the bounds of type variables. + Link<DartType> typeLink = element.typeVariables; + Link<Node> nodeLink = node.nodes; + while (!nodeLink.isEmpty) { + TypeVariableType typeVariable = typeLink.head; + SourceString typeName = typeVariable.name; + TypeVariable typeNode = nodeLink.head; + if (nameSet.contains(typeName)) { + error(typeNode, MessageKind.DUPLICATE_TYPE_VARIABLE_NAME, + {'typeVariableName': typeName}); + } + nameSet.add(typeName); + + TypeVariableElement variableElement = typeVariable.element; + if (typeNode.bound != null) { + DartType boundType = typeResolver.resolveTypeAnnotation( + typeNode.bound, scope, element, onFailure: warning); + if (boundType != null && boundType.element == variableElement) { + // TODO(johnniwinther): Check for more general cycles, like + // [: <A extends B, B extends C, C extends B> :]. + warning(node, MessageKind.CYCLIC_TYPE_VARIABLE, + {'typeVariableName': variableElement.name}); + } else if (boundType != null) { + variableElement.bound = boundType; + } else { + // TODO(johnniwinther): Should be an erroneous type. + variableElement.bound = compiler.objectClass.computeType(compiler); + } + } else { + variableElement.bound = compiler.objectClass.computeType(compiler); + } + nodeLink = nodeLink.tail; + typeLink = typeLink.tail; + } + assert(typeLink.isEmpty); + } +} + +class TypedefResolverVisitor extends TypeDefinitionVisitor { + TypedefElement get element => super.element; + + TypedefResolverVisitor(Compiler compiler, TypedefElement typedefElement) + : super(compiler, typedefElement); + + visitTypedef(Typedef node) { + TypedefType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + resolveTypeVariableBounds(node.typeParameters); + + element.functionSignature = SignatureResolver.analyze( + compiler, node.formals, node.returnType, element); + + element.alias = compiler.computeFunctionType( + element, element.functionSignature); + + // TODO(johnniwinther): Check for cyclic references in the typedef alias. + } +} + +/** + * The implementation of [ResolverTask.resolveClass]. + * + * This visitor has to be extra careful as it is building the basic + * element information, and cannot safely look at other elements as + * this may lead to cycles. + * + * This visitor can assume that the supertypes have already been + * resolved, but it cannot call [ResolverTask.resolveClass] directly + * or indirectly (through [ClassElement.ensureResolved]) for any other + * types. + */ +class ClassResolverVisitor extends TypeDefinitionVisitor { + ClassElement get element => super.element; + + ClassResolverVisitor(Compiler compiler, ClassElement classElement) + : super(compiler, classElement); + + DartType visitClassNode(ClassNode node) { + compiler.ensure(element != null); + compiler.ensure(element.resolutionState == STATE_STARTED); + + InterfaceType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + // TODO(ahe): It is not safe to call resolveTypeVariableBounds yet. + // As a side-effect, this may get us back here trying to + // resolve this class again. + resolveTypeVariableBounds(node.typeParameters); + + // Setup the supertype for the element. + assert(element.supertype == null); + if (node.superclass != null) { + MixinApplication superMixin = node.superclass.asMixinApplication(); + if (superMixin != null) { + DartType supertype = resolveSupertype(element, superMixin.superclass); + Link<Node> link = superMixin.mixins.nodes; + while (!link.isEmpty) { + supertype = applyMixin(supertype, visit(link.head)); + link = link.tail; + } + element.supertype = supertype; + } else { + element.supertype = resolveSupertype(element, node.superclass); + } + } + + // If the super type isn't specified, we make it Object. + final objectElement = compiler.objectClass; + if (!identical(element, objectElement) && element.supertype == null) { + if (objectElement == null) { + compiler.internalError("Internal error: cannot resolve Object", + node: node); + } else { + objectElement.ensureResolved(compiler); + } + element.supertype = objectElement.computeType(compiler); + } + + assert(element.interfaces == null); + element.interfaces = resolveInterfaces(node.interfaces, node.superclass); + calculateAllSupertypes(element); + + if (node.defaultClause != null) { + element.defaultClass = visit(node.defaultClause); + } + element.addDefaultConstructorIfNeeded(compiler); + return element.computeType(compiler); + } + + DartType visitNamedMixinApplication(NamedMixinApplication node) { + compiler.ensure(element != null); + compiler.ensure(element.resolutionState == STATE_STARTED); + + InterfaceType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + resolveTypeVariableBounds(node.typeParameters); + + // Generate anonymous mixin application elements for the + // intermediate mixin applications (excluding the last). + DartType supertype = resolveSupertype(element, node.superclass); + Link<Node> link = node.mixins.nodes; + while (!link.tail.isEmpty) { + supertype = applyMixin(supertype, visit(link.head)); + link = link.tail; + } + doApplyMixinTo(element, supertype, visit(link.head)); + return element.computeType(compiler); + } + + DartType applyMixin(DartType supertype, DartType mixinType) { + String superName = supertype.name.slowToString(); + String mixinName = mixinType.name.slowToString(); + ClassElement mixinApplication = new MixinApplicationElementX( + new SourceString("${superName}_${mixinName}"), + element.getCompilationUnit(), + compiler.getNextFreeClassId(), + element.parseNode(compiler), + Modifiers.EMPTY); // TODO(kasperl): Should this be abstract? + doApplyMixinTo(mixinApplication, supertype, mixinType); + mixinApplication.resolutionState = STATE_DONE; + mixinApplication.supertypeLoadState = STATE_DONE; + return mixinApplication.computeType(compiler); + } + + void doApplyMixinTo(MixinApplicationElement mixinApplication, + DartType supertype, + DartType mixinType) { + assert(mixinApplication.supertype == null); + mixinApplication.supertype = supertype; + + // Named mixin application may have an 'implements' clause. + NamedMixinApplication namedMixinApplication = + mixinApplication.parseNode(compiler).asNamedMixinApplication(); + Link<DartType> interfaces = (namedMixinApplication != null) + ? resolveInterfaces(namedMixinApplication.interfaces, + namedMixinApplication.superclass) + : const Link<DartType>(); + + // The class that is the result of a mixin application implements + // the interface of the class that was mixed in so always prepend + // that to the interface list. + interfaces = interfaces.prepend(mixinType); + assert(mixinApplication.interfaces == null); + mixinApplication.interfaces = interfaces; + + assert(mixinApplication.mixin == null); + mixinApplication.mixin = resolveMixinFor(mixinApplication, mixinType); + mixinApplication.addDefaultConstructorIfNeeded(compiler); + calculateAllSupertypes(mixinApplication); + } + + ClassElement resolveMixinFor(MixinApplicationElement mixinApplication, + DartType mixinType) { + ClassElement mixin = mixinType.element; + mixin.ensureResolved(compiler); + + // Check for cycles in the mixin chain. + ClassElement previous = mixinApplication; // For better error messages. + ClassElement current = mixin; + while (current != null && current.isMixinApplication) { + MixinApplicationElement currentMixinApplication = current; + if (currentMixinApplication == mixinApplication) { + compiler.reportErrorCode( + mixinApplication, MessageKind.ILLEGAL_MIXIN_CYCLE, + {'mixinName1': current.name, 'mixinName2': previous.name}); + // We have found a cycle in the mixin chain. Return null as + // the mixin for this application to avoid getting into + // infinite recursion when traversing members. + return null; + } + previous = current; + current = currentMixinApplication.mixin; + } + compiler.world.registerMixinUse(mixinApplication, mixin); + return mixin; + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitTypeAnnotation(TypeAnnotation node) { + return visit(node.typeName); + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitIdentifier(Identifier node) { + Element element = scope.lookup(node.source); + if (element == null) { + error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node}); + return null; + } else if (!element.impliesType() && !element.isTypeVariable()) { + error(node, MessageKind.NOT_A_TYPE, {'node': node}); + return null; + } else { + if (element.isTypeVariable()) { + TypeVariableElement variableElement = element; + return variableElement.type; + } else if (element.isTypedef()) { + compiler.unimplemented('visitIdentifier for typedefs', node: node); + } else { + // TODO(ngeoffray): Use type variables. + return element.computeType(compiler); + } + } + return null; + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitSend(Send node) { + Identifier prefix = node.receiver.asIdentifier(); + if (prefix == null) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return null; + } + Element element = scope.lookup(prefix.source); + if (element == null || !identical(element.kind, ElementKind.PREFIX)) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return null; + } + PrefixElement prefixElement = element; + Identifier selector = node.selector.asIdentifier(); + var e = prefixElement.lookupLocalMember(selector.source); + if (e == null || !e.impliesType()) { + error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, + {'typeName': node.selector}); + return null; + } + return e.computeType(compiler); + } + + DartType resolveSupertype(ClassElement cls, TypeAnnotation superclass) { + DartType supertype = typeResolver.resolveTypeAnnotation( + superclass, scope, cls, onFailure: error); + if (supertype != null) { + if (identical(supertype.kind, TypeKind.MALFORMED_TYPE)) { + // Error has already been reported. + return null; + } else if (!identical(supertype.kind, TypeKind.INTERFACE)) { + // TODO(johnniwinther): Handle dynamic. + error(superclass.typeName, MessageKind.CLASS_NAME_EXPECTED); + return null; + } else if (isBlackListed(supertype)) { + error(superclass, MessageKind.CANNOT_EXTEND, {'type': supertype}); + return null; + } + } + return supertype; + } + + Link<DartType> resolveInterfaces(NodeList interfaces, Node superclass) { + Link<DartType> result = const Link<DartType>(); + if (interfaces == null) return result; + for (Link<Node> link = interfaces.nodes; !link.isEmpty; link = link.tail) { + DartType interfaceType = typeResolver.resolveTypeAnnotation( + link.head, scope, element, onFailure: error); + if (interfaceType != null) { + if (identical(interfaceType.kind, TypeKind.MALFORMED_TYPE)) { + // Error has already been reported. + } else if (!identical(interfaceType.kind, TypeKind.INTERFACE)) { + // TODO(johnniwinther): Handle dynamic. + TypeAnnotation typeAnnotation = link.head; + error(typeAnnotation.typeName, MessageKind.CLASS_NAME_EXPECTED); + } else { + if (interfaceType == element.supertype) { + compiler.reportErrorCode( + superclass, + MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS, + {'type': interfaceType}); + compiler.reportErrorCode( + link.head, + MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS, + {'type': interfaceType}); + } + if (result.contains(interfaceType)) { + compiler.reportErrorCode( + link.head, + MessageKind.DUPLICATE_IMPLEMENTS, + {'type': interfaceType}); + } + result = result.prepend(interfaceType); + if (isBlackListed(interfaceType)) { + error(link.head, MessageKind.CANNOT_IMPLEMENT, + {'type': interfaceType}); + } + } + } + } + return result; + } + + void calculateAllSupertypes(ClassElement cls) { + // TODO(karlklose): Check if type arguments match, if a class + // element occurs more than once in the supertypes. + if (cls.allSupertypes != null) return; + final DartType supertype = cls.supertype; + if (supertype != null) { + var allSupertypes = new LinkBuilder<DartType>(); + addAllSupertypes(allSupertypes, supertype); + for (Link<DartType> interfaces = cls.interfaces; + !interfaces.isEmpty; + interfaces = interfaces.tail) { + addAllSupertypes(allSupertypes, interfaces.head); + } + cls.allSupertypes = allSupertypes.toLink(); + } else { + assert(identical(cls, compiler.objectClass)); + cls.allSupertypes = const Link<DartType>(); + } + } + + /** + * Adds [type] and all supertypes of [type] to [builder] while substituting + * type variables. + */ + void addAllSupertypes(LinkBuilder<DartType> builder, InterfaceType type) { + builder.addLast(type); + Link<DartType> typeArguments = type.typeArguments; + ClassElement classElement = type.element; + Link<DartType> typeVariables = classElement.typeVariables; + Link<DartType> supertypes = classElement.allSupertypes; + assert(invariant(element, supertypes != null, + message: "Supertypes not computed on $classElement " + "during resolution of $element")); + while (!supertypes.isEmpty) { + DartType supertype = supertypes.head; + builder.addLast(supertype.subst(typeArguments, typeVariables)); + supertypes = supertypes.tail; + } + } + + isBlackListed(DartType type) { + LibraryElement lib = element.getLibrary(); + return + !identical(lib, compiler.coreLibrary) && + !identical(lib, compiler.jsHelperLibrary) && + !identical(lib, compiler.interceptorsLibrary) && + (identical(type.element, compiler.dynamicClass) || + identical(type.element, compiler.boolClass) || + identical(type.element, compiler.numClass) || + identical(type.element, compiler.intClass) || + identical(type.element, compiler.doubleClass) || + identical(type.element, compiler.stringClass) || + identical(type.element, compiler.nullClass) || + identical(type.element, compiler.functionClass)); + } +} + +class ClassSupertypeResolver extends CommonResolverVisitor { + Scope context; + ClassElement classElement; + + ClassSupertypeResolver(Compiler compiler, ClassElement cls) + : context = Scope.buildEnclosingScope(cls), + this.classElement = cls, + super(compiler); + + void loadSupertype(ClassElement element, Node from) { + compiler.resolver.loadSupertypes(element, from); + element.ensureResolved(compiler); + } + + void visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + link.head.accept(this); + } + } + + void visitClassNode(ClassNode node) { + if (node.superclass == null) { + if (!identical(classElement, compiler.objectClass)) { + loadSupertype(compiler.objectClass, node); + } + } else { + node.superclass.accept(this); + } + visitNodeList(node.interfaces); + } + + void visitMixinApplication(MixinApplication node) { + node.superclass.accept(this); + visitNodeList(node.mixins); + } + + void visitTypeAnnotation(TypeAnnotation node) { + node.typeName.accept(this); + } + + void visitIdentifier(Identifier node) { + Element element = context.lookup(node.source); + if (element == null) { + error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node}); + } else if (!element.impliesType()) { + error(node, MessageKind.NOT_A_TYPE, {'node': node}); + } else { + if (element.isClass()) { + loadSupertype(element, node); + } else { + compiler.reportErrorCode(node, MessageKind.CLASS_NAME_EXPECTED); + } + } + } + + void visitSend(Send node) { + Identifier prefix = node.receiver.asIdentifier(); + if (prefix == null) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return; + } + Element element = context.lookup(prefix.source); + if (element == null || !identical(element.kind, ElementKind.PREFIX)) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return; + } + PrefixElement prefixElement = element; + Identifier selector = node.selector.asIdentifier(); + var e = prefixElement.lookupLocalMember(selector.source); + if (e == null || !e.impliesType()) { + error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, + {'typeName': node.selector}); + return; + } + loadSupertype(e, node); + } +} + +class VariableDefinitionsVisitor extends CommonResolverVisitor<SourceString> { + VariableDefinitions definitions; + ResolverVisitor resolver; + ElementKind kind; + VariableListElement variables; + + VariableDefinitionsVisitor(Compiler compiler, + this.definitions, this.resolver, this.kind) + : super(compiler) { + variables = new VariableListElementX.node( + definitions, ElementKind.VARIABLE_LIST, resolver.enclosingElement); + } + + SourceString visitSendSet(SendSet node) { + assert(node.arguments.tail.isEmpty); // Sanity check + resolver.visit(node.arguments.head); + return visit(node.selector); + } + + SourceString visitIdentifier(Identifier node) { + // The variable is initialized to null. + resolver.world.registerInstantiatedClass(compiler.nullClass); + return node.source; + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + SourceString name = visit(link.head); + VariableElement element = + new VariableElementX(name, variables, kind, link.head); + resolver.defineElement(link.head, element); + } + } +} + +/** + * [SignatureResolver] resolves function signatures. + */ +class SignatureResolver extends CommonResolverVisitor<Element> { + final Element enclosingElement; + Link<Element> optionalParameters = const Link<Element>(); + int optionalParameterCount = 0; + bool optionalParametersAreNamed = false; + VariableDefinitions currentDefinitions; + + SignatureResolver(Compiler compiler, this.enclosingElement) : super(compiler); + + Element visitNodeList(NodeList node) { + // This must be a list of optional arguments. + String value = node.beginToken.stringValue; + if ((!identical(value, '[')) && (!identical(value, '{'))) { + internalError(node, "expected optional parameters"); + } + optionalParametersAreNamed = (identical(value, '{')); + LinkBuilder<Element> elements = analyzeNodes(node.nodes); + optionalParameterCount = elements.length; + optionalParameters = elements.toLink(); + return null; + } + + Element visitVariableDefinitions(VariableDefinitions node) { + Link<Node> definitions = node.definitions.nodes; + if (definitions.isEmpty) { + cancel(node, 'internal error: no parameter definition'); + return null; + } + if (!definitions.tail.isEmpty) { + cancel(definitions.tail.head, 'internal error: extra definition'); + return null; + } + Node definition = definitions.head; + if (definition is NodeList) { + cancel(node, 'optional parameters are not implemented'); + } + + if (currentDefinitions != null) { + cancel(node, 'function type parameters not supported'); + } + currentDefinitions = node; + Element element = definition.accept(this); + currentDefinitions = null; + return element; + } + + Element visitIdentifier(Identifier node) { + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + // Ensure a parameter is not typed 'void'. + variables.computeType(compiler); + return new VariableElementX(node.source, variables, + ElementKind.PARAMETER, node); + } + + SourceString getParameterName(Send node) { + var identifier = node.selector.asIdentifier(); + if (identifier != null) { + // Normal parameter: [:Type name:]. + return identifier.source; + } else { + // Function type parameter: [:void name(DartType arg):]. + var functionExpression = node.selector.asFunctionExpression(); + if (functionExpression != null && + functionExpression.name.asIdentifier() != null) { + return functionExpression.name.asIdentifier().source; + } else { + cancel(node, + 'internal error: unimplemented receiver on parameter send'); + } + } + } + + // The only valid [Send] can be in constructors and must be of the form + // [:this.x:] (where [:x:] represents an instance field). + FieldParameterElement visitSend(Send node) { + FieldParameterElement element; + if (node.receiver.asIdentifier() == null || + !node.receiver.asIdentifier().isThis()) { + error(node, MessageKind.INVALID_PARAMETER); + } else if (!identical(enclosingElement.kind, + ElementKind.GENERATIVE_CONSTRUCTOR)) { + error(node, MessageKind.FIELD_PARAMETER_NOT_ALLOWED); + } else { + SourceString name = getParameterName(node); + Element fieldElement = currentClass.lookupLocalMember(name); + if (fieldElement == null || + !identical(fieldElement.kind, ElementKind.FIELD)) { + error(node, MessageKind.NOT_A_FIELD, {'fieldName': name}); + } else if (!fieldElement.isInstanceMember()) { + error(node, MessageKind.NOT_INSTANCE_FIELD, {'fieldName': name}); + } + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + element = new FieldParameterElementX(name, fieldElement, variables, node); + } + return element; + } + + Element visitSendSet(SendSet node) { + Element element; + if (node.receiver != null) { + element = visitSend(node); + } else if (node.selector.asIdentifier() != null) { + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + element = new VariableElementX(node.selector.asIdentifier().source, + variables, ElementKind.PARAMETER, node); + } + // Visit the value. The compile time constant handler will + // make sure it's a compile time constant. + resolveExpression(node.arguments.head); + return element; + } + + Element visitFunctionExpression(FunctionExpression node) { + // This is a function typed parameter. + // TODO(ahe): Resolve the function type. + return visit(node.name); + } + + LinkBuilder<Element> analyzeNodes(Link<Node> link) { + LinkBuilder<Element> elements = new LinkBuilder<Element>(); + for (; !link.isEmpty; link = link.tail) { + Element element = link.head.accept(this); + if (element != null) { + elements.addLast(element); + } else { + // If parameter is null, the current node should be the last, + // and a list of optional named parameters. + if (!link.tail.isEmpty || (link.head is !NodeList)) { + internalError(link.head, "expected optional parameters"); + } + } + } + return elements; + } + + /** + * Resolves formal parameters and return type to a [FunctionSignature]. + */ + static FunctionSignature analyze(Compiler compiler, + NodeList formalParameters, + Node returnNode, + Element element) { + SignatureResolver visitor = new SignatureResolver(compiler, element); + Link<Element> parameters = const Link<Element>(); + int requiredParameterCount = 0; + if (formalParameters == null) { + if (!element.isGetter()) { + compiler.reportErrorCode(element, MessageKind.MISSING_FORMALS); + } + } else { + if (element.isGetter()) { + if (!identical(formalParameters.getEndToken().next.stringValue, + // TODO(ahe): Remove the check for native keyword. + 'native')) { + if (compiler.rejectDeprecatedFeatures && + // TODO(ahe): Remove isPlatformLibrary check. + !element.getLibrary().isPlatformLibrary) { + compiler.reportErrorCode(formalParameters, + MessageKind.EXTRA_FORMALS); + } else { + compiler.onDeprecatedFeature(formalParameters, 'getter parameters'); + } + } + } + LinkBuilder<Element> parametersBuilder = + visitor.analyzeNodes(formalParameters.nodes); + requiredParameterCount = parametersBuilder.length; + parameters = parametersBuilder.toLink(); + } + DartType returnType = compiler.resolveReturnType(element, returnNode); + if (element.isSetter() && (requiredParameterCount != 1 || + visitor.optionalParameterCount != 0)) { + // If there are no formal parameters, we already reported an error above. + if (formalParameters != null) { + compiler.reportErrorCode(formalParameters, + MessageKind.ILLEGAL_SETTER_FORMALS); + } + } + if (element.isGetter() && (requiredParameterCount != 0 + || visitor.optionalParameterCount != 0)) { + compiler.reportErrorCode(formalParameters, MessageKind.EXTRA_FORMALS); + } + return new FunctionSignatureX(parameters, + visitor.optionalParameters, + requiredParameterCount, + visitor.optionalParameterCount, + visitor.optionalParametersAreNamed, + returnType); + } + + // TODO(ahe): This is temporary. + void resolveExpression(Node node) { + if (node == null) return; + node.accept(new ResolverVisitor(compiler, enclosingElement, + new TreeElementMapping(enclosingElement))); + } + + // TODO(ahe): This is temporary. + ClassElement get currentClass { + return enclosingElement.isMember() + ? enclosingElement.getEnclosingClass() : null; + } +} + +class ConstructorResolver extends CommonResolverVisitor<Element> { + final ResolverVisitor resolver; + bool inConstContext = false; + DartType type; + + ConstructorResolver(Compiler compiler, this.resolver) : super(compiler); + + visitNode(Node node) { + throw 'not supported'; + } + + failOrReturnErroneousElement(Element enclosing, Node diagnosticNode, + SourceString targetName, MessageKind kind, + Map arguments) { + if (inConstContext) { + error(diagnosticNode, kind, arguments); + } else { + ResolutionWarning warning = new ResolutionWarning(kind, arguments); + compiler.reportWarning(diagnosticNode, warning); + return new ErroneousElementX(kind, arguments, targetName, enclosing); + } + } + + Selector createConstructorSelector(SourceString constructorName) { + return constructorName == const SourceString('') + ? new Selector.callDefaultConstructor( + resolver.enclosingElement.getLibrary()) + : new Selector.callConstructor( + constructorName, + resolver.enclosingElement.getLibrary()); + } + + // TODO(ngeoffray): method named lookup should not report errors. + FunctionElement lookupConstructor(ClassElement cls, + Node diagnosticNode, + SourceString constructorName) { + cls.ensureResolved(compiler); + Selector selector = createConstructorSelector(constructorName); + Element result = cls.lookupConstructor(selector); + if (result == null) { + String fullConstructorName = + resolver.compiler.resolver.constructorNameForDiagnostics( + cls.name, + constructorName); + return failOrReturnErroneousElement( + cls, + diagnosticNode, + new SourceString(fullConstructorName), + MessageKind.CANNOT_FIND_CONSTRUCTOR, + {'constructorName': fullConstructorName}); + } else if (inConstContext && !result.modifiers.isConst()) { + error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST); + } + return result; + } + + visitNewExpression(NewExpression node) { + inConstContext = node.isConst(); + Node selector = node.send.selector; + Element e = visit(selector); + return finishConstructorReference(e, node.send.selector, node); + } + + /// Finishes resolution of a constructor reference and records the + /// type of the constructed instance on [expression]. + FunctionElement finishConstructorReference(Element e, + Node diagnosticNode, + Node expression) { + // Find the unnamed constructor if the reference resolved to a + // class. + if (!Elements.isUnresolved(e) && e.isClass()) { + ClassElement cls = e; + cls.ensureResolved(compiler); + if (cls.isInterface() && (cls.defaultClass == null)) { + // TODO(ahe): Remove this check and error message when we + // don't have interfaces anymore. + error(diagnosticNode, + MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': cls.name}); + } + // The unnamed constructor may not exist, so [e] may become unresolved. + e = lookupConstructor(cls, diagnosticNode, const SourceString('')); + } + if (type == null) { + if (Elements.isUnresolved(e)) { + type = compiler.dynamicClass.computeType(compiler); + } else { + type = e.getEnclosingClass().computeType(compiler).asRaw(); + } + } + resolver.mapping.setType(expression, type); + return e; + } + + visitTypeAnnotation(TypeAnnotation node) { + assert(invariant(node, type == null)); + type = resolver.resolveTypeRequired(node); + return resolver.mapping[node]; + } + + visitSend(Send node) { + Element e = visit(node.receiver); + if (Elements.isUnresolved(e)) return e; + Identifier name = node.selector.asIdentifier(); + if (name == null) internalError(node.selector, 'unexpected node'); + + if (identical(e.kind, ElementKind.CLASS)) { + ClassElement cls = e; + cls.ensureResolved(compiler); + if (cls.isInterface() && (cls.defaultClass == null)) { + error(node.receiver, + MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': cls.name}); + } + return lookupConstructor(cls, name, name.source); + } else if (identical(e.kind, ElementKind.PREFIX)) { + PrefixElement prefix = e; + e = prefix.lookupLocalMember(name.source); + if (e == null) { + return failOrReturnErroneousElement(resolver.enclosingElement, name, + name.source, + MessageKind.CANNOT_RESOLVE, + {'name': name}); + } else if (!identical(e.kind, ElementKind.CLASS)) { + error(node, MessageKind.NOT_A_TYPE, {'node': name}); + } + } else { + internalError(node.receiver, 'unexpected element $e'); + } + return e; + } + + Element visitIdentifier(Identifier node) { + SourceString name = node.source; + Element e = resolver.lookup(node, name); + // TODO(johnniwinther): Change errors to warnings, cf. 11.11.1. + if (e == null) { + return failOrReturnErroneousElement(resolver.enclosingElement, node, name, + MessageKind.CANNOT_RESOLVE, + {'name': name}); + } else if (e.isErroneous()) { + return e; + } else if (identical(e.kind, ElementKind.TYPEDEF)) { + error(node, MessageKind.CANNOT_INSTANTIATE_TYPEDEF, + {'typedefName': name}); + } else if (identical(e.kind, ElementKind.TYPE_VARIABLE)) { + error(node, MessageKind.CANNOT_INSTANTIATE_TYPE_VARIABLE, + {'typeVariableName': name}); + } else if (!identical(e.kind, ElementKind.CLASS) + && !identical(e.kind, ElementKind.PREFIX)) { + error(node, MessageKind.NOT_A_TYPE, {'node': name}); + } + return e; + } + + /// Assumed to be called by [resolveRedirectingFactory]. + Element visitReturn(Return node) { + Node expression = node.expression; + return finishConstructorReference(visit(expression), + expression, expression); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart new file mode 100644 index 0000000..130486c --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart
@@ -0,0 +1,30 @@ +// 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. + +library resolution; + +import 'dart:collection' show Queue, LinkedHashMap; + +import '../dart2jslib.dart' hide Diagnostic; +import '../dart_types.dart'; +import '../../compiler.dart' show Diagnostic; +import '../tree/tree.dart'; +import '../elements/elements.dart'; +import '../elements/modelx.dart' + show FunctionElementX, + ErroneousElementX, + VariableElementX, + FieldParameterElementX, + VariableListElementX, + FunctionSignatureX, + LabelElementX, + TargetElementX, + MixinApplicationElementX; +import '../util/util.dart'; +import '../scanner/scannerlib.dart' show PartialMetadataAnnotation; + +import 'secret_tree_element.dart' show getTreeElement, setTreeElement; + +part 'members.dart'; +part 'scope.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart new file mode 100644 index 0000000..92e7840 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart
@@ -0,0 +1,163 @@ +// 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. + +part of resolution; + +abstract class Scope { + /** + * Adds [element] to this scope. This operation is only allowed on mutable + * scopes such as [MethodScope] and [BlockScope]. + */ + Element add(Element element); + + /** + * Looks up the [Element] for [name] in this scope. + */ + Element lookup(SourceString name); + + static Scope buildEnclosingScope(Element element) { + return element.enclosingElement != null + ? element.enclosingElement.buildScope() : element.buildScope(); + } +} + +abstract class NestedScope extends Scope { + final Scope parent; + + NestedScope(this.parent); + + Element lookup(SourceString name) { + Element result = localLookup(name); + if (result != null) return result; + return parent.lookup(name); + } + + Element localLookup(SourceString name); + + static Scope buildEnclosingScope(Element element) { + return element.enclosingElement != null + ? element.enclosingElement.buildScope() : element.buildScope(); + } +} + +/** + * [TypeDeclarationScope] defines the outer scope of a type declaration in + * which the declared type variables and the entities in the enclosing scope are + * available but where declared and inherited members are not available. This + * scope is only used for class/interface declarations during resolution of the + * class hierarchy. In all other cases [ClassScope] is used. + */ +class TypeDeclarationScope extends NestedScope { + final TypeDeclarationElement element; + + TypeDeclarationScope(parent, this.element) + : super(parent) { + assert(parent != null); + } + + Element add(Element newElement) { + throw "Cannot add element to TypeDeclarationScope"; + } + + Element lookupTypeVariable(SourceString name) { + Link<DartType> typeVariableLink = element.typeVariables; + while (!typeVariableLink.isEmpty) { + TypeVariableType typeVariable = typeVariableLink.head; + if (typeVariable.name == name) { + return typeVariable.element; + } + typeVariableLink = typeVariableLink.tail; + } + return null; + } + + Element localLookup(SourceString name) => lookupTypeVariable(name); + + String toString() => + 'TypeDeclarationScope($element)'; +} + +abstract class MutableScope extends NestedScope { + final Map<SourceString, Element> elements; + + MutableScope(Scope parent) + : super(parent), + this.elements = new Map<SourceString, Element>() { + assert(parent != null); + } + + Element add(Element newElement) { + if (elements.containsKey(newElement.name)) { + return elements[newElement.name]; + } + elements[newElement.name] = newElement; + return newElement; + } + + Element localLookup(SourceString name) => elements[name]; +} + +class MethodScope extends MutableScope { + final Element element; + + MethodScope(Scope parent, this.element) + : super(parent); + + String toString() => 'MethodScope($element${elements.keys.toList()})'; +} + +class BlockScope extends MutableScope { + BlockScope(Scope parent) : super(parent); + + String toString() => 'BlockScope(${elements.keys.toList()})'; +} + +/** + * [ClassScope] defines the inner scope of a class/interface declaration in + * which declared members, declared type variables, entities in the enclosing + * scope and inherited members are available, in the given order. + */ +class ClassScope extends TypeDeclarationScope { + ClassElement get element => super.element; + + ClassScope(Scope parentScope, ClassElement element) + : super(parentScope, element) { + assert(parent != null); + } + + Element localLookup(SourceString name) { + Element result = element.lookupLocalMember(name); + if (result != null) return result; + return super.localLookup(name); + } + + Element lookup(SourceString name) { + Element result = localLookup(name); + if (result != null) return result; + result = parent.lookup(name); + if (result != null) return result; + return element.lookupSuperMember(name); + } + + Element add(Element newElement) { + throw "Cannot add an element in a class scope"; + } + + String toString() => 'ClassScope($element)'; +} + +class LibraryScope implements Scope { + final LibraryElement library; + + LibraryScope(LibraryElement this.library); + + Element localLookup(SourceString name) => library.find(name); + Element lookup(SourceString name) => localLookup(name); + + Element add(Element newElement) { + throw "Cannot add an element to a library scope"; + } + + String toString() => 'LibraryScope($library)'; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart b/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart new file mode 100644 index 0000000..e8ee0f1 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart
@@ -0,0 +1,46 @@ +// 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. + +/** + * Encapsulates the field [TreeElementMixin._element]. + * + * This library is an implementation detail of dart2js, and should not + * be imported except by resolution and tree node libraries, or for + * testing. + * + * We have taken great care to ensure AST nodes can be cached between + * compiler instances. Part of this is requires that we always access + * resolution results through [TreeElements]. + * + * So please, do not add additional elements to this library, and do + * not import it. + */ +library secret_tree_element; + +/** + * The superclass of all AST nodes. + */ +abstract class TreeElementMixin { + // Deliberately using [Object] here to thwart code completion. + // You're not really supposed to access this field anyways. + Object _element; +} + +/** + * Do not call this method directly. Instead, use an instance of + * [TreeElements]. + * + * Using [Object] as return type to thwart code completion. + */ +Object getTreeElement(TreeElementMixin node) { + return node._element; +} + +/** + * Do not call this method directly. Instead, use an instance of + * [TreeElements]. + */ +void setTreeElement(TreeElementMixin node, Object value) { + node._element = value; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart b/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart new file mode 100644 index 0000000..3bfadb2 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart
@@ -0,0 +1,67 @@ +// 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. + +part of dart2js; + +abstract class ResolvedVisitor<R> extends Visitor<R> { + TreeElements elements; + + ResolvedVisitor(this.elements); + + R visitSend(Send node) { + if (node.isSuperCall) { + return visitSuperSend(node); + } else if (node.isOperator) { + return visitOperatorSend(node); + } else if (node.isPropertyAccess) { + Element element = elements[node]; + if (!Elements.isUnresolved(element) && element.impliesType()) { + // A reference to a class literal, typedef or type variable. + return visitTypeReferenceSend(node); + } else { + return visitGetterSend(node); + } + } else if (Elements.isClosureSend(node, elements[node])) { + return visitClosureSend(node); + } else { + Element element = elements[node]; + if (Elements.isUnresolved(element)) { + if (element == null) { + // Example: f() with 'f' unbound. + // This can only happen inside an instance method. + return visitDynamicSend(node); + } else { + return visitStaticSend(node); + } + } else if (element.impliesType()) { + // A reference to a class literal, typedef or type variable. + return visitTypeReferenceSend(node); + } else if (element.isInstanceMember()) { + // Example: f() with 'f' bound to instance method. + return visitDynamicSend(node); + } else if (!element.isInstanceMember()) { + // Example: A.f() or f() with 'f' bound to a static function. + // Also includes new A() or new A.named() which is treated like a + // static call to a factory. + return visitStaticSend(node); + } else { + internalError("Cannot generate code for send", node: node); + } + } + } + + R visitSuperSend(Send node); + R visitOperatorSend(Send node); + R visitGetterSend(Send node); + R visitClosureSend(Send node); + R visitDynamicSend(Send node); + R visitStaticSend(Send node); + R visitTypeReferenceSend(Send node); + + void internalError(String reason, {Node node}); + + R visitNode(Node node) { + internalError("Unhandled node", node: node); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart new file mode 100644 index 0000000..01f8e9e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart
@@ -0,0 +1,183 @@ +// Copyright (c) 2011, 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. + +part of scanner_implementation; + +abstract +class ArrayBasedScanner<S extends SourceString> extends AbstractScanner<S> { + int get charOffset => byteOffset + extraCharOffset; + final Token tokens; + Token tail; + int tokenStart; + int byteOffset; + final bool includeComments; + + /** Since the input is UTF8, some characters are represented by more + * than one byte. [extraCharOffset] tracks the difference. */ + int extraCharOffset; + Link<BeginGroupToken> groupingStack = const Link<BeginGroupToken>(); + + ArrayBasedScanner(this.includeComments) + : this.extraCharOffset = 0, + this.tokenStart = -1, + this.byteOffset = -1, + this.tokens = new Token(EOF_INFO, -1) { + this.tail = this.tokens; + } + + int advance() { + int next = nextByte(); + return next; + } + + int select(int choice, PrecedenceInfo yes, PrecedenceInfo no) { + int next = advance(); + if (identical(next, choice)) { + appendPrecedenceToken(yes); + return advance(); + } else { + appendPrecedenceToken(no); + return next; + } + } + + void appendPrecedenceToken(PrecedenceInfo info) { + tail.next = new Token(info, tokenStart); + tail = tail.next; + } + + void appendStringToken(PrecedenceInfo info, String value) { + tail.next = new StringToken(info, value, tokenStart); + tail = tail.next; + } + + void appendKeywordToken(Keyword keyword) { + String syntax = keyword.syntax; + + // Type parameters and arguments cannot contain 'this' or 'super'. + if (identical(syntax, 'this') || identical(syntax, 'super')) discardOpenLt(); + tail.next = new KeywordToken(keyword, tokenStart); + tail = tail.next; + } + + void appendEofToken() { + tail.next = new Token(EOF_INFO, charOffset); + tail = tail.next; + // EOF points to itself so there's always infinite look-ahead. + tail.next = tail; + discardOpenLt(); + while (!groupingStack.isEmpty) { + unmatchedBeginGroup(groupingStack.head); + groupingStack = groupingStack.tail; + } + } + + void beginToken() { + tokenStart = charOffset; + } + + Token firstToken() { + return tokens.next; + } + + Token previousToken() { + return tail; + } + + void addToCharOffset(int offset) { + extraCharOffset += offset; + } + + void appendWhiteSpace(int next) { + // Do nothing, we don't collect white space. + } + + void appendBeginGroup(PrecedenceInfo info, String value) { + Token token = new BeginGroupToken(info, value, tokenStart); + tail.next = token; + tail = tail.next; + if (!identical(info.kind, LT_TOKEN)) discardOpenLt(); + groupingStack = groupingStack.prepend(token); + } + + int appendEndGroup(PrecedenceInfo info, String value, int openKind) { + assert(!identical(openKind, LT_TOKEN)); + appendStringToken(info, value); + discardOpenLt(); + if (groupingStack.isEmpty) { + return advance(); + } + BeginGroupToken begin = groupingStack.head; + if (!identical(begin.kind, openKind)) { + if (!identical(openKind, OPEN_CURLY_BRACKET_TOKEN) || + !identical(begin.kind, STRING_INTERPOLATION_TOKEN)) { + // Not ending string interpolation. + return error(new SourceString('Unmatched ${begin.stringValue}')); + } + // We're ending an interpolated expression. + begin.endGroup = tail; + groupingStack = groupingStack.tail; + // Using "start-of-text" to signal that we're back in string + // scanning mode. + return $STX; + } + begin.endGroup = tail; + groupingStack = groupingStack.tail; + return advance(); + } + + void appendGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendGtGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendGtGtGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendComment() { + if (!includeComments) return; + SourceString value = utf8String(tokenStart, -1); + appendByteStringToken(COMMENT_INFO, value); + } + + void discardOpenLt() { + while (!groupingStack.isEmpty + && identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + } + + void unmatchedBeginGroup(BeginGroupToken begin); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart new file mode 100644 index 0000000..4cf9eeb --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
@@ -0,0 +1,40 @@ +// Copyright (c) 2011, 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. + +part of parser; + +/** + * Scanner that reads from a byte array and creates tokens that points + * to the same array. + */ +class ByteArrayScanner extends ArrayBasedScanner<ByteString> { + final List<int> bytes; + + ByteArrayScanner(List<int> this.bytes, [bool includeComments = false]) + : super(includeComments); + + int nextByte() => byteAt(++byteOffset); + + int peek() => byteAt(byteOffset + 1); + + int byteAt(int index) => bytes[index]; + + AsciiString asciiString(int start, int offset) { + return AsciiString.of(bytes, start, byteOffset - start + offset); + } + + Utf8String utf8String(int start, int offset) { + return Utf8String.of(bytes, start, byteOffset - start + offset + 1); + } + + void appendByteStringToken(PrecedenceInfo info, ByteString value) { + tail.next = new ByteStringToken(info, value, tokenStart); + tail = tail.next; + } + + // This method should be equivalent to the one in super. However, + // this is a *HOT* method and Dart VM performs better if it is easy + // to inline. + int advance() => bytes[++byteOffset]; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart new file mode 100644 index 0000000..4d79898 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart
@@ -0,0 +1,154 @@ +// Copyright (c) 2011, 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. + +/** + * An abstract string representation. + */ +abstract class ByteString extends Iterable<int> implements SourceString { + final List<int> bytes; + final int offset; + final int length; + int _hashCode; + + ByteString(List<int> this.bytes, int this.offset, int this.length); + + String get charset; + + String slowToString() => new String.fromCharCodes( + new Utf8Decoder(bytes, offset, length).decodeRest()); + + String toString() => "ByteString(${slowToString()})"; + + bool operator ==(other) { + throw "should be overridden in subclass"; + } + + Iterator<int> get iterator => new Utf8Decoder(bytes, offset, length); + + int get hashCode { + if (_hashCode == null) { + _hashCode = computeHashCode(); + } + return _hashCode; + } + + int computeHashCode() { + int code = 1; + int end = offset + length; + for (int i = offset; i < end; i++) { + code += 19 * code + bytes[i]; + } + return code; + } + + printOn(StringBuffer sb) { + sb.add(slowToString()); + } + + bool get isEmpty => length == 0; + bool isPrivate() => !isEmpty && identical(bytes[offset], $_); + + String get stringValue => null; +} + +/** + * A string that consists purely of 7bit ASCII characters. + */ +class AsciiString extends ByteString { + final String charset = "ASCII"; + + AsciiString(List<int> bytes, int offset, int length) + : super(bytes, offset, length); + + static AsciiString of(List<int> bytes, int offset, int length) { + AsciiString string = new AsciiString(bytes, offset, length); + return string; + } + + Iterator<int> get iterator => new AsciiStringIterator(bytes); + + SourceString copyWithoutQuotes(int initial, int terminal) { + return new AsciiString(bytes, offset + initial, + length - initial - terminal); + } + + + static AsciiString fromString(String string) { + List<int> bytes = string.charCodes; + return AsciiString.of(bytes, 0, bytes.length); + } +} + + +class AsciiStringIterator implements Iterator<int> { + final List<int> bytes; + int offset; + final int end; + int _current; + + AsciiStringIterator(List<int> bytes) + : this.bytes = bytes, offset = 0, end = bytes.length; + AsciiStringIterator.range(List<int> bytes, int from, int length) + : this.bytes = bytes, offset = from, end = from + length; + + int get current => _current; + bool moveNext() { + if (offset < end) { + _current = bytes[offset++]; + return true; + } + _current = null; + return false; + } +} + + +/** + * A string that consists of characters that can be encoded as UTF-8. + */ +class Utf8String extends ByteString { + final String charset = "UTF8"; + + Utf8String(List<int> bytes, int offset, int length) + : super(bytes, offset, length); + + static Utf8String of(List<int> bytes, int offset, int length) { + return new Utf8String(bytes, offset, length); + } + + static Utf8String fromString(String string) { + throw "not implemented yet"; + } + + Iterator<int> get iterator => new Utf8Decoder(bytes, 0, length); + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert((){ + // Only allow dropping ASCII characters, to guarantee that + // the resulting Utf8String is still valid. + for (int i = 0; i < initial; i++) { + if (bytes[offset + i] >= 0x80) return false; + } + for (int i = 0; i < terminal; i++) { + if (bytes[offset + length - terminal + i] >= 0x80) return false; + } + return true; + }); + // TODO(lrn): Check that first and last bytes use the same type of quotes. + return new Utf8String(bytes, offset + initial, + length - initial - terminal); + } +} + +/** + * A ByteString-valued token. + */ +class ByteStringToken extends Token { + final ByteString value; + + ByteStringToken(PrecedenceInfo info, ByteString this.value, int charOffset) + : super(info, charOffset); + + String toString() => value.toString(); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart new file mode 100644 index 0000000..1e359aa --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart
@@ -0,0 +1,197 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class ClassElementParser extends PartialParser { + ClassElementParser(Listener listener) : super(listener); + + Token parseClassBody(Token token) => fullParseClassBody(token); +} + +class PartialClassElement extends ClassElementX { + final Token beginToken; + final Token endToken; + ClassNode cachedNode; + + PartialClassElement(SourceString name, + Token this.beginToken, + Token this.endToken, + Element enclosing, + int id) + : super(name, enclosing, id, STATE_NOT_STARTED); + + void set supertypeLoadState(int state) { + assert(state == supertypeLoadState + 1); + assert(state <= STATE_DONE); + super.supertypeLoadState = state; + } + + void set resolutionState(int state) { + assert(state == resolutionState + 1); + assert(state <= STATE_DONE); + super.resolutionState = state; + } + + ClassNode parseNode(Compiler compiler) { + if (cachedNode != null) return cachedNode; + compiler.withCurrentElement(this, () { + compiler.parser.measure(() { + MemberListener listener = new MemberListener(compiler, this); + Parser parser = new ClassElementParser(listener); + Token token = parser.parseTopLevelDeclaration(beginToken); + assert(identical(token, endToken.next)); + cachedNode = listener.popNode(); + assert(listener.nodes.isEmpty); + }); + compiler.patchParser.measure(() { + if (isPatched) { + // TODO(lrn): Perhaps extract functionality so it doesn't + // need compiler. + compiler.patchParser.parsePatchClassNode(patch); + } + }); + }); + return cachedNode; + } + + Token position() => beginToken; + + // TODO(johnniwinther): Ensure that modifiers are always available. + Modifiers get modifiers => + cachedNode != null ? cachedNode.modifiers : Modifiers.EMPTY; + + bool isInterface() => identical(beginToken.stringValue, "interface"); +} + +class MemberListener extends NodeListener { + final ClassElement enclosingElement; + + MemberListener(DiagnosticListener listener, + Element enclosingElement) + : this.enclosingElement = enclosingElement, + super(listener, enclosingElement.getCompilationUnit()); + + bool isConstructorName(Node nameNode) { + if (enclosingElement == null || + enclosingElement.kind != ElementKind.CLASS) { + return false; + } + SourceString name; + if (nameNode.asIdentifier() != null) { + name = nameNode.asIdentifier().source; + } else { + Send send = nameNode.asSend(); + name = send.receiver.asIdentifier().source; + } + return enclosingElement.name == name; + } + + SourceString getMethodNameHack(Node methodName) { + Send send = methodName.asSend(); + if (send == null) return methodName.asIdentifier().source; + Identifier receiver = send.receiver.asIdentifier(); + Identifier selector = send.selector.asIdentifier(); + Operator operator = selector.asOperator(); + if (operator != null) { + assert(identical(receiver.source.stringValue, 'operator')); + // TODO(ahe): It is a hack to compare to ')', but it beats + // parsing the node. + bool isUnary = identical(operator.token.next.next.stringValue, ')'); + return Elements.constructOperatorName(operator.source, isUnary); + } else { + if (receiver == null) { + listener.cancel('library prefix in named factory constructor not ' + 'implemented', node: send.receiver); + } + if (receiver.source != enclosingElement.name) { + listener.onDeprecatedFeature(receiver, 'interface factories'); + } + return Elements.constructConstructorName(receiver.source, + selector.source); + } + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + super.endMethod(getOrSet, beginToken, endToken); + FunctionExpression method = popNode(); + pushNode(null); + bool isConstructor = isConstructorName(method.name); + SourceString name = getMethodNameHack(method.name); + ElementKind kind = ElementKind.FUNCTION; + if (isConstructor) { + if (getOrSet != null) { + recoverableError('illegal modifier', token: getOrSet); + } + kind = ElementKind.GENERATIVE_CONSTRUCTOR; + } else if (getOrSet != null) { + kind = (identical(getOrSet.stringValue, 'get')) + ? ElementKind.GETTER : ElementKind.SETTER; + } + Element memberElement = + new PartialFunctionElement(name, beginToken, getOrSet, endToken, + kind, method.modifiers, enclosingElement); + addMember(memberElement); + } + + void endFactoryMethod(Token beginToken, Token endToken) { + super.endFactoryMethod(beginToken, endToken); + FunctionExpression method = popNode(); + pushNode(null); + SourceString name = getMethodNameHack(method.name); + Identifier singleIdentifierName = method.name.asIdentifier(); + if (singleIdentifierName != null && singleIdentifierName.source == name) { + if (name != enclosingElement.name) { + listener.onDeprecatedFeature(method.name, 'interface factories'); + } + } + ElementKind kind = ElementKind.FUNCTION; + Element memberElement = + new PartialFunctionElement(name, beginToken, null, endToken, + kind, method.modifiers, enclosingElement); + addMember(memberElement); + } + + void endFields(int count, Token beginToken, Token endToken) { + super.endFields(count, beginToken, endToken); + VariableDefinitions variableDefinitions = popNode(); + Modifiers modifiers = variableDefinitions.modifiers; + pushNode(null); + void buildFieldElement(SourceString name, Element fields) { + Element element = + new VariableElementX(name, fields, ElementKind.FIELD, null); + addMember(element); + } + buildFieldElements(modifiers, variableDefinitions.definitions, + enclosingElement, + buildFieldElement, beginToken, endToken); + } + + void endInitializer(Token assignmentOperator) { + pushNode(null); // Super expects an expression, but + // ClassElementParser just skips expressions. + super.endInitializer(assignmentOperator); + } + + void endInitializers(int count, Token beginToken, Token endToken) { + pushNode(null); + } + + void addMember(Element memberElement) { + for (Link link = metadata; !link.isEmpty; link = link.tail) { + memberElement.addMetadata(link.head); + } + metadata = const Link<MetadataAnnotation>(); + enclosingElement.addMember(memberElement, listener); + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + popNode(); // Discard arguments. + if (periodBeforeName != null) { + popNode(); // Discard name. + } + popNode(); // Discard node (Send or Identifier). + pushMetadata(new PartialMetadataAnnotation(beginToken, endToken)); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart new file mode 100644 index 0000000..5d3c9a8 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart
@@ -0,0 +1,235 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +/** + * A keyword in the Dart programming language. + */ +class Keyword extends Iterable<int> implements SourceString { + static const List<Keyword> values = const <Keyword> [ + const Keyword("assert"), + const Keyword("break"), + const Keyword("case"), + const Keyword("catch"), + const Keyword("class"), + const Keyword("const"), + const Keyword("continue"), + const Keyword("default"), + const Keyword("do"), + const Keyword("else"), + const Keyword("extends"), + const Keyword("false"), + const Keyword("final"), + const Keyword("finally"), + const Keyword("for"), + const Keyword("if"), + const Keyword("in"), + const Keyword("new"), + const Keyword("null"), + const Keyword("return"), + const Keyword("super"), + const Keyword("switch"), + const Keyword("this"), + const Keyword("throw"), + const Keyword("true"), + const Keyword("try"), + const Keyword("var"), + const Keyword("void"), + const Keyword("while"), + const Keyword("with"), + + // TODO(ahe): Don't think this is a reserved word. + // See: http://dartbug.com/5579 + const Keyword("is", info: IS_INFO), + + const Keyword("abstract", isBuiltIn: true), + const Keyword("as", info: AS_INFO, isBuiltIn: true), + const Keyword("dynamic", isBuiltIn: true), + const Keyword("export", isBuiltIn: true), + const Keyword("external", isBuiltIn: true), + const Keyword("factory", isBuiltIn: true), + const Keyword("get", isBuiltIn: true), + const Keyword("implements", isBuiltIn: true), + const Keyword("import", isBuiltIn: true), + const Keyword("interface", isBuiltIn: true), + const Keyword("library", isBuiltIn: true), + const Keyword("operator", isBuiltIn: true), + const Keyword("part", isBuiltIn: true), + const Keyword("set", isBuiltIn: true), + const Keyword("static", isBuiltIn: true), + const Keyword("typedef", isBuiltIn: true), + + const Keyword("hide", isPseudo: true), + const Keyword("native", isPseudo: true), + const Keyword("of", isPseudo: true), + const Keyword("on", isPseudo: true), + const Keyword("show", isPseudo: true), + const Keyword("source", isPseudo: true) ]; + + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + static const DYNAMIC_DEPRECATED = const Keyword("Dynamic", isBuiltIn: true); + + final String syntax; + final bool isPseudo; + final bool isBuiltIn; + final PrecedenceInfo info; + + static Map<String, Keyword> _keywords; + static Map<String, Keyword> get keywords { + if (_keywords == null) { + _keywords = computeKeywordMap(); + } + return _keywords; + } + + const Keyword(String this.syntax, + {bool this.isPseudo: false, + bool this.isBuiltIn: false, + PrecedenceInfo this.info: KEYWORD_INFO}); + + static Map<String, Keyword> computeKeywordMap() { + Map<String, Keyword> result = new LinkedHashMap<String, Keyword>(); + for (Keyword keyword in values) { + result[keyword.syntax] = keyword; + } + return result; + } + + int get hashCode => syntax.hashCode; + + bool operator ==(other) { + return other is SourceString && toString() == other.slowToString(); + } + + Iterator<int> get iterator => new StringCodeIterator(syntax); + + void printOn(StringBuffer sb) { + sb.add(syntax); + } + + String toString() => syntax; + String slowToString() => syntax; + String get stringValue => syntax; + + SourceString copyWithoutQuotes(int initial, int terminal) { + // TODO(lrn): consider remodelling to avoid having this method in keywords. + return this; + } + + bool get isEmpty => false; + bool isPrivate() => false; +} + +/** + * Abstract state in a state machine for scanning keywords. + */ +abstract class KeywordState { + bool isLeaf(); + KeywordState next(int c); + Keyword get keyword; + + static KeywordState _KEYWORD_STATE; + static KeywordState get KEYWORD_STATE { + if (_KEYWORD_STATE == null) { + List<String> strings = + new List<String>.fixedLength(Keyword.values.length); + for (int i = 0; i < Keyword.values.length; i++) { + strings[i] = Keyword.values[i].syntax; + } + strings.sort((a,b) => a.compareTo(b)); + _KEYWORD_STATE = computeKeywordStateTable(0, strings, 0, strings.length); + } + return _KEYWORD_STATE; + } + + static KeywordState computeKeywordStateTable(int start, List<String> strings, + int offset, int length) { + List<KeywordState> result = new List<KeywordState>.fixedLength(26); + assert(length != 0); + int chunk = 0; + int chunkStart = -1; + bool isLeaf = false; + for (int i = offset; i < offset + length; i++) { + if (strings[i].length == start) { + isLeaf = true; + } + if (strings[i].length > start) { + int c = strings[i].charCodeAt(start); + if (chunk != c) { + if (chunkStart != -1) { + assert(result[chunk - $a] == null); + result[chunk - $a] = computeKeywordStateTable(start + 1, strings, + chunkStart, + i - chunkStart); + } + chunkStart = i; + chunk = c; + } + } + } + if (chunkStart != -1) { + assert(result[chunk - $a] == null); + result[chunk - $a] = + computeKeywordStateTable(start + 1, strings, chunkStart, + offset + length - chunkStart); + } else { + assert(length == 1); + return new LeafKeywordState(strings[offset]); + } + if (isLeaf) { + return new ArrayKeywordState(result, strings[offset]); + } else { + return new ArrayKeywordState(result, null); + } + } +} + +/** + * A state with multiple outgoing transitions. + */ +class ArrayKeywordState extends KeywordState { + final List<KeywordState> table; + final Keyword keyword; + + ArrayKeywordState(List<KeywordState> this.table, String syntax) + : keyword = (syntax == null) ? null : Keyword.keywords[syntax]; + + bool isLeaf() => false; + + KeywordState next(int c) => table[c - $a]; + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add("["); + if (keyword != null) { + sb.add("*"); + sb.add(keyword); + sb.add(" "); + } + List<KeywordState> foo = table; + for (int i = 0; i < foo.length; i++) { + if (foo[i] != null) { + sb.add("${new String.fromCharCodes([i + $a])}: ${foo[i]}; "); + } + } + sb.add("]"); + return sb.toString(); + } +} + +/** + * A state that has no outgoing transitions. + */ +class LeafKeywordState extends KeywordState { + final Keyword keyword; + + LeafKeywordState(String syntax) : keyword = Keyword.keywords[syntax]; + + bool isLeaf() => true; + + KeywordState next(int c) => null; + + String toString() => keyword.syntax; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart new file mode 100644 index 0000000..e535f62 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart
@@ -0,0 +1,2064 @@ +// 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. + +part of scanner; + +const bool VERBOSE = false; + +/** + * A parser event listener that does nothing except throw exceptions + * on parser errors. + */ +class Listener { + void beginArgumentDefinitionTest(Token token) { + } + + void endArgumentDefinitionTest(Token beginToken, Token endToken) { + } + + void beginArguments(Token token) { + } + + void endArguments(int count, Token beginToken, Token endToken) { + } + + void beginBlock(Token token) { + } + + void endBlock(int count, Token beginToken, Token endToken) { + } + + void beginCascade(Token token) { + } + + void endCascade() { + } + + void beginClassBody(Token token) { + } + + void endClassBody(int memberCount, Token beginToken, Token endToken) { + } + + void beginClassDeclaration(Token token) { + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + } + + void beginCombinators(Token token) { + } + + void endCombinators(int count) { + } + + void beginCompilationUnit(Token token) { + } + + void endCompilationUnit(int count, Token token) { + } + + void beginConstructorReference(Token start) { + } + + void endConstructorReference(Token start, Token periodBeforeName, + Token endToken) { + } + + void beginDoWhileStatement(Token token) { + } + + void endDoWhileStatement(Token doKeyword, Token whileKeyword, + Token endToken) { + } + + void beginExport(Token token) { + } + + void endExport(Token exportKeyword, Token semicolon) { + } + + void beginExpressionStatement(Token token) { + } + + void endExpressionStatement(Token token) { + } + + void beginDefaultClause(Token token) { + } + + void handleNoDefaultClause(Token token) { + } + + void endDefaultClause(Token defaultKeyword) { + } + + void beginFactoryMethod(Token token) { + } + + void endFactoryMethod(Token beginToken, Token endToken) { + } + + void beginFormalParameter(Token token) { + } + + void endFormalParameter(Token thisKeyword) { + } + + void handleNoFormalParameters(Token token) { + } + + void beginFormalParameters(Token token) { + } + + void endFormalParameters(int count, Token beginToken, Token endToken) { + } + + void endFields(int count, Token beginToken, Token endToken) { + } + + void beginForStatement(Token token) { + } + + void endForStatement(int updateExpressionCount, + Token beginToken, Token endToken) { + } + + void endForIn(Token beginToken, Token inKeyword, Token endToken) { + } + + void beginFunction(Token token) { + } + + void endFunction(Token getOrSet, Token endToken) { + } + + void beginFunctionDeclaration(Token token) { + } + + void endFunctionDeclaration(Token token) { + } + + void beginFunctionBody(Token token) { + } + + void endFunctionBody(int count, Token beginToken, Token endToken) { + } + + void handleNoFunctionBody(Token token) { + } + + void beginFunctionName(Token token) { + } + + void endFunctionName(Token token) { + } + + void beginFunctionTypeAlias(Token token) { + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + } + + void beginMixinApplication(Token token) { + } + + void endMixinApplication() { + } + + void beginNamedMixinApplication(Token token) { + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + } + + void beginHide(Token hideKeyword) { + } + + void endHide(Token hideKeyword) { + } + + void beginIdentifierList(Token token) { + } + + void endIdentifierList(int count) { + } + + void beginTypeList(Token token) { + } + + void endTypeList(int count) { + } + + void beginIfStatement(Token token) { + } + + void endIfStatement(Token ifToken, Token elseToken) { + } + + void beginImport(Token importKeyword) { + } + + void endImport(Token importKeyword, Token asKeyword, Token semicolon) { + } + + void beginInitializedIdentifier(Token token) { + } + + void endInitializedIdentifier() { + } + + void beginInitializer(Token token) { + } + + void endInitializer(Token assignmentOperator) { + } + + void beginInitializers(Token token) { + } + + void endInitializers(int count, Token beginToken, Token endToken) { + } + + void handleNoInitializers() { + } + + void beginInterface(Token token) { + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + } + + void handleLabel(Token token) { + } + + void beginLabeledStatement(Token token, int labelCount) { + } + + void endLabeledStatement(int labelCount) { + } + + void beginLibraryName(Token token) { + } + + void endLibraryName(Token libraryKeyword, Token semicolon) { + } + + void beginLiteralMapEntry(Token token) { + } + + void endLiteralMapEntry(Token colon, Token endToken) { + } + + void beginLiteralString(Token token) { + } + + void endLiteralString(int interpolationCount) { + } + + void handleStringJuxtaposition(int literalCount) { + } + + void beginMember(Token token) { + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + } + + void beginMetadata(Token token) { + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + } + + void beginOptionalFormalParameters(Token token) { + } + + void endOptionalFormalParameters(int count, + Token beginToken, Token endToken) { + } + + void beginPart(Token token) { + } + + void endPart(Token partKeyword, Token semicolon) { + } + + void beginPartOf(Token token) { + } + + void endPartOf(Token partKeyword, Token semicolon) { + } + + void beginRedirectingFactoryBody(Token token) { + } + + void endRedirectingFactoryBody(Token beginToken, Token endToken) { + } + + void beginReturnStatement(Token token) { + } + + void endReturnStatement(bool hasExpression, + Token beginToken, Token endToken) { + } + + void beginScriptTag(Token token) { + } + + void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) { + } + + void beginSend(Token token) { + } + + void endSend(Token token) { + } + + void beginShow(Token showKeyword) { + } + + void endShow(Token showKeyword) { + } + + void beginSwitchStatement(Token token) { + } + + void endSwitchStatement(Token switchKeyword, Token endToken) { + } + + void beginSwitchBlock(Token token) { + } + + void endSwitchBlock(int caseCount, Token beginToken, Token endToken) { + } + + void beginThrowStatement(Token token) { + } + + void endThrowStatement(Token throwToken, Token endToken) { + } + + void endRethrowStatement(Token throwToken, Token endToken) { + } + + void endTopLevelDeclaration(Token token) { + } + + void beginTopLevelMember(Token token) { + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + } + + void beginTryStatement(Token token) { + } + + void handleCaseMatch(Token caseKeyword, Token colon) { + } + + void handleCatchBlock(Token onKeyword, Token catchKeyword) { + } + + void handleFinallyBlock(Token finallyKeyword) { + } + + void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) { + } + + void endType(Token beginToken, Token endToken) { + } + + void beginTypeArguments(Token token) { + } + + void endTypeArguments(int count, Token beginToken, Token endToken) { + } + + void handleNoTypeArguments(Token token) { + } + + void beginTypeVariable(Token token) { + } + + void endTypeVariable(Token token) { + } + + void beginTypeVariables(Token token) { + } + + void endTypeVariables(int count, Token beginToken, Token endToken) { + } + + void beginUnamedFunction(Token token) { + } + + void endUnamedFunction(Token token) { + } + + void beginVariablesDeclaration(Token token) { + } + + void endVariablesDeclaration(int count, Token endToken) { + } + + void beginWhileStatement(Token token) { + } + + void endWhileStatement(Token whileKeyword, Token endToken) { + } + + void handleAsOperator(Token operathor, Token endToken) { + // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed. + } + + void handleAssignmentExpression(Token token) { + } + + void handleBinaryExpression(Token token) { + } + + void handleConditionalExpression(Token question, Token colon) { + } + + void handleConstExpression(Token token) { + } + + void handleFunctionTypedFormalParameter(Token token) { + } + + void handleIdentifier(Token token) { + } + + void handleIndexedExpression(Token openCurlyBracket, + Token closeCurlyBracket) { + } + + void handleIsOperator(Token operathor, Token not, Token endToken) { + // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed. + } + + void handleLiteralBool(Token token) { + } + + void handleBreakStatement(bool hasTarget, + Token breakKeyword, Token endToken) { + } + + void handleContinueStatement(bool hasTarget, + Token continueKeyword, Token endToken) { + } + + void handleEmptyStatement(Token token) { + } + + void handleAssertStatement(Token assertKeyword, Token semicolonToken) { + } + + /** Called with either the token containing a double literal, or + * an immediately preceding "unary plus" token. + */ + void handleLiteralDouble(Token token) { + } + + /** Called with either the token containing an integer literal, + * or an immediately preceding "unary plus" token. + */ + void handleLiteralInt(Token token) { + } + + void handleLiteralList(int count, Token beginToken, Token constKeyword, + Token endToken) { + } + + void handleLiteralMap(int count, Token beginToken, Token constKeyword, + Token endToken) { + } + + void handleLiteralNull(Token token) { + } + + void handleModifier(Token token) { + } + + void handleModifiers(int count) { + } + + void handleNamedArgument(Token colon) { + } + + void handleNewExpression(Token token) { + } + + void handleNoArguments(Token token) { + } + + void handleNoExpression(Token token) { + } + + void handleNoType(Token token) { + } + + void handleNoTypeVariables(Token token) { + } + + void handleOperatorName(Token operatorKeyword, Token token) { + } + + void handleParenthesizedExpression(BeginGroupToken token) { + } + + void handleQualified(Token period) { + } + + void handleStringPart(Token token) { + } + + void handleSuperExpression(Token token) { + } + + void handleSwitchCase(int labelCount, int expressionCount, + Token defaultKeyword, int statementCount, + Token firstToken, Token endToken) { + } + + void handleThisExpression(Token token) { + } + + void handleUnaryPostfixAssignmentExpression(Token token) { + } + + void handleUnaryPrefixExpression(Token token) { + } + + void handleUnaryPrefixAssignmentExpression(Token token) { + } + + void handleValuedFormalParameter(Token equals, Token token) { + } + + void handleVoidKeyword(Token token) { + } + + Token expected(String string, Token token) { + error("expected '$string', but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + void expectedIdentifier(Token token) { + error("expected identifier, but got '${token.slowToString()}'", token); + } + + Token expectedType(Token token) { + error("expected a type, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedExpression(Token token) { + error("expected an expression, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token unexpected(Token token) { + error("unexpected token '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedBlockToSkip(Token token) { + error("expected a block, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedFunctionBody(Token token) { + error("expected a function body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedClassBody(Token token) { + error("expected a class body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedClassBodyToSkip(Token token) { + error("expected a class body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Link<Token> expectedDeclaration(Token token) { + error("expected a declaration, but got '${token.slowToString()}'", token); + return const Link<Token>(); + } + + Token unmatched(Token token) { + error("unmatched '${token.slowToString()}'", token); + return skipToEof(token); + } + + skipToEof(Token token) { + while (!identical(token.info, EOF_INFO)) { + token = token.next; + } + return token; + } + + void recoverableError(String message, {Token token, Node node}) { + if (token == null && node != null) { + token = node.getBeginToken(); + } + error(message, token); + } + + void error(String message, Token token) { + throw new ParserError("$message @ ${token.charOffset}"); + } +} + +class ParserError { + final String reason; + ParserError(this.reason); + toString() => reason; +} + +typedef int IdGenerator(); + +/** + * A parser event listener designed to work with [PartialParser]. It + * builds elements representing the top-level declarations found in + * the parsed compilation unit and records them in + * [compilationUnitElement]. + */ +class ElementListener extends Listener { + final IdGenerator idGenerator; + final DiagnosticListener listener; + final CompilationUnitElement compilationUnitElement; + final StringValidator stringValidator; + Link<StringQuoting> interpolationScope; + + Link<Node> nodes = const Link<Node>(); + + Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>(); + + ElementListener(DiagnosticListener listener, + this.compilationUnitElement, + this.idGenerator) + : this.listener = listener, + stringValidator = new StringValidator(listener), + interpolationScope = const Link<StringQuoting>(); + + void pushQuoting(StringQuoting quoting) { + interpolationScope = interpolationScope.prepend(quoting); + } + + StringQuoting popQuoting() { + StringQuoting result = interpolationScope.head; + interpolationScope = interpolationScope.tail; + return result; + } + + StringNode popLiteralString() { + StringNode node = popNode(); + // TODO(lrn): Handle interpolations in script tags. + if (node.isInterpolation) { + listener.cancel("String interpolation not supported in library tags", + node: node); + return null; + } + return node; + } + + bool allowLibraryTags() { + // Library tags are only allowed in the library file itself, not + // in sourced files. + LibraryElement library = compilationUnitElement.getLibrary(); + return !compilationUnitElement.hasMembers + && library.entryCompilationUnit == compilationUnitElement; + } + + void endLibraryName(Token libraryKeyword, Token semicolon) { + Expression name = popNode(); + addLibraryTag(new LibraryName(libraryKeyword, name, + popMetadata(compilationUnitElement))); + } + + void endImport(Token importKeyword, Token asKeyword, Token semicolon) { + NodeList combinators = popNode(); + Identifier prefix; + if (asKeyword != null) { + prefix = popNode(); + } + StringNode uri = popLiteralString(); + addLibraryTag(new Import(importKeyword, uri, prefix, combinators, + popMetadata(compilationUnitElement))); + } + + void endExport(Token exportKeyword, Token semicolon) { + NodeList combinators = popNode(); + StringNode uri = popNode(); + addLibraryTag(new Export(exportKeyword, uri, combinators, + popMetadata(compilationUnitElement))); + } + + void endCombinators(int count) { + if (0 == count) { + pushNode(null); + } else { + pushNode(makeNodeList(count, null, null, " ")); + } + } + + void endHide(Token hideKeyword) => pushCombinator(hideKeyword); + + void endShow(Token showKeyword) => pushCombinator(showKeyword); + + void pushCombinator(Token keywordToken) { + NodeList identifiers = popNode(); + pushNode(new Combinator(identifiers, keywordToken)); + } + + void endIdentifierList(int count) { + pushNode(makeNodeList(count, null, null, ",")); + } + + void endTypeList(int count) { + pushNode(makeNodeList(count, null, null, ",")); + } + + void endPart(Token partKeyword, Token semicolon) { + StringNode uri = popLiteralString(); + addLibraryTag(new Part(partKeyword, uri, + popMetadata(compilationUnitElement))); + } + + void endPartOf(Token partKeyword, Token semicolon) { + Expression name = popNode(); + addPartOfTag(new PartOf(partKeyword, name, + popMetadata(compilationUnitElement))); + } + + void addPartOfTag(PartOf tag) { + compilationUnitElement.setPartOf(tag, listener); + } + + void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) { + LiteralString prefix = null; + Identifier argumentName = null; + if (hasPrefix) { + prefix = popLiteralString(); + argumentName = popNode(); + } + LiteralString firstArgument = popLiteralString(); + Identifier tag = popNode(); + ScriptTag scriptTag = new ScriptTag(tag, firstArgument, argumentName, + prefix, beginToken, endToken); + if (const SourceString('import') == tag.source || + const SourceString('source') == tag.source || + const SourceString('library') == tag.source) { + addScriptTag(scriptTag); + } else { + recoverableError('unknown tag: ${tag.source.slowToString()}', node: tag); + } + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + if (periodBeforeName != null) { + popNode(); // Discard name. + } + popNode(); // Discard node (Send or Identifier). + pushMetadata(new PartialMetadataAnnotation(beginToken, endToken)); + } + + void endTopLevelDeclaration(Token token) { + if (!metadata.isEmpty) { + recoverableError('Error: Metadata not supported here.', + token: metadata.head.beginToken); + metadata = const Link<MetadataAnnotation>(); + } + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + SourceString nativeTagInfo = native.checkForNativeClass(this); + NodeList interfaces = + makeNodeList(interfacesCount, implementsKeyword, null, ","); + Node supertype = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + int id = idGenerator(); + ClassElement element = new PartialClassElement( + name.source, beginToken, endToken, compilationUnitElement, id); + element.nativeTagInfo = nativeTagInfo; + pushElement(element); + rejectBuiltInIdentifier(name); + } + + void rejectBuiltInIdentifier(Identifier name) { + if (name.source is Keyword) { + Keyword keyword = name.source; + if (!keyword.isPseudo) { + recoverableError('illegal name ${keyword.syntax}', node: name); + } + } + } + + void endDefaultClause(Token defaultKeyword) { + NodeList typeParameters = popNode(); + Node name = popNode(); + pushNode(new TypeAnnotation(name, typeParameters)); + } + + void handleNoDefaultClause(Token token) { + pushNode(null); + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + // TODO(ahe): Record the defaultClause. + Node defaultClause = popNode(); + NodeList supertypes = + makeNodeList(supertypeCount, extendsKeyword, null, ","); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + int id = idGenerator(); + pushElement(new PartialClassElement( + name.source, interfaceKeyword, endToken, compilationUnitElement, id)); + rejectBuiltInIdentifier(name); + listener.onDeprecatedFeature(interfaceKeyword, 'interface declarations'); + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + NodeList typeVariables = popNode(); // TOOD(karlklose): do not throw away. + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushElement(new PartialTypedefElement(name.source, compilationUnitElement, + typedefKeyword)); + rejectBuiltInIdentifier(name); + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + NodeList interfaces = (implementsKeyword != null) ? popNode() : null; + MixinApplication mixinApplication = popNode(); + Modifiers modifiers = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + NamedMixinApplication namedMixinApplication = new NamedMixinApplication( + name, typeParameters, modifiers, mixinApplication, interfaces, + typedefKeyword, endToken); + + int id = idGenerator(); + Element enclosing = compilationUnitElement; + pushElement(new MixinApplicationElementX(name.source, enclosing, id, + namedMixinApplication, + modifiers)); + rejectBuiltInIdentifier(name); + } + + void endMixinApplication() { + NodeList mixins = popNode(); + TypeAnnotation superclass = popNode(); + pushNode(new MixinApplication(superclass, mixins)); + } + + void handleVoidKeyword(Token token) { + pushNode(new TypeAnnotation(new Identifier(token), null)); + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + Identifier name = popNode(); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + ElementKind kind; + if (getOrSet == null) { + kind = ElementKind.FUNCTION; + } else if (identical(getOrSet.stringValue, 'get')) { + kind = ElementKind.GETTER; + } else if (identical(getOrSet.stringValue, 'set')) { + kind = ElementKind.SETTER; + } + pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet, + endToken, kind, + modifiers, compilationUnitElement)); + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + void buildFieldElement(SourceString name, Element fields) { + pushElement(new VariableElementX(name, fields, ElementKind.FIELD, null)); + } + NodeList variables = makeNodeList(count, null, null, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + buildFieldElements(modifiers, variables, compilationUnitElement, + buildFieldElement, + beginToken, endToken); + } + + void buildFieldElements(Modifiers modifiers, + NodeList variables, + Element enclosingElement, + void buildFieldElement(SourceString name, + Element fields), + Token beginToken, Token endToken) { + Element fields = new PartialFieldListElement(beginToken, + endToken, + modifiers, + enclosingElement); + for (Link<Node> variableNodes = variables.nodes; + !variableNodes.isEmpty; + variableNodes = variableNodes.tail) { + Expression initializedIdentifier = variableNodes.head; + Identifier identifier = initializedIdentifier.asIdentifier(); + if (identifier == null) { + identifier = initializedIdentifier.asSendSet().selector.asIdentifier(); + } + SourceString name = identifier.source; + buildFieldElement(name, fields); + } + } + + void handleIdentifier(Token token) { + pushNode(new Identifier(token)); + } + + void handleQualified(Token period) { + Identifier last = popNode(); + Expression first = popNode(); + pushNode(new Send(first, last)); + } + + void handleNoType(Token token) { + pushNode(null); + } + + void endTypeVariable(Token token) { + TypeAnnotation bound = popNode(); + Identifier name = popNode(); + pushNode(new TypeVariable(name, bound)); + } + + void endTypeVariables(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleNoTypeVariables(token) { + pushNode(null); + } + + void endTypeArguments(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleNoTypeArguments(Token token) { + pushNode(null); + } + + void endType(Token beginToken, Token endToken) { + NodeList typeArguments = popNode(); + Expression typeName = popNode(); + pushNode(new TypeAnnotation(typeName, typeArguments)); + } + + void handleParenthesizedExpression(BeginGroupToken token) { + Expression expression = popNode(); + pushNode(new ParenthesizedExpression(expression, token)); + } + + void handleModifier(Token token) { + pushNode(new Identifier(token)); + } + + void handleModifiers(int count) { + if (count == 0) { + pushNode(Modifiers.EMPTY); + } else { + NodeList modifierNodes = makeNodeList(count, null, null, ' '); + pushNode(new Modifiers(modifierNodes)); + } + } + + Token expected(String string, Token token) { + listener.cancel("expected '$string', but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + + void expectedIdentifier(Token token) { + listener.cancel("expected identifier, but got '${token.slowToString()}'", + token: token); + pushNode(null); + } + + Token expectedType(Token token) { + listener.cancel("expected a type, but got '${token.slowToString()}'", + token: token); + pushNode(null); + return skipToEof(token); + } + + Token expectedExpression(Token token) { + listener.cancel("expected an expression, but got '${token.slowToString()}'", + token: token); + pushNode(null); + return skipToEof(token); + } + + Token unexpected(Token token) { + String message = "unexpected token '${token.slowToString()}'"; + if (token.info == BAD_INPUT_INFO) { + message = token.stringValue; + } + listener.cancel(message, token: token); + return skipToEof(token); + } + + Token expectedBlockToSkip(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeBlockToSkip(this, token); + } else { + return unexpected(token); + } + } + + Token expectedFunctionBody(Token token) { + String printString = token.slowToString(); + listener.cancel("expected a function body, but got '$printString'", + token: token); + return skipToEof(token); + } + + Token expectedClassBody(Token token) { + listener.cancel("expected a class body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + + Token expectedClassBodyToSkip(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeClassBodyToSkip(this, token); + } else { + return unexpected(token); + } + } + + Link<Token> expectedDeclaration(Token token) { + listener.cancel("expected a declaration, but got '${token.slowToString()}'", + token: token); + return const Link<Token>(); + } + + Token unmatched(Token token) { + listener.cancel("unmatched '${token.slowToString()}'", token: token); + return skipToEof(token); + } + + void recoverableError(String message, {Token token, Node node}) { + listener.cancel(message, token: token, node: node); + } + + void pushElement(Element element) { + popMetadata(element); + compilationUnitElement.addMember(element, listener); + } + + Link<MetadataAnnotation> popMetadata(Element element) { + var result = metadata; + for (Link link = metadata; !link.isEmpty; link = link.tail) { + element.addMetadata(link.head); + } + metadata = const Link<MetadataAnnotation>(); + return result; + } + + void pushMetadata(MetadataAnnotation annotation) { + metadata = metadata.prepend(annotation); + } + + // TODO(ahe): Remove this method. + void addScriptTag(ScriptTag tag) { + listener.onDeprecatedFeature(tag, '# tags'); + addLibraryTag(tag.toLibraryTag()); + } + + void addLibraryTag(LibraryTag tag) { + if (!allowLibraryTags()) { + recoverableError('library tags not allowed here', node: tag); + } + compilationUnitElement.getImplementationLibrary().addTag(tag, listener); + } + + void pushNode(Node node) { + nodes = nodes.prepend(node); + if (VERBOSE) log("push $nodes"); + } + + Node popNode() { + assert(!nodes.isEmpty); + Node node = nodes.head; + nodes = nodes.tail; + if (VERBOSE) log("pop $nodes"); + return node; + } + + Node peekNode() { + assert(!nodes.isEmpty); + Node node = nodes.head; + if (VERBOSE) log("peek $node"); + return node; + } + + void log(message) { + print(message); + } + + NodeList makeNodeList(int count, Token beginToken, Token endToken, + String delimiter) { + Link<Node> poppedNodes = const Link<Node>(); + for (; count > 0; --count) { + // This effectively reverses the order of nodes so they end up + // in correct (source) order. + poppedNodes = poppedNodes.prepend(popNode()); + } + SourceString sourceDelimiter = + (delimiter == null) ? null : new SourceString(delimiter); + return new NodeList(beginToken, poppedNodes, endToken, sourceDelimiter); + } + + void beginLiteralString(Token token) { + SourceString source = token.value; + StringQuoting quoting = StringValidator.quotingFromString(source); + pushQuoting(quoting); + // Just wrap the token for now. At the end of the interpolation, + // when we know how many there are, go back and validate the tokens. + pushNode(new LiteralString(token, null)); + } + + void handleStringPart(Token token) { + // Just push an unvalidated token now, and replace it when we know the + // end of the interpolation. + pushNode(new LiteralString(token, null)); + } + + void endLiteralString(int count) { + StringQuoting quoting = popQuoting(); + + Link<StringInterpolationPart> parts = + const Link<StringInterpolationPart>(); + // Parts of the string interpolation are popped in reverse order, + // starting with the last literal string part. + bool isLast = true; + for (int i = 0; i < count; i++) { + LiteralString string = popNode(); + DartString validation = + stringValidator.validateInterpolationPart(string.token, quoting, + isFirst: false, + isLast: isLast); + // Replace the unvalidated LiteralString with a new LiteralString + // object that has the validation result included. + string = new LiteralString(string.token, validation); + Expression expression = popNode(); + parts = parts.prepend(new StringInterpolationPart(expression, string)); + isLast = false; + } + + LiteralString string = popNode(); + DartString validation = + stringValidator.validateInterpolationPart(string.token, quoting, + isFirst: true, + isLast: isLast); + string = new LiteralString(string.token, validation); + if (isLast) { + pushNode(string); + } else { + NodeList partNodes = + new NodeList(null, parts, null, const SourceString("")); + pushNode(new StringInterpolation(string, partNodes)); + } + } + + void handleStringJuxtaposition(int stringCount) { + assert(stringCount != 0); + Expression accumulator = popNode(); + stringCount--; + while (stringCount > 0) { + Expression expression = popNode(); + accumulator = new StringJuxtaposition(expression, accumulator); + stringCount--; + } + pushNode(accumulator); + } +} + +class NodeListener extends ElementListener { + NodeListener(DiagnosticListener listener, CompilationUnitElement element) + : super(listener, element, null); + + void addLibraryTag(LibraryTag tag) { + pushNode(tag); + } + + void addPartOfTag(PartOf tag) { + pushNode(tag); + } + + void endArgumentDefinitionTest(Token beginToken, Token endToken) { + pushNode(new Send.prefix(popNode(), new Operator(beginToken))); + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + NodeList body = popNode(); + NodeList interfaces = + makeNodeList(interfacesCount, implementsKeyword, null, ","); + Node supertype = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + Modifiers modifiers = popNode(); + pushNode(new ClassNode(modifiers, name, typeParameters, supertype, + interfaces, null, beginToken, extendsKeyword, body, + endToken)); + } + + void endCompilationUnit(int count, Token token) { + pushNode(makeNodeList(count, null, null, '\n')); + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + NodeList formals = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushNode(new Typedef(returnType, name, typeParameters, formals, + typedefKeyword, endToken)); + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + NodeList interfaces = (implementsKeyword != null) ? popNode() : null; + Node mixinApplication = popNode(); + Modifiers modifiers = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + pushNode(new NamedMixinApplication(name, typeParameters, + modifiers, mixinApplication, + interfaces, + typedefKeyword, endToken)); + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + NodeList body = popNode(); + TypeAnnotation defaultClause = popNode(); + NodeList supertypes = makeNodeList(supertypeCount, extendsKeyword, + null, ','); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + pushNode(new ClassNode(Modifiers.EMPTY, name, typeParameters, null, + supertypes, defaultClause, interfaceKeyword, null, + body, endToken)); + } + + void endClassBody(int memberCount, Token beginToken, Token endToken) { + pushNode(makeNodeList(memberCount, beginToken, endToken, null)); + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + NodeList variables = makeNodeList(count, null, endToken, ","); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(null, modifiers, variables)); + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + Statement body = popNode(); + NodeList formalParameters = popNode(); + Identifier name = popNode(); + Modifiers modifiers = popNode(); + ElementKind kind; + if (getOrSet == null) { + kind = ElementKind.FUNCTION; + } else if (identical(getOrSet.stringValue, 'get')) { + kind = ElementKind.GETTER; + } else if (identical(getOrSet.stringValue, 'set')) { + kind = ElementKind.SETTER; + } + pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet, + endToken, kind, + modifiers, compilationUnitElement)); + } + + void endFormalParameter(Token thisKeyword) { + Expression name = popNode(); + if (thisKeyword != null) { + Identifier thisIdentifier = new Identifier(thisKeyword); + if (name.asSend() == null) { + name = new Send(thisIdentifier, name); + } else { + name = name.asSend().copyWithReceiver(thisIdentifier); + } + } + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode( + new VariableDefinitions(type, modifiers, new NodeList.singleton(name))); + } + + void endFormalParameters(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ",")); + } + + void handleNoFormalParameters(Token token) { + pushNode(null); + } + + void endArguments(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ",")); + } + + void handleNoArguments(Token token) { + pushNode(null); + } + + void endConstructorReference(Token start, Token periodBeforeName, + Token endToken) { + Identifier name = null; + if (periodBeforeName != null) { + name = popNode(); + } + NodeList typeArguments = popNode(); + Node classReference = popNode(); + if (typeArguments != null) { + classReference = new TypeAnnotation(classReference, typeArguments); + } else { + Identifier identifier = classReference.asIdentifier(); + Send send = classReference.asSend(); + if (identifier != null) { + // TODO(ahe): Should be: + // classReference = new Send(null, identifier); + classReference = identifier; + } else if (send != null) { + classReference = send; + } else { + internalError(node: classReference); + } + } + Node constructor = classReference; + if (name != null) { + // Either typeName<args>.name or x.y.name. + constructor = new Send(classReference, name); + } + pushNode(constructor); + } + + void endRedirectingFactoryBody(Token beginToken, + Token endToken) { + pushNode(new Return(beginToken, endToken, popNode())); + } + + void endReturnStatement(bool hasExpression, + Token beginToken, Token endToken) { + Expression expression = hasExpression ? popNode() : null; + pushNode(new Return(beginToken, endToken, expression)); + } + + void endExpressionStatement(Token token) { + pushNode(new ExpressionStatement(popNode(), token)); + } + + void handleOnError(Token token, var errorInformation) { + listener.cancel("internal error: '${token.value}': ${errorInformation}", + token: token); + } + + Token expectedFunctionBody(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeFunctionBody(this, token); + } else { + listener.cancel( + "expected a function body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + } + + Token expectedClassBody(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeClassBody(this, token); + } else { + listener.cancel( + "expected a class body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + } + + void handleLiteralInt(Token token) { + pushNode(new LiteralInt(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralDouble(Token token) { + pushNode(new LiteralDouble(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralBool(Token token) { + pushNode(new LiteralBool(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralNull(Token token) { + pushNode(new LiteralNull(token)); + } + + void handleBinaryExpression(Token token) { + Node argument = popNode(); + Node receiver = popNode(); + String tokenString = token.stringValue; + if (identical(tokenString, '.') || identical(tokenString, '..')) { + Send argumentSend = argument.asSend(); + if (argumentSend == null) { + // TODO(ahe): The parser should diagnose this problem, not + // this listener. + listener.cancel('Syntax error: Expected an identifier.', + node: argument); + } + if (argumentSend.receiver != null) internalError(node: argument); + if (argument is SendSet) internalError(node: argument); + pushNode(argument.asSend().copyWithReceiver(receiver)); + } else { + NodeList arguments = new NodeList.singleton(argument); + pushNode(new Send(receiver, new Operator(token), arguments)); + } + if (identical(tokenString, '===') || identical(tokenString, '!==')) { + listener.onDeprecatedFeature(token, tokenString); + } + } + + void beginCascade(Token token) { + pushNode(new CascadeReceiver(popNode(), token)); + } + + void endCascade() { + pushNode(new Cascade(popNode())); + } + + void handleAsOperator(Token operathor, Token endToken) { + TypeAnnotation type = popNode(); + Expression expression = popNode(); + NodeList arguments = new NodeList.singleton(type); + pushNode(new Send(expression, new Operator(operathor), arguments)); + } + + void handleAssignmentExpression(Token token) { + Node arg = popNode(); + Node node = popNode(); + Send send = node.asSend(); + if (send == null || !(send.isPropertyAccess || send.isIndex)) { + reportNotAssignable(node); + } + if (send.asSendSet() != null) internalError(node: send); + NodeList arguments; + if (send.isIndex) { + Link<Node> link = const Link<Node>().prepend(arg); + link = link.prepend(send.arguments.head); + arguments = new NodeList(null, link); + } else { + arguments = new NodeList.singleton(arg); + } + Operator op = new Operator(token); + pushNode(new SendSet(send.receiver, send.selector, op, arguments)); + } + + void reportNotAssignable(Node node) { + // TODO(ahe): The parser should diagnose this problem, not this + // listener. + listener.cancel('Syntax error: Not assignable.', node: node); + } + + void handleConditionalExpression(Token question, Token colon) { + Node elseExpression = popNode(); + Node thenExpression = popNode(); + Node condition = popNode(); + pushNode(new Conditional( + condition, thenExpression, elseExpression, question, colon)); + } + + void endSend(Token token) { + NodeList arguments = popNode(); + Node selector = popNode(); + // TODO(ahe): Handle receiver. + pushNode(new Send(null, selector, arguments)); + } + + void endFunctionBody(int count, Token beginToken, Token endToken) { + if (count == 0 && beginToken == null) { + pushNode(new EmptyStatement(endToken)); + } else { + pushNode(new Block(makeNodeList(count, beginToken, endToken, null))); + } + } + + void handleNoFunctionBody(Token token) { + pushNode(null); + } + + void endFunction(Token getOrSet, Token endToken) { + Statement body = popNode(); + NodeList initializers = popNode(); + NodeList formals = popNode(); + // The name can be an identifier or a send in case of named constructors. + Expression name = popNode(); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new FunctionExpression(name, formals, body, type, + modifiers, initializers, getOrSet)); + } + + void endFunctionDeclaration(Token endToken) { + pushNode(new FunctionDeclaration(popNode())); + } + + void endVariablesDeclaration(int count, Token endToken) { + // TODO(ahe): Pick one name for this concept, either + // VariablesDeclaration or VariableDefinitions. + NodeList variables = makeNodeList(count, null, endToken, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(type, modifiers, variables)); + } + + void endInitializer(Token assignmentOperator) { + Expression initializer = popNode(); + NodeList arguments = new NodeList.singleton(initializer); + Expression name = popNode(); + Operator op = new Operator(assignmentOperator); + pushNode(new SendSet(null, name, op, arguments)); + } + + void endIfStatement(Token ifToken, Token elseToken) { + Statement elsePart = (elseToken == null) ? null : popNode(); + Statement thenPart = popNode(); + ParenthesizedExpression condition = popNode(); + pushNode(new If(condition, thenPart, elsePart, ifToken, elseToken)); + } + + void endForStatement(int updateExpressionCount, + Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList updates = makeNodeList(updateExpressionCount, null, null, ','); + Statement condition = popNode(); + Node initializer = popNode(); + pushNode(new For(initializer, condition, updates, body, beginToken)); + } + + void handleNoExpression(Token token) { + pushNode(null); + } + + void endDoWhileStatement(Token doKeyword, Token whileKeyword, + Token endToken) { + Expression condition = popNode(); + Statement body = popNode(); + pushNode(new DoWhile(body, condition, doKeyword, whileKeyword, endToken)); + } + + void endWhileStatement(Token whileKeyword, Token endToken) { + Statement body = popNode(); + Expression condition = popNode(); + pushNode(new While(condition, body, whileKeyword)); + } + + void endBlock(int count, Token beginToken, Token endToken) { + pushNode(new Block(makeNodeList(count, beginToken, endToken, null))); + } + + void endThrowStatement(Token throwToken, Token endToken) { + Expression expression = popNode(); + pushNode(new Throw(expression, throwToken, endToken)); + } + + void endRethrowStatement(Token throwToken, Token endToken) { + pushNode(new Throw(null, throwToken, endToken)); + } + + void handleUnaryPrefixExpression(Token token) { + pushNode(new Send.prefix(popNode(), new Operator(token))); + } + + void handleSuperExpression(Token token) { + pushNode(new Identifier(token)); + } + + void handleThisExpression(Token token) { + pushNode(new Identifier(token)); + } + + void handleUnaryAssignmentExpression(Token token, bool isPrefix) { + Node node = popNode(); + Send send = node.asSend(); + if (send == null) { + reportNotAssignable(node); + } + if (!(send.isPropertyAccess || send.isIndex)) { + reportNotAssignable(node); + } + if (send.asSendSet() != null) internalError(node: send); + Node argument = null; + if (send.isIndex) argument = send.arguments.head; + Operator op = new Operator(token); + + if (isPrefix) { + pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument)); + } else { + pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument)); + } + } + + void handleUnaryPostfixAssignmentExpression(Token token) { + handleUnaryAssignmentExpression(token, false); + } + + void handleUnaryPrefixAssignmentExpression(Token token) { + handleUnaryAssignmentExpression(token, true); + } + + void endInitializers(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, null, ',')); + } + + void handleNoInitializers() { + pushNode(null); + } + + void endFields(int count, Token beginToken, Token endToken) { + NodeList variables = makeNodeList(count, null, endToken, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(type, modifiers, variables)); + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList initializers = popNode(); + NodeList formalParameters = popNode(); + Expression name = popNode(); + TypeAnnotation returnType = popNode(); + Modifiers modifiers = popNode(); + pushNode(new FunctionExpression(name, formalParameters, body, returnType, + modifiers, initializers, getOrSet)); + } + + void handleLiteralMap(int count, Token beginToken, Token constKeyword, + Token endToken) { + NodeList entries = makeNodeList(count, beginToken, endToken, ','); + NodeList typeArguments = popNode(); + pushNode(new LiteralMap(typeArguments, entries, constKeyword)); + } + + void endLiteralMapEntry(Token colon, Token endToken) { + Expression value = popNode(); + Expression key = popNode(); + if (key.asStringNode() == null) { + recoverableError('expected a string', node: key); + } + pushNode(new LiteralMapEntry(key, colon, value)); + } + + void handleLiteralList(int count, Token beginToken, Token constKeyword, + Token endToken) { + NodeList elements = makeNodeList(count, beginToken, endToken, ','); + pushNode(new LiteralList(popNode(), elements, constKeyword)); + } + + void handleIndexedExpression(Token openSquareBracket, + Token closeSquareBracket) { + NodeList arguments = + makeNodeList(1, openSquareBracket, closeSquareBracket, null); + Node receiver = popNode(); + Token token = + new StringToken(INDEX_INFO, '[]', openSquareBracket.charOffset); + Node selector = new Operator(token); + pushNode(new Send(receiver, selector, arguments)); + } + + void handleNewExpression(Token token) { + NodeList arguments = popNode(); + Node name = popNode(); + pushNode(new NewExpression(token, new Send(null, name, arguments))); + } + + void handleConstExpression(Token token) { + // [token] carries the 'const' information. + handleNewExpression(token); + } + + void handleOperatorName(Token operatorKeyword, Token token) { + Operator op = new Operator(token); + pushNode(new Send(new Identifier(operatorKeyword), op, null)); + } + + void handleNamedArgument(Token colon) { + Expression expression = popNode(); + Identifier name = popNode(); + pushNode(new NamedArgument(name, colon, expression)); + } + + void endOptionalFormalParameters(int count, + Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleFunctionTypedFormalParameter(Token endToken) { + NodeList formals = popNode(); + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushNode(null); // Signal "no type" to endFormalParameter. + pushNode(new FunctionExpression(name, formals, null, returnType, + Modifiers.EMPTY, null, null)); + } + + void handleValuedFormalParameter(Token equals, Token token) { + Expression defaultValue = popNode(); + Expression parameterName = popNode(); + pushNode(new SendSet(null, parameterName, new Operator(equals), + new NodeList.singleton(defaultValue))); + } + + void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) { + Block finallyBlock = null; + if (finallyKeyword != null) { + finallyBlock = popNode(); + } + NodeList catchBlocks = makeNodeList(catchCount, null, null, null); + Block tryBlock = popNode(); + pushNode(new TryStatement(tryBlock, catchBlocks, finallyBlock, + tryKeyword, finallyKeyword)); + } + + void handleCaseMatch(Token caseKeyword, Token colon) { + pushNode(new CaseMatch(caseKeyword, popNode(), colon)); + } + + void handleCatchBlock(Token onKeyword, Token catchKeyword) { + Block block = popNode(); + NodeList formals = catchKeyword != null? popNode(): null; + TypeAnnotation type = onKeyword != null ? popNode() : null; + pushNode(new CatchBlock(type, formals, block, onKeyword, catchKeyword)); + } + + void endSwitchStatement(Token switchKeyword, Token endToken) { + NodeList cases = popNode(); + ParenthesizedExpression expression = popNode(); + pushNode(new SwitchStatement(expression, cases, switchKeyword)); + } + + void endSwitchBlock(int caseCount, Token beginToken, Token endToken) { + Link<Node> caseNodes = const Link<Node>(); + while (caseCount > 0) { + SwitchCase switchCase = popNode(); + caseNodes = caseNodes.prepend(switchCase); + caseCount--; + } + pushNode(new NodeList(beginToken, caseNodes, endToken, null)); + } + + void handleSwitchCase(int labelCount, int caseCount, + Token defaultKeyword, int statementCount, + Token firstToken, Token endToken) { + NodeList statements = makeNodeList(statementCount, null, null, null); + NodeList labelsAndCases = + makeNodeList(labelCount + caseCount, null, null, null); + pushNode(new SwitchCase(labelsAndCases, defaultKeyword, statements, + firstToken)); + } + + void handleBreakStatement(bool hasTarget, + Token breakKeyword, Token endToken) { + Identifier target = null; + if (hasTarget) { + target = popNode(); + } + pushNode(new BreakStatement(target, breakKeyword, endToken)); + } + + void handleContinueStatement(bool hasTarget, + Token continueKeyword, Token endToken) { + Identifier target = null; + if (hasTarget) { + target = popNode(); + } + pushNode(new ContinueStatement(target, continueKeyword, endToken)); + } + + void handleEmptyStatement(Token token) { + pushNode(new EmptyStatement(token)); + } + + void endFactoryMethod(Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList formals = popNode(); + Node name = popNode(); + + // TODO(ahe): Move this parsing to the parser. + int modifierCount = 0; + Token modifier = beginToken; + if (modifier.stringValue == "external") { + handleModifier(modifier); + modifierCount++; + modifier = modifier.next; + } + if (modifier.stringValue == "const") { + handleModifier(modifier); + modifierCount++; + modifier = modifier.next; + } + assert(modifier.stringValue == "factory"); + handleModifier(modifier); + modifierCount++; + handleModifiers(modifierCount); + Modifiers modifiers = popNode(); + + pushNode(new FunctionExpression(name, formals, body, null, + modifiers, null, null)); + } + + void endForIn(Token beginToken, Token inKeyword, Token endToken) { + Statement body = popNode(); + Expression expression = popNode(); + Node declaredIdentifier = popNode(); + pushNode(new ForIn(declaredIdentifier, expression, body, + beginToken, inKeyword)); + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + NodeList arguments = popNode(); + if (arguments == null) { + // This is a constant expression. + Identifier name; + if (periodBeforeName != null) { + name = popNode(); + } + NodeList typeArguments = popNode(); + Node receiver = popNode(); + if (typeArguments != null) { + receiver = new TypeAnnotation(receiver, typeArguments); + recoverableError('Error: type arguments are not allowed here', + node: typeArguments); + } else { + Identifier identifier = receiver.asIdentifier(); + Send send = receiver.asSend(); + if (identifier != null) { + receiver = new Send(null, identifier); + } else if (send == null) { + internalError(node: receiver); + } + } + Send send = receiver; + if (name != null) { + send = new Send(receiver, name); + } + pushNode(send); + } else { + // This is a const constructor call. + endConstructorReference(beginToken, periodBeforeName, endToken); + Node constructor = popNode(); + pushNode(new NewExpression(beginToken, + new Send(null, constructor, arguments))); + } + } + + void handleAssertStatement(Token assertKeyword, Token semicolonToken) { + NodeList arguments = popNode(); + Node selector = new Identifier(assertKeyword); + Node send = new Send(null, selector, arguments); + pushNode(new ExpressionStatement(send, semicolonToken)); + } + + void endUnamedFunction(Token token) { + Statement body = popNode(); + NodeList formals = popNode(); + pushNode(new FunctionExpression(null, formals, body, null, + Modifiers.EMPTY, null, null)); + } + + void handleIsOperator(Token operathor, Token not, Token endToken) { + TypeAnnotation type = popNode(); + Expression expression = popNode(); + Node argument; + if (not != null) { + argument = new Send.prefix(type, new Operator(not)); + } else { + argument = type; + } + + NodeList arguments = new NodeList.singleton(argument); + pushNode(new Send(expression, new Operator(operathor), arguments)); + } + + void handleLabel(Token colon) { + Identifier name = popNode(); + pushNode(new Label(name, colon)); + } + + void endLabeledStatement(int labelCount) { + Statement statement = popNode(); + NodeList labels = makeNodeList(labelCount, null, null, null); + pushNode(new LabeledStatement(labels, statement)); + } + + void log(message) { + listener.log(message); + } + + void internalError({Token token, Node node}) { + // TODO(ahe): This should call listener.internalError. + Spannable spannable = (token == null) ? node : token; + throw new SpannableAssertionFailure(spannable, 'internal error in parser'); + } +} + +class PartialFunctionElement extends FunctionElementX { + final Token beginToken; + final Token getOrSet; + final Token endToken; + + PartialFunctionElement(SourceString name, + Token this.beginToken, + Token this.getOrSet, + Token this.endToken, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : super(name, kind, modifiers, enclosing); + + FunctionExpression parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + parseFunction(Parser p) { + if (isMember() && modifiers.isFactory()) { + p.parseFactoryMethod(beginToken); + } else { + p.parseFunction(beginToken, getOrSet); + } + } + cachedNode = parse(listener, getCompilationUnit(), parseFunction); + return cachedNode; + } + + Token position() { + return findMyName(beginToken); + } + + PartialFunctionElement cloneTo(Element enclosing, + DiagnosticListener listener) { + if (patch != null) { + listener.cancel("Cloning a patched function.", element: this); + } + PartialFunctionElement result = new PartialFunctionElement( + name, beginToken, getOrSet, endToken, kind, modifiers, enclosing); + return result; + } +} + +class PartialFieldListElement extends VariableListElementX { + final Token beginToken; + final Token endToken; + + PartialFieldListElement(Token this.beginToken, + Token this.endToken, + Modifiers modifiers, + Element enclosing) + : super(ElementKind.VARIABLE_LIST, modifiers, enclosing); + + VariableDefinitions parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + getCompilationUnit(), + (p) => p.parseVariablesDeclaration(beginToken)); + if (!cachedNode.modifiers.isVar() && + !cachedNode.modifiers.isFinal() && + !cachedNode.modifiers.isConst() && + cachedNode.type == null) { + listener.cancel('A field declaration must start with var, final, ' + 'const, or a type annotation.', + node: cachedNode); + } + return cachedNode; + } + + Token position() => beginToken; // findMyName doesn't work. I'm nameless. + + PartialFieldListElement cloneTo(Element enclosing, + DiagnosticListener listener) { + PartialFieldListElement result = new PartialFieldListElement( + beginToken, endToken, modifiers, enclosing); + return result; + } +} + +class PartialTypedefElement extends TypedefElementX { + final Token token; + + PartialTypedefElement(SourceString name, Element enclosing, this.token) + : super(name, enclosing); + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + getCompilationUnit(), + (p) => p.parseTopLevelDeclaration(token)); + return cachedNode; + } + + position() => findMyName(token); + + PartialTypedefElement cloneTo(Element enclosing, + DiagnosticListener listener) { + PartialTypedefElement result = + new PartialTypedefElement(name, enclosing, token); + return result; + } +} + +/// A [MetadataAnnotation] which is constructed on demand. +class PartialMetadataAnnotation extends MetadataAnnotationX { + final Token beginToken; + final Token tokenAfterEndToken; + Expression cachedNode; + Constant value; + + PartialMetadataAnnotation(this.beginToken, this.tokenAfterEndToken); + + Token get endToken { + Token token = beginToken; + while (token.kind != EOF_TOKEN) { + if (identical(token.next, tokenAfterEndToken)) return token; + token = token.next; + } + } + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + annotatedElement.getCompilationUnit(), + (p) => p.parseMetadata(beginToken)); + return cachedNode; + } +} + +Node parse(DiagnosticListener diagnosticListener, + CompilationUnitElement element, + doParse(Parser parser)) { + NodeListener listener = new NodeListener(diagnosticListener, element); + doParse(new Parser(listener)); + Node node = listener.popNode(); + assert(listener.nodes.isEmpty); + return node; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart new file mode 100644 index 0000000..5a85e00 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart
@@ -0,0 +1,2231 @@ +// 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. + +part of scanner; + +/** + * An event generating parser of Dart programs. This parser expects + * all tokens in a linked list (aka a token stream). + * + * The class [Scanner] is used to generate a token stream. See the + * file scanner.dart. + * + * Subclasses of the class [Listener] are used to listen to events. + * + * Most methods of this class belong in one of two major categories: + * parse metods and peek methods. Parse methods all have the prefix + * parse, and peek methods all have the prefix peek. + * + * Parse methods generate events (by calling methods on [listener]) + * and return the next token to parse. Peek methods do not generate + * events (except for errors) and may return null. + * + * Parse methods are generally named parseGrammarProductionSuffix. The + * suffix can be one of "opt", or "star". "opt" means zero or one + * matches, "star" means zero or more matches. For example, + * [parseMetadataStar] corresponds to this grammar snippet: [: + * metadata* :], and [parseTypeOpt] corresponds to: [: type? :]. + */ +class Parser { + final Listener listener; + bool mayParseFunctionExpressions = true; + + Parser(this.listener); + + Token parseUnit(Token token) { + listener.beginCompilationUnit(token); + int count = 0; + while (!identical(token.kind, EOF_TOKEN)) { + token = parseTopLevelDeclaration(token); + listener.endTopLevelDeclaration(token); + count++; + } + listener.endCompilationUnit(count, token); + return token; + } + + Token parseTopLevelDeclaration(Token token) { + token = parseMetadataStar(token); + final String value = token.stringValue; + if (identical(value, 'interface')) { + return parseInterface(token); + } else if ((identical(value, 'abstract')) || (identical(value, 'class'))) { + return parseClass(token); + } else if (identical(value, 'typedef')) { + return parseTypedef(token); + } else if (identical(value, '#')) { + return parseScriptTags(token); + } else if (identical(value, 'library')) { + return parseLibraryName(token); + } else if (identical(value, 'import')) { + return parseImport(token); + } else if (identical(value, 'export')) { + return parseExport(token); + } else if (identical(value, 'part')) { + return parsePartOrPartOf(token); + } else { + return parseTopLevelMember(token); + } + } + + /// library qualified ';' + Token parseLibraryName(Token token) { + Token libraryKeyword = token; + listener.beginLibraryName(libraryKeyword); + assert(optional('library', token)); + token = parseQualified(token.next); + Token semicolon = token; + token = expect(';', token); + listener.endLibraryName(libraryKeyword, semicolon); + return token; + } + + /// import uri (as identifier)? combinator* ';' + Token parseImport(Token token) { + Token importKeyword = token; + listener.beginImport(importKeyword); + assert(optional('import', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + Token asKeyword; + if (optional('as', token)) { + asKeyword = token; + token = parseIdentifier(token.next); + } + token = parseCombinators(token); + Token semicolon = token; + token = expect(';', token); + listener.endImport(importKeyword, asKeyword, semicolon); + return token; + } + + /// export uri combinator* ';' + Token parseExport(Token token) { + Token exportKeyword = token; + listener.beginExport(exportKeyword); + assert(optional('export', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + token = parseCombinators(token); + Token semicolon = token; + token = expect(';', token); + listener.endExport(exportKeyword, semicolon); + return token; + } + + Token parseCombinators(Token token) { + listener.beginCombinators(token); + int count = 0; + while (true) { + String value = token.stringValue; + if (identical('hide', value)) { + token = parseHide(token); + } else if (identical('show', value)) { + token = parseShow(token); + } else { + listener.endCombinators(count); + return token; + } + count++; + } + } + + /// hide identifierList + Token parseHide(Token token) { + Token hideKeyword = token; + listener.beginHide(hideKeyword); + assert(optional('hide', token)); + token = parseIdentifierList(token.next); + listener.endHide(hideKeyword); + return token; + } + + /// show identifierList + Token parseShow(Token token) { + Token showKeyword = token; + listener.beginShow(showKeyword); + assert(optional('show', token)); + token = parseIdentifierList(token.next); + listener.endShow(showKeyword); + return token; + } + + /// identifier (, identifier)* + Token parseIdentifierList(Token token) { + listener.beginIdentifierList(token); + token = parseIdentifier(token); + int count = 1; + while (optional(',', token)) { + token = parseIdentifier(token.next); + count++; + } + listener.endIdentifierList(count); + return token; + } + + /// type (, type)* + Token parseTypeList(Token token) { + listener.beginTypeList(token); + token = parseType(token); + int count = 1; + while (optional(',', token)) { + token = parseType(token.next); + count++; + } + listener.endTypeList(count); + return token; + } + + Token parsePartOrPartOf(Token token) { + assert(optional('part', token)); + if (optional('of', token.next)) { + return parsePartOf(token); + } else { + return parsePart(token); + } + } + + Token parsePart(Token token) { + Token partKeyword = token; + listener.beginPart(token); + assert(optional('part', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + Token semicolon = token; + token = expect(';', token); + listener.endPart(partKeyword, semicolon); + return token; + } + + Token parsePartOf(Token token) { + listener.beginPartOf(token); + assert(optional('part', token)); + assert(optional('of', token.next)); + Token partKeyword = token; + token = parseQualified(token.next.next); + Token semicolon = token; + token = expect(';', token); + listener.endPartOf(partKeyword, semicolon); + return token; + } + + Token parseMetadataStar(Token token) { + while (optional('@', token)) { + token = parseMetadata(token); + } + return token; + } + + /** + * Parse + * [: '@' qualified (‘.’ identifier)? (arguments)? :] + */ + Token parseMetadata(Token token) { + listener.beginMetadata(token); + Token atToken = token; + assert(optional('@', token)); + token = parseIdentifier(token.next); + token = parseQualifiedRestOpt(token); + token = parseTypeArgumentsOpt(token); + Token period = null; + if (optional('.', token)) { + period = token; + token = parseIdentifier(token.next); + } + token = parseArgumentsOpt(token); + listener.endMetadata(atToken, period, token); + return token; + } + + Token parseInterface(Token token) { + Token interfaceKeyword = token; + listener.beginInterface(token); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + int supertypeCount = 0; + Token extendsKeyword = null; + if (optional('extends', token)) { + extendsKeyword = token; + do { + token = parseType(token.next); + ++supertypeCount; + } while (optional(',', token)); + } + token = parseDefaultClauseOpt(token); + token = parseInterfaceBody(token); + listener.endInterface(supertypeCount, interfaceKeyword, + extendsKeyword, token); + return token.next; + } + + Token parseInterfaceBody(Token token) { + return parseClassBody(token); + } + + Token parseTypedef(Token token) { + Token typedefKeyword = token; + if (optional('=', peekAfterType(token.next))) { + listener.beginNamedMixinApplication(token); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + token = expect('=', token); + token = parseModifiers(token); + token = parseMixinApplication(token); + Token implementsKeyword = null; + if (optional('implements', token)) { + implementsKeyword = token; + token = parseTypeList(token.next); + } + listener.endNamedMixinApplication( + typedefKeyword, implementsKeyword, token); + } else { + listener.beginFunctionTypeAlias(token); + token = parseReturnTypeOpt(token.next); + token = parseIdentifier(token); + token = parseTypeVariablesOpt(token); + token = parseFormalParameters(token); + listener.endFunctionTypeAlias(typedefKeyword, token); + } + return expect(';', token); + } + + Token parseMixinApplication(Token token) { + listener.beginMixinApplication(token); + token = parseType(token); + token = expect('with', token); + token = parseTypeList(token); + listener.endMixinApplication(); + return token; + } + + Token parseReturnTypeOpt(Token token) { + if (identical(token.stringValue, 'void')) { + listener.handleVoidKeyword(token); + return token.next; + } else { + return parseTypeOpt(token); + } + } + + Token parseFormalParametersOpt(Token token) { + if (optional('(', token)) { + return parseFormalParameters(token); + } else { + listener.handleNoFormalParameters(token); + return token; + } + } + + Token parseFormalParameters(Token token) { + Token begin = token; + listener.beginFormalParameters(begin); + expect('(', token); + int parameterCount = 0; + if (optional(')', token.next)) { + listener.endFormalParameters(parameterCount, begin, token.next); + return token.next.next; + } + do { + ++parameterCount; + token = token.next; + String value = token.stringValue; + if (identical(value, '[')) { + token = parseOptionalFormalParameters(token, false); + break; + } else if (identical(value, '{')) { + token = parseOptionalFormalParameters(token, true); + break; + } + token = parseFormalParameter(token); + } while (optional(',', token)); + listener.endFormalParameters(parameterCount, begin, token); + return expect(')', token); + } + + Token parseFormalParameter(Token token) { + listener.beginFormalParameter(token); + token = parseModifiers(token); + // TODO(ahe): Validate that there are formal parameters if void. + token = parseReturnTypeOpt(token); + Token thisKeyword = null; + if (optional('this', token)) { + thisKeyword = token; + // TODO(ahe): Validate field initializers are only used in + // constructors, and not for function-typed arguments. + token = expect('.', token.next); + } + token = parseIdentifier(token); + if (optional('(', token)) { + token = parseFormalParameters(token); + listener.handleFunctionTypedFormalParameter(token); + } + String value = token.stringValue; + if ((identical('=', value)) || (identical(':', value))) { + // TODO(ahe): Validate that these are only used for optional parameters. + Token equal = token; + token = parseExpression(token.next); + listener.handleValuedFormalParameter(equal, token); + } + listener.endFormalParameter(thisKeyword); + return token; + } + + Token parseOptionalFormalParameters(Token token, bool isNamed) { + Token begin = token; + listener.beginOptionalFormalParameters(begin); + assert((isNamed && optional('{', token)) || optional('[', token)); + int parameterCount = 0; + do { + token = token.next; + token = parseFormalParameter(token); + ++parameterCount; + } while (optional(',', token)); + listener.endOptionalFormalParameters(parameterCount, begin, token); + if (isNamed) { + return expect('}', token); + } else { + return expect(']', token); + } + } + + Token parseTypeOpt(Token token) { + String value = token.stringValue; + if (!identical(value, 'this')) { + Token peek = peekAfterExpectedType(token); + if (peek.isIdentifier() || optional('this', peek)) { + return parseType(token); + } + } + listener.handleNoType(token); + return token; + } + + bool isValidTypeReference(Token token) { + final kind = token.kind; + if (identical(kind, IDENTIFIER_TOKEN)) return true; + if (identical(kind, KEYWORD_TOKEN)) { + Keyword keyword = token.value; + String value = keyword.stringValue; + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + return keyword.isPseudo + || (identical(value, 'dynamic')) + || (identical(value, 'Dynamic')) + || (identical(value, 'void')); + } + return false; + } + + Token parseDefaultClauseOpt(Token token) { + if (isDefaultKeyword(token)) { + // TODO(ahe): Remove support for 'factory' in this position. + Token defaultKeyword = token; + listener.beginDefaultClause(defaultKeyword); + token = parseIdentifier(token.next); + token = parseQualifiedRestOpt(token); + token = parseTypeVariablesOpt(token); + listener.endDefaultClause(defaultKeyword); + } else { + listener.handleNoDefaultClause(token); + } + return token; + } + + Token parseQualified(Token token) { + token = parseIdentifier(token); + while (optional('.', token)) { + token = parseQualifiedRest(token); + } + return token; + } + + Token parseQualifiedRestOpt(Token token) { + if (optional('.', token)) { + return parseQualifiedRest(token); + } else { + return token; + } + } + + Token parseQualifiedRest(Token token) { + assert(optional('.', token)); + Token period = token; + token = parseIdentifier(token.next); + listener.handleQualified(period); + return token; + } + + bool isDefaultKeyword(Token token) { + String value = token.stringValue; + if (identical(value, 'default')) return true; + if (identical(value, 'factory')) { + listener.recoverableError("expected 'default'", token: token); + return true; + } + return false; + } + + Token skipBlock(Token token) { + if (!optional('{', token)) { + return listener.expectedBlockToSkip(token); + } + BeginGroupToken beginGroupToken = token; + Token endGroup = beginGroupToken.endGroup; + if (endGroup == null) { + return listener.unmatched(beginGroupToken); + } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { + return listener.unmatched(beginGroupToken); + } + return beginGroupToken.endGroup; + } + + Token parseClass(Token token) { + Token begin = token; + listener.beginClassDeclaration(token); + int modifierCount = 0; + if (optional('abstract', token)) { + listener.handleModifier(token); + modifierCount++; + token = token.next; + } + listener.handleModifiers(modifierCount); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + Token extendsKeyword; + if (optional('extends', token)) { + extendsKeyword = token; + if (optional('with', peekAfterType(token.next))) { + token = parseMixinApplication(token.next); + } else { + token = parseType(token.next); + } + } else { + extendsKeyword = null; + listener.handleNoType(token); + } + Token implementsKeyword; + int interfacesCount = 0; + if (optional('implements', token)) { + implementsKeyword = token; + do { + token = parseType(token.next); + ++interfacesCount; + } while (optional(',', token)); + } + token = parseClassBody(token); + listener.endClassDeclaration(interfacesCount, begin, extendsKeyword, + implementsKeyword, token); + return token.next; + } + + Token parseStringPart(Token token) { + if (identical(token.kind, STRING_TOKEN)) { + listener.handleStringPart(token); + return token.next; + } else { + return listener.expected('string', token); + } + } + + Token parseIdentifier(Token token) { + if (token.isIdentifier()) { + listener.handleIdentifier(token); + } else { + listener.expectedIdentifier(token); + } + return token.next; + } + + Token expect(String string, Token token) { + if (!identical(string, token.stringValue)) { + return listener.expected(string, token); + } + return token.next; + } + + Token parseTypeVariable(Token token) { + listener.beginTypeVariable(token); + token = parseIdentifier(token); + if (optional('extends', token)) { + token = parseType(token.next); + } else { + listener.handleNoType(token); + } + listener.endTypeVariable(token); + return token; + } + + /** + * Returns true if the stringValue of the [token] is [value]. + */ + bool optional(String value, Token token) { + return identical(value, token.stringValue); + } + + /** + * Returns true if the stringValue of the [token] is either [value1], + * [value2], [value3], or [value4]. + */ + bool isOneOf4(Token token, + String value1, String value2, String value3, String value4) { + String stringValue = token.stringValue; + return identical(value1, stringValue) || + identical(value2, stringValue) || + identical(value3, stringValue) || + identical(value4, stringValue); + } + + bool notEofOrValue(String value, Token token) { + return !identical(token.kind, EOF_TOKEN) && + !identical(value, token.stringValue); + } + + Token parseType(Token token) { + Token begin = token; + if (isValidTypeReference(token)) { + token = parseIdentifier(token); + token = parseQualifiedRestOpt(token); + } else { + token = listener.expectedType(token); + } + token = parseTypeArgumentsOpt(token); + listener.endType(begin, token); + return token; + } + + Token parseTypeArgumentsOpt(Token token) { + return parseStuff(token, + (t) => listener.beginTypeArguments(t), + (t) => parseType(t), + (c, bt, et) => listener.endTypeArguments(c, bt, et), + (t) => listener.handleNoTypeArguments(t)); + } + + Token parseTypeVariablesOpt(Token token) { + return parseStuff(token, + (t) => listener.beginTypeVariables(t), + (t) => parseTypeVariable(t), + (c, bt, et) => listener.endTypeVariables(c, bt, et), + (t) => listener.handleNoTypeVariables(t)); + } + + // TODO(ahe): Clean this up. + Token parseStuff(Token token, Function beginStuff, Function stuffParser, + Function endStuff, Function handleNoStuff) { + if (optional('<', token)) { + Token begin = token; + beginStuff(begin); + int count = 0; + do { + token = stuffParser(token.next); + ++count; + } while (optional(',', token)); + Token next = token.next; + if (identical(token.stringValue, '>>')) { + token = new Token(GT_INFO, token.charOffset); + token.next = new Token(GT_INFO, token.charOffset + 1); + token.next.next = next; + } else if (identical(token.stringValue, '>>>')) { + token = new Token(GT_INFO, token.charOffset); + token.next = new Token(GT_GT_INFO, token.charOffset + 1); + token.next.next = next; + } + endStuff(count, begin, token); + return expect('>', token); + } + handleNoStuff(token); + return token; + } + + Token parseTopLevelMember(Token token) { + Token start = token; + listener.beginTopLevelMember(token); + + Link<Token> identifiers = findMemberName(token); + if (identifiers.isEmpty) { + return listener.unexpected(start); + } + Token name = identifiers.head; + identifiers = identifiers.tail; + Token getOrSet; + if (!identifiers.isEmpty) { + String value = identifiers.head.stringValue; + if ((identical(value, 'get')) || (identical(value, 'set'))) { + getOrSet = identifiers.head; + identifiers = identifiers.tail; + } + } + Token type; + if (!identifiers.isEmpty) { + if (isValidTypeReference(identifiers.head)) { + type = identifiers.head; + identifiers = identifiers.tail; + } + } + parseModifierList(identifiers.reverse()); + if (type == null) { + listener.handleNoType(token); + } else { + parseReturnTypeOpt(type); + } + token = parseIdentifier(name); + + bool isField; + while (true) { + // Loop to allow the listener to rewrite the token stream for + // error handling. + final String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '{')) + || (identical(value, '=>'))) { + isField = false; + break; + } else if ((identical(value, '=')) || (identical(value, ','))) { + isField = true; + break; + } else if (identical(value, ';')) { + if (getOrSet != null) { + // If we found a "get" keyword, this must be an abstract + // getter. + isField = (!identical(getOrSet.stringValue, 'get')); + // TODO(ahe): This feels like a hack. + } else { + isField = true; + } + break; + } else { + token = listener.unexpected(token); + if (identical(token.kind, EOF_TOKEN)) { + // TODO(ahe): This is a hack. It would be better to tell the + // listener more explicitly that it must pop an identifier. + listener.endTopLevelFields(1, start, token); + return token; + } + } + } + if (isField) { + int fieldCount = 1; + token = parseVariableInitializerOpt(token); + while (optional(',', token)) { + token = parseIdentifier(token.next); + token = parseVariableInitializerOpt(token); + ++fieldCount; + } + expectSemicolon(token); + listener.endTopLevelFields(fieldCount, start, token); + } else { + token = parseFormalParametersOpt(token); + token = parseFunctionBody(token, false); + listener.endTopLevelMethod(start, getOrSet, token); + } + return token.next; + } + + Link<Token> findMemberName(Token token) { + Token start = token; + Link<Token> identifiers = const Link<Token>(); + while (!identical(token.kind, EOF_TOKEN)) { + String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '{')) + || (identical(value, '=>'))) { + // A method. + return identifiers; + } else if ((identical(value, '=')) || (identical(value, ';')) + || (identical(value, ','))) { + // A field or abstract getter. + return identifiers; + } + identifiers = identifiers.prepend(token); + if (isValidTypeReference(token)) { + // type ... + if (optional('.', token.next)) { + // type '.' ... + if (token.next.next.isIdentifier()) { + // type '.' identifier + token = token.next.next; + } + } + if (optional('<', token.next)) { + if (token.next is BeginGroupToken) { + BeginGroupToken beginGroup = token.next; + token = beginGroup.endGroup; + } + } + } + token = token.next; + } + return listener.expectedDeclaration(start); + } + + Token parseVariableInitializerOpt(Token token) { + if (optional('=', token)) { + Token assignment = token; + listener.beginInitializer(token); + token = parseExpression(token.next); + listener.endInitializer(assignment); + } + return token; + } + + Token parseInitializersOpt(Token token) { + if (optional(':', token)) { + return parseInitializers(token); + } else { + listener.handleNoInitializers(); + return token; + } + } + + Token parseInitializers(Token token) { + Token begin = token; + listener.beginInitializers(begin); + expect(':', token); + int count = 0; + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = false; + do { + token = parseExpression(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.endInitializers(count, begin, token); + return token; + } + + Token parseScriptTags(Token token) { + Token begin = token; + listener.beginScriptTag(token); + token = parseIdentifier(token.next); + token = expect('(', token); + token = parseLiteralStringOrRecoverExpression(token); + bool hasPrefix = false; + if (optional(',', token)) { + hasPrefix = true; + token = parseIdentifier(token.next); + token = expect(':', token); + token = parseLiteralStringOrRecoverExpression(token); + } + token = expect(')', token); + listener.endScriptTag(hasPrefix, begin, token); + return expectSemicolon(token); + } + + Token parseLiteralStringOrRecoverExpression(Token token) { + if (identical(token.kind, STRING_TOKEN)) { + return parseLiteralString(token); + } else { + listener.recoverableError("unexpected", token: token); + return parseExpression(token); + } + } + + Token expectSemicolon(Token token) { + return expect(';', token); + } + + bool isModifier(Token token) { + final String value = token.stringValue; + return (identical('final', value)) || + (identical('var', value)) || + (identical('const', value)) || + (identical('abstract', value)) || + (identical('static', value)) || + (identical('external', value)); + } + + Token parseModifier(Token token) { + assert(isModifier(token)); + listener.handleModifier(token); + return token.next; + } + + void parseModifierList(Link<Token> tokens) { + int count = 0; + for (; !tokens.isEmpty; tokens = tokens.tail) { + Token token = tokens.head; + if (isModifier(token)) { + parseModifier(token); + } else { + listener.unexpected(token); + } + count++; + } + listener.handleModifiers(count); + } + + Token parseModifiers(Token token) { + int count = 0; + while (identical(token.kind, KEYWORD_TOKEN)) { + if (!isModifier(token)) + break; + token = parseModifier(token); + count++; + } + listener.handleModifiers(count); + return token; + } + + Token peekAfterType(Token token) { + // TODO(ahe): Also handle var? + // We are looking at "identifier ...". + Token peek = token.next; + if (identical(peek.kind, PERIOD_TOKEN)) { + if (peek.next.isIdentifier()) { + // Look past a library prefix. + peek = peek.next.next; + } + } + // We are looking at "qualified ...". + if (identical(peek.kind, LT_TOKEN)) { + // Possibly generic type. + // We are looking at "qualified '<'". + BeginGroupToken beginGroupToken = peek; + Token gtToken = beginGroupToken.endGroup; + if (gtToken != null) { + // We are looking at "qualified '<' ... '>' ...". + return gtToken.next; + } + } + return peek; + } + + /** + * Returns the token after the type which is expected to begin at [token]. + * If [token] is not the start of a type, [Listener.unexpectedType] is called. + */ + Token peekAfterExpectedType(Token token) { + if (!identical('void', token.stringValue) && !token.isIdentifier()) { + return listener.expectedType(token); + } + return peekAfterType(token); + } + + Token parseClassBody(Token token) { + Token begin = token; + listener.beginClassBody(token); + if (!optional('{', token)) { + token = listener.expectedClassBody(token); + } + token = token.next; + int count = 0; + while (notEofOrValue('}', token)) { + token = parseMember(token); + ++count; + } + expect('}', token); + listener.endClassBody(count, begin, token); + return token; + } + + bool isGetOrSet(Token token) { + final String value = token.stringValue; + return (identical(value, 'get')) || (identical(value, 'set')); + } + + bool isFactoryDeclaration(Token token) { + if (optional('external', token)) token = token.next; + if (optional('const', token)) token = token.next; + return optional('factory', token); + } + + Token parseMember(Token token) { + token = parseMetadataStar(token); + String value = token.stringValue; + if (isFactoryDeclaration(token)) { + return parseFactoryMethod(token); + } + Token start = token; + listener.beginMember(token); + + Link<Token> identifiers = findMemberName(token); + if (identifiers.isEmpty) { + return listener.unexpected(start); + } + Token name = identifiers.head; + identifiers = identifiers.tail; + if (!identifiers.isEmpty) { + if (optional('operator', identifiers.head)) { + name = identifiers.head; + identifiers = identifiers.tail; + } + } + Token getOrSet; + if (!identifiers.isEmpty) { + if (isGetOrSet(identifiers.head)) { + getOrSet = identifiers.head; + identifiers = identifiers.tail; + } + } + Token type; + if (!identifiers.isEmpty) { + if (isValidTypeReference(identifiers.head)) { + type = identifiers.head; + identifiers = identifiers.tail; + } + } + parseModifierList(identifiers.reverse()); + if (type == null) { + listener.handleNoType(token); + } else { + parseReturnTypeOpt(type); + } + + if (optional('operator', name)) { + token = parseOperatorName(name); + } else { + token = parseIdentifier(name); + } + bool isField; + while (true) { + // Loop to allow the listener to rewrite the token stream for + // error handling. + final String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '.')) + || (identical(value, '{')) || (identical(value, '=>'))) { + isField = false; + break; + } else if (identical(value, ';')) { + if (getOrSet != null) { + // If we found a "get" keyword, this must be an abstract + // getter. + isField = (!identical(getOrSet.stringValue, 'get')); + // TODO(ahe): This feels like a hack. + } else { + isField = true; + } + break; + } else if ((identical(value, '=')) || (identical(value, ','))) { + isField = true; + break; + } else { + token = listener.unexpected(token); + if (identical(token.kind, EOF_TOKEN)) { + // TODO(ahe): This is a hack, see parseTopLevelMember. + listener.endFields(1, start, token); + return token; + } + } + } + if (isField) { + int fieldCount = 1; + token = parseVariableInitializerOpt(token); + if (getOrSet != null) { + listener.recoverableError("unexpected", token: getOrSet); + } + while (optional(',', token)) { + // TODO(ahe): Count these. + token = parseIdentifier(token.next); + token = parseVariableInitializerOpt(token); + ++fieldCount; + } + expectSemicolon(token); + listener.endFields(fieldCount, start, token); + } else { + token = parseQualifiedRestOpt(token); + token = parseFormalParametersOpt(token); + token = parseInitializersOpt(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endMethod(getOrSet, start, token); + } + return token.next; + } + + Token parseFactoryMethod(Token token) { + assert(isFactoryDeclaration(token)); + Token start = token; + if (identical(token.stringValue, 'external')) token = token.next; + Token constKeyword = null; + if (optional('const', token)) { + constKeyword = token; + token = token.next; + } + Token factoryKeyword = token; + listener.beginFactoryMethod(factoryKeyword); + token = token.next; // Skip 'factory'. + token = parseConstructorReference(token); + token = parseFormalParameters(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endFactoryMethod(start, token); + return token.next; + } + + Token parseOperatorName(Token token) { + assert(optional('operator', token)); + if (isUserDefinableOperator(token.next.stringValue)) { + Token operator = token; + token = token.next; + listener.handleOperatorName(operator, token); + return token.next; + } else { + return parseIdentifier(token); + } + } + + Token parseFunction(Token token, Token getOrSet) { + listener.beginFunction(token); + token = parseModifiers(token); + if (identical(getOrSet, token)) token = token.next; + if (optional('operator', token)) { + listener.handleNoType(token); + listener.beginFunctionName(token); + token = parseOperatorName(token); + } else { + token = parseReturnTypeOpt(token); + if (identical(getOrSet, token)) token = token.next; + listener.beginFunctionName(token); + if (optional('operator', token)) { + token = parseOperatorName(token); + } else { + token = parseIdentifier(token); + } + } + token = parseQualifiedRestOpt(token); + listener.endFunctionName(token); + token = parseFormalParametersOpt(token); + token = parseInitializersOpt(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endFunction(getOrSet, token); + return token.next; + } + + Token parseUnamedFunction(Token token) { + listener.beginUnamedFunction(token); + token = parseFormalParameters(token); + bool isBlock = optional('{', token); + token = parseFunctionBody(token, true); + listener.endUnamedFunction(token); + return isBlock ? token.next : token; + } + + Token parseFunctionDeclaration(Token token) { + listener.beginFunctionDeclaration(token); + token = parseFunction(token, null); + listener.endFunctionDeclaration(token); + return token; + } + + Token parseFunctionExpression(Token token) { + listener.beginFunction(token); + listener.handleModifiers(0); + token = parseReturnTypeOpt(token); + listener.beginFunctionName(token); + token = parseIdentifier(token); + listener.endFunctionName(token); + token = parseFormalParameters(token); + listener.handleNoInitializers(); + bool isBlock = optional('{', token); + token = parseFunctionBody(token, true); + listener.endFunction(null, token); + return isBlock ? token.next : token; + } + + Token parseConstructorReference(Token token) { + Token start = token; + listener.beginConstructorReference(start); + token = parseIdentifier(token); + token = parseQualifiedRestOpt(token); + token = parseTypeArgumentsOpt(token); + Token period = null; + if (optional('.', token)) { + period = token; + token = parseIdentifier(token.next); + } + listener.endConstructorReference(start, period, token); + return token; + } + + Token parseRedirectingFactoryBody(Token token) { + listener.beginRedirectingFactoryBody(token); + assert(optional('=', token)); + Token equals = token; + token = parseConstructorReference(token.next); + Token semicolon = token; + expectSemicolon(token); + listener.endRedirectingFactoryBody(equals, semicolon); + return token; + } + + Token parseFunctionBody(Token token, bool isExpression) { + if (optional(';', token)) { + listener.endFunctionBody(0, null, token); + return token; + } else if (optional('=>', token)) { + Token begin = token; + token = parseExpression(token.next); + if (!isExpression) { + expectSemicolon(token); + listener.endReturnStatement(true, begin, token); + } else { + listener.endReturnStatement(true, begin, null); + } + return token; + } + Token begin = token; + int statementCount = 0; + if (!optional('{', token)) { + return listener.expectedFunctionBody(token); + } + + listener.beginFunctionBody(begin); + token = token.next; + while (notEofOrValue('}', token)) { + token = parseStatement(token); + ++statementCount; + } + listener.endFunctionBody(statementCount, begin, token); + expect('}', token); + return token; + } + + Token parseStatement(Token token) { + final value = token.stringValue; + if (identical(token.kind, IDENTIFIER_TOKEN)) { + return parseExpressionStatementOrDeclaration(token); + } else if (identical(value, '{')) { + return parseBlock(token); + } else if (identical(value, 'return')) { + return parseReturnStatement(token); + } else if (identical(value, 'var') || identical(value, 'final')) { + return parseVariablesDeclaration(token); + } else if (identical(value, 'if')) { + return parseIfStatement(token); + } else if (identical(value, 'for')) { + return parseForStatement(token); + } else if (identical(value, 'throw')) { + return parseThrowStatement(token); + } else if (identical(value, 'void')) { + return parseExpressionStatementOrDeclaration(token); + } else if (identical(value, 'while')) { + return parseWhileStatement(token); + } else if (identical(value, 'do')) { + return parseDoWhileStatement(token); + } else if (identical(value, 'try')) { + return parseTryStatement(token); + } else if (identical(value, 'switch')) { + return parseSwitchStatement(token); + } else if (identical(value, 'break')) { + return parseBreakStatement(token); + } else if (identical(value, 'continue')) { + return parseContinueStatement(token); + } else if (identical(value, 'assert')) { + return parseAssertStatement(token); + } else if (identical(value, ';')) { + return parseEmptyStatement(token); + } else if (identical(value, 'const')) { + return parseExpressionStatementOrConstDeclaration(token); + } else if (token.isIdentifier()) { + return parseExpressionStatementOrDeclaration(token); + } else { + return parseExpressionStatement(token); + } + } + + Token parseReturnStatement(Token token) { + Token begin = token; + listener.beginReturnStatement(begin); + assert(identical('return', token.stringValue)); + token = token.next; + if (optional(';', token)) { + listener.endReturnStatement(false, begin, token); + } else { + token = parseExpression(token); + listener.endReturnStatement(true, begin, token); + } + return expectSemicolon(token); + } + + Token peekIdentifierAfterType(Token token) { + Token peek = peekAfterType(token); + if (peek != null && peek.isIdentifier()) { + // We are looking at "type identifier". + return peek; + } else { + return null; + } + } + + Token peekIdentifierAfterOptionalType(Token token) { + Token peek = peekIdentifierAfterType(token); + if (peek != null) { + // We are looking at "type identifier". + return peek; + } else if (token.isIdentifier()) { + // We are looking at "identifier". + return token; + } else { + return null; + } + } + + Token parseExpressionStatementOrDeclaration(Token token) { + assert(token.isIdentifier() || identical(token.stringValue, 'void')); + Token identifier = peekIdentifierAfterType(token); + if (identifier != null) { + assert(identifier.isIdentifier()); + Token afterId = identifier.next; + int afterIdKind = afterId.kind; + if (identical(afterIdKind, EQ_TOKEN) || + identical(afterIdKind, SEMICOLON_TOKEN) || + identical(afterIdKind, COMMA_TOKEN)) { + // We are looking at "type identifier" followed by '=', ';', ','. + return parseVariablesDeclaration(token); + } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) { + // We are looking at "type identifier '('". + BeginGroupToken beginParen = afterId; + Token endParen = beginParen.endGroup; + Token afterParens = endParen.next; + if (optional('{', afterParens) || optional('=>', afterParens)) { + // We are looking at "type identifier '(' ... ')'" followed + // by '=>' or '{'. + return parseFunctionDeclaration(token); + } + } + // Fall-through to expression statement. + } else { + if (optional(':', token.next)) { + return parseLabeledStatement(token); + } else if (optional('(', token.next)) { + BeginGroupToken begin = token.next; + String afterParens = begin.endGroup.next.stringValue; + if (identical(afterParens, '{') || identical(afterParens, '=>')) { + return parseFunctionDeclaration(token); + } + } + } + return parseExpressionStatement(token); + } + + Token parseExpressionStatementOrConstDeclaration(Token token) { + assert(identical(token.stringValue, 'const')); + if (isModifier(token.next)) { + return parseVariablesDeclaration(token); + } + Token identifier = peekIdentifierAfterOptionalType(token.next); + if (identifier != null) { + assert(identifier.isIdentifier()); + Token afterId = identifier.next; + int afterIdKind = afterId.kind; + if (identical(afterIdKind, EQ_TOKEN) || + identical(afterIdKind, SEMICOLON_TOKEN) || + identical(afterIdKind, COMMA_TOKEN)) { + // We are looking at "const type identifier" followed by '=', ';', or + // ','. + return parseVariablesDeclaration(token); + } + // Fall-through to expression statement. + } + return parseExpressionStatement(token); + } + + Token parseLabel(Token token) { + token = parseIdentifier(token); + Token colon = token; + token = expect(':', token); + listener.handleLabel(colon); + return token; + } + + Token parseLabeledStatement(Token token) { + int labelCount = 0; + do { + token = parseLabel(token); + labelCount++; + } while (token.isIdentifier() && optional(':', token.next)); + listener.beginLabeledStatement(token, labelCount); + token = parseStatement(token); + listener.endLabeledStatement(labelCount); + return token; + } + + Token parseExpressionStatement(Token token) { + listener.beginExpressionStatement(token); + token = parseExpression(token); + listener.endExpressionStatement(token); + return expectSemicolon(token); + } + + Token parseExpression(Token token) { + return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true); + } + + Token parseExpressionWithoutCascade(Token token) { + return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false); + } + + Token parseConditionalExpressionRest(Token token) { + assert(optional('?', token)); + Token question = token; + token = parseExpressionWithoutCascade(token.next); + Token colon = token; + token = expect(':', token); + token = parseExpressionWithoutCascade(token); + listener.handleConditionalExpression(question, colon); + return token; + } + + Token parsePrecedenceExpression(Token token, int precedence, + bool allowCascades) { + assert(precedence >= 1); + assert(precedence <= POSTFIX_PRECEDENCE); + token = parseUnaryExpression(token, allowCascades); + PrecedenceInfo info = token.info; + int tokenLevel = info.precedence; + for (int level = tokenLevel; level >= precedence; --level) { + while (identical(tokenLevel, level)) { + Token operator = token; + if (identical(tokenLevel, CASCADE_PRECEDENCE)) { + if (!allowCascades) { + return token; + } + token = parseCascadeExpression(token); + } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) { + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, level, allowCascades); + listener.handleAssignmentExpression(operator); + } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) { + if (identical(info, PERIOD_INFO)) { + // Left associative, so we recurse at the next higher + // precedence level. However, POSTFIX_PRECEDENCE is the + // highest level, so we just call parseUnaryExpression + // directly. + token = parseUnaryExpression(token.next, allowCascades); + listener.handleBinaryExpression(operator); + } else if ((identical(info, OPEN_PAREN_INFO)) || + (identical(info, OPEN_SQUARE_BRACKET_INFO))) { + token = parseArgumentOrIndexStar(token); + } else if ((identical(info, PLUS_PLUS_INFO)) || + (identical(info, MINUS_MINUS_INFO))) { + listener.handleUnaryPostfixAssignmentExpression(token); + token = token.next; + } else { + token = listener.unexpected(token); + } + } else if (identical(info, IS_INFO)) { + token = parseIsOperatorRest(token); + } else if (identical(info, AS_INFO)) { + token = parseAsOperatorRest(token); + } else if (identical(info, QUESTION_INFO)) { + token = parseConditionalExpressionRest(token); + } else { + // Left associative, so we recurse at the next higher + // precedence level. + token = parsePrecedenceExpression(token.next, level + 1, + allowCascades); + listener.handleBinaryExpression(operator); + } + info = token.info; + tokenLevel = info.precedence; + } + } + return token; + } + + Token parseCascadeExpression(Token token) { + listener.beginCascade(token); + assert(optional('..', token)); + Token cascadeOperator = token; + token = token.next; + if (optional('[', token)) { + token = parseArgumentOrIndexStar(token); + } else if (token.isIdentifier()) { + token = parseSend(token); + listener.handleBinaryExpression(cascadeOperator); + } else { + return listener.unexpected(token); + } + Token mark; + do { + mark = token; + if (optional('.', token)) { + Token period = token; + token = parseSend(token.next); + listener.handleBinaryExpression(period); + } + token = parseArgumentOrIndexStar(token); + } while (!identical(mark, token)); + + if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) { + Token assignment = token; + token = parseExpressionWithoutCascade(token.next); + listener.handleAssignmentExpression(assignment); + } + listener.endCascade(); + return token; + } + + Token parseUnaryExpression(Token token, bool allowCascades) { + String value = token.stringValue; + // Prefix: + if (identical(value, '+')) { + // Dart only allows "prefix plus" as an initial part of a + // decimal literal. We scan it as a separate token and let + // the parser listener combine it with the digits. + Token next = token.next; + if (identical(next.charOffset, token.charOffset + 1)) { + if (identical(next.kind, INT_TOKEN)) { + listener.handleLiteralInt(token); + return next.next; + } + if (identical(next.kind, DOUBLE_TOKEN)) { + listener.handleLiteralDouble(token); + return next.next; + } + } + listener.recoverableError("Unexpected token '+'", token: token); + return parsePrecedenceExpression(next, POSTFIX_PRECEDENCE, + allowCascades); + } else if ((identical(value, '!')) || + (identical(value, '-')) || + (identical(value, '~'))) { + Token operator = token; + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE, + allowCascades); + listener.handleUnaryPrefixExpression(operator); + } else if ((identical(value, '++')) || identical(value, '--')) { + // TODO(ahe): Validate this is used correctly. + Token operator = token; + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE, + allowCascades); + listener.handleUnaryPrefixAssignmentExpression(operator); + } else { + token = parsePrimary(token); + } + return token; + } + + Token parseArgumentOrIndexStar(Token token) { + while (true) { + if (optional('[', token)) { + Token openSquareBracket = token; + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + token = parseExpression(token.next); + mayParseFunctionExpressions = old; + listener.handleIndexedExpression(openSquareBracket, token); + token = expect(']', token); + } else if (optional('(', token)) { + token = parseArguments(token); + listener.endSend(token); + } else { + break; + } + } + return token; + } + + Token parsePrimary(Token token) { + final kind = token.kind; + if (identical(kind, IDENTIFIER_TOKEN)) { + return parseSendOrFunctionLiteral(token); + } else if (identical(kind, INT_TOKEN) + || identical(kind, HEXADECIMAL_TOKEN)) { + return parseLiteralInt(token); + } else if (identical(kind, DOUBLE_TOKEN)) { + return parseLiteralDouble(token); + } else if (identical(kind, STRING_TOKEN)) { + return parseLiteralString(token); + } else if (identical(kind, KEYWORD_TOKEN)) { + final value = token.stringValue; + if ((identical(value, 'true')) || (identical(value, 'false'))) { + return parseLiteralBool(token); + } else if (identical(value, 'null')) { + return parseLiteralNull(token); + } else if (identical(value, 'this')) { + return parseThisExpression(token); + } else if (identical(value, 'super')) { + return parseSuperExpression(token); + } else if (identical(value, 'new')) { + return parseNewExpression(token); + } else if (identical(value, 'const')) { + return parseConstExpression(token); + } else if (identical(value, 'void')) { + return parseFunctionExpression(token); + } else if (token.isIdentifier()) { + return parseSendOrFunctionLiteral(token); + } else { + return listener.expectedExpression(token); + } + } else if (identical(kind, OPEN_PAREN_TOKEN)) { + return parseParenthesizedExpressionOrFunctionLiteral(token); + } else if ((identical(kind, LT_TOKEN)) || + (identical(kind, OPEN_SQUARE_BRACKET_TOKEN)) || + (identical(kind, OPEN_CURLY_BRACKET_TOKEN)) || + identical(token.stringValue, '[]')) { + return parseLiteralListOrMap(token); + } else if (identical(kind, QUESTION_TOKEN)) { + return parseArgumentDefinitionTest(token); + } else { + return listener.expectedExpression(token); + } + } + + Token parseArgumentDefinitionTest(Token token) { + Token questionToken = token; + listener.beginArgumentDefinitionTest(questionToken); + assert(optional('?', token)); + token = parseIdentifier(token.next); + listener.endArgumentDefinitionTest(questionToken, token); + return token; + } + + Token parseParenthesizedExpressionOrFunctionLiteral(Token token) { + BeginGroupToken beginGroup = token; + int kind = beginGroup.endGroup.next.kind; + if (mayParseFunctionExpressions && + (identical(kind, FUNCTION_TOKEN) + || identical(kind, OPEN_CURLY_BRACKET_TOKEN))) { + return parseUnamedFunction(token); + } else { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + token = parseParenthesizedExpression(token); + mayParseFunctionExpressions = old; + return token; + } + } + + Token parseParenthesizedExpression(Token token) { + var begin = token; + token = expect('(', token); + token = parseExpression(token); + if (!identical(begin.endGroup, token)) { + listener.unexpected(token); + token = begin.endGroup; + } + listener.handleParenthesizedExpression(begin); + return expect(')', token); + } + + Token parseThisExpression(Token token) { + listener.handleThisExpression(token); + token = token.next; + if (optional('(', token)) { + // Constructor forwarding. + token = parseArguments(token); + listener.endSend(token); + } + return token; + } + + Token parseSuperExpression(Token token) { + listener.handleSuperExpression(token); + token = token.next; + if (optional('(', token)) { + // Super constructor. + token = parseArguments(token); + listener.endSend(token); + } + return token; + } + + Token parseLiteralListOrMap(Token token) { + Token constKeyword = null; + if (optional('const', token)) { + constKeyword = token; + token = token.next; + } + token = parseTypeArgumentsOpt(token); + Token beginToken = token; + int count = 0; + if (optional('{', token)) { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + if (optional('}', token.next)) { + token = token.next; + break; + } + token = parseMapLiteralEntry(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.handleLiteralMap(count, beginToken, constKeyword, token); + return expect('}', token); + } else if (optional('[', token)) { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + if (optional(']', token.next)) { + token = token.next; + break; + } + token = parseExpression(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.handleLiteralList(count, beginToken, constKeyword, token); + return expect(']', token); + } else if (optional('[]', token)) { + listener.handleLiteralList(0, token, constKeyword, token); + return token.next; + } else { + listener.unexpected(token); + } + } + + Token parseMapLiteralEntry(Token token) { + listener.beginLiteralMapEntry(token); + // Assume the listener rejects non-string keys. + token = parseExpression(token); + Token colon = token; + token = expect(':', token); + token = parseExpression(token); + listener.endLiteralMapEntry(colon, token); + return token; + } + + Token parseSendOrFunctionLiteral(Token token) { + if (!mayParseFunctionExpressions) return parseSend(token); + Token peek = peekAfterExpectedType(token); + if (identical(peek.kind, IDENTIFIER_TOKEN) && isFunctionDeclaration(peek.next)) { + return parseFunctionExpression(token); + } else if (isFunctionDeclaration(token.next)) { + return parseFunctionExpression(token); + } else { + return parseSend(token); + } + } + + bool isFunctionDeclaration(Token token) { + if (optional('(', token)) { + BeginGroupToken begin = token; + String afterParens = begin.endGroup.next.stringValue; + if (identical(afterParens, '{') || identical(afterParens, '=>')) { + return true; + } + } + return false; + } + + Token parseRequiredArguments(Token token) { + if (optional('(', token)) { + token = parseArguments(token); + } else { + listener.handleNoArguments(token); + token = listener.unexpected(token); + } + return token; + } + + Token parseNewExpression(Token token) { + Token newKeyword = token; + token = expect('new', token); + token = parseConstructorReference(token); + token = parseRequiredArguments(token); + listener.handleNewExpression(newKeyword); + return token; + } + + Token parseConstExpression(Token token) { + Token constKeyword = token; + token = expect('const', token); + final String value = token.stringValue; + if ((identical(value, '<')) || + (identical(value, '[')) || + (identical(value, '[]')) || + (identical(value, '{'))) { + return parseLiteralListOrMap(constKeyword); + } + token = parseConstructorReference(token); + token = parseRequiredArguments(token); + listener.handleConstExpression(constKeyword); + return token; + } + + Token parseLiteralInt(Token token) { + listener.handleLiteralInt(token); + return token.next; + } + + Token parseLiteralDouble(Token token) { + listener.handleLiteralDouble(token); + return token.next; + } + + Token parseLiteralString(Token token) { + token = parseSingleLiteralString(token); + int count = 1; + while (identical(token.kind, STRING_TOKEN)) { + token = parseSingleLiteralString(token); + count++; + } + if (count > 1) { + listener.handleStringJuxtaposition(count); + } + return token; + } + + /** + * Only called when [:token.kind === STRING_TOKEN:]. + */ + Token parseSingleLiteralString(Token token) { + listener.beginLiteralString(token); + // Parsing the prefix, for instance 'x of 'x${id}y${id}z' + token = token.next; + int interpolationCount = 0; + var kind = token.kind; + while (kind != EOF_TOKEN) { + if (identical(kind, STRING_INTERPOLATION_TOKEN)) { + // Parsing ${expression}. + token = token.next; + token = parseExpression(token); + token = expect('}', token); + } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) { + // Parsing $identifier. + token = token.next; + token = parseExpression(token); + } else { + break; + } + ++interpolationCount; + // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z' + token = parseStringPart(token); + kind = token.kind; + } + listener.endLiteralString(interpolationCount); + return token; + } + + Token parseLiteralBool(Token token) { + listener.handleLiteralBool(token); + return token.next; + } + + Token parseLiteralNull(Token token) { + listener.handleLiteralNull(token); + return token.next; + } + + Token parseSend(Token token) { + listener.beginSend(token); + token = parseIdentifier(token); + token = parseArgumentsOpt(token); + listener.endSend(token); + return token; + } + + Token parseArgumentsOpt(Token token) { + if (!optional('(', token)) { + listener.handleNoArguments(token); + return token; + } else { + return parseArguments(token); + } + } + + Token parseArguments(Token token) { + Token begin = token; + listener.beginArguments(begin); + assert(identical('(', token.stringValue)); + int argumentCount = 0; + if (optional(')', token.next)) { + listener.endArguments(argumentCount, begin, token.next); + return token.next.next; + } + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + Token colon = null; + if (optional(':', token.next.next)) { + token = parseIdentifier(token.next); + colon = token; + } + token = parseExpression(token.next); + if (colon != null) listener.handleNamedArgument(colon); + ++argumentCount; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.endArguments(argumentCount, begin, token); + return expect(')', token); + } + + Token parseIsOperatorRest(Token token) { + assert(optional('is', token)); + Token operator = token; + Token not = null; + if (optional('!', token.next)) { + token = token.next; + not = token; + } + token = parseType(token.next); + listener.handleIsOperator(operator, not, token); + String value = token.stringValue; + if (identical(value, 'is') || identical(value, 'as')) { + // The is- and as-operators cannot be chained, but they can take part of + // expressions like: foo is Foo || foo is Bar. + listener.unexpected(token); + } + return token; + } + + Token parseAsOperatorRest(Token token) { + assert(optional('as', token)); + Token operator = token; + token = parseType(token.next); + listener.handleAsOperator(operator, token); + String value = token.stringValue; + if (identical(value, 'is') || identical(value, 'as')) { + // The is- and as-operators cannot be chained. + listener.unexpected(token); + } + return token; + } + + Token parseVariablesDeclaration(Token token) { + return parseVariablesDeclarationMaybeSemicolon(token, true); + } + + Token parseVariablesDeclarationNoSemicolon(Token token) { + return parseVariablesDeclarationMaybeSemicolon(token, false); + } + + Token parseVariablesDeclarationMaybeSemicolon(Token token, + bool endWithSemicolon) { + int count = 1; + listener.beginVariablesDeclaration(token); + token = parseModifiers(token); + token = parseTypeOpt(token); + token = parseOptionallyInitializedIdentifier(token); + while (optional(',', token)) { + token = parseOptionallyInitializedIdentifier(token.next); + ++count; + } + if (endWithSemicolon) { + Token semicolon = token; + token = expectSemicolon(semicolon); + listener.endVariablesDeclaration(count, semicolon); + return token; + } else { + listener.endVariablesDeclaration(count, null); + return token; + } + } + + Token parseOptionallyInitializedIdentifier(Token token) { + listener.beginInitializedIdentifier(token); + token = parseIdentifier(token); + token = parseVariableInitializerOpt(token); + listener.endInitializedIdentifier(); + return token; + } + + Token parseIfStatement(Token token) { + Token ifToken = token; + listener.beginIfStatement(ifToken); + token = expect('if', token); + token = parseParenthesizedExpression(token); + token = parseStatement(token); + Token elseToken = null; + if (optional('else', token)) { + elseToken = token; + token = parseStatement(token.next); + } + listener.endIfStatement(ifToken, elseToken); + return token; + } + + Token parseForStatement(Token token) { + Token forToken = token; + listener.beginForStatement(forToken); + token = expect('for', token); + token = expect('(', token); + token = parseVariablesDeclarationOrExpressionOpt(token); + if (optional('in', token)) { + return parseForInRest(forToken, token); + } else { + return parseForRest(forToken, token); + } + } + + Token parseVariablesDeclarationOrExpressionOpt(Token token) { + final String value = token.stringValue; + if (identical(value, ';')) { + listener.handleNoExpression(token); + return token; + } else if ((identical(value, 'var')) || (identical(value, 'final'))) { + return parseVariablesDeclarationNoSemicolon(token); + } + Token identifier = peekIdentifierAfterType(token); + if (identifier != null) { + assert(identifier.isIdentifier()); + if (isOneOf4(identifier.next, '=', ';', ',', 'in')) { + return parseVariablesDeclarationNoSemicolon(token); + } + } + return parseExpression(token); + } + + Token parseForRest(Token forToken, Token token) { + token = expectSemicolon(token); + if (optional(';', token)) { + token = parseEmptyStatement(token); + } else { + token = parseExpressionStatement(token); + } + int expressionCount = 0; + while (true) { + if (optional(')', token)) break; + token = parseExpression(token); + ++expressionCount; + if (optional(',', token)) { + token = token.next; + } else { + break; + } + } + token = expect(')', token); + token = parseStatement(token); + listener.endForStatement(expressionCount, forToken, token); + return token; + } + + Token parseForInRest(Token forToken, Token token) { + assert(optional('in', token)); + Token inKeyword = token; + token = parseExpression(token.next); + token = expect(')', token); + token = parseStatement(token); + listener.endForIn(forToken, inKeyword, token); + return token; + } + + Token parseWhileStatement(Token token) { + Token whileToken = token; + listener.beginWhileStatement(whileToken); + token = expect('while', token); + token = parseParenthesizedExpression(token); + token = parseStatement(token); + listener.endWhileStatement(whileToken, token); + return token; + } + + Token parseDoWhileStatement(Token token) { + Token doToken = token; + listener.beginDoWhileStatement(doToken); + token = expect('do', token); + token = parseStatement(token); + Token whileToken = token; + token = expect('while', token); + token = parseParenthesizedExpression(token); + listener.endDoWhileStatement(doToken, whileToken, token); + return expectSemicolon(token); + } + + Token parseBlock(Token token) { + Token begin = token; + listener.beginBlock(begin); + int statementCount = 0; + token = expect('{', token); + while (notEofOrValue('}', token)) { + token = parseStatement(token); + ++statementCount; + } + listener.endBlock(statementCount, begin, token); + return expect('}', token); + } + + Token parseThrowStatement(Token token) { + Token throwToken = token; + listener.beginThrowStatement(throwToken); + token = expect('throw', token); + if (optional(';', token)) { + listener.endRethrowStatement(throwToken, token); + return token.next; + } else { + token = parseExpression(token); + listener.endThrowStatement(throwToken, token); + return expectSemicolon(token); + } + } + + Token parseTryStatement(Token token) { + assert(optional('try', token)); + Token tryKeyword = token; + listener.beginTryStatement(tryKeyword); + token = parseBlock(token.next); + int catchCount = 0; + + String value = token.stringValue; + while (identical(value, 'catch') || identical(value, 'on')) { + var onKeyword = null; + if (identical(value, 'on')) { + // on qualified catchPart? + onKeyword = token; + token = parseType(token.next); + value = token.stringValue; + } + Token catchKeyword = null; + if (identical(value, 'catch')) { + catchKeyword = token; + // TODO(ahe): Validate the "parameters". + token = parseFormalParameters(token.next); + } + token = parseBlock(token); + ++catchCount; + listener.handleCatchBlock(onKeyword, catchKeyword); + value = token.stringValue; // while condition + } + + Token finallyKeyword = null; + if (optional('finally', token)) { + finallyKeyword = token; + token = parseBlock(token.next); + listener.handleFinallyBlock(finallyKeyword); + } + listener.endTryStatement(catchCount, tryKeyword, finallyKeyword); + return token; + } + + Token parseSwitchStatement(Token token) { + assert(optional('switch', token)); + Token switchKeyword = token; + listener.beginSwitchStatement(switchKeyword); + token = parseParenthesizedExpression(token.next); + token = parseSwitchBlock(token); + listener.endSwitchStatement(switchKeyword, token); + return token.next; + } + + Token parseSwitchBlock(Token token) { + Token begin = token; + listener.beginSwitchBlock(begin); + token = expect('{', token); + int caseCount = 0; + while (!identical(token.kind, EOF_TOKEN)) { + if (optional('}', token)) { + break; + } + token = parseSwitchCase(token); + ++caseCount; + } + listener.endSwitchBlock(caseCount, begin, token); + expect('}', token); + return token; + } + + /** + * Peek after the following labels (if any). The following token + * is used to determine if the labels belong to a statement or a + * switch case. + */ + Token peekPastLabels(Token token) { + while (token.isIdentifier() && optional(':', token.next)) { + token = token.next.next; + } + return token; + } + + /** + * Parse a group of labels, cases and possibly a default keyword and + * the statements that they select. + */ + Token parseSwitchCase(Token token) { + Token begin = token; + Token defaultKeyword = null; + int expressionCount = 0; + int labelCount = 0; + Token peek = peekPastLabels(token); + while (true) { + // Loop until we find something that can't be part of a switch case. + String value = peek.stringValue; + if (identical(value, 'default')) { + while (!identical(token, peek)) { + token = parseLabel(token); + labelCount++; + } + defaultKeyword = token; + token = expect(':', token.next); + peek = token; + break; + } else if (identical(value, 'case')) { + while (!identical(token, peek)) { + token = parseLabel(token); + labelCount++; + } + Token caseKeyword = token; + token = parseExpression(token.next); + Token colonToken = token; + token = expect(':', token); + listener.handleCaseMatch(caseKeyword, colonToken); + expressionCount++; + peek = peekPastLabels(token); + } else { + if (expressionCount == 0) { + listener.expected("case", token); + } + break; + } + } + // Finally zero or more statements. + int statementCount = 0; + while (!identical(token.kind, EOF_TOKEN)) { + String value = peek.stringValue; + if ((identical(value, 'case')) || + (identical(value, 'default')) || + ((identical(value, '}')) && (identical(token, peek)))) { + // A label just before "}" will be handled as a statement error. + break; + } else { + token = parseStatement(token); + } + statementCount++; + peek = peekPastLabels(token); + } + listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword, + statementCount, begin, token); + return token; + } + + Token parseBreakStatement(Token token) { + assert(optional('break', token)); + Token breakKeyword = token; + token = token.next; + bool hasTarget = false; + if (token.isIdentifier()) { + token = parseIdentifier(token); + hasTarget = true; + } + listener.handleBreakStatement(hasTarget, breakKeyword, token); + return expectSemicolon(token); + } + + Token parseAssertStatement(Token token) { + Token assertKeyword = token; + token = expect('assert', token); + expect('(', token); + token = parseArguments(token); + listener.handleAssertStatement(assertKeyword, token); + return expectSemicolon(token); + } + + Token parseContinueStatement(Token token) { + assert(optional('continue', token)); + Token continueKeyword = token; + token = token.next; + bool hasTarget = false; + if (token.isIdentifier()) { + token = parseIdentifier(token); + hasTarget = true; + } + listener.handleContinueStatement(hasTarget, continueKeyword, token); + return expectSemicolon(token); + } + + Token parseEmptyStatement(Token token) { + listener.handleEmptyStatement(token); + return expectSemicolon(token); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart new file mode 100644 index 0000000..eca572b --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart
@@ -0,0 +1,14 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class ParserTask extends CompilerTask { + ParserTask(Compiler compiler) : super(compiler); + String get name => 'Parser'; + + Node parse(Element element) { + return measure(() => element.parseNode(compiler)); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart new file mode 100644 index 0000000..02f409a --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart
@@ -0,0 +1,130 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class PartialParser extends Parser { + PartialParser(Listener listener) : super(listener); + + Token parseClassBody(Token token) => skipClassBody(token); + + Token fullParseClassBody(Token token) => super.parseClassBody(token); + + Token parseExpression(Token token) => skipExpression(token); + + Token parseArgumentsOpt(Token token) { + // This method is overridden for two reasons: + // 1. Avoid generating events for arguments. + // 2. Avoid calling skip expression for each argument (which doesn't work). + if (optional('(', token)) { + BeginGroupToken begin = token; + return begin.endGroup.next; + } else { + return token; + } + } + + Token skipExpression(Token token) { + while (true) { + final kind = token.kind; + final value = token.stringValue; + if ((identical(kind, EOF_TOKEN)) || + (identical(value, ';')) || + (identical(value, ',')) || + (identical(value, ']'))) + return token; + if (identical(value, '=')) { + var nextValue = token.next.stringValue; + if (identical(nextValue, 'const')) { + token = token.next; + nextValue = token.next.stringValue; + } + if (identical(nextValue, '{')) { + // Handle cases like this: + // class Foo { + // var map; + // Foo() : map = {}; + // } + BeginGroupToken begin = token.next; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + continue; + } + if (identical(nextValue, '<')) { + // Handle cases like this: + // class Foo { + // var map; + // Foo() : map = <String, Foo>{}; + // } + BeginGroupToken begin = token.next; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + if (identical(token.stringValue, '{')) { + begin = token; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + } + continue; + } + } + if (!mayParseFunctionExpressions && identical(value, '{')) return token; + if (token is BeginGroupToken) { + BeginGroupToken begin = token; + token = (begin.endGroup != null) ? begin.endGroup : token; + } + token = token.next; + } + } + + Token skipClassBody(Token token) { + if (!optional('{', token)) { + return listener.expectedClassBodyToSkip(token); + } + BeginGroupToken beginGroupToken = token; + Token endGroup = beginGroupToken.endGroup; + if (endGroup == null) { + return listener.unmatched(beginGroupToken); + } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { + return listener.unmatched(beginGroupToken); + } + return endGroup; + } + + Token parseFunctionBody(Token token, bool isExpression) { + assert(!isExpression); + String value = token.stringValue; + if (identical(value, ';')) { + // No body. + } else if (identical(value, '=>')) { + token = parseExpression(token.next); + expectSemicolon(token); + } else if (value == '=') { + token = parseRedirectingFactoryBody(token); + expectSemicolon(token); + } else { + token = skipBlock(token); + } + // There is no "skipped function body event", so we use + // handleNoFunctionBody instead. + listener.handleNoFunctionBody(token); + return token; + } + + Token parseFormalParameters(Token token) => skipFormals(token); + + Token skipFormals(Token token) { + listener.beginOptionalFormalParameters(token); + if (!optional('(', token)) { + if (optional(';', token)) { + listener.recoverableError("expected '('", token: token); + return token; + } + return listener.unexpected(token); + } + BeginGroupToken beginGroupToken = token; + Token endToken = beginGroupToken.endGroup; + listener.endFormalParameters(0, token, endToken); + return endToken.next; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart new file mode 100644 index 0000000..123b650 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart
@@ -0,0 +1,875 @@ +// 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. + +part of scanner; + +abstract class Scanner { + Token tokenize(); +} + +/** + * Common base class for a Dart scanner. + */ +abstract class AbstractScanner<T extends SourceString> implements Scanner { + int advance(); + int nextByte(); + + /** + * Returns the current character or byte depending on the underlying input + * kind. For example, [StringScanner] operates on [String] and thus returns + * characters (Unicode codepoints represented as int) whereas + * [ByteArrayScanner] operates on byte arrays and thus returns bytes. + */ + int peek(); + + /** + * Appends a fixed token based on whether the current char is [choice] or not. + * If the current char is [choice] a fixed token whose kind and content + * is determined by [yes] is appended, otherwise a fixed token whose kind + * and content is determined by [no] is appended. + */ + int select(int choice, PrecedenceInfo yes, PrecedenceInfo no); + + /** + * Appends a fixed token whose kind and content is determined by [info]. + */ + void appendPrecedenceToken(PrecedenceInfo info); + + /** + * Appends a token whose kind is determined by [info] and content is [value]. + */ + void appendStringToken(PrecedenceInfo info, String value); + + /** + * Appends a token whose kind is determined by [info] and content is defined + * by the SourceString [value]. + */ + void appendByteStringToken(PrecedenceInfo info, T value); + + /** + * Appends a keyword token whose kind is determined by [keyword]. + */ + void appendKeywordToken(Keyword keyword); + void appendWhiteSpace(int next); + void appendEofToken(); + + /** + * Creates an ASCII SourceString whose content begins at the source byte + * offset [start] and ends at [offset] bytes from the current byte offset of + * the scanner. For example, if the current byte offset is 10, + * [:asciiString(0,-1):] creates an ASCII SourceString whose content is found + * at the [0,9[ byte interval of the source text. + */ + T asciiString(int start, int offset); + T utf8String(int start, int offset); + Token firstToken(); + Token previousToken(); + void beginToken(); + void addToCharOffset(int offset); + int get charOffset; + int get byteOffset; + void appendBeginGroup(PrecedenceInfo info, String value); + int appendEndGroup(PrecedenceInfo info, String value, int openKind); + void appendGt(PrecedenceInfo info, String value); + void appendGtGt(PrecedenceInfo info, String value); + void appendGtGtGt(PrecedenceInfo info, String value); + void appendComment(); + + /** + * We call this method to discard '<' from the "grouping" stack + * (maintained by subclasses). + * + * [PartialParser.skipExpression] relies on the fact that we do not + * create groups for stuff like: + * [:a = b < c, d = e > f:]. + * + * In other words, this method is called when the scanner recognizes + * something which cannot possibly be part of a type + * parameter/argument list. + */ + void discardOpenLt(); + + // TODO(ahe): Move this class to implementation. + + Token tokenize() { + int next = advance(); + while (!identical(next, $EOF)) { + next = bigSwitch(next); + } + appendEofToken(); + return firstToken(); + } + + int bigSwitch(int next) { + beginToken(); + if (identical(next, $SPACE) || identical(next, $TAB) + || identical(next, $LF) || identical(next, $CR)) { + appendWhiteSpace(next); + next = advance(); + while (identical(next, $SPACE)) { + appendWhiteSpace(next); + next = advance(); + } + return next; + } + + if ($a <= next && next <= $z) { + if (identical($r, next)) { + return tokenizeRawStringKeywordOrIdentifier(next); + } + return tokenizeKeywordOrIdentifier(next, true); + } + + if (($A <= next && next <= $Z) || identical(next, $_) || identical(next, $$)) { + return tokenizeIdentifier(next, byteOffset, true); + } + + if (identical(next, $LT)) { + return tokenizeLessThan(next); + } + + if (identical(next, $GT)) { + return tokenizeGreaterThan(next); + } + + if (identical(next, $EQ)) { + return tokenizeEquals(next); + } + + if (identical(next, $BANG)) { + return tokenizeExclamation(next); + } + + if (identical(next, $PLUS)) { + return tokenizePlus(next); + } + + if (identical(next, $MINUS)) { + return tokenizeMinus(next); + } + + if (identical(next, $STAR)) { + return tokenizeMultiply(next); + } + + if (identical(next, $PERCENT)) { + return tokenizePercent(next); + } + + if (identical(next, $AMPERSAND)) { + return tokenizeAmpersand(next); + } + + if (identical(next, $BAR)) { + return tokenizeBar(next); + } + + if (identical(next, $CARET)) { + return tokenizeCaret(next); + } + + if (identical(next, $OPEN_SQUARE_BRACKET)) { + return tokenizeOpenSquareBracket(next); + } + + if (identical(next, $TILDE)) { + return tokenizeTilde(next); + } + + if (identical(next, $BACKSLASH)) { + appendPrecedenceToken(BACKSLASH_INFO); + return advance(); + } + + if (identical(next, $HASH)) { + return tokenizeTag(next); + } + + if (identical(next, $OPEN_PAREN)) { + appendBeginGroup(OPEN_PAREN_INFO, "("); + return advance(); + } + + if (identical(next, $CLOSE_PAREN)) { + return appendEndGroup(CLOSE_PAREN_INFO, ")", OPEN_PAREN_TOKEN); + } + + if (identical(next, $COMMA)) { + appendPrecedenceToken(COMMA_INFO); + return advance(); + } + + if (identical(next, $COLON)) { + appendPrecedenceToken(COLON_INFO); + return advance(); + } + + if (identical(next, $SEMICOLON)) { + appendPrecedenceToken(SEMICOLON_INFO); + // Type parameters and arguments cannot contain semicolon. + discardOpenLt(); + return advance(); + } + + if (identical(next, $QUESTION)) { + appendPrecedenceToken(QUESTION_INFO); + return advance(); + } + + if (identical(next, $CLOSE_SQUARE_BRACKET)) { + return appendEndGroup(CLOSE_SQUARE_BRACKET_INFO, "]", + OPEN_SQUARE_BRACKET_TOKEN); + } + + if (identical(next, $BACKPING)) { + appendPrecedenceToken(BACKPING_INFO); + return advance(); + } + + if (identical(next, $OPEN_CURLY_BRACKET)) { + appendBeginGroup(OPEN_CURLY_BRACKET_INFO, "{"); + return advance(); + } + + if (identical(next, $CLOSE_CURLY_BRACKET)) { + return appendEndGroup(CLOSE_CURLY_BRACKET_INFO, "}", + OPEN_CURLY_BRACKET_TOKEN); + } + + if (identical(next, $SLASH)) { + return tokenizeSlashOrComment(next); + } + + if (identical(next, $AT)) { + return tokenizeAt(next); + } + + if (identical(next, $DQ) || identical(next, $SQ)) { + return tokenizeString(next, byteOffset, false); + } + + if (identical(next, $PERIOD)) { + return tokenizeDotsOrNumber(next); + } + + if (identical(next, $0)) { + return tokenizeHexOrNumber(next); + } + + // TODO(ahe): Would a range check be faster? + if (identical(next, $1) || identical(next, $2) || identical(next, $3) + || identical(next, $4) || identical(next, $5) || identical(next, $6) + || identical(next, $7) || identical(next, $8) || identical(next, $9)) { + return tokenizeNumber(next); + } + + if (identical(next, $EOF)) { + return $EOF; + } + if (next < 0x1f) { + return error(new SourceString("unexpected character $next")); + } + + // The following are non-ASCII characters. + + if (identical(next, $NBSP)) { + appendWhiteSpace(next); + return advance(); + } + + return tokenizeIdentifier(next, byteOffset, true); + } + + int tokenizeTag(int next) { + // # or #!.*[\n\r] + if (byteOffset == 0) { + if (identical(peek(), $BANG)) { + do { + next = advance(); + } while (!identical(next, $LF) && !identical(next, $CR) && !identical(next, $EOF)); + return next; + } + } + appendPrecedenceToken(HASH_INFO); + return advance(); + } + + int tokenizeTilde(int next) { + // ~ ~/ ~/= + next = advance(); + if (identical(next, $SLASH)) { + return select($EQ, TILDE_SLASH_EQ_INFO, TILDE_SLASH_INFO); + } else { + appendPrecedenceToken(TILDE_INFO); + return next; + } + } + + int tokenizeOpenSquareBracket(int next) { + // [ [] []= + next = advance(); + if (identical(next, $CLOSE_SQUARE_BRACKET)) { + Token token = previousToken(); + if (token is KeywordToken && identical(token.value.stringValue, 'operator')) { + return select($EQ, INDEX_EQ_INFO, INDEX_INFO); + } + } + appendBeginGroup(OPEN_SQUARE_BRACKET_INFO, "["); + return next; + } + + int tokenizeCaret(int next) { + // ^ ^= + return select($EQ, CARET_EQ_INFO, CARET_INFO); + } + + int tokenizeBar(int next) { + // | || |= + next = advance(); + if (identical(next, $BAR)) { + appendPrecedenceToken(BAR_BAR_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(BAR_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(BAR_INFO); + return next; + } + } + + int tokenizeAmpersand(int next) { + // && &= & + next = advance(); + if (identical(next, $AMPERSAND)) { + appendPrecedenceToken(AMPERSAND_AMPERSAND_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(AMPERSAND_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(AMPERSAND_INFO); + return next; + } + } + + int tokenizePercent(int next) { + // % %= + return select($EQ, PERCENT_EQ_INFO, PERCENT_INFO); + } + + int tokenizeMultiply(int next) { + // * *= + return select($EQ, STAR_EQ_INFO, STAR_INFO); + } + + int tokenizeMinus(int next) { + // - -- -= + next = advance(); + if (identical(next, $MINUS)) { + appendPrecedenceToken(MINUS_MINUS_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(MINUS_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(MINUS_INFO); + return next; + } + } + + + int tokenizePlus(int next) { + // + ++ += + next = advance(); + if (identical($PLUS, next)) { + appendPrecedenceToken(PLUS_PLUS_INFO); + return advance(); + } else if (identical($EQ, next)) { + appendPrecedenceToken(PLUS_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(PLUS_INFO); + return next; + } + } + + int tokenizeExclamation(int next) { + // ! != !== + next = advance(); + if (identical(next, $EQ)) { + return select($EQ, BANG_EQ_EQ_INFO, BANG_EQ_INFO); + } + appendPrecedenceToken(BANG_INFO); + return next; + } + + int tokenizeEquals(int next) { + // = == === + + // Type parameters and arguments cannot contain any token that + // starts with '='. + discardOpenLt(); + + next = advance(); + if (identical(next, $EQ)) { + return select($EQ, EQ_EQ_EQ_INFO, EQ_EQ_INFO); + } else if (identical(next, $GT)) { + appendPrecedenceToken(FUNCTION_INFO); + return advance(); + } + appendPrecedenceToken(EQ_INFO); + return next; + } + + int tokenizeGreaterThan(int next) { + // > >= >> >>= >>> >>>= + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(GT_EQ_INFO); + return advance(); + } else if (identical($GT, next)) { + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(GT_GT_EQ_INFO); + return advance(); + } else { + appendGtGt(GT_GT_INFO, ">>"); + return next; + } + } else { + appendGt(GT_INFO, ">"); + return next; + } + } + + int tokenizeLessThan(int next) { + // < <= << <<= + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(LT_EQ_INFO); + return advance(); + } else if (identical($LT, next)) { + return select($EQ, LT_LT_EQ_INFO, LT_LT_INFO); + } else { + appendBeginGroup(LT_INFO, "<"); + return next; + } + } + + int tokenizeNumber(int next) { + int start = byteOffset; + while (true) { + next = advance(); + if ($0 <= next && next <= $9) { + continue; + } else if (identical(next, $PERIOD)) { + return tokenizeFractionPart(advance(), start); + } else if (identical(next, $e) || identical(next, $E) + || identical(next, $d) || identical(next, $D)) { + return tokenizeFractionPart(next, start); + } else { + appendByteStringToken(INT_INFO, asciiString(start, 0)); + return next; + } + } + } + + int tokenizeHexOrNumber(int next) { + int x = peek(); + if (identical(x, $x) || identical(x, $X)) { + advance(); + return tokenizeHex(x); + } + return tokenizeNumber(next); + } + + int tokenizeHex(int next) { + int start = byteOffset - 1; + bool hasDigits = false; + while (true) { + next = advance(); + if (($0 <= next && next <= $9) + || ($A <= next && next <= $F) + || ($a <= next && next <= $f)) { + hasDigits = true; + } else { + if (!hasDigits) { + return error(const SourceString("hex digit expected")); + } + appendByteStringToken(HEXADECIMAL_INFO, asciiString(start, 0)); + return next; + } + } + } + + int tokenizeDotsOrNumber(int next) { + int start = byteOffset; + next = advance(); + if (($0 <= next && next <= $9)) { + return tokenizeFractionPart(next, start); + } else if (identical($PERIOD, next)) { + return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO); + } else { + appendPrecedenceToken(PERIOD_INFO); + return next; + } + } + + int tokenizeFractionPart(int next, int start) { + bool done = false; + bool hasDigit = false; + LOOP: while (!done) { + if ($0 <= next && next <= $9) { + hasDigit = true; + } else if (identical($e, next) || identical($E, next)) { + hasDigit = true; + next = tokenizeExponent(advance()); + done = true; + continue LOOP; + } else { + done = true; + continue LOOP; + } + next = advance(); + } + if (!hasDigit) { + appendByteStringToken(INT_INFO, asciiString(start, -1)); + if (identical($PERIOD, next)) { + return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO); + } + // TODO(ahe): Wrong offset for the period. + appendPrecedenceToken(PERIOD_INFO); + return bigSwitch(next); + } + if (identical(next, $d) || identical(next, $D)) { + next = advance(); + } + appendByteStringToken(DOUBLE_INFO, asciiString(start, 0)); + return next; + } + + int tokenizeExponent(int next) { + if (identical(next, $PLUS) || identical(next, $MINUS)) { + next = advance(); + } + bool hasDigits = false; + while (true) { + if ($0 <= next && next <= $9) { + hasDigits = true; + } else { + if (!hasDigits) { + return error(const SourceString("digit expected")); + } + return next; + } + next = advance(); + } + } + + int tokenizeSlashOrComment(int next) { + next = advance(); + if (identical($STAR, next)) { + return tokenizeMultiLineComment(next); + } else if (identical($SLASH, next)) { + return tokenizeSingleLineComment(next); + } else if (identical($EQ, next)) { + appendPrecedenceToken(SLASH_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(SLASH_INFO); + return next; + } + } + + int tokenizeSingleLineComment(int next) { + while (true) { + next = advance(); + if (identical($LF, next) || identical($CR, next) || identical($EOF, next)) { + appendComment(); + return next; + } + } + } + + int tokenizeMultiLineComment(int next) { + int nesting = 1; + next = advance(); + while (true) { + if (identical($EOF, next)) { + // TODO(ahe): Report error. + return next; + } else if (identical($STAR, next)) { + next = advance(); + if (identical($SLASH, next)) { + --nesting; + if (0 == nesting) { + next = advance(); + appendComment(); + return next; + } else { + next = advance(); + } + } + } else if (identical($SLASH, next)) { + next = advance(); + if (identical($STAR, next)) { + next = advance(); + ++nesting; + } + } else { + next = advance(); + } + } + } + + int tokenizeRawStringKeywordOrIdentifier(int next) { + int nextnext = peek(); + if (identical(nextnext, $DQ) || identical(nextnext, $SQ)) { + int start = byteOffset; + next = advance(); + return tokenizeString(next, start, true); + } + return tokenizeKeywordOrIdentifier(next, true); + } + + int tokenizeKeywordOrIdentifier(int next, bool allowDollar) { + KeywordState state = KeywordState.KEYWORD_STATE; + int start = byteOffset; + while (state != null && $a <= next && next <= $z) { + state = state.next(next); + next = advance(); + } + if (state == null || state.keyword == null) { + return tokenizeIdentifier(next, start, allowDollar); + } + if (($A <= next && next <= $Z) || + ($0 <= next && next <= $9) || + identical(next, $_) || + identical(next, $$)) { + return tokenizeIdentifier(next, start, allowDollar); + } else if (next < 128) { + appendKeywordToken(state.keyword); + return next; + } else { + return tokenizeIdentifier(next, start, allowDollar); + } + } + + int tokenizeIdentifier(int next, int start, bool allowDollar) { + bool isAscii = true; + + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + bool isDynamicBuiltIn = false; + + if (identical(next, $D)) { + next = advance(); + if (identical(next, $y)) { + next = advance(); + if (identical(next, $n)) { + next = advance(); + if (identical(next, $a)) { + next = advance(); + if (identical(next, $m)) { + next = advance(); + if (identical(next, $i)) { + next = advance(); + if (identical(next, $c)) { + isDynamicBuiltIn = true; + next = advance(); + } + } + } + } + } + } + } + + while (true) { + if (($a <= next && next <= $z) || + ($A <= next && next <= $Z) || + ($0 <= next && next <= $9) || + identical(next, $_) || + (identical(next, $$) && allowDollar)) { + isDynamicBuiltIn = false; + next = advance(); + } else if ((next < 128) || (identical(next, $NBSP))) { + // Identifier ends here. + if (start == byteOffset) { + return error(const SourceString("expected identifier")); + } else if (isDynamicBuiltIn) { + appendKeywordToken(Keyword.DYNAMIC_DEPRECATED); + } else if (isAscii) { + appendByteStringToken(IDENTIFIER_INFO, asciiString(start, 0)); + } else { + appendByteStringToken(BAD_INPUT_INFO, utf8String(start, -1)); + } + return next; + } else { + isDynamicBuiltIn = false; + int nonAsciiStart = byteOffset; + do { + next = nextByte(); + if (identical(next, $NBSP)) break; + } while (next > 127); + String string = utf8String(nonAsciiStart, -1).slowToString(); + isAscii = false; + int byteLength = nonAsciiStart - byteOffset; + addToCharOffset(string.length - byteLength); + } + } + } + + int tokenizeAt(int next) { + int start = byteOffset; + next = advance(); + appendPrecedenceToken(AT_INFO); + return next; + } + + int tokenizeString(int next, int start, bool raw) { + int quoteChar = next; + next = advance(); + if (identical(quoteChar, next)) { + next = advance(); + if (identical(quoteChar, next)) { + // Multiline string. + return tokenizeMultiLineString(quoteChar, start, raw); + } else { + // Empty string. + appendByteStringToken(STRING_INFO, utf8String(start, -1)); + return next; + } + } + if (raw) { + return tokenizeSingleLineRawString(next, quoteChar, start); + } else { + return tokenizeSingleLineString(next, quoteChar, start); + } + } + + static bool isHexDigit(int character) { + if ($0 <= character && character <= $9) return true; + character |= 0x20; + return ($a <= character && character <= $f); + } + + int tokenizeSingleLineString(int next, int quoteChar, int start) { + while (!identical(next, quoteChar)) { + if (identical(next, $BACKSLASH)) { + next = advance(); + } else if (identical(next, $$)) { + next = tokenizeStringInterpolation(start); + start = byteOffset; + continue; + } + if (next <= $CR + && (identical(next, $LF) || identical(next, $CR) || identical(next, $EOF))) { + return error(const SourceString("unterminated string literal")); + } + next = advance(); + } + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + + int tokenizeStringInterpolation(int start) { + appendByteStringToken(STRING_INFO, utf8String(start, -1)); + beginToken(); // $ starts here. + int next = advance(); + if (identical(next, $OPEN_CURLY_BRACKET)) { + return tokenizeInterpolatedExpression(next, start); + } else { + return tokenizeInterpolatedIdentifier(next, start); + } + } + + int tokenizeInterpolatedExpression(int next, int start) { + appendBeginGroup(STRING_INTERPOLATION_INFO, "\${"); + beginToken(); // The expression starts here. + next = advance(); + while (!identical(next, $EOF) && !identical(next, $STX)) { + next = bigSwitch(next); + } + if (identical(next, $EOF)) return next; + next = advance(); + beginToken(); // The string interpolation suffix starts here. + return next; + } + + int tokenizeInterpolatedIdentifier(int next, int start) { + appendPrecedenceToken(STRING_INTERPOLATION_IDENTIFIER_INFO); + beginToken(); // The identifier starts here. + next = tokenizeKeywordOrIdentifier(next, false); + beginToken(); // The string interpolation suffix starts here. + return next; + } + + int tokenizeSingleLineRawString(int next, int quoteChar, int start) { + next = advance(); + while (next != $EOF) { + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } else if (identical(next, $LF) || identical(next, $CR)) { + return error(const SourceString("unterminated string literal")); + } + next = advance(); + } + return error(const SourceString("unterminated string literal")); + } + + int tokenizeMultiLineRawString(int quoteChar, int start) { + int next = advance(); + outer: while (!identical(next, $EOF)) { + while (!identical(next, quoteChar)) { + next = advance(); + if (identical(next, $EOF)) break outer; + } + next = advance(); + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + } + } + return error(const SourceString("unterminated string literal")); + } + + int tokenizeMultiLineString(int quoteChar, int start, bool raw) { + if (raw) return tokenizeMultiLineRawString(quoteChar, start); + int next = advance(); + while (!identical(next, $EOF)) { + if (identical(next, $$)) { + next = tokenizeStringInterpolation(start); + start = byteOffset; + continue; + } + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + } + continue; + } + if (identical(next, $BACKSLASH)) { + next = advance(); + if (identical(next, $EOF)) break; + } + next = advance(); + } + return error(const SourceString("unterminated string literal")); + } + + int error(SourceString message) { + appendByteStringToken(BAD_INPUT_INFO, message); + return advance(); // Ensure progress. + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart new file mode 100644 index 0000000..1bd8327 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart
@@ -0,0 +1,11 @@ +// Copyright (c) 2011, 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. + +library scanner_implementation; + +import 'scannerlib.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +part 'array_based_scanner.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart new file mode 100644 index 0000000..0738355 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart
@@ -0,0 +1,53 @@ +// 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. + +part of scanner; + +class ScannerTask extends CompilerTask { + ScannerTask(Compiler compiler) : super(compiler); + String get name => 'Scanner'; + + void scanLibrary(LibraryElement library) { + var compilationUnit = library.entryCompilationUnit; + var canonicalUri = library.canonicalUri.toString(); + var resolvedUri = compilationUnit.script.uri.toString(); + if (canonicalUri == resolvedUri) { + compiler.log("scanning library $canonicalUri"); + } else { + compiler.log("scanning library $canonicalUri ($resolvedUri)"); + } + scan(compilationUnit); + } + + void scan(CompilationUnitElement compilationUnit) { + measure(() { + scanElements(compilationUnit); + }); + } + + void scanElements(CompilationUnitElement compilationUnit) { + Script script = compilationUnit.script; + Token tokens = new StringScanner(script.text, + includeComments: compiler.preserveComments).tokenize(); + if (compiler.preserveComments) { + tokens = compiler.processAndStripComments(tokens); + } + compiler.dietParser.dietParse(compilationUnit, tokens); + } +} + +class DietParserTask extends CompilerTask { + DietParserTask(Compiler compiler) : super(compiler); + final String name = 'Diet Parser'; + + dietParse(CompilationUnitElement compilationUnit, Token tokens) { + measure(() { + Function idGenerator = compiler.getNextFreeClassId; + ElementListener listener = + new ElementListener(compiler, compilationUnit, idGenerator); + PartialParser parser = new PartialParser(listener); + parser.parseUnit(tokens); + }); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart new file mode 100644 index 0000000..e3cd591 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart
@@ -0,0 +1,38 @@ +// Copyright (c) 2011, 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. + +library scanner; + +import 'dart:collection' show LinkedHashMap; +import 'dart:uri'; + +import 'scanner_implementation.dart'; +import '../elements/elements.dart'; +import '../elements/modelx.dart' + show FunctionElementX, + TypedefElementX, + VariableElementX, + VariableListElementX, + ClassElementX, + MetadataAnnotationX, + MixinApplicationElementX; +import '../dart2jslib.dart'; +import '../native_handler.dart' as native; +import '../string_validator.dart'; +import '../tree/tree.dart'; +import '../util/characters.dart'; +import '../util/util.dart'; +// TODO(ahe): Rename prefix to 'api' when VM bug is fixed. +import '../../compiler.dart' as api_s; + +part 'class_element_parser.dart'; +part 'keyword.dart'; +part 'listener.dart'; +part 'parser.dart'; +part 'parser_task.dart'; +part 'partial_parser.dart'; +part 'scanner.dart'; +part 'scanner_task.dart'; +part 'string_scanner.dart'; +part 'token.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart new file mode 100644 index 0000000..0fa3489 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart
@@ -0,0 +1,106 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +/** + * Scanner that reads from a String and creates tokens that points to + * substrings. + */ +class StringScanner extends ArrayBasedScanner<SourceString> { + final String string; + + StringScanner(String this.string, {bool includeComments: false}) + : super(includeComments); + + int nextByte() => charAt(++byteOffset); + + int peek() => charAt(byteOffset + 1); + + int charAt(index) + => (string.length > index) ? string.charCodeAt(index) : $EOF; + + SourceString asciiString(int start, int offset) { + return new SubstringWrapper(string, start, byteOffset + offset); + } + + SourceString utf8String(int start, int offset) { + return new SubstringWrapper(string, start, byteOffset + offset + 1); + } + + void appendByteStringToken(PrecedenceInfo info, SourceString value) { + // assert(kind != $a || keywords.get(value) == null); + tail.next = new StringToken.fromSource(info, value, tokenStart); + tail = tail.next; + } + + void unmatchedBeginGroup(BeginGroupToken begin) { + SourceString error = new SourceString('unmatched "${begin.stringValue}"'); + Token close = + new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset); + // We want to ensure that unmatched BeginGroupTokens are reported + // as errors. However, the rest of the parser assume the groups + // are well-balanced and will never look at the endGroup + // token. This is a nice property that allows us to skip quickly + // over correct code. By inserting an additional error token in + // the stream, we can keep ignoring endGroup tokens. + Token next = + new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset); + begin.endGroup = close; + close.next = next; + next.next = begin.next; + } +} + +class SubstringWrapper extends Iterable<int> implements SourceString { + final String internalString; + final int begin; + final int end; + int cashedHash = 0; + String cachedSubString; + + SubstringWrapper(String this.internalString, + int this.begin, int this.end); + + int get hashCode { + if (0 == cashedHash) { + cashedHash = slowToString().hashCode; + } + return cashedHash; + } + + bool operator ==(other) { + return other is SourceString && slowToString() == other.slowToString(); + } + + void printOn(StringBuffer sb) { + sb.add(internalString.substring(begin, end)); + } + + String slowToString() { + if (cachedSubString == null) { + cachedSubString = internalString.substring(begin, end); + } + return cachedSubString; + } + + String toString() => "SubstringWrapper(${slowToString()})"; + + String get stringValue => null; + + Iterator<int> get iterator => + new StringCodeIterator.substring(internalString, begin, end); + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert(0 <= initial); + assert(0 <= terminal); + assert(initial + terminal <= internalString.length); + return new SubstringWrapper(internalString, + begin + initial, end - terminal); + } + + bool get isEmpty => begin == end; + + bool isPrivate() => !isEmpty && identical(internalString.charCodeAt(begin), $_); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart b/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart new file mode 100644 index 0000000..a236eae --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart
@@ -0,0 +1,530 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +const int EOF_TOKEN = 0; + +const int KEYWORD_TOKEN = $k; +const int IDENTIFIER_TOKEN = $a; +const int BAD_INPUT_TOKEN = $X; +const int DOUBLE_TOKEN = $d; +const int INT_TOKEN = $i; +const int HEXADECIMAL_TOKEN = $x; +const int STRING_TOKEN = $SQ; + +const int AMPERSAND_TOKEN = $AMPERSAND; +const int BACKPING_TOKEN = $BACKPING; +const int BACKSLASH_TOKEN = $BACKSLASH; +const int BANG_TOKEN = $BANG; +const int BAR_TOKEN = $BAR; +const int COLON_TOKEN = $COLON; +const int COMMA_TOKEN = $COMMA; +const int EQ_TOKEN = $EQ; +const int GT_TOKEN = $GT; +const int HASH_TOKEN = $HASH; +const int OPEN_CURLY_BRACKET_TOKEN = $OPEN_CURLY_BRACKET; +const int OPEN_SQUARE_BRACKET_TOKEN = $OPEN_SQUARE_BRACKET; +const int OPEN_PAREN_TOKEN = $OPEN_PAREN; +const int LT_TOKEN = $LT; +const int MINUS_TOKEN = $MINUS; +const int PERIOD_TOKEN = $PERIOD; +const int PLUS_TOKEN = $PLUS; +const int QUESTION_TOKEN = $QUESTION; +const int AT_TOKEN = $AT; +const int CLOSE_CURLY_BRACKET_TOKEN = $CLOSE_CURLY_BRACKET; +const int CLOSE_SQUARE_BRACKET_TOKEN = $CLOSE_SQUARE_BRACKET; +const int CLOSE_PAREN_TOKEN = $CLOSE_PAREN; +const int SEMICOLON_TOKEN = $SEMICOLON; +const int SLASH_TOKEN = $SLASH; +const int TILDE_TOKEN = $TILDE; +const int STAR_TOKEN = $STAR; +const int PERCENT_TOKEN = $PERCENT; +const int CARET_TOKEN = $CARET; + +const int STRING_INTERPOLATION_TOKEN = 128; +const int LT_EQ_TOKEN = STRING_INTERPOLATION_TOKEN + 1; +const int FUNCTION_TOKEN = LT_EQ_TOKEN + 1; +const int SLASH_EQ_TOKEN = FUNCTION_TOKEN + 1; +const int PERIOD_PERIOD_PERIOD_TOKEN = SLASH_EQ_TOKEN + 1; +const int PERIOD_PERIOD_TOKEN = PERIOD_PERIOD_PERIOD_TOKEN + 1; +const int EQ_EQ_EQ_TOKEN = PERIOD_PERIOD_TOKEN + 1; +const int EQ_EQ_TOKEN = EQ_EQ_EQ_TOKEN + 1; +const int LT_LT_EQ_TOKEN = EQ_EQ_TOKEN + 1; +const int LT_LT_TOKEN = LT_LT_EQ_TOKEN + 1; +const int GT_EQ_TOKEN = LT_LT_TOKEN + 1; +const int GT_GT_EQ_TOKEN = GT_EQ_TOKEN + 1; +const int INDEX_EQ_TOKEN = GT_GT_EQ_TOKEN + 1; +const int INDEX_TOKEN = INDEX_EQ_TOKEN + 1; +const int BANG_EQ_EQ_TOKEN = INDEX_TOKEN + 1; +const int BANG_EQ_TOKEN = BANG_EQ_EQ_TOKEN + 1; +const int AMPERSAND_AMPERSAND_TOKEN = BANG_EQ_TOKEN + 1; +const int AMPERSAND_EQ_TOKEN = AMPERSAND_AMPERSAND_TOKEN + 1; +const int BAR_BAR_TOKEN = AMPERSAND_EQ_TOKEN + 1; +const int BAR_EQ_TOKEN = BAR_BAR_TOKEN + 1; +const int STAR_EQ_TOKEN = BAR_EQ_TOKEN + 1; +const int PLUS_PLUS_TOKEN = STAR_EQ_TOKEN + 1; +const int PLUS_EQ_TOKEN = PLUS_PLUS_TOKEN + 1; +const int MINUS_MINUS_TOKEN = PLUS_EQ_TOKEN + 1; +const int MINUS_EQ_TOKEN = MINUS_MINUS_TOKEN + 1; +const int TILDE_SLASH_EQ_TOKEN = MINUS_EQ_TOKEN + 1; +const int TILDE_SLASH_TOKEN = TILDE_SLASH_EQ_TOKEN + 1; +const int PERCENT_EQ_TOKEN = TILDE_SLASH_TOKEN + 1; +const int GT_GT_TOKEN = PERCENT_EQ_TOKEN + 1; +const int CARET_EQ_TOKEN = GT_GT_TOKEN + 1; +const int COMMENT_TOKEN = CARET_EQ_TOKEN + 1; +const int STRING_INTERPOLATION_IDENTIFIER_TOKEN = COMMENT_TOKEN + 1; + +// TODO(ahe): Get rid of this. +const int UNKNOWN_TOKEN = 1024; + +/** + * A token that doubles as a linked list. + */ +class Token implements Spannable { + /** + * The precedence info for this token. [info] determines the kind and the + * precedence level of this token. + */ + final PrecedenceInfo info; + + /** + * The character offset of the start of this token within the source text. + */ + final int charOffset; + + /** + * The next token in the token stream. + */ + Token next; + + Token(PrecedenceInfo this.info, int this.charOffset); + + get value => info.value; + + /** + * Returns the string value for keywords and symbols. For instance 'class' for + * the [CLASS] keyword token and '*' for a [Token] based on [STAR_INFO]. For + * other tokens, such identifiers, strings, numbers, etc, [stringValue] + * returns [:null:]. + * + * [stringValue] should only be used for testing keywords and symbols. + */ + String get stringValue => info.value.stringValue; + + /** + * The kind enum of this token as determined by its [info]. + */ + int get kind => info.kind; + + /** + * The precedence level for this token. + */ + int get precedence => info.precedence; + + bool isIdentifier() => identical(kind, IDENTIFIER_TOKEN); + + /** + * Returns a textual representation of this token to be used for debugging + * purposes. The resulting string might contain information about the + * structure of the token, for example 'StringToken(foo)' for the identifier + * token 'foo'. Use [slowToString] for the text actually parsed by the token. + */ + String toString() => info.value.toString(); + + /** + * The text parsed by this token. + */ + String slowToString() => toString(); + + /** + * The number of characters parsed by this token. + */ + int get slowCharCount { + if (info == BAD_INPUT_INFO) { + // This is a token that wraps around an error message. Return 1 + // instead of the size of the length of the error message. + return 1; + } else { + return slowToString().length; + } + } +} + +/** + * A keyword token. + */ +class KeywordToken extends Token { + final Keyword value; + String get stringValue => value.syntax; + + KeywordToken(Keyword value, int charOffset) + : this.value = value, super(value.info, charOffset); + + bool isIdentifier() => value.isPseudo || value.isBuiltIn; + + String toString() => value.syntax; +} + +/** + * A String-valued token. + */ +class StringToken extends Token { + final SourceString value; + String get stringValue => value.stringValue; + + StringToken(PrecedenceInfo info, String value, int charOffset) + : this.fromSource(info, new SourceString(value), charOffset); + + StringToken.fromSource(PrecedenceInfo info, this.value, int charOffset) + : super(info, charOffset); + + String toString() => "StringToken(${value.slowToString()})"; + + String slowToString() => value.slowToString(); +} + +abstract class SourceString extends Iterable<int> { + const factory SourceString(String string) = StringWrapper; + + void printOn(StringBuffer sb); + + /** Gives a [SourceString] that is not including the [initial] first and + * [terminal] last characters. This is only intended to be used to remove + * quotes from string literals (including an initial '@' for raw strings). + */ + SourceString copyWithoutQuotes(int initial, int terminal); + + String get stringValue; + + String slowToString(); + + bool get isEmpty; + + bool isPrivate(); +} + +class StringWrapper extends Iterable<int> implements SourceString { + final String stringValue; + + const StringWrapper(String this.stringValue); + + int get hashCode => stringValue.hashCode; + + bool operator ==(other) { + return other is SourceString && toString() == other.slowToString(); + } + + Iterator<int> get iterator => new StringCodeIterator(stringValue); + + void printOn(StringBuffer sb) { + sb.add(stringValue); + } + + String toString() => stringValue; + + String slowToString() => stringValue; + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert(0 <= initial); + assert(0 <= terminal); + assert(initial + terminal <= stringValue.length); + return new StringWrapper( + stringValue.substring(initial, stringValue.length - terminal)); + } + + bool get isEmpty => stringValue.isEmpty; + + bool isPrivate() => !isEmpty && identical(stringValue.charCodeAt(0), $_); +} + +class StringCodeIterator implements Iterator<int> { + final String string; + int index; + final int end; + int _current; + + StringCodeIterator(String string) : + this.string = string, index = 0, end = string.length; + + StringCodeIterator.substring(this.string, this.index, this.end) { + assert(0 <= index); + assert(index <= end); + assert(end <= string.length); + } + + int get current => _current; + + bool moveNext() { + _current = null; + if (index >= end) return false; + _current = string.charCodeAt(index++); + return true; + } +} + +class BeginGroupToken extends StringToken { + Token endGroup; + BeginGroupToken(PrecedenceInfo info, String value, int charOffset) + : super(info, value, charOffset); +} + +bool isUserDefinableOperator(String value) { + return + isBinaryOperator(value) || + isMinusOperator(value) || + isTernaryOperator(value) || + isUnaryOperator(value); +} + +bool isUnaryOperator(String value) => identical(value, '~'); + +bool isBinaryOperator(String value) { + return + (identical(value, '==')) || + (identical(value, '[]')) || + (identical(value, '*')) || + (identical(value, '/')) || + (identical(value, '%')) || + (identical(value, '~/')) || + (identical(value, '+')) || + (identical(value, '<<')) || + (identical(value, '>>')) || + (identical(value, '>=')) || + (identical(value, '>')) || + (identical(value, '<=')) || + (identical(value, '<')) || + (identical(value, '&')) || + (identical(value, '^')) || + (identical(value, '|')); +} + +bool isTernaryOperator(String value) => identical(value, '[]='); + +bool isMinusOperator(String value) => identical(value, '-'); + +class PrecedenceInfo { + final SourceString value; + final int precedence; + final int kind; + + const PrecedenceInfo(this.value, this.precedence, this.kind); + + toString() => 'PrecedenceInfo($value, $precedence, $kind)'; +} + +// TODO(ahe): The following are not tokens in Dart. +const PrecedenceInfo BACKPING_INFO = + const PrecedenceInfo(const SourceString('`'), 0, BACKPING_TOKEN); +const PrecedenceInfo BACKSLASH_INFO = + const PrecedenceInfo(const SourceString('\\'), 0, BACKSLASH_TOKEN); +const PrecedenceInfo PERIOD_PERIOD_PERIOD_INFO = + const PrecedenceInfo(const SourceString('...'), 0, + PERIOD_PERIOD_PERIOD_TOKEN); + +/** + * The cascade operator has the lowest precedence of any operator + * except assignment. + */ +const int CASCADE_PRECEDENCE = 2; +const PrecedenceInfo PERIOD_PERIOD_INFO = + const PrecedenceInfo(const SourceString('..'), CASCADE_PRECEDENCE, + PERIOD_PERIOD_TOKEN); + +const PrecedenceInfo BANG_INFO = + const PrecedenceInfo(const SourceString('!'), 0, BANG_TOKEN); +const PrecedenceInfo COLON_INFO = + const PrecedenceInfo(const SourceString(':'), 0, COLON_TOKEN); +const PrecedenceInfo INDEX_INFO = + const PrecedenceInfo(const SourceString('[]'), 0, INDEX_TOKEN); +const PrecedenceInfo MINUS_MINUS_INFO = + const PrecedenceInfo(const SourceString('--'), POSTFIX_PRECEDENCE, + MINUS_MINUS_TOKEN); +const PrecedenceInfo PLUS_PLUS_INFO = + const PrecedenceInfo(const SourceString('++'), POSTFIX_PRECEDENCE, + PLUS_PLUS_TOKEN); +const PrecedenceInfo TILDE_INFO = + const PrecedenceInfo(const SourceString('~'), 0, TILDE_TOKEN); + +const PrecedenceInfo FUNCTION_INFO = + const PrecedenceInfo(const SourceString('=>'), 0, FUNCTION_TOKEN); +const PrecedenceInfo HASH_INFO = + const PrecedenceInfo(const SourceString('#'), 0, HASH_TOKEN); +const PrecedenceInfo INDEX_EQ_INFO = + const PrecedenceInfo(const SourceString('[]='), 0, INDEX_EQ_TOKEN); +const PrecedenceInfo SEMICOLON_INFO = + const PrecedenceInfo(const SourceString(';'), 0, SEMICOLON_TOKEN); +const PrecedenceInfo COMMA_INFO = + const PrecedenceInfo(const SourceString(','), 0, COMMA_TOKEN); + +const PrecedenceInfo AT_INFO = + const PrecedenceInfo(const SourceString('@'), 0, AT_TOKEN); + +// Assignment operators. +const int ASSIGNMENT_PRECEDENCE = 1; +const PrecedenceInfo AMPERSAND_EQ_INFO = + const PrecedenceInfo(const SourceString('&='), + ASSIGNMENT_PRECEDENCE, AMPERSAND_EQ_TOKEN); +const PrecedenceInfo BAR_EQ_INFO = + const PrecedenceInfo(const SourceString('|='), + ASSIGNMENT_PRECEDENCE, BAR_EQ_TOKEN); +const PrecedenceInfo CARET_EQ_INFO = + const PrecedenceInfo(const SourceString('^='), + ASSIGNMENT_PRECEDENCE, CARET_EQ_TOKEN); +const PrecedenceInfo EQ_INFO = + const PrecedenceInfo(const SourceString('='), + ASSIGNMENT_PRECEDENCE, EQ_TOKEN); +const PrecedenceInfo GT_GT_EQ_INFO = + const PrecedenceInfo(const SourceString('>>='), + ASSIGNMENT_PRECEDENCE, GT_GT_EQ_TOKEN); +const PrecedenceInfo LT_LT_EQ_INFO = + const PrecedenceInfo(const SourceString('<<='), + ASSIGNMENT_PRECEDENCE, LT_LT_EQ_TOKEN); +const PrecedenceInfo MINUS_EQ_INFO = + const PrecedenceInfo(const SourceString('-='), + ASSIGNMENT_PRECEDENCE, MINUS_EQ_TOKEN); +const PrecedenceInfo PERCENT_EQ_INFO = + const PrecedenceInfo(const SourceString('%='), + ASSIGNMENT_PRECEDENCE, PERCENT_EQ_TOKEN); +const PrecedenceInfo PLUS_EQ_INFO = + const PrecedenceInfo(const SourceString('+='), + ASSIGNMENT_PRECEDENCE, PLUS_EQ_TOKEN); +const PrecedenceInfo SLASH_EQ_INFO = + const PrecedenceInfo(const SourceString('/='), + ASSIGNMENT_PRECEDENCE, SLASH_EQ_TOKEN); +const PrecedenceInfo STAR_EQ_INFO = + const PrecedenceInfo(const SourceString('*='), + ASSIGNMENT_PRECEDENCE, STAR_EQ_TOKEN); +const PrecedenceInfo TILDE_SLASH_EQ_INFO = + const PrecedenceInfo(const SourceString('~/='), + ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN); + +const PrecedenceInfo QUESTION_INFO = + const PrecedenceInfo(const SourceString('?'), 3, QUESTION_TOKEN); + +const PrecedenceInfo BAR_BAR_INFO = + const PrecedenceInfo(const SourceString('||'), 4, BAR_BAR_TOKEN); + +const PrecedenceInfo AMPERSAND_AMPERSAND_INFO = + const PrecedenceInfo(const SourceString('&&'), 5, AMPERSAND_AMPERSAND_TOKEN); + +const PrecedenceInfo BAR_INFO = + const PrecedenceInfo(const SourceString('|'), 6, BAR_TOKEN); + +const PrecedenceInfo CARET_INFO = + const PrecedenceInfo(const SourceString('^'), 7, CARET_TOKEN); + +const PrecedenceInfo AMPERSAND_INFO = + const PrecedenceInfo(const SourceString('&'), 8, AMPERSAND_TOKEN); + +// Equality operators. +const PrecedenceInfo BANG_EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('!=='), 9, BANG_EQ_EQ_TOKEN); +const PrecedenceInfo BANG_EQ_INFO = + const PrecedenceInfo(const SourceString('!='), 9, BANG_EQ_TOKEN); +const PrecedenceInfo EQ_EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('==='), 9, EQ_EQ_EQ_TOKEN); +const PrecedenceInfo EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('=='), 9, EQ_EQ_TOKEN); + +// Relational operators. +const PrecedenceInfo GT_EQ_INFO = + const PrecedenceInfo(const SourceString('>='), 10, GT_EQ_TOKEN); +const PrecedenceInfo GT_INFO = + const PrecedenceInfo(const SourceString('>'), 10, GT_TOKEN); +const PrecedenceInfo IS_INFO = + const PrecedenceInfo(const SourceString('is'), 10, KEYWORD_TOKEN); +const PrecedenceInfo AS_INFO = + const PrecedenceInfo(const SourceString('as'), 10, KEYWORD_TOKEN); +const PrecedenceInfo LT_EQ_INFO = + const PrecedenceInfo(const SourceString('<='), 10, LT_EQ_TOKEN); +const PrecedenceInfo LT_INFO = + const PrecedenceInfo(const SourceString('<'), 10, LT_TOKEN); + +// Shift operators. +const PrecedenceInfo GT_GT_INFO = + const PrecedenceInfo(const SourceString('>>'), 11, GT_GT_TOKEN); +const PrecedenceInfo LT_LT_INFO = + const PrecedenceInfo(const SourceString('<<'), 11, LT_LT_TOKEN); + +// Additive operators. +const PrecedenceInfo MINUS_INFO = + const PrecedenceInfo(const SourceString('-'), 12, MINUS_TOKEN); +const PrecedenceInfo PLUS_INFO = + const PrecedenceInfo(const SourceString('+'), 12, PLUS_TOKEN); + +// Multiplicative operators. +const PrecedenceInfo PERCENT_INFO = + const PrecedenceInfo(const SourceString('%'), 13, PERCENT_TOKEN); +const PrecedenceInfo SLASH_INFO = + const PrecedenceInfo(const SourceString('/'), 13, SLASH_TOKEN); +const PrecedenceInfo STAR_INFO = + const PrecedenceInfo(const SourceString('*'), 13, STAR_TOKEN); +const PrecedenceInfo TILDE_SLASH_INFO = + const PrecedenceInfo(const SourceString('~/'), 13, TILDE_SLASH_TOKEN); + +const int POSTFIX_PRECEDENCE = 14; +const PrecedenceInfo PERIOD_INFO = + const PrecedenceInfo(const SourceString('.'), POSTFIX_PRECEDENCE, + PERIOD_TOKEN); + +const PrecedenceInfo KEYWORD_INFO = + const PrecedenceInfo(const SourceString('keyword'), 0, KEYWORD_TOKEN); + +const PrecedenceInfo EOF_INFO = + const PrecedenceInfo(const SourceString('EOF'), 0, EOF_TOKEN); + +const PrecedenceInfo IDENTIFIER_INFO = + const PrecedenceInfo(const SourceString('identifier'), 0, IDENTIFIER_TOKEN); + +const PrecedenceInfo BAD_INPUT_INFO = + const PrecedenceInfo(const SourceString('malformed input'), 0, + BAD_INPUT_TOKEN); + +const PrecedenceInfo OPEN_PAREN_INFO = + const PrecedenceInfo(const SourceString('('), POSTFIX_PRECEDENCE, + OPEN_PAREN_TOKEN); + +const PrecedenceInfo CLOSE_PAREN_INFO = + const PrecedenceInfo(const SourceString(')'), 0, CLOSE_PAREN_TOKEN); + +const PrecedenceInfo OPEN_CURLY_BRACKET_INFO = + const PrecedenceInfo(const SourceString('{'), 0, OPEN_CURLY_BRACKET_TOKEN); + +const PrecedenceInfo CLOSE_CURLY_BRACKET_INFO = + const PrecedenceInfo(const SourceString('}'), 0, CLOSE_CURLY_BRACKET_TOKEN); + +const PrecedenceInfo INT_INFO = + const PrecedenceInfo(const SourceString('int'), 0, INT_TOKEN); + +const PrecedenceInfo STRING_INFO = + const PrecedenceInfo(const SourceString('string'), 0, STRING_TOKEN); + +const PrecedenceInfo OPEN_SQUARE_BRACKET_INFO = + const PrecedenceInfo(const SourceString('['), POSTFIX_PRECEDENCE, + OPEN_SQUARE_BRACKET_TOKEN); + +const PrecedenceInfo CLOSE_SQUARE_BRACKET_INFO = + const PrecedenceInfo(const SourceString(']'), 0, CLOSE_SQUARE_BRACKET_TOKEN); + +const PrecedenceInfo DOUBLE_INFO = + const PrecedenceInfo(const SourceString('double'), 0, DOUBLE_TOKEN); + +const PrecedenceInfo STRING_INTERPOLATION_INFO = + const PrecedenceInfo(const SourceString('\${'), 0, + STRING_INTERPOLATION_TOKEN); + +const PrecedenceInfo STRING_INTERPOLATION_IDENTIFIER_INFO = + const PrecedenceInfo(const SourceString('\$'), 0, + STRING_INTERPOLATION_IDENTIFIER_TOKEN); + +const PrecedenceInfo HEXADECIMAL_INFO = + const PrecedenceInfo(const SourceString('hexadecimal'), 0, HEXADECIMAL_TOKEN); + +const PrecedenceInfo COMMENT_INFO = + const PrecedenceInfo(const SourceString('comment'), 0, COMMENT_TOKEN); + +// For reporting lexical errors. +const PrecedenceInfo ERROR_INFO = + const PrecedenceInfo(const SourceString('?'), 0, UNKNOWN_TOKEN);
diff --git a/pkgs/markdown/lib/src/compiler/implementation/script.dart b/pkgs/markdown/lib/src/compiler/implementation/script.dart new file mode 100644 index 0000000..b130616 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/script.dart
@@ -0,0 +1,24 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class Script { + // TODO(kasperl): Once MockFile in tests/compiler/dart2js/parser_helper.dart + // implements SourceFile, we should be able to type the [file] field as + // such. + final file; + + /** + * The readable URI from which this script was loaded. + * + * See [LibraryLoader] for terminology on URIs. + */ + final Uri uri; + + Script(this.uri, this.file); + + String get text => (file == null) ? null : file.text; + String get name => (file == null) ? null : file.filename; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_file.dart b/pkgs/markdown/lib/src/compiler/implementation/source_file.dart new file mode 100644 index 0000000..14a4175 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/source_file.dart
@@ -0,0 +1,105 @@ +// 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. + +library source_file; + +import 'dart:math'; + +import 'colors.dart' as colors; + +/** + * Represents a file of source code. + */ +class SourceFile { + + /** The name of the file. */ + final String filename; + + /** The text content of the file. */ + final String text; + + List<int> _lineStarts; + + SourceFile(this.filename, this.text); + + List<int> get lineStarts { + if (_lineStarts == null) { + var starts = [0]; + var index = 0; + while (index < text.length) { + index = text.indexOf('\n', index) + 1; + if (index <= 0) break; + starts.add(index); + } + starts.add(text.length + 1); + _lineStarts = starts; + } + return _lineStarts; + } + + int getLine(int position) { + List<int> starts = lineStarts; + if (position < 0 || starts.last <= position) { + throw 'bad position #$position in file $filename with ' + 'length ${text.length}.'; + } + int first = 0; + int count = starts.length; + while (count > 1) { + int step = count ~/ 2; + int middle = first + step; + int lineStart = starts[middle]; + if (position < lineStart) { + count = step; + } else { + first = middle; + count -= step; + } + } + return first; + } + + int getColumn(int line, int position) { + return position - lineStarts[line]; + } + + /** + * Create a pretty string representation from a character position + * in the file. + */ + String getLocationMessage(String message, int start, int end, + bool includeText, String color(String x)) { + var line = getLine(start); + var column = getColumn(line, start); + + var buf = new StringBuffer( + '${filename}:${line + 1}:${column + 1}: $message'); + if (includeText) { + buf.add('\n'); + var textLine; + // +1 for 0-indexing, +1 again to avoid the last line of the file + if ((line + 2) < _lineStarts.length) { + textLine = text.substring(_lineStarts[line], _lineStarts[line+1]); + } else { + textLine = '${text.substring(_lineStarts[line])}\n'; + } + + int toColumn = min(column + (end-start), textLine.length); + buf.add(textLine.substring(0, column)); + buf.add(color(textLine.substring(column, toColumn))); + buf.add(textLine.substring(toColumn)); + + int i = 0; + for (; i < column; i++) { + buf.add(' '); + } + + for (; i < toColumn; i++) { + buf.add(color('^')); + } + } + + return buf.toString(); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart b/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart new file mode 100644 index 0000000..c100694 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart
@@ -0,0 +1,125 @@ +// 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. + +library source_file_provider; + +import 'dart:async'; +import 'dart:uri'; +import 'dart:io'; +import 'dart:utf'; + +import '../compiler.dart' as api show Diagnostic; +import 'dart2js.dart' show AbortLeg; +import 'colors.dart' as colors; +import 'source_file.dart'; +import 'filenames.dart'; +import 'util/uri_extras.dart'; + +String readAll(String filename) { + var file = (new File(filename)).openSync(FileMode.READ); + var length = file.lengthSync(); + var buffer = new List<int>.fixedLength(length); + var bytes = file.readListSync(buffer, 0, length); + file.closeSync(); + return new String.fromCharCodes(new Utf8Decoder(buffer).decodeRest()); +} + +class SourceFileProvider { + bool isWindows = (Platform.operatingSystem == 'windows'); + Uri cwd = getCurrentDirectory(); + Map<String, SourceFile> sourceFiles = <String, SourceFile>{}; + int dartCharactersRead = 0; + + Future<String> readStringFromUri(Uri resourceUri) { + if (resourceUri.scheme != 'file') { + throw new ArgumentError("Unknown scheme in uri '$resourceUri'"); + } + String source; + try { + source = readAll(uriPathToNative(resourceUri.path)); + } on FileIOException catch (ex) { + throw 'Error: Cannot read "${relativize(cwd, resourceUri, isWindows)}" ' + '(${ex.osError}).'; + } + dartCharactersRead += source.length; + sourceFiles[resourceUri.toString()] = + new SourceFile(relativize(cwd, resourceUri, isWindows), source); + return new Future.immediate(source); + } +} + +void silentDiagnosticHandler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { +} + +class FormattingDiagnosticHandler { + final SourceFileProvider provider; + bool showWarnings = true; + bool verbose = false; + bool isAborting = false; + bool enableColors = false; + bool throwOnError = false; + + final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal; + final int INFO = + api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal; + + FormattingDiagnosticHandler(SourceFileProvider this.provider); + + void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) { + if (!verbose && identical(kind, api.Diagnostic.VERBOSE_INFO)) return; + if (enableColors) { + print('${colors.green("info:")} $message'); + } else { + print('info: $message'); + } + } + + void diagnosticHandler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { + // TODO(ahe): Remove this when source map is handled differently. + if (identical(kind.name, 'source map')) return; + + if (isAborting) return; + isAborting = identical(kind, api.Diagnostic.CRASH); + bool fatal = (kind.ordinal & FATAL) != 0; + bool isInfo = (kind.ordinal & INFO) != 0; + if (isInfo && uri == null && !identical(kind, api.Diagnostic.INFO)) { + info(message, kind); + return; + } + var color; + if (!enableColors) { + color = (x) => x; + } else if (identical(kind, api.Diagnostic.ERROR)) { + color = colors.red; + } else if (identical(kind, api.Diagnostic.WARNING)) { + color = colors.magenta; + } else if (identical(kind, api.Diagnostic.LINT)) { + color = colors.magenta; + } else if (identical(kind, api.Diagnostic.CRASH)) { + color = colors.red; + } else if (identical(kind, api.Diagnostic.INFO)) { + color = colors.green; + } else { + throw 'Unknown kind: $kind (${kind.ordinal})'; + } + if (uri == null) { + assert(fatal); + print(color(message)); + } else if (fatal || showWarnings) { + SourceFile file = provider.sourceFiles[uri.toString()]; + if (file == null) { + throw '$uri: file is null'; + } + print(file.getLocationMessage(color(message), begin, end, true, color)); + } + if (fatal && throwOnError) { + isAborting = true; + throw new AbortLeg(message); + } + } +} + +
diff --git a/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart b/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart new file mode 100644 index 0000000..a2cd6fc --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart
@@ -0,0 +1,192 @@ +// 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. + +library source_map_builder; + +import 'util/util.dart'; +import 'scanner/scannerlib.dart' show Token; +import 'source_file.dart'; + +class SourceMapBuilder { + static const int VLQ_BASE_SHIFT = 5; + static const int VLQ_BASE_MASK = (1 << 5) - 1; + static const int VLQ_CONTINUATION_BIT = 1 << 5; + static const int VLQ_CONTINUATION_MASK = 1 << 5; + static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' + 'opqrstuvwxyz0123456789+/'; + + List<SourceMapEntry> entries; + + Map<String, int> sourceUrlMap; + List<String> sourceUrlList; + Map<String, int> sourceNameMap; + List<String> sourceNameList; + + int previousTargetLine; + int previousTargetColumn; + int previousSourceUrlIndex; + int previousSourceLine; + int previousSourceColumn; + int previousSourceNameIndex; + bool firstEntryInLine; + + SourceMapBuilder() { + entries = new List<SourceMapEntry>(); + + sourceUrlMap = new Map<String, int>(); + sourceUrlList = new List<String>(); + sourceNameMap = new Map<String, int>(); + sourceNameList = new List<String>(); + + previousTargetLine = 0; + previousTargetColumn = 0; + previousSourceUrlIndex = 0; + previousSourceLine = 0; + previousSourceColumn = 0; + previousSourceNameIndex = 0; + firstEntryInLine = true; + } + + void addMapping(int targetOffset, SourceFileLocation sourceLocation) { + entries.add(new SourceMapEntry(sourceLocation, targetOffset)); + } + + void printStringListOn(List<String> strings, StringBuffer buffer) { + bool first = true; + buffer.add('['); + for (String string in strings) { + if (!first) buffer.add(','); + buffer.add('"'); + writeJsonEscapedCharsOn(string, buffer); + buffer.add('"'); + first = false; + } + buffer.add(']'); + } + + String build(SourceFile targetFile) { + StringBuffer mappingsBuffer = new StringBuffer(); + entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile, + mappingsBuffer)); + StringBuffer buffer = new StringBuffer(); + buffer.add('{\n'); + buffer.add(' "version": 3,\n'); + buffer.add(' "sourceRoot": "",\n'); + buffer.add(' "sources": '); + printStringListOn(sourceUrlList, buffer); + buffer.add(',\n'); + buffer.add(' "names": '); + printStringListOn(sourceNameList, buffer); + buffer.add(',\n'); + buffer.add(' "mappings": "'); + buffer.add(mappingsBuffer); + buffer.add('"\n}\n'); + return buffer.toString(); + } + + void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer output) { + int targetLine = targetFile.getLine(entry.targetOffset); + int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset); + + if (targetLine > previousTargetLine) { + for (int i = previousTargetLine; i < targetLine; ++i) { + output.add(';'); + } + previousTargetLine = targetLine; + previousTargetColumn = 0; + firstEntryInLine = true; + } + + if (!firstEntryInLine) { + output.add(','); + } + firstEntryInLine = false; + + encodeVLQ(output, targetColumn - previousTargetColumn); + previousTargetColumn = targetColumn; + + if (entry.sourceLocation == null) return; + + String sourceUrl = entry.sourceLocation.getSourceUrl(); + int sourceLine = entry.sourceLocation.getLine(); + int sourceColumn = entry.sourceLocation.getColumn(); + String sourceName = entry.sourceLocation.getSourceName(); + + int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap); + encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex); + previousSourceUrlIndex = sourceUrlIndex; + + encodeVLQ(output, sourceLine - previousSourceLine); + previousSourceLine = sourceLine; + encodeVLQ(output, sourceColumn - previousSourceColumn); + previousSourceColumn = sourceColumn; + + if (sourceName == null) { + return; + } + + int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); + encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); + previousSourceNameIndex = sourceNameIndex; + } + + int indexOf(List<String> list, String value, Map<String, int> map) { + return map.putIfAbsent(value, () { + int index = list.length; + map[value] = index; + list.add(value); + return index; + }); + } + + static void encodeVLQ(StringBuffer output, int value) { + int signBit = 0; + if (value < 0) { + signBit = 1; + value = -value; + } + value = (value << 1) | signBit; + do { + int digit = value & VLQ_BASE_MASK; + value >>= VLQ_BASE_SHIFT; + if (value > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + output.add(BASE64_DIGITS[digit]); + } while (value > 0); + } +} + +class SourceMapEntry { + SourceFileLocation sourceLocation; + int targetOffset; + + SourceMapEntry(this.sourceLocation, this.targetOffset); +} + +class SourceFileLocation { + SourceFile sourceFile; + Token token; + int line; + + SourceFileLocation(this.sourceFile, this.token) { + assert(isValid()); + } + + String getSourceUrl() => sourceFile.filename; + + int getLine() { + if (line == null) line = sourceFile.getLine(token.charOffset); + return line; + } + + int getColumn() => sourceFile.getColumn(getLine(), token.charOffset); + + String getSourceName() { + if (token.isIdentifier()) return token.slowToString(); + return null; + } + + bool isValid() => token.charOffset < sourceFile.text.length; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart new file mode 100644 index 0000000..0e10ecf --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart
@@ -0,0 +1,630 @@ +// 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. + +part of ssa; + +class BailoutInfo { + int instructionId; + int bailoutId; + BailoutInfo(this.instructionId, this.bailoutId); +} + +/** + * Keeps track of the execution environment for instructions. An + * execution environment contains the SSA instructions that are live. + */ +class Environment { + final Set<HInstruction> lives; + final List<HBasicBlock> loopMarkers; + Environment() : lives = new Set<HInstruction>(), + loopMarkers = new List<HBasicBlock>(); + Environment.from(Environment other) + : lives = new Set<HInstruction>.from(other.lives), + loopMarkers = new List<HBasicBlock>.from(other.loopMarkers); + + void remove(HInstruction instruction) { + lives.remove(instruction); + } + + void add(HInstruction instruction) { + // If the instruction is a check, we add its checked input + // instead. This allows sharing the same environment between + // different type guards. + // + // Also, we don't need to add code motion invariant instructions + // in the live set (because we generate them at use-site), except + // for parameters that are not 'this', which is always passed as + // the receiver. + if (instruction is HCheck) { + add(instruction.checkedInput); + } else if (!instruction.isCodeMotionInvariant() + || (instruction is HParameterValue && instruction is !HThis)) { + lives.add(instruction); + } else { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + add(instruction.inputs[i]); + } + } + } + + void addAll(Environment other) { + lives.addAll(other.lives); + } + + bool get isEmpty => lives.isEmpty && loopMarkers.isEmpty; +} + + +/** + * Visits the graph in dominator order and inserts TypeGuards in places where + * we consider the guard to be of value. + * + * Might modify the [types] in an inconsistent way. No further analysis should + * rely on them. + */ +class SsaTypeGuardInserter extends HGraphVisitor implements OptimizationPhase { + final Compiler compiler; + final String name = 'SsaTypeGuardInserter'; + final CodegenWorkItem work; + final HTypeMap types; + bool calledInLoop = false; + bool isRecursiveMethod = false; + int stateId = 1; + + SsaTypeGuardInserter(this.compiler, this.work, this.types); + + void visitGraph(HGraph graph) { + isRecursiveMethod = graph.isRecursiveMethod; + calledInLoop = graph.calledInLoop; + work.guards = <HTypeGuard>[]; + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + block.forEachPhi(visitInstruction); + + HInstruction instruction = block.first; + while (instruction != null) { + // Note that visitInstruction (from the phis and here) might insert an + // HTypeGuard instruction. We have to skip those. + if (instruction is !HTypeGuard) visitInstruction(instruction); + instruction = instruction.next; + } + } + + // Primitive types that are not null are valuable. These include + // indexable arrays. + bool typeValuable(HType type) { + return type.isPrimitive() && !type.isNull(); + } + + bool get hasTypeGuards => work.guards.length != 0; + + bool typeGuardWouldBeValuable(HInstruction instruction, + HType speculativeType) { + // If the type itself is not valuable, do not generate a guard for it. + if (!typeValuable(speculativeType)) return false; + + // Do not insert a type guard if the instruction has a type + // annotation that disagrees with the speculated type. + Element source = instruction.sourceElement; + if (source != null) { + DartType sourceType = source.computeType(compiler); + DartType speculatedType = speculativeType.computeType(compiler); + JavaScriptBackend backend = compiler.backend; + if (speculatedType != null) { + // Use the num type instead of JSNumber because JSNumber + // is not assignment compatible with int and double, but we + // still want to generate a type guard. + if (speculatedType.element == backend.jsNumberClass) { + speculatedType = compiler.numClass.computeType(compiler); + } + if (!compiler.types.isAssignable(speculatedType, sourceType)) { + return false; + } + } + } + + // Insert type guards for recursive methods. + if (isRecursiveMethod) return true; + + // Insert type guards if there are uses in loops. + bool isNested(HBasicBlock inner, HBasicBlock outer) { + if (identical(inner, outer)) return false; + if (outer == null) return true; + while (inner != null) { + if (identical(inner, outer)) return true; + inner = inner.parentLoopHeader; + } + return false; + } + + // If the instruction is not in a loop then the header will be null. + HBasicBlock currentLoopHeader = instruction.block.enclosingLoopHeader; + for (HInstruction user in instruction.usedBy) { + HBasicBlock userLoopHeader = user.block.enclosingLoopHeader; + if (isNested(userLoopHeader, currentLoopHeader)) return true; + } + + bool isIndexOperatorOnIndexablePrimitive(instruction, types) { + return instruction is HIndex + || (instruction is HInvokeDynamicMethod + && instruction.isIndexOperatorOnIndexablePrimitive(types)); + } + + // To speed up computations on values loaded from arrays, we + // insert type guards for builtin array indexing operations in + // nested loops. Since this can blow up code size quite + // significantly, we only do it if type guards have already been + // inserted for this method. The code size price for an additional + // type guard is much smaller than the first one that causes the + // generation of a bailout method. + if (hasTypeGuards + && isIndexOperatorOnIndexablePrimitive(instruction, types)) { + HBasicBlock loopHeader = instruction.block.enclosingLoopHeader; + if (loopHeader != null && loopHeader.parentLoopHeader != null) { + return true; + } + } + + // If the instruction is used by a phi where a guard would be + // valuable, put the guard on that instruction. + for (HInstruction user in instruction.usedBy) { + if (user is HPhi + && user.block.id > instruction.id + && typeGuardWouldBeValuable(user, speculativeType)) { + return true; + } + } + + // Insert type guards if the method is likely to be called in a + // loop. + return calledInLoop; + } + + bool shouldInsertTypeGuard(HInstruction instruction, + HType speculativeType, + HType computedType) { + if (!speculativeType.isUseful()) return false; + // If the types agree we don't need to check. + if (speculativeType == computedType) return false; + // If a bailout check is more expensive than doing the actual operation + // don't do it either. + return typeGuardWouldBeValuable(instruction, speculativeType); + } + + void visitInstruction(HInstruction instruction) { + HType speculativeType = types[instruction]; + HType computedType = instruction.computeTypeFromInputTypes(types, compiler); + // Currently the type in [types] is the speculative type each instruction + // would like to have. We start by recomputing the type non-speculatively. + // If we add a type guard then the guard will expose the speculative type. + // If we don't add a type guard then this avoids that subsequent + // instructions use the wrong (speculative) type. + // + // Note that just setting the speculative type of the instruction is not + // complete since the type could lead to a phi node which in turn could + // change the speculative type. In this case we might miss some guards we + // would have liked to insert. Most of the time this should however be + // fine, due to dominator-order visiting. + types[instruction] = computedType; + + if (shouldInsertTypeGuard(instruction, speculativeType, computedType)) { + HInstruction insertionPoint; + if (instruction is HPhi) { + insertionPoint = instruction.block.first; + } else if (instruction is HParameterValue) { + // We insert the type guard at the end of the entry block + // because if a parameter is live, it must be kept in the live + // environment. Not doing so would mean we could visit a + // parameter and remove it from the environment before + // visiting a type guard. + insertionPoint = instruction.block.last; + } else { + insertionPoint = instruction.next; + } + // If the previous instruction is also a type guard, then both + // guards have the same environment, and can therefore share the + // same state id. + HBailoutTarget target; + int state; + if (insertionPoint.previous is HTypeGuard) { + HTypeGuard other = insertionPoint.previous; + target = other.bailoutTarget; + } else { + state = stateId++; + target = new HBailoutTarget(state); + insertionPoint.block.addBefore(insertionPoint, target); + } + HTypeGuard guard = new HTypeGuard(speculativeType, instruction, target); + types[guard] = speculativeType; + work.guards.add(guard); + instruction.block.rewrite(instruction, guard); + insertionPoint.block.addBefore(insertionPoint, guard); + } + } +} + +/** + * Computes the environment for each SSA instruction: visits the graph + * in post-dominator order. Removes an instruction from the environment + * and adds its inputs to the environment at the instruction's + * definition. + * + * At the end of the computation, insert type guards in the graph. + */ +class SsaEnvironmentBuilder extends HBaseVisitor implements OptimizationPhase { + final Compiler compiler; + final String name = 'SsaEnvironmentBuilder'; + + final Map<HBailoutTarget, Environment> capturedEnvironments; + final Map<HBasicBlock, Environment> liveInstructions; + Environment environment; + /** + * The set of current loop headers that dominate the current block. + */ + Set<HBasicBlock> loopMarkers; + + SsaEnvironmentBuilder(Compiler this.compiler) + : capturedEnvironments = new Map<HBailoutTarget, Environment>(), + liveInstructions = new Map<HBasicBlock, Environment>(), + loopMarkers = new Set<HBasicBlock>(); + + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + if (!liveInstructions[graph.entry].isEmpty) { + compiler.internalError('Bailout environment computation', + node: compiler.currentElement.parseNode(compiler)); + } + updateLoopMarkers(); + insertCapturedEnvironments(); + } + + void updateLoopMarkers() { + // If the block is a loop header, we need to merge the loop + // header's live instructions into every environment that contains + // the loop marker. + // For example with the following loop (read the example in + // reverse): + // + // while (true) { <-- (4) update the marker with the environment + // use(x); <-- (3) environment = {x} + // bailout; <-- (2) has the marker when computed + // } <-- (1) create a loop marker + // + // The bailout instruction first captures the marker, but it + // will be replaced by the live environment at the loop entry, + // in this case {x}. + capturedEnvironments.forEach((ignoredInstruction, env) { + env.loopMarkers.forEach((HBasicBlock header) { + env.addAll(liveInstructions[header]); + }); + env.loopMarkers.clear(); + }); + } + + void visitBasicBlock(HBasicBlock block) { + environment = new Environment(); + + // Add to the environment the live instructions of its successor, as well as + // the inputs of the phis of the successor that flow from this block. + for (int i = 0; i < block.successors.length; i++) { + HBasicBlock successor = block.successors[i]; + Environment successorEnv = liveInstructions[successor]; + if (successorEnv != null) { + environment.addAll(successorEnv); + } else { + // If we haven't computed the liveInstructions of that successor, we + // know it must be a loop header. + assert(successor.isLoopHeader()); + assert(!block.isLoopHeader()); + loopMarkers.add(successor); + } + + int index = successor.predecessors.indexOf(block); + for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) { + environment.add(phi.inputs[index]); + } + } + + if (block.isLoopHeader()) { + loopMarkers.remove(block); + } + + // If the block is a loop header, we're adding all [loopMarkers] + // after removing it from the list of [loopMarkers], because + // it will just recompute the loop phis. + environment.loopMarkers.addAll(loopMarkers); + + // Iterate over all instructions to remove an instruction from the + // environment and add its inputs. + HInstruction instruction = block.last; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.previous; + } + + // We just remove the phis from the environment. The inputs of the + // phis will be put in the environment of the predecessors. + for (HPhi phi = block.phis.first; phi != null; phi = phi.next) { + environment.remove(phi); + } + + // Finally save the liveInstructions of that block. + liveInstructions[block] = environment; + } + + void visitBailoutTarget(HBailoutTarget target) { + visitInstruction(target); + capturedEnvironments[target] = new Environment.from(environment); + } + + void visitInstruction(HInstruction instruction) { + environment.remove(instruction); + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + environment.add(instruction.inputs[i]); + } + } + + /** + * Stores all live variables in the bailout target and the guards. + */ + void insertCapturedEnvironments() { + capturedEnvironments.forEach((HBailoutTarget target, Environment env) { + assert(target.inputs.length == 0); + target.inputs.addAll(env.lives); + // TODO(floitsch): we should add the bailout-target's input variables + // as input to the guards only in the optimized version. The + // non-optimized version does not use the bailout guards and it is + // unnecessary to keep the variables alive until the check. + for (HTypeGuard guard in target.usedBy) { + // A type-guard initially only has two inputs: the guarded instruction + // and the bailout-target. Only after adding the environment is it + // allowed to have more inputs. + assert(guard.inputs.length == 2); + guard.inputs.addAll(env.lives); + } + for (HInstruction live in env.lives) { + live.usedBy.add(target); + live.usedBy.addAll(target.usedBy); + } + }); + } +} + +/** + * Propagates bailout information to blocks that need it. This visitor + * is run before codegen, to know which blocks have to deal with + * bailouts. + */ +class SsaBailoutPropagator extends HBaseVisitor { + final Compiler compiler; + /** + * A list to propagate bailout information to blocks that start a + * guarded or labeled list of statements. Currently, these blocks + * are: + * - first block of a then branch, + * - first block of an else branch, + * - a loop header, + * - labeled block. + */ + final List<HBasicBlock> blocks; + + /** + * The current subgraph we are visiting. + */ + SubGraph subGraph; + + /** + * The current block information we are visiting. + */ + HBlockInformation currentBlockInformation; + + /** + * Max number of arguments to the bailout (not counting the state). + */ + int bailoutArity; + /** + * A map from variables to their names. These are the names in the + * unoptimized (bailout) version of the function. Their names could be + * different in the optimized version. + */ + VariableNames variableNames; + /** + * Maps from the variable names to their positions in the argument list of the + * bailout instruction. Because of the way the variable allocator works, + * several variables can end up with the same name (if their live ranges do + * not overlap), therefore they can have the same position in the bailout + * argument list + */ + Map<String, int> parameterNames; + + /** + * If set to true, the graph has either multiple bailouts in + * different places, or a bailout inside an if or a loop. For such a + * graph, the code generator will emit a generic switch. + */ + bool hasComplexBailoutTargets = false; + + /** + * The first type guard in the graph. + */ + HBailoutTarget firstBailoutTarget; + + /** + * If set, it is the first block in the graph where we generate + * code. Blocks before this one are dead code in the bailout + * version. + */ + + SsaBailoutPropagator(this.compiler, this.variableNames) + : blocks = <HBasicBlock>[], + bailoutArity = 0, + parameterNames = new Map<String, int>(); + + void visitGraph(HGraph graph) { + subGraph = new SubGraph(graph.entry, graph.exit); + visitBasicBlock(graph.entry); + if (!blocks.isEmpty) { + compiler.internalError('Bailout propagation', + node: compiler.currentElement.parseNode(compiler)); + } + } + + /** + * Returns true if we can visit the given [blockFlow]. False + * otherwise. Currently, try/catch and switch are not in bailout + * methods, so this method only deals with loops and labeled blocks. + * If [blockFlow] is a labeled block or a loop, we also visit the + * continuation of the block flow. + */ + bool handleBlockFlow(HBlockFlow blockFlow) { + HBlockInformation body = blockFlow.body; + + // We reach here again when starting to visit a subgraph. Just + // return to visiting the block. + if (currentBlockInformation == body) return false; + + HBlockInformation oldInformation = currentBlockInformation; + if (body is HLabeledBlockInformation) { + currentBlockInformation = body; + HLabeledBlockInformation info = body; + visitStatements(info.body, newFlow: true); + } else if (body is HLoopBlockInformation) { + currentBlockInformation = body; + HLoopBlockInformation info = body; + if (info.initializer != null) { + visitExpression(info.initializer); + } + blocks.addLast(info.loopHeader); + if (!info.isDoWhile()) { + visitExpression(info.condition); + } + visitStatements(info.body, newFlow: false); + if (info.isDoWhile()) { + visitExpression(info.condition); + } + if (info.updates != null) { + visitExpression(info.updates); + } + blocks.removeLast(); + } else { + assert(body is! HTryBlockInformation); + assert(body is! HSwitchBlockInformation); + // [HIfBlockInformation] is handled by visitIf. + return false; + } + + currentBlockInformation = oldInformation; + if (blockFlow.continuation != null) { + visitBasicBlock(blockFlow.continuation); + } + return true; + } + + void visitBasicBlock(HBasicBlock block) { + // Abort traversal if we are leaving the currently active sub-graph. + if (!subGraph.contains(block)) return; + + HBlockFlow blockFlow = block.blockFlow; + if (blockFlow != null && handleBlockFlow(blockFlow)) return; + + HInstruction instruction = block.first; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.next; + } + } + + void visitExpression(HSubExpressionBlockInformation info) { + visitSubGraph(info.subExpression); + } + + /** + * Visit the statements in [info]. If [newFlow] is true, we add the + * first block of [statements] to the list of [blocks]. + */ + void visitStatements(HSubGraphBlockInformation info, {bool newFlow}) { + SubGraph graph = info.subGraph; + if (newFlow) blocks.addLast(graph.start); + visitSubGraph(graph); + if (newFlow) blocks.removeLast(); + } + + void visitSubGraph(SubGraph graph) { + SubGraph oldSubGraph = subGraph; + subGraph = graph; + visitBasicBlock(graph.start); + subGraph = oldSubGraph; + } + + void visitIf(HIf instruction) { + int preVisitedBlocks = 0; + HIfBlockInformation info = instruction.blockInformation.body; + visitStatements(info.thenGraph, newFlow: true); + preVisitedBlocks++; + visitStatements(info.elseGraph, newFlow: true); + preVisitedBlocks++; + + HBasicBlock joinBlock = instruction.joinBlock; + if (joinBlock != null + && !identical(joinBlock.dominator, instruction.block)) { + // The join block is dominated by a block in one of the branches. + // The subgraph traversal never reached it, so we visit it here + // instead. + visitBasicBlock(joinBlock); + } + + // Visit all the dominated blocks that are not part of the then or else + // branches, and is not the join block. + // Depending on how the then/else branches terminate + // (e.g., return/throw/break) there can be any number of these. + List<HBasicBlock> dominated = instruction.block.dominatedBlocks; + int dominatedCount = dominated.length; + for (int i = preVisitedBlocks; i < dominatedCount; i++) { + HBasicBlock dominatedBlock = dominated[i]; + visitBasicBlock(dominatedBlock); + } + } + + void visitGoto(HGoto goto) { + HBasicBlock block = goto.block; + HBasicBlock successor = block.successors[0]; + if (identical(successor.dominator, block)) { + visitBasicBlock(block.successors[0]); + } + } + + void visitLoopBranch(HLoopBranch branch) { + // For a do-while loop, the body has already been visited. + if (!branch.isDoWhile()) { + visitBasicBlock(branch.block.dominatedBlocks[0]); + } + } + + visitBailoutTarget(HBailoutTarget target) { + int inputLength = target.inputs.length; + for (HInstruction input in target.inputs) { + String inputName = variableNames.getName(input); + int position = parameterNames[inputName]; + if (position == null) { + position = parameterNames[inputName] = bailoutArity++; + } + } + + if (blocks.isEmpty) { + if (firstBailoutTarget == null) { + firstBailoutTarget = target; + } else { + hasComplexBailoutTargets = true; + } + } else { + hasComplexBailoutTargets = true; + blocks.forEach((HBasicBlock block) { + block.bailoutTargets.add(target); + }); + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart new file mode 100644 index 0000000..ac19fea --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart
@@ -0,0 +1,5034 @@ +// 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. + +part of ssa; + +/** + * A special element for the extra parameter taken by intercepted + * methods. We need to override [Element.computeType] because our + * optimizers may look at its declared type. + */ +class InterceptedElement extends ElementX { + final HType ssaType; + InterceptedElement(this.ssaType, Element enclosing) + : super(const SourceString('receiver'), + ElementKind.PARAMETER, + enclosing); + + DartType computeType(Compiler compiler) => ssaType.computeType(compiler); +} + +class SsaBuilderTask extends CompilerTask { + final CodeEmitterTask emitter; + // Loop tracking information. + final Set<FunctionElement> functionsCalledInLoop; + final Map<SourceString, Selector> selectorsCalledInLoop; + final JavaScriptBackend backend; + + String get name => 'SSA builder'; + + SsaBuilderTask(JavaScriptBackend backend) + : emitter = backend.emitter, + functionsCalledInLoop = new Set<FunctionElement>(), + selectorsCalledInLoop = new Map<SourceString, Selector>(), + backend = backend, + super(backend.compiler); + + HGraph build(CodegenWorkItem work) { + return measure(() { + Element element = work.element.implementation; + HInstruction.idCounter = 0; + ConstantSystem constantSystem = compiler.backend.constantSystem; + SsaBuilder builder = new SsaBuilder(constantSystem, this, work); + HGraph graph; + ElementKind kind = element.kind; + if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR)) { + graph = compileConstructor(builder, work); + } else if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY) || + identical(kind, ElementKind.FUNCTION) || + identical(kind, ElementKind.GETTER) || + identical(kind, ElementKind.SETTER)) { + graph = builder.buildMethod(element); + } else if (identical(kind, ElementKind.FIELD)) { + graph = builder.buildLazyInitializer(element); + } else { + compiler.internalErrorOnElement(element, + 'unexpected element kind $kind'); + } + assert(graph.isValid()); + if (!identical(kind, ElementKind.FIELD)) { + bool inLoop = functionsCalledInLoop.contains(element.declaration); + if (!inLoop) { + Selector selector = selectorsCalledInLoop[element.name]; + inLoop = selector != null && selector.applies(element, compiler); + } + graph.calledInLoop = inLoop; + + // If there is an estimate of the parameter types assume these types + // when compiling. + // TODO(karlklose,ngeoffray): add a check to make sure that element is + // of type FunctionElement. + FunctionElement function = element; + OptionalParameterTypes defaultValueTypes = null; + FunctionSignature signature = function.computeSignature(compiler); + if (signature.optionalParameterCount > 0) { + defaultValueTypes = + new OptionalParameterTypes(signature.optionalParameterCount); + int index = 0; + signature.forEachOptionalParameter((Element parameter) { + Constant defaultValue = builder.compileVariable(parameter); + HType type = HGraph.mapConstantTypeToSsaType(defaultValue); + defaultValueTypes.update(index, parameter.name, type); + index++; + }); + } else { + // TODO(ahe): I have disabled type optimizations for + // optional arguments as the types are stored in the wrong + // order. + HTypeList parameterTypes = + backend.optimisticParameterTypes(element.declaration, + defaultValueTypes); + if (!parameterTypes.allUnknown) { + int i = 0; + signature.forEachParameter((Element param) { + builder.parameters[param].guaranteedType = parameterTypes[i++]; + }); + } + backend.registerParameterTypesOptimization( + element.declaration, parameterTypes, defaultValueTypes); + } + } + + if (compiler.tracer.enabled) { + String name; + if (element.isMember()) { + String className = element.getEnclosingClass().name.slowToString(); + String memberName = element.name.slowToString(); + name = "$className.$memberName"; + if (element.isGenerativeConstructorBody()) { + name = "$name (body)"; + } + } else { + name = "${element.name.slowToString()}"; + } + compiler.tracer.traceCompilation(name, work.compilationContext); + compiler.tracer.traceGraph('builder', graph); + } + return graph; + }); + } + + HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) { + // The body of the constructor will be generated in a separate function. + final ClassElement classElement = work.element.getEnclosingClass(); + return builder.buildFactory(classElement.implementation, + work.element.implementation); + } +} + +/** + * Keeps track of locals (including parameters and phis) when building. The + * 'this' reference is treated as parameter and hence handled by this class, + * too. + */ +class LocalsHandler { + /** + * The values of locals that can be directly accessed (without redirections + * to boxes or closure-fields). + * + * [directLocals] is iterated, so it is a [LinkedHashMap] to make the + * iteration order a function only of insertions and not a function of + * e.g. Element hash codes. I'd prefer to use a SortedMap but some elements + * don't have source locations for [Elements.compareByPosition]. + */ + LinkedHashMap<Element, HInstruction> directLocals; + Map<Element, Element> redirectionMapping; + SsaBuilder builder; + ClosureClassMap closureData; + + LocalsHandler(this.builder) + : directLocals = new LinkedHashMap<Element, HInstruction>(), + redirectionMapping = new Map<Element, Element>(); + + get typesTask => builder.compiler.typesTask; + + /** + * Creates a new [LocalsHandler] based on [other]. We only need to + * copy the [directLocals], since the other fields can be shared + * throughout the AST visit. + */ + LocalsHandler.from(LocalsHandler other) + : directLocals = + new LinkedHashMap<Element, HInstruction>.from(other.directLocals), + redirectionMapping = other.redirectionMapping, + builder = other.builder, + closureData = other.closureData; + + /** + * Redirects accesses from element [from] to element [to]. The [to] element + * must be a boxed variable or a variable that is stored in a closure-field. + */ + void redirectElement(Element from, Element to) { + assert(redirectionMapping[from] == null); + redirectionMapping[from] = to; + assert(isStoredInClosureField(from) || isBoxed(from)); + } + + HInstruction createBox() { + // TODO(floitsch): Clean up this hack. Should we create a box-object by + // just creating an empty object literal? + HInstruction box = new HForeign(const LiteralDartString("{}"), + HType.UNKNOWN, + <HInstruction>[]); + builder.add(box); + return box; + } + + /** + * If the scope (function or loop) [node] has captured variables then this + * method creates a box and sets up the redirections. + */ + void enterScope(Node node, Element element) { + // See if any variable in the top-scope of the function is captured. If yes + // we need to create a box-object. + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData != null) { + HInstruction box; + // The scope has captured variables. + if (element != null && element.isGenerativeConstructorBody()) { + // The box is passed as a parameter to a generative + // constructor body. + box = builder.addParameter(scopeData.boxElement); + } else { + box = createBox(); + } + // Add the box to the known locals. + directLocals[scopeData.boxElement] = box; + // Make sure that accesses to the boxed locals go into the box. We also + // need to make sure that parameters are copied into the box if necessary. + scopeData.capturedVariableMapping.forEach((Element from, Element to) { + // The [from] can only be a parameter for function-scopes and not + // loop scopes. + if (from.isParameter() && !element.isGenerativeConstructorBody()) { + // Now that the redirection is set up, the update to the local will + // write the parameter value into the box. + // Store the captured parameter in the box. Get the current value + // before we put the redirection in place. + // We don't need to update the local for a generative + // constructor body, because it receives a box that already + // contains the updates as the last parameter. + HInstruction instruction = readLocal(from); + redirectElement(from, to); + updateLocal(from, instruction); + } else { + redirectElement(from, to); + } + }); + } + } + + /** + * Replaces the current box with a new box and copies over the given list + * of elements from the old box into the new box. + */ + void updateCaptureBox(Element boxElement, List<Element> toBeCopiedElements) { + // Create a new box and copy over the values from the old box into the + // new one. + HInstruction oldBox = readLocal(boxElement); + HInstruction newBox = createBox(); + for (Element boxedVariable in toBeCopiedElements) { + // [readLocal] uses the [boxElement] to find its box. By replacing it + // behind its back we can still get to the old values. + updateLocal(boxElement, oldBox); + HInstruction oldValue = readLocal(boxedVariable); + updateLocal(boxElement, newBox); + updateLocal(boxedVariable, oldValue); + } + updateLocal(boxElement, newBox); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + void startFunction(Element element, Expression node) { + assert(invariant(node, element.isImplementation)); + Compiler compiler = builder.compiler; + closureData = compiler.closureToClassMapper.computeClosureToClassMapping( + element, node, builder.elements); + + if (element is FunctionElement) { + FunctionElement functionElement = element; + FunctionSignature params = functionElement.computeSignature(compiler); + params.orderedForEachParameter((Element parameterElement) { + if (element.isGenerativeConstructorBody()) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData != null + && scopeData.capturedVariableMapping.containsKey( + parameterElement)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to have it. + return; + } + } + HInstruction parameter = builder.addParameter(parameterElement); + builder.parameters[parameterElement] = parameter; + directLocals[parameterElement] = parameter; + parameter.guaranteedType = + builder.mapInferredType( + typesTask.getGuaranteedTypeOfElement(parameterElement)); + }); + } + + enterScope(node, element); + + // If the freeVariableMapping is not empty, then this function was a + // nested closure that captures variables. Redirect the captured + // variables to fields in the closure. + closureData.freeVariableMapping.forEach((Element from, Element to) { + redirectElement(from, to); + }); + if (closureData.isClosure()) { + // Inside closure redirect references to itself to [:this:]. + HThis thisInstruction = new HThis(closureData.thisElement); + builder.graph.thisInstruction = thisInstruction; + builder.graph.entry.addAtEntry(thisInstruction); + updateLocal(closureData.closureElement, thisInstruction); + } else if (element.isInstanceMember() + || element.isGenerativeConstructor()) { + // Once closures have been mapped to classes their instance members might + // not have any thisElement if the closure was created inside a static + // context. + ClassElement cls = element.getEnclosingClass(); + DartType type = cls.computeType(builder.compiler); + HThis thisInstruction = new HThis(closureData.thisElement, + new HBoundedType.nonNull(type)); + builder.graph.thisInstruction = thisInstruction; + builder.graph.entry.addAtEntry(thisInstruction); + directLocals[closureData.thisElement] = thisInstruction; + } + + // If this method is an intercepted method, add the extra + // parameter to it, that is the actual receiver. + ClassElement cls = element.getEnclosingClass(); + if (builder.backend.isInterceptorClass(cls)) { + HType type = HType.UNKNOWN; + if (cls == builder.backend.jsArrayClass) { + type = HType.READABLE_ARRAY; + } else if (cls == builder.backend.jsStringClass) { + type = HType.STRING; + } else if (cls == builder.backend.jsNumberClass) { + type = HType.NUMBER; + } else if (cls == builder.backend.jsIntClass) { + type = HType.INTEGER; + } else if (cls == builder.backend.jsDoubleClass) { + type = HType.DOUBLE; + } else if (cls == builder.backend.jsNullClass) { + type = HType.NULL; + } else if (cls == builder.backend.jsBoolClass) { + type = HType.BOOLEAN; + } + Element parameter = new InterceptedElement(type, element); + HParameterValue value = new HParameterValue(parameter); + builder.graph.entry.addAfter( + directLocals[closureData.thisElement], value); + directLocals[closureData.thisElement] = value; + value.guaranteedType = type; + } + } + + bool hasValueForDirectLocal(Element element) { + assert(element != null); + assert(isAccessedDirectly(element)); + return directLocals[element] != null; + } + + /** + * Returns true if the local can be accessed directly. Boxed variables or + * captured variables that are stored in the closure-field return [false]. + */ + bool isAccessedDirectly(Element element) { + assert(element != null); + return redirectionMapping[element] == null + && !closureData.usedVariablesInTry.contains(element); + } + + bool isStoredInClosureField(Element element) { + assert(element != null); + if (isAccessedDirectly(element)) return false; + Element redirectTarget = redirectionMapping[element]; + if (redirectTarget == null) return false; + if (redirectTarget.isMember()) { + assert(redirectTarget is ClosureFieldElement); + return true; + } + return false; + } + + bool isBoxed(Element element) { + if (isAccessedDirectly(element)) return false; + if (isStoredInClosureField(element)) return false; + return redirectionMapping[element] != null; + } + + bool isUsedInTry(Element element) { + return closureData.usedVariablesInTry.contains(element); + } + + /** + * Returns an [HInstruction] for the given element. If the element is + * boxed or stored in a closure then the method generates code to retrieve + * the value. + */ + HInstruction readLocal(Element element) { + if (isAccessedDirectly(element)) { + if (directLocals[element] == null) { + builder.compiler.internalError("Cannot find value $element", + element: element); + } + return directLocals[element]; + } else if (isStoredInClosureField(element)) { + Element redirect = redirectionMapping[element]; + HInstruction receiver = readLocal(closureData.closureElement); + HInstruction fieldGet = new HFieldGet(redirect, receiver); + builder.add(fieldGet); + return fieldGet; + } else if (isBoxed(element)) { + Element redirect = redirectionMapping[element]; + // In the function that declares the captured variable the box is + // accessed as direct local. Inside the nested closure the box is + // accessed through a closure-field. + // Calling [readLocal] makes sure we generate the correct code to get + // the box. + assert(redirect.enclosingElement.isVariable()); + HInstruction box = readLocal(redirect.enclosingElement); + HInstruction lookup = new HFieldGet(redirect, box); + builder.add(lookup); + return lookup; + } else { + assert(isUsedInTry(element)); + HLocalValue local = getLocal(element); + HInstruction variable = new HLocalGet(element, local); + builder.add(variable); + return variable; + } + } + + HType cachedTypeOfThis; + + HInstruction readThis() { + HInstruction res = readLocal(closureData.thisElement); + if (res.guaranteedType == null) { + if (cachedTypeOfThis == null) { + assert(closureData.isClosure()); + Element element = closureData.thisElement; + ClassElement cls = element.enclosingElement.getEnclosingClass(); + DartType type = cls.computeType(builder.compiler); + cachedTypeOfThis = new HBoundedType.nonNull(type); + } + res.guaranteedType = cachedTypeOfThis; + } + return res; + } + + HLocalValue getLocal(Element element) { + // If the element is a parameter, we already have a + // HParameterValue for it. We cannot create another one because + // it could then have another name than the real parameter. And + // the other one would not know it is just a copy of the real + // parameter. + if (element.isParameter()) return builder.parameters[element]; + + return builder.activationVariables.putIfAbsent(element, () { + HLocalValue local = new HLocalValue(element); + builder.graph.entry.addAtExit(local); + return local; + }); + } + + /** + * Sets the [element] to [value]. If the element is boxed or stored in a + * closure then the method generates code to set the value. + */ + void updateLocal(Element element, HInstruction value) { + assert(!isStoredInClosureField(element)); + if (isAccessedDirectly(element)) { + directLocals[element] = value; + } else if (isBoxed(element)) { + Element redirect = redirectionMapping[element]; + // The box itself could be captured, or be local. A local variable that + // is captured will be boxed, but the box itself will be a local. + // Inside the closure the box is stored in a closure-field and cannot + // be accessed directly. + assert(redirect.enclosingElement.isVariable()); + HInstruction box = readLocal(redirect.enclosingElement); + builder.add(new HFieldSet(redirect, box, value)); + } else { + assert(isUsedInTry(element)); + HLocalValue local = getLocal(element); + builder.add(new HLocalSet(element, local, value)); + } + } + + /** + * This function must be called before visiting any children of the loop. In + * particular it needs to be called before executing the initializers. + * + * The [LocalsHandler] will make the boxes and updates at the right moment. + * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] (for + * [For] loops) at the correct places. For phi-handling [beginLoopHeader] and + * [endLoop] must also be called. + * + * The correct place for the box depends on the given loop. In most cases + * the box will be created when entering the loop-body: while, do-while, and + * for-in (assuming the call to [:next:] is inside the body) can always be + * constructed this way. + * + * Things are slightly more complicated for [For] loops. If no declared + * loop variable is boxed then the loop-body approach works here too. If a + * loop-variable is boxed we need to introduce a new box for the + * loop-variable before we enter the initializer so that the initializer + * writes the values into the box. In any case we need to create the box + * before the condition since the condition could box the variable. + * Since the first box is created outside the actual loop we have a second + * location where a box is created: just before the updates. This is + * necessary since updates are considered to be part of the next iteration + * (and can again capture variables). + * + * For example the following Dart code prints 1 3 -- 3 4. + * + * var fs = []; + * for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) { + * i++; + * } + * print("--"); + * for (var i = 0; i < 2; i++) fs[i](); + * + * We solve this by emitting the following code (only for [For] loops): + * <Create box> <== move the first box creation outside the loop. + * <initializer>; + * loop-entry: + * if (!<condition>) goto loop-exit; + * <body> + * <update box> // create a new box and copy the captured loop-variables. + * <updates> + * goto loop-entry; + * loop-exit: + */ + void startLoop(Node node) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + if (scopeData.hasBoxedLoopVariables()) { + // If there are boxed loop variables then we set up the box and + // redirections already now. This way the initializer can write its + // values into the box. + // For other loops the box will be created when entering the body. + enterScope(node, null); + } + } + + void beginLoopHeader(Node node, HBasicBlock loopEntry) { + // Create a copy because we modify the map while iterating over it. + Map<Element, HInstruction> saved = + new LinkedHashMap<Element, HInstruction>.from(directLocals); + + // Create phis for all elements in the definitions environment. + saved.forEach((Element element, HInstruction instruction) { + if (isAccessedDirectly(element)) { + // We know 'this' cannot be modified. + if (!identical(element, closureData.thisElement)) { + HPhi phi = new HPhi.singleInput(element, instruction); + loopEntry.addPhi(phi); + directLocals[element] = phi; + } else { + directLocals[element] = instruction; + } + } + }); + } + + void enterLoopBody(Node node) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + // If there are no declared boxed loop variables then we did not create the + // box before the initializer and we have to create the box now. + if (!scopeData.hasBoxedLoopVariables()) { + enterScope(node, null); + } + } + + void enterLoopUpdates(Loop node) { + // If there are declared boxed loop variables then the updates might have + // access to the box and we must switch to a new box before executing the + // updates. + // In all other cases a new box will be created when entering the body of + // the next iteration. + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + if (scopeData.hasBoxedLoopVariables()) { + updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables); + } + } + + void endLoop(HBasicBlock loopEntry) { + // If the loop has an aborting body, we don't update the loop + // phis. + if (loopEntry.predecessors.length == 1) return; + loopEntry.forEachPhi((HPhi phi) { + Element element = phi.sourceElement; + HInstruction postLoopDefinition = directLocals[element]; + phi.addInput(postLoopDefinition); + }); + } + + /** + * Merge [otherLocals] into this locals handler, creating phi-nodes when + * there is a conflict. + * If a phi node is necessary, it will use this handler's instruction as the + * first input, and the otherLocals instruction as the second. + */ + void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) { + // If an element is in one map but not the other we can safely + // ignore it. It means that a variable was declared in the + // block. Since variable declarations are scoped the declared + // variable cannot be alive outside the block. Note: this is only + // true for nodes where we do joins. + Map<Element, HInstruction> joinedLocals = + new LinkedHashMap<Element, HInstruction>(); + otherLocals.directLocals.forEach((element, instruction) { + // We know 'this' cannot be modified. + if (identical(element, closureData.thisElement)) { + assert(directLocals[element] == instruction); + joinedLocals[element] = instruction; + } else { + HInstruction mine = directLocals[element]; + if (mine == null) return; + if (identical(instruction, mine)) { + joinedLocals[element] = instruction; + } else { + HInstruction phi = + new HPhi.manyInputs(element, <HInstruction>[mine, instruction]); + joinBlock.addPhi(phi); + joinedLocals[element] = phi; + } + } + }); + directLocals = joinedLocals; + } + + /** + * The current localsHandler is not used for its values, only for its + * declared variables. This is a way to exclude local values from the + * result when they are no longer in scope. + * Returns the new LocalsHandler to use (may not be [this]). + */ + LocalsHandler mergeMultiple(List<LocalsHandler> locals, + HBasicBlock joinBlock) { + assert(locals.length > 0); + if (locals.length == 1) return locals[0]; + Map<Element, HInstruction> joinedLocals = + new LinkedHashMap<Element,HInstruction>(); + HInstruction thisValue = null; + directLocals.forEach((Element element, HInstruction instruction) { + if (!identical(element, closureData.thisElement)) { + HPhi phi = new HPhi.noInputs(element); + joinedLocals[element] = phi; + joinBlock.addPhi(phi); + } else { + // We know that "this" never changes, if it's there. + // Save it for later. While merging, there is no phi for "this", + // so we don't have to special case it in the merge loop. + thisValue = instruction; + } + }); + for (LocalsHandler local in locals) { + local.directLocals.forEach((Element element, HInstruction instruction) { + HPhi phi = joinedLocals[element]; + if (phi != null) { + phi.addInput(instruction); + } + }); + } + if (thisValue != null) { + // If there was a "this" for the scope, add it to the new locals. + joinedLocals[closureData.thisElement] = thisValue; + } + directLocals = joinedLocals; + return this; + } +} + + +// Represents a single break/continue instruction. +class JumpHandlerEntry { + final HJump jumpInstruction; + final LocalsHandler locals; + bool isBreak() => jumpInstruction is HBreak; + bool isContinue() => jumpInstruction is HContinue; + JumpHandlerEntry(this.jumpInstruction, this.locals); +} + + +abstract class JumpHandler { + factory JumpHandler(SsaBuilder builder, TargetElement target) { + return new TargetJumpHandler(builder, target); + } + void generateBreak([LabelElement label]); + void generateContinue([LabelElement label]); + void forEachBreak(void action(HBreak instruction, LocalsHandler locals)); + void forEachContinue(void action(HContinue instruction, + LocalsHandler locals)); + bool hasAnyContinue(); + bool hasAnyBreak(); + void close(); + final TargetElement target; + List<LabelElement> labels(); +} + +// Insert break handler used to avoid null checks when a target isn't +// used as the target of a break, and therefore doesn't need a break +// handler associated with it. +class NullJumpHandler implements JumpHandler { + final Compiler compiler; + + NullJumpHandler(this.compiler); + + void generateBreak([LabelElement label]) { + compiler.internalError('generateBreak should not be called'); + } + + void generateContinue([LabelElement label]) { + compiler.internalError('generateContinue should not be called'); + } + + void forEachBreak(Function ignored) { } + void forEachContinue(Function ignored) { } + void close() { } + bool hasAnyContinue() => false; + bool hasAnyBreak() => false; + + List<LabelElement> labels() => const <LabelElement>[]; + TargetElement get target => null; +} + +// Records breaks until a target block is available. +// Breaks are always forward jumps. +// Continues in loops are implemented as breaks of the body. +// Continues in switches is currently not handled. +class TargetJumpHandler implements JumpHandler { + final SsaBuilder builder; + final TargetElement target; + final List<JumpHandlerEntry> jumps; + + TargetJumpHandler(SsaBuilder builder, this.target) + : this.builder = builder, + jumps = <JumpHandlerEntry>[] { + assert(builder.jumpTargets[target] == null); + builder.jumpTargets[target] = this; + } + + void generateBreak([LabelElement label]) { + HInstruction breakInstruction; + if (label == null) { + breakInstruction = new HBreak(target); + } else { + breakInstruction = new HBreak.toLabel(label); + } + LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); + builder.close(breakInstruction); + jumps.add(new JumpHandlerEntry(breakInstruction, locals)); + } + + void generateContinue([LabelElement label]) { + HInstruction continueInstruction; + if (label == null) { + continueInstruction = new HContinue(target); + } else { + continueInstruction = new HContinue.toLabel(label); + } + LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); + builder.close(continueInstruction); + jumps.add(new JumpHandlerEntry(continueInstruction, locals)); + } + + void forEachBreak(Function action) { + for (JumpHandlerEntry entry in jumps) { + if (entry.isBreak()) action(entry.jumpInstruction, entry.locals); + } + } + + void forEachContinue(Function action) { + for (JumpHandlerEntry entry in jumps) { + if (entry.isContinue()) action(entry.jumpInstruction, entry.locals); + } + } + + bool hasAnyContinue() { + for (JumpHandlerEntry entry in jumps) { + if (entry.isContinue()) return true; + } + return false; + } + + bool hasAnyBreak() { + for (JumpHandlerEntry entry in jumps) { + if (entry.isBreak()) return true; + } + return false; + } + + void close() { + // The mapping from TargetElement to JumpHandler is no longer needed. + builder.jumpTargets.remove(target); + } + + List<LabelElement> labels() { + List<LabelElement> result = null; + for (LabelElement element in target.labels) { + if (result == null) result = <LabelElement>[]; + result.add(element); + } + return (result == null) ? const <LabelElement>[] : result; + } +} + +class SsaBuilder extends ResolvedVisitor implements Visitor { + final SsaBuilderTask builder; + final JavaScriptBackend backend; + final CodegenWorkItem work; + final ConstantSystem constantSystem; + HGraph graph; + LocalsHandler localsHandler; + HInstruction rethrowableException; + Map<Element, HInstruction> parameters; + final RuntimeTypeInformation rti; + HParameterValue lastAddedParameter; + + Map<TargetElement, JumpHandler> jumpTargets; + + /** + * Variables stored in the current activation. These variables are + * being updated in try/catch blocks, and should be + * accessed indirectly through [HLocalGet] and [HLocalSet]. + */ + Map<Element, HLocalValue> activationVariables; + + // We build the Ssa graph by simulating a stack machine. + List<HInstruction> stack; + + // The current block to add instructions to. Might be null, if we are + // visiting dead code. + HBasicBlock current; + // The most recently opened block. Has the same value as [current] while + // the block is open, but unlike [current], it isn't cleared when the current + // block is closed. + HBasicBlock lastOpenedBlock; + + final List<Element> sourceElementStack; + + Element get currentElement => sourceElementStack.last.declaration; + Compiler get compiler => builder.compiler; + CodeEmitterTask get emitter => builder.emitter; + + SsaBuilder(this.constantSystem, SsaBuilderTask builder, CodegenWorkItem work) + : this.builder = builder, + this.backend = builder.backend, + this.work = work, + graph = new HGraph(), + stack = new List<HInstruction>(), + activationVariables = new Map<Element, HLocalValue>(), + jumpTargets = new Map<TargetElement, JumpHandler>(), + parameters = new Map<Element, HInstruction>(), + sourceElementStack = <Element>[work.element], + inliningStack = <InliningState>[], + rti = builder.backend.rti, + super(work.resolutionTree) { + localsHandler = new LocalsHandler(this); + } + + static const MAX_INLINING_DEPTH = 3; + static const MAX_INLINING_SOURCE_SIZE = 128; + List<InliningState> inliningStack; + Element returnElement; + DartType returnType; + bool inTryStatement = false; + + /** + * Compiles compile-time constants. Never returns [:null:]. If the + * initial value is not a compile-time constants, it reports an + * internal error. + */ + Constant compileConstant(VariableElement element) { + return compiler.constantHandler.compileConstant(element); + } + + Constant compileVariable(VariableElement element) { + return compiler.constantHandler.compileVariable(element); + } + + bool isLazilyInitialized(VariableElement element) { + Constant initialValue = compileVariable(element); + return initialValue == null; + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [functionElement] must be an implementation element. + */ + HGraph buildMethod(FunctionElement functionElement) { + assert(invariant(functionElement, functionElement.isImplementation)); + FunctionExpression function = functionElement.parseNode(compiler); + assert(function != null); + assert(!function.modifiers.isExternal()); + assert(elements[function] != null); + openFunction(functionElement, function); + SourceString name = functionElement.name; + // If [functionElement] is operator== we explicitely add a null + // check at the beginning of the method. This is to avoid having + // call sites do the null check. + if (name == const SourceString('==')) { + handleIf( + function, + () { + HParameterValue parameter = parameters.values.first; + push(new HIdentity( + parameter, graph.addConstantNull(constantSystem))); + }, + () { + HReturn ret = new HReturn( + graph.addConstantBool(false, constantSystem)); + close(ret).addSuccessor(graph.exit); + }, + null); + } + function.body.accept(this); + return closeFunction(); + } + + HGraph buildLazyInitializer(VariableElement variable) { + SendSet node = variable.parseNode(compiler); + openFunction(variable, node); + Link<Node> link = node.arguments; + assert(!link.isEmpty && link.tail.isEmpty); + visit(link.head); + HInstruction value = pop(); + value = potentiallyCheckType(value, variable.computeType(compiler)); + close(new HReturn(value)).addSuccessor(graph.exit); + return closeFunction(); + } + + /** + * Returns the constructor body associated with the given constructor or + * creates a new constructor body, if none can be found. + * + * Returns [:null:] if the constructor does not have a body. + */ + ConstructorBodyElement getConstructorBody(FunctionElement constructor) { + assert(constructor.isGenerativeConstructor()); + assert(invariant(constructor, constructor.isImplementation)); + if (constructor.isSynthesized) return null; + FunctionExpression node = constructor.parseNode(compiler); + // If we know the body doesn't have any code, we don't generate it. + if (!node.hasBody()) return null; + if (node.hasEmptyBody()) return null; + ClassElement classElement = constructor.getEnclosingClass(); + ConstructorBodyElement bodyElement; + classElement.forEachBackendMember((Element backendMember) { + if (backendMember.isGenerativeConstructorBody()) { + ConstructorBodyElement body = backendMember; + if (body.constructor == constructor) { + // TODO(kasperl): Find a way of stopping the iteration + // through the backend members. + bodyElement = backendMember; + } + } + }); + if (bodyElement == null) { + bodyElement = new ConstructorBodyElementX(constructor); + // [:resolveMethodElement:] require the passed element to be a + // declaration. + TreeElements treeElements = + compiler.enqueuer.resolution.getCachedElements( + constructor.declaration); + classElement.addBackendMember(bodyElement); + + if (constructor.isPatch) { + // Create origin body element for patched constructors. + bodyElement.origin = new ConstructorBodyElementX(constructor.origin); + bodyElement.origin.patch = bodyElement; + classElement.origin.addBackendMember(bodyElement.origin); + } + compiler.enqueuer.codegen.addToWorkList(bodyElement.declaration, + treeElements); + } + assert(bodyElement.isGenerativeConstructorBody()); + return bodyElement; + } + + HParameterValue addParameter(Element element) { + HParameterValue result = new HParameterValue(element); + if (lastAddedParameter == null) { + graph.entry.addBefore(graph.entry.first, result); + } else { + graph.entry.addAfter(lastAddedParameter, result); + } + lastAddedParameter = result; + return result; + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + InliningState enterInlinedMethod(PartialFunctionElement function, + Selector selector, + Link<Node> arguments, + Node currentNode) { + assert(invariant(function, function.isImplementation)); + + // Once we start to compile the arguments we must be sure that we don't + // abort. + List<HInstruction> compiledArguments = new List<HInstruction>(); + bool succeeded = addStaticSendArgumentsToList(selector, + arguments, + function, + compiledArguments); + assert(succeeded); + + FunctionSignature signature = function.computeSignature(compiler); + int index = 0; + signature.orderedForEachParameter((Element parameter) { + HInstruction argument = compiledArguments[index++]; + localsHandler.updateLocal(parameter, argument); + potentiallyCheckType(argument, parameter.computeType(compiler)); + }); + + if (function.isConstructor()) { + ClassElement enclosing = function.getEnclosingClass(); + if (compiler.world.needsRti(enclosing)) { + assert(currentNode is NewExpression); + InterfaceType type = elements.getType(currentNode); + Link<DartType> typeVariable = enclosing.typeVariables; + type.typeArguments.forEach((DartType argument) { + HInstruction instruction = + analyzeTypeArgument(argument, currentNode); + localsHandler.updateLocal(typeVariable.head.element, instruction); + typeVariable = typeVariable.tail; + }); + while (!typeVariable.isEmpty) { + localsHandler.updateLocal(typeVariable.head.element, + graph.addConstantNull(constantSystem)); + typeVariable = typeVariable.tail; + } + } + } + InliningState state = + new InliningState(function, returnElement, returnType, elements, stack); + + // TODO(kasperl): Bad smell. We shouldn't be constructing elements here. + returnElement = new ElementX(const SourceString("result"), + ElementKind.VARIABLE, + function); + localsHandler.updateLocal(returnElement, + graph.addConstantNull(constantSystem)); + elements = compiler.enqueuer.resolution.getCachedElements(function); + assert(elements != null); + returnType = signature.returnType; + stack = <HInstruction>[]; + inliningStack.add(state); + return state; + } + + void leaveInlinedMethod(InliningState state) { + InliningState poppedState = inliningStack.removeLast(); + assert(state == poppedState); + elements = state.oldElements; + stack.add(localsHandler.readLocal(returnElement)); + returnElement = state.oldReturnElement; + returnType = state.oldReturnType; + assert(stack.length == 1); + state.oldStack.add(stack[0]); + stack = state.oldStack; + } + + /** + * Try to inline [element] within the currect context of the + * builder. The insertion point is the state of the builder. + */ + bool tryInlineMethod(Element element, + Selector selector, + Link<Node> arguments, + Node currentNode) { + if (compiler.disableInlining) return false; + // Ensure that [element] is an implementation element. + element = element.implementation; + // TODO(floitsch): we should be able to inline inside lazy initializers. + if (!currentElement.isFunction()) return false; + // TODO(floitsch): find a cleaner way to know if the element is a function + // containing nodes. + // [PartialFunctionElement]s are [FunctionElement]s that have [Node]s. + if (element is !PartialFunctionElement) return false; + // TODO(ngeoffray): try to inline generative constructors. They + // don't have any body, which make it more difficult. + if (element.isGenerativeConstructor()) return false; + if (inliningStack.length > MAX_INLINING_DEPTH) return false; + // Don't inline recursive calls. We use the same elements for the inlined + // functions and would thus clobber our local variables. + // Use [:element.declaration:] since [work.element] is always a declaration. + if (currentElement == element.declaration) return false; + for (int i = 0; i < inliningStack.length; i++) { + if (inliningStack[i].function == element) return false; + } + PartialFunctionElement function = element; + int sourceSize = + function.endToken.charOffset - function.beginToken.charOffset; + if (sourceSize > MAX_INLINING_SOURCE_SIZE) return false; + if (!selector.applies(function, compiler)) return false; + FunctionExpression functionExpression = function.parseNode(compiler); + TreeElements newElements = + compiler.enqueuer.resolution.getCachedElements(function); + if (newElements == null) { + compiler.internalError("Element not resolved: $function"); + } + if (!InlineWeeder.canBeInlined(functionExpression, newElements)) { + return false; + } + + InliningState state = enterInlinedMethod( + function, selector, arguments, currentNode); + inlinedFrom(element, () { + functionExpression.body.accept(this); + }); + leaveInlinedMethod(state); + return true; + } + + inlinedFrom(Element element, f()) { + return compiler.withCurrentElement(element, () { + sourceElementStack.add(element); + var result = f(); + sourceElementStack.removeLast(); + return result; + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [constructor] and [constructors] must all be implementation + * elements. + */ + void inlineSuperOrRedirect(FunctionElement constructor, + Selector selector, + Link<Node> arguments, + List<FunctionElement> constructors, + Map<Element, HInstruction> fieldValues, + FunctionElement inlinedFromElement) { + compiler.withCurrentElement(constructor, () { + assert(invariant(constructor, constructor.isImplementation)); + constructors.addLast(constructor); + + List<HInstruction> compiledArguments = new List<HInstruction>(); + bool succeeded = + inlinedFrom(inlinedFromElement, + () => addStaticSendArgumentsToList(selector, + arguments, + constructor, + compiledArguments)); + if (!succeeded) { + // Non-matching super and redirects are compile-time errors and thus + // checked by the resolver. + compiler.internalError( + "Parameters and arguments didn't match for super/redirect call", + element: constructor); + } + + sourceElementStack.add(constructor.enclosingElement); + buildFieldInitializers(constructor.enclosingElement.implementation, + fieldValues); + sourceElementStack.removeLast(); + + int index = 0; + FunctionSignature params = constructor.computeSignature(compiler); + params.orderedForEachParameter((Element parameter) { + HInstruction argument = compiledArguments[index++]; + // Because we are inlining the initializer, we must update + // what was given as parameter. This will be used in case + // there is a parameter check expression in the initializer. + parameters[parameter] = argument; + localsHandler.updateLocal(parameter, argument); + // Don't forget to update the field, if the parameter is of the + // form [:this.x:]. + if (parameter.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParameterElement = parameter; + fieldValues[fieldParameterElement.fieldElement] = argument; + } + }); + + // Build the initializers in the context of the new constructor. + TreeElements oldElements = elements; + elements = + compiler.enqueuer.resolution.getCachedElements(constructor); + + ClosureClassMap oldClosureData = localsHandler.closureData; + Node node = constructor.parseNode(compiler); + ClosureClassMap newClosureData = + compiler.closureToClassMapper.computeClosureToClassMapping( + constructor, node, elements); + // The [:this:] element now refers to the one in the new closure + // data, that is the [:this:] of the super constructor. We + // update the element to refer to the current [:this:]. + localsHandler.updateLocal(newClosureData.thisElement, + localsHandler.readThis()); + localsHandler.closureData = newClosureData; + + params.orderedForEachParameter((Element parameterElement) { + if (elements.isParameterChecked(parameterElement)) { + addParameterCheckInstruction(parameterElement); + } + }); + localsHandler.enterScope(node, constructor); + buildInitializers(constructor, constructors, fieldValues); + localsHandler.closureData = oldClosureData; + elements = oldElements; + }); + } + + /** + * Run through the initializers and inline all field initializers. Recursively + * inlines super initializers. + * + * The constructors of the inlined initializers is added to [constructors] + * with sub constructors having a lower index than super constructors. + * + * Invariant: The [constructor] and elements in [constructors] must all be + * implementation elements. + */ + void buildInitializers(FunctionElement constructor, + List<FunctionElement> constructors, + Map<Element, HInstruction> fieldValues) { + assert(invariant(constructor, constructor.isImplementation)); + FunctionExpression functionNode = constructor.parseNode(compiler); + + bool foundSuperOrRedirect = false; + + if (functionNode.initializers != null) { + Link<Node> initializers = functionNode.initializers.nodes; + for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) { + assert(link.head is Send); + if (link.head is !SendSet) { + // A super initializer or constructor redirection. + Send call = link.head; + assert(Initializers.isSuperConstructorCall(call) || + Initializers.isConstructorRedirect(call)); + FunctionElement target = elements[call]; + Selector selector = elements.getSelector(call); + Link<Node> arguments = call.arguments; + inlineSuperOrRedirect(target, selector, arguments, constructors, + fieldValues, constructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link<Node> arguments = init.arguments; + assert(!arguments.isEmpty && arguments.tail.isEmpty); + sourceElementStack.add(constructor); + visit(arguments.head); + sourceElementStack.removeLast(); + fieldValues[elements[init]] = pop(); + } + } + } + + if (!foundSuperOrRedirect) { + // No super initializer found. Try to find the default constructor if + // the class is not Object. + ClassElement enclosingClass = constructor.getEnclosingClass(); + ClassElement superClass = enclosingClass.superclass; + if (!enclosingClass.isObject(compiler)) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + Selector selector = + new Selector.callDefaultConstructor(enclosingClass.getLibrary()); + // TODO(johnniwinther): Should we find injected constructors as well? + FunctionElement target = superClass.lookupConstructor(selector); + if (target == null) { + compiler.internalError("no default constructor available"); + } + inlineSuperOrRedirect(target.implementation, + selector, + const Link<Node>(), + constructors, + fieldValues, + constructor); + } + } + } + + /** + * Run through the fields of [cls] and add their potential + * initializers. + * + * Invariant: [classElement] must be an implementation element. + */ + void buildFieldInitializers(ClassElement classElement, + Map<Element, HInstruction> fieldValues) { + assert(invariant(classElement, classElement.isImplementation)); + classElement.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + TreeElements definitions = compiler.analyzeElement(member); + Node node = member.parseNode(compiler); + SendSet assignment = node.asSendSet(); + HInstruction value; + if (assignment == null) { + value = graph.addConstantNull(constantSystem); + } else { + Node right = assignment.arguments.head; + TreeElements savedElements = elements; + elements = definitions; + right.accept(this); + elements = savedElements; + value = pop(); + } + fieldValues[member] = value; + }, + includeBackendMembers: true, + includeSuperMembers: false); + } + + + /** + * Build the factory function corresponding to the constructor + * [functionElement]: + * - Initialize fields with the values of the field initializers of the + * current constructor and super constructors or constructors redirected + * to, starting from the current constructor. + * - Call the the constructor bodies, starting from the constructor(s) in the + * super class(es). + * + * Invariant: Both [classElement] and [functionElement] must be + * implementation elements. + */ + HGraph buildFactory(ClassElement classElement, + FunctionElement functionElement) { + assert(invariant(classElement, classElement.isImplementation)); + assert(invariant(functionElement, functionElement.isImplementation)); + FunctionExpression function = functionElement.parseNode(compiler); + // Note that constructors (like any other static function) do not need + // to deal with optional arguments. It is the callers job to provide all + // arguments as if they were positional. + + // The initializer list could contain closures. + openFunction(functionElement, function); + + Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>(); + + // Compile the possible initialization code for local fields and + // super fields. + buildFieldInitializers(classElement, fieldValues); + + // Compile field-parameters such as [:this.x:]. + FunctionSignature params = functionElement.computeSignature(compiler); + params.orderedForEachParameter((Element element) { + if (element.kind == ElementKind.FIELD_PARAMETER) { + // If the [element] is a field-parameter then + // initialize the field element with its value. + FieldParameterElement fieldParameterElement = element; + HInstruction parameterValue = localsHandler.readLocal(element); + fieldValues[fieldParameterElement.fieldElement] = parameterValue; + } + }); + + // Analyze the constructor and all referenced constructors and collect + // initializers and constructor bodies. + List<FunctionElement> constructors = <FunctionElement>[functionElement]; + buildInitializers(functionElement, constructors, fieldValues); + + // Call the JavaScript constructor with the fields as argument. + List<HInstruction> constructorArguments = <HInstruction>[]; + classElement.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + constructorArguments.add(potentiallyCheckType( + fieldValues[member], member.computeType(compiler))); + }, + includeBackendMembers: true, + includeSuperMembers: true); + + InterfaceType type = classElement.computeType(compiler); + HType ssaType = new HBoundedType.exact(type); + HForeignNew newObject = new HForeignNew(classElement, + ssaType, + constructorArguments); + add(newObject); + + // Create the runtime type information, if needed. + List<HInstruction> inputs = <HInstruction>[]; + if (compiler.world.needsRti(classElement)) { + classElement.typeVariables.forEach((TypeVariableType typeVariable) { + inputs.add(localsHandler.directLocals[typeVariable.element]); + }); + callSetRuntimeTypeInfo(classElement, inputs, newObject); + } + + // Generate calls to the constructor bodies. + for (int index = constructors.length - 1; index >= 0; index--) { + FunctionElement constructor = constructors[index]; + assert(invariant(functionElement, constructor.isImplementation)); + ConstructorBodyElement body = getConstructorBody(constructor); + if (body == null) continue; + List bodyCallInputs = <HInstruction>[]; + bodyCallInputs.add(newObject); + FunctionSignature functionSignature = body.computeSignature(compiler); + functionSignature.orderedForEachParameter((parameter) { + if (!localsHandler.isBoxed(parameter)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to pass it. + bodyCallInputs.add(localsHandler.readLocal(parameter)); + } + }); + + // If parameters are checked, we pass the already computed + // boolean to the constructor body. + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(constructor); + Node node = constructor.parseNode(compiler); + ClosureClassMap parameterClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(node); + functionSignature.orderedForEachParameter((parameter) { + if (elements.isParameterChecked(parameter)) { + Element fieldCheck = + parameterClosureData.parametersWithSentinel[parameter]; + bodyCallInputs.add(localsHandler.readLocal(fieldCheck)); + } + }); + + // If there are locals that escape (ie used in closures), we + // pass the box to the constructor. + ClosureScope scopeData = parameterClosureData.capturingScopes[node]; + if (scopeData != null) { + bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); + } + + // TODO(ahe): The constructor name is statically resolved. See + // SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner + // way to do this? + SourceString name = + new SourceString(backend.namer.getName(body.declaration)); + // TODO(kasperl): This seems fishy. We shouldn't be inventing all + // these selectors. Maybe the resolver can do more of the work + // for us here? + LibraryElement library = body.getLibrary(); + Selector selector = new Selector.call( + name, library, bodyCallInputs.length - 1); + HInvokeDynamic invoke = + new HInvokeDynamicMethod(selector, bodyCallInputs); + invoke.element = body; + add(invoke); + } + close(new HReturn(newObject)).addSuccessor(graph.exit); + return closeFunction(); + } + + void addParameterCheckInstruction(Element element) { + HInstruction check; + Element checkResultElement = + localsHandler.closureData.parametersWithSentinel[element]; + if (currentElement.isGenerativeConstructorBody()) { + // A generative constructor body receives extra parameters that + // indicate if a parameter was passed to the factory. + check = addParameter(checkResultElement); + } else { + // This is the code we emit for a parameter that is being checked + // on whether it was given at value at the call site: + // + // foo([a = 42]) { + // if (?a) print('parameter passed $a'); + // } + // + // foo([a = 42]) { + // var t1 = identical(a, sentinel); + // if (t1) a = 42; + // if (!t1) print('parameter passed ' + a); + // } + + // Fetch the original default value of [element]; + Constant constant = compileVariable(element); + HConstant defaultValue = constant == null + ? graph.addConstantNull(constantSystem) + : graph.addConstant(constant); + + // Emit the equality check with the sentinel. + HConstant sentinel = graph.addConstant(SentinelConstant.SENTINEL); + HInstruction operand = parameters[element]; + check = new HIdentity(sentinel, operand); + add(check); + + // If the check succeeds, we must update the parameter with the + // default value. + handleIf(element.parseNode(compiler), + () => stack.add(check), + () => localsHandler.updateLocal(element, defaultValue), + null); + + // Create the instruction that parameter checks will use. + check = new HNot(check); + add(check); + } + + localsHandler.updateLocal(checkResultElement, check); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [functionElement] must be the implementation element. + */ + void openFunction(Element element, Expression node) { + assert(invariant(element, element.isImplementation)); + HBasicBlock block = graph.addNewBlock(); + open(graph.entry); + + localsHandler.startFunction(element, node); + close(new HGoto()).addSuccessor(block); + + open(block); + + if (element is FunctionElement) { + FunctionElement functionElement = element; + FunctionSignature signature = functionElement.computeSignature(compiler); + signature.orderedForEachParameter((Element parameterElement) { + if (elements.isParameterChecked(parameterElement)) { + addParameterCheckInstruction(parameterElement); + } + }); + + // Put the type checks in the first successor of the entry, + // because that is where the type guards will also be inserted. + // This way we ensure that a type guard will dominate the type + // check. + signature.orderedForEachParameter((Element parameterElement) { + if (element.isGenerativeConstructorBody()) { + ClosureScope scopeData = + localsHandler.closureData.capturingScopes[node]; + if (scopeData != null + && scopeData.capturedVariableMapping.containsKey( + parameterElement)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to have it. + return; + } + } + HInstruction newParameter = potentiallyCheckType( + localsHandler.directLocals[parameterElement], + parameterElement.computeType(compiler)); + localsHandler.directLocals[parameterElement] = newParameter; + }); + + returnType = signature.returnType; + } else { + // Otherwise it is a lazy initializer which does not have parameters. + assert(element is VariableElement); + } + + // Add the type parameters of the class as parameters of this + // method. + var enclosing = element.enclosingElement; + if (element.isConstructor() && compiler.world.needsRti(enclosing)) { + enclosing.typeVariables.forEach((TypeVariableType typeVariable) { + HParameterValue param = addParameter(typeVariable.element); + localsHandler.directLocals[typeVariable.element] = param; + }); + } + } + + HInstruction potentiallyCheckType( + HInstruction original, DartType type, + { int kind: HTypeConversion.CHECKED_MODE_CHECK }) { + if (!compiler.enableTypeAssertions) return original; + HInstruction other = original.convertType(compiler, type, kind); + if (other != original) add(other); + return other; + } + + HGraph closeFunction() { + // TODO(kasperl): Make this goto an implicit return. + if (!isAborted()) close(new HGoto()).addSuccessor(graph.exit); + graph.finalize(); + return graph; + } + + HBasicBlock addNewBlock() { + HBasicBlock block = graph.addNewBlock(); + // If adding a new block during building of an expression, it is due to + // conditional expressions or short-circuit logical operators. + return block; + } + + void open(HBasicBlock block) { + block.open(); + current = block; + lastOpenedBlock = block; + } + + HBasicBlock close(HControlFlow end) { + HBasicBlock result = current; + current.close(end); + current = null; + return result; + } + + void goto(HBasicBlock from, HBasicBlock to) { + from.close(new HGoto()); + from.addSuccessor(to); + } + + bool isAborted() { + return current == null; + } + + /** + * Creates a new block, transitions to it from any current block, and + * opens the new block. + */ + HBasicBlock openNewBlock() { + HBasicBlock newBlock = addNewBlock(); + if (!isAborted()) goto(current, newBlock); + open(newBlock); + return newBlock; + } + + void add(HInstruction instruction) { + current.add(instruction); + } + + void addWithPosition(HInstruction instruction, Node node) { + add(attachPosition(instruction, node)); + } + + void push(HInstruction instruction) { + add(instruction); + stack.add(instruction); + } + + void pushWithPosition(HInstruction instruction, Node node) { + push(attachPosition(instruction, node)); + } + + HInstruction pop() { + return stack.removeLast(); + } + + void dup() { + stack.add(stack.last); + } + + HInstruction popBoolified() { + HInstruction value = pop(); + if (compiler.enableTypeAssertions) { + return potentiallyCheckType( + value, + compiler.boolClass.computeType(compiler), + kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); + } + HInstruction result = new HBoolify(value); + add(result); + return result; + } + + HInstruction attachPosition(HInstruction target, Node node) { + target.sourcePosition = sourceFileLocationForBeginToken(node); + return target; + } + + SourceFileLocation sourceFileLocationForBeginToken(Node node) => + sourceFileLocationForToken(node, node.getBeginToken()); + + SourceFileLocation sourceFileLocationForEndToken(Node node) => + sourceFileLocationForToken(node, node.getEndToken()); + + SourceFileLocation sourceFileLocationForToken(Node node, Token token) { + Element element = sourceElementStack.last; + // TODO(johnniwinther): remove the 'element.patch' hack. + if (element is FunctionElement) { + FunctionElement functionElement = element; + if (functionElement.patch != null) element = functionElement.patch; + } + Script script = element.getCompilationUnit().script; + SourceFile sourceFile = script.file; + SourceFileLocation location = new SourceFileLocation(sourceFile, token); + if (!location.isValid()) { + throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message( + {'offset': token.charOffset, + 'fileName': sourceFile.filename, + 'length': sourceFile.text.length}); + } + return location; +} + + void visit(Node node) { + if (node != null) node.accept(this); + } + + visitBlock(Block node) { + for (Link<Node> link = node.statements.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + if (isAborted()) { + // The block has been aborted by a return or a throw. + if (!stack.isEmpty) compiler.cancel('non-empty instruction stack'); + return; + } + } + assert(!current.isClosed()); + if (!stack.isEmpty) compiler.cancel('non-empty instruction stack'); + } + + visitClassNode(ClassNode node) { + compiler.internalError('visitClassNode should not be called', node: node); + } + + visitExpressionStatement(ExpressionStatement node) { + visit(node.expression); + pop(); + } + + /** + * Creates a new loop-header block. The previous [current] block + * is closed with an [HGoto] and replaced by the newly created block. + * Also notifies the locals handler that we're entering a loop. + */ + JumpHandler beginLoopHeader(Node node) { + assert(!isAborted()); + HBasicBlock previousBlock = close(new HGoto()); + + JumpHandler jumpHandler = createJumpHandler(node); + HBasicBlock loopEntry = graph.addNewLoopHeaderBlock( + jumpHandler.target, + jumpHandler.labels()); + previousBlock.addSuccessor(loopEntry); + open(loopEntry); + + localsHandler.beginLoopHeader(node, loopEntry); + return jumpHandler; + } + + /** + * Ends the loop: + * - creates a new block and adds it as successor to the [branchBlock]. + * - opens the new block (setting as [current]). + * - notifies the locals handler that we're exiting a loop. + */ + void endLoop(HBasicBlock loopEntry, + HBasicBlock branchBlock, + JumpHandler jumpHandler, + LocalsHandler savedLocals) { + if (branchBlock == null && !jumpHandler.hasAnyBreak()) return; + + HBasicBlock loopExitBlock = addNewBlock(); + assert(branchBlock == null || branchBlock.successors.length == 1); + List<LocalsHandler> breakLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { + breakInstruction.block.addSuccessor(loopExitBlock); + breakLocals.add(locals); + }); + if (branchBlock != null) { + branchBlock.addSuccessor(loopExitBlock); + } + open(loopExitBlock); + localsHandler.endLoop(loopEntry); + if (!breakLocals.isEmpty) { + breakLocals.add(savedLocals); + localsHandler = savedLocals.mergeMultiple(breakLocals, loopExitBlock); + } else { + localsHandler = savedLocals; + } + } + + HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) { + if (statements == null) return null; + return new HSubGraphBlockInformation(statements); + } + + HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) { + if (expression == null) return null; + return new HSubExpressionBlockInformation(expression); + } + + // For while loops, initializer and update are null. + // The condition function must return a boolean result. + // None of the functions must leave anything on the stack. + void handleLoop(Node loop, + void initialize(), + HInstruction condition(), + void update(), + void body()) { + // Generate: + // <initializer> + // loop-entry: + // if (!<condition>) goto loop-exit; + // <body> + // <updates> + // goto loop-entry; + // loop-exit: + + localsHandler.startLoop(loop); + + // The initializer. + SubExpression initializerGraph = null; + HBasicBlock startBlock; + if (initialize != null) { + HBasicBlock initializerBlock = openNewBlock(); + startBlock = initializerBlock; + initialize(); + assert(!isAborted()); + initializerGraph = + new SubExpression(initializerBlock, current); + } + + JumpHandler jumpHandler = beginLoopHeader(loop); + HLoopInformation loopInfo = current.loopInformation; + HBasicBlock conditionBlock = current; + if (startBlock == null) startBlock = conditionBlock; + + HInstruction conditionInstruction = condition(); + HBasicBlock conditionExitBlock = + close(new HLoopBranch(conditionInstruction)); + SubExpression conditionExpression = + new SubExpression(conditionBlock, conditionExitBlock); + + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + + // The body. + HBasicBlock beginBodyBlock = addNewBlock(); + conditionExitBlock.addSuccessor(beginBodyBlock); + open(beginBodyBlock); + + localsHandler.enterLoopBody(loop); + body(); + + SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock); + HBasicBlock bodyBlock = current; + if (current != null) close(new HGoto()); + + SubExpression updateGraph; + + // Check that the loop has at least one back-edge. + if (jumpHandler.hasAnyContinue() || bodyBlock != null) { + // Update. + // We create an update block, even when we are in a while loop. There the + // update block is the jump-target for continue statements. We could avoid + // the creation if there is no continue, but for now we always create it. + HBasicBlock updateBlock = addNewBlock(); + + List<LocalsHandler> continueLocals = <LocalsHandler>[]; + jumpHandler.forEachContinue((HContinue instruction, + LocalsHandler locals) { + instruction.block.addSuccessor(updateBlock); + continueLocals.add(locals); + }); + + + if (bodyBlock != null) { + continueLocals.add(localsHandler); + bodyBlock.addSuccessor(updateBlock); + } + + open(updateBlock); + localsHandler = + continueLocals[0].mergeMultiple(continueLocals, updateBlock); + + HLabeledBlockInformation labelInfo; + List<LabelElement> labels = jumpHandler.labels(); + TargetElement target = elements[loop]; + if (!labels.isEmpty) { + beginBodyBlock.setBlockFlow( + new HLabeledBlockInformation( + new HSubGraphBlockInformation(bodyGraph), + jumpHandler.labels(), + isContinue: true), + updateBlock); + } else if (target != null && target.isContinueTarget) { + beginBodyBlock.setBlockFlow( + new HLabeledBlockInformation.implicit( + new HSubGraphBlockInformation(bodyGraph), + target, + isContinue: true), + updateBlock); + } + + localsHandler.enterLoopUpdates(loop); + + update(); + + HBasicBlock updateEndBlock = close(new HGoto()); + // The back-edge completing the cycle. + updateEndBlock.addSuccessor(conditionBlock); + updateGraph = new SubExpression(updateBlock, updateEndBlock); + } + + if (jumpHandler.hasAnyContinue() || bodyBlock != null) { + endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); + conditionBlock.postProcessLoopHeader(); + HLoopBlockInformation info = + new HLoopBlockInformation( + HLoopBlockInformation.loopType(loop), + wrapExpressionGraph(initializerGraph), + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + wrapExpressionGraph(updateGraph), + conditionBlock.loopInformation.target, + conditionBlock.loopInformation.labels, + sourceFileLocationForBeginToken(loop), + sourceFileLocationForEndToken(loop)); + + startBlock.setBlockFlow(info, current); + loopInfo.loopBlockInformation = info; + } else { + // There is no back edge for the loop, so we turn the code into: + // if (condition) { + // body; + // } else { + // // We always create an empty else block to avoid critical edges. + // } + // + // If there is any break in the body, we attach a synthetic + // label to the if. + HBasicBlock elseBlock = addNewBlock(); + open(elseBlock); + close(new HGoto()); + endLoop(conditionBlock, null, jumpHandler, savedLocals); + + // [endLoop] will not create an exit block if there are no + // breaks. + if (current == null) open(addNewBlock()); + elseBlock.addSuccessor(current); + SubGraph elseGraph = new SubGraph(elseBlock, elseBlock); + // Remove the loop information attached to the header. + conditionBlock.loopInformation = null; + + // Remove the [HLoopBranch] instruction and replace it with + // [HIf]. + HInstruction condition = conditionExitBlock.last.inputs[0]; + conditionExitBlock.addAtExit(new HIf(condition)); + conditionExitBlock.addSuccessor(elseBlock); + conditionExitBlock.remove(conditionExitBlock.last); + HIfBlockInformation info = + new HIfBlockInformation( + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + wrapStatementGraph(elseGraph)); + + conditionBlock.setBlockFlow(info, current); + HIf ifBlock = conditionBlock.last; + ifBlock.blockInformation = conditionBlock.blockFlow; + + // If the body has any break, attach a synthesized label to the + // if block. + if (jumpHandler.hasAnyBreak()) { + TargetElement target = elements[loop]; + LabelElement label = target.addLabel(null, 'loop'); + label.setBreakTarget(); + SubGraph labelGraph = new SubGraph(conditionBlock, current); + HLabeledBlockInformation labelInfo = new HLabeledBlockInformation( + new HSubGraphBlockInformation(labelGraph), + <LabelElement>[label]); + + conditionBlock.setBlockFlow(labelInfo, current); + + jumpHandler.forEachBreak((HBreak breakInstruction, _) { + HBasicBlock block = breakInstruction.block; + block.addAtExit(new HBreak.toLabel(label)); + block.remove(breakInstruction); + }); + } + } + jumpHandler.close(); + } + + visitFor(For node) { + assert(node.body != null); + void buildInitializer() { + if (node.initializer == null) return; + Node initializer = node.initializer; + if (initializer != null) { + visit(initializer); + if (initializer.asExpression() != null) { + pop(); + } + } + } + HInstruction buildCondition() { + if (node.condition == null) { + return graph.addConstantBool(true, constantSystem); + } + visit(node.condition); + return popBoolified(); + } + void buildUpdate() { + for (Expression expression in node.update) { + visit(expression); + assert(!isAborted()); + // The result of the update instruction isn't used, and can just + // be dropped. + HInstruction updateInstruction = pop(); + } + } + void buildBody() { + visit(node.body); + } + handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody); + } + + visitWhile(While node) { + HInstruction buildCondition() { + visit(node.condition); + return popBoolified(); + } + handleLoop(node, + () {}, + buildCondition, + () {}, + () { visit(node.body); }); + } + + visitDoWhile(DoWhile node) { + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + localsHandler.startLoop(node); + JumpHandler jumpHandler = beginLoopHeader(node); + HLoopInformation loopInfo = current.loopInformation; + HBasicBlock loopEntryBlock = current; + HBasicBlock bodyEntryBlock = current; + TargetElement target = elements[node]; + bool hasContinues = target != null && target.isContinueTarget; + if (hasContinues) { + // Add extra block to hang labels on. + // It doesn't currently work if they are on the same block as the + // HLoopInfo. The handling of HLabeledBlockInformation will visit a + // SubGraph that starts at the same block again, so the HLoopInfo is + // either handled twice, or it's handled after the labeled block info, + // both of which generate the wrong code. + // Using a separate block is just a simple workaround. + bodyEntryBlock = openNewBlock(); + } + localsHandler.enterLoopBody(node); + visit(node.body); + + // If there are no continues we could avoid the creation of the condition + // block. This could also lead to a block having multiple entries and exits. + HBasicBlock bodyExitBlock; + bool isAbortingBody = false; + if (current != null) { + bodyExitBlock = close(new HGoto()); + } else { + isAbortingBody = true; + bodyExitBlock = lastOpenedBlock; + } + + SubExpression conditionExpression; + HBasicBlock conditionEndBlock; + if (!isAbortingBody || hasContinues) { + HBasicBlock conditionBlock = addNewBlock(); + + List<LocalsHandler> continueLocals = <LocalsHandler>[]; + jumpHandler.forEachContinue((HContinue instruction, + LocalsHandler locals) { + instruction.block.addSuccessor(conditionBlock); + continueLocals.add(locals); + }); + + if (!isAbortingBody) { + bodyExitBlock.addSuccessor(conditionBlock); + } + + if (!continueLocals.isEmpty) { + if (!isAbortingBody) continueLocals.add(localsHandler); + localsHandler = + savedLocals.mergeMultiple(continueLocals, conditionBlock); + SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); + List<LabelElement> labels = jumpHandler.labels(); + HSubGraphBlockInformation bodyInfo = + new HSubGraphBlockInformation(bodyGraph); + HLabeledBlockInformation info; + if (!labels.isEmpty) { + info = new HLabeledBlockInformation(bodyInfo, labels, + isContinue: true); + } else { + info = new HLabeledBlockInformation.implicit(bodyInfo, target, + isContinue: true); + } + bodyEntryBlock.setBlockFlow(info, conditionBlock); + } + open(conditionBlock); + + visit(node.condition); + assert(!isAborted()); + HInstruction conditionInstruction = popBoolified(); + conditionEndBlock = close( + new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); + + HBasicBlock avoidCriticalEdge = addNewBlock(); + conditionEndBlock.addSuccessor(avoidCriticalEdge); + open(avoidCriticalEdge); + close(new HGoto()); + avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. + + conditionExpression = + new SubExpression(conditionBlock, conditionEndBlock); + } + + endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler); + if (!isAbortingBody || hasContinues) { + loopEntryBlock.postProcessLoopHeader(); + SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock); + HLoopBlockInformation loopBlockInfo = + new HLoopBlockInformation( + HLoopBlockInformation.DO_WHILE_LOOP, + null, + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + null, + loopEntryBlock.loopInformation.target, + loopEntryBlock.loopInformation.labels, + sourceFileLocationForBeginToken(node), + sourceFileLocationForEndToken(node)); + loopEntryBlock.setBlockFlow(loopBlockInfo, current); + loopInfo.loopBlockInformation = loopBlockInfo; + } else { + // If the loop has no back edge, we remove the loop information + // on the header. + loopEntryBlock.loopInformation = null; + + // If the body of the loop has any break, we attach a + // synthesized label to the body. + if (jumpHandler.hasAnyBreak()) { + SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); + TargetElement target = elements[node]; + LabelElement label = target.addLabel(null, 'loop'); + label.setBreakTarget(); + HLabeledBlockInformation info = new HLabeledBlockInformation( + new HSubGraphBlockInformation(bodyGraph), <LabelElement>[label]); + loopEntryBlock.setBlockFlow(info, current); + jumpHandler.forEachBreak((HBreak breakInstruction, _) { + HBasicBlock block = breakInstruction.block; + block.addAtExit(new HBreak.toLabel(label)); + block.remove(breakInstruction); + }); + } + } + jumpHandler.close(); + } + + visitFunctionExpression(FunctionExpression node) { + ClosureClassMap nestedClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(node); + assert(nestedClosureData != null); + assert(nestedClosureData.closureClassElement != null); + ClassElement closureClassElement = + nestedClosureData.closureClassElement; + FunctionElement callElement = nestedClosureData.callElement; + // TODO(ahe): This should be registered in codegen, not here. + compiler.enqueuer.codegen.addToWorkList(callElement, elements); + // TODO(ahe): This should be registered in codegen, not here. + compiler.enqueuer.codegen.registerInstantiatedClass(closureClassElement); + assert(!closureClassElement.hasLocalScopeMembers); + + List<HInstruction> capturedVariables = <HInstruction>[]; + closureClassElement.forEachBackendMember((Element member) { + // The backendMembers also contains the call method(s). We are only + // interested in the fields. + if (member.isField()) { + Element capturedLocal = nestedClosureData.capturedFieldMapping[member]; + assert(capturedLocal != null); + capturedVariables.add(localsHandler.readLocal(capturedLocal)); + } + }); + + HType type = new HBoundedType.exact( + compiler.functionClass.computeType(compiler)); + push(new HForeignNew(closureClassElement, type, capturedVariables)); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visit(node.function); + localsHandler.updateLocal(elements[node], pop()); + } + + visitIdentifier(Identifier node) { + if (node.isThis()) { + stack.add(localsHandler.readThis()); + } else { + compiler.internalError("SsaBuilder.visitIdentifier on non-this", + node: node); + } + } + + visitIf(If node) { + handleIf(node, + () => visit(node.condition), + () => visit(node.thenPart), + node.elsePart != null ? () => visit(node.elsePart) : null); + } + + void handleIf(Node diagnosticNode, + void visitCondition(), void visitThen(), void visitElse()) { + SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode); + branchBuilder.handleIf(visitCondition, visitThen, visitElse); + } + + void visitLogicalAndOr(Send node, Operator op) { + SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node); + branchBuilder.handleLogicalAndOrWithLeftNode( + node.receiver, + () { visit(node.argumentsNode); }, + isAnd: (const SourceString("&&") == op.source)); + } + + + void visitLogicalNot(Send node) { + assert(node.argumentsNode is Prefix); + visit(node.receiver); + HNot not = new HNot(popBoolified()); + pushWithPosition(not, node); + } + + void visitUnary(Send node, Operator op) { + if (node.isParameterCheck) { + Element element = elements[node.receiver]; + Node function = element.enclosingElement.parseNode(compiler); + ClosureClassMap parameterClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(function); + Element fieldCheck = + parameterClosureData.parametersWithSentinel[element]; + stack.add(localsHandler.readLocal(fieldCheck)); + return; + } + assert(node.argumentsNode is Prefix); + visit(node.receiver); + assert(!identical(op.token.kind, PLUS_TOKEN)); + HInstruction operand = pop(); + + // See if we can constant-fold right away. This avoids rewrites later on. + if (operand is HConstant) { + UnaryOperation operation = constantSystem.lookupUnary(op.source); + HConstant constant = operand; + Constant folded = operation.fold(constant.constant); + if (folded != null) { + stack.add(graph.addConstant(folded)); + return; + } + } + + HInvokeDynamicMethod result = + buildInvokeDynamic(node, elements.getSelector(node), operand, []); + pushWithPosition(result, node); + } + + void visitBinary( + HInstruction left, Operator op, HInstruction right, Send send) { + Selector selector = null; + // TODO(ngeoffray): The resolver creates these selectors already + // but does not put them on the [send] instruction. + switch (op.source.stringValue) { + case "+": + case "+=": + case "++": + selector = new Selector.binaryOperator(const SourceString('+')); + break; + case "-": + case "-=": + case "--": + selector = new Selector.binaryOperator(const SourceString('-')); + break; + case "*": + case "*=": + selector = new Selector.binaryOperator(const SourceString('*')); + break; + case "/": + case "/=": + selector = new Selector.binaryOperator(const SourceString('/')); + break; + case "~/": + case "~/=": + selector = new Selector.binaryOperator(const SourceString('~/')); + break; + case "%": + case "%=": + selector = new Selector.binaryOperator(const SourceString('%')); + break; + case "<<": + case "<<=": + selector = new Selector.binaryOperator(const SourceString('<<')); + break; + case ">>": + case ">>=": + selector = new Selector.binaryOperator(const SourceString('>>')); + break; + case "|": + case "|=": + selector = new Selector.binaryOperator(const SourceString('|')); + break; + case "&": + case "&=": + selector = new Selector.binaryOperator(const SourceString('&')); + break; + case "^": + case "^=": + selector = new Selector.binaryOperator(const SourceString('^')); + break; + case "==": + case "!=": + selector = new Selector.binaryOperator(const SourceString('==')); + break; + case "<": + selector = new Selector.binaryOperator(const SourceString('<')); + break; + case "<=": + selector = new Selector.binaryOperator(const SourceString('<=')); + break; + case ">": + selector = new Selector.binaryOperator(const SourceString('>')); + break; + case ">=": + selector = new Selector.binaryOperator(const SourceString('>=')); + break; + case "===": + pushWithPosition(new HIdentity(left, right), op); + return; + case "!==": + HIdentity eq = new HIdentity(left, right); + add(eq); + pushWithPosition(new HNot(eq), op); + return; + default: + compiler.internalError("Unexpected operator $op", node: op); + break; + } + + pushWithPosition( + buildInvokeDynamic(send, selector, left, [right]), + op); + if (op.source.stringValue == '!=') { + HBoolify bl = new HBoolify(pop()); + add(bl); + pushWithPosition(new HNot(bl), op); + } + } + + HInstruction generateInstanceSendReceiver(Send send) { + assert(Elements.isInstanceSend(send, elements)); + if (send.receiver == null) { + return localsHandler.readThis(); + } + visit(send.receiver); + return pop(); + } + + String getTargetName(ErroneousElement error, [String prefix]) { + String result = error.name.slowToString(); + if (?prefix) { + result = '$prefix $result'; + } + return result; + } + + /** + * Returns a set of interceptor classes that contain a member whose + * signature matches the given [selector]. + */ + Set<ClassElement> getInterceptedClassesOn(Selector selector) { + return backend.getInterceptedClassesOn(selector); + } + + void generateInstanceGetterWithCompiledReceiver(Send send, + HInstruction receiver) { + assert(Elements.isInstanceSend(send, elements)); + // TODO(kasperl): This is a convoluted way of checking if we're + // generating code for a compound assignment. If we are, we need + // to get the selector from the mapping for the AST selector node. + Selector selector = (send.asSendSet() == null) + ? elements.getSelector(send) + : elements.getSelector(send.selector); + assert(selector.isGetter()); + SourceString getterName = selector.name; + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + if (interceptedClasses != null) { + // If we're using an interceptor class, emit a call to the + // interceptor method and then the actual dynamic call on the + // interceptor object. + HInstruction instruction = + invokeInterceptor(interceptedClasses, receiver, send); + instruction = new HInvokeDynamicGetter( + selector, null, instruction, !hasGetter); + // Add the receiver as an argument to the getter call on the + // interceptor. + instruction.inputs.add(receiver); + pushWithPosition(instruction, send); + } else { + pushWithPosition( + new HInvokeDynamicGetter(selector, null, receiver, !hasGetter), send); + } + } + + void generateGetter(Send send, Element element) { + if (Elements.isStaticOrTopLevelField(element)) { + Constant value; + if (element.isField() && !element.isAssignable()) { + // A static final or const. Get its constant value and inline it if + // the value can be compiled eagerly. + value = compileVariable(element); + } + if (value != null) { + stack.add(graph.addConstant(value)); + } else if (element.isField() && isLazilyInitialized(element)) { + push(new HLazyStatic(element)); + } else { + if (element.isGetter()) { + Selector selector = elements.getSelector(send); + if (tryInlineMethod(element, selector, const Link<Node>(), send)) { + return; + } + } + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + push(new HStatic(element.declaration)); + if (element.isGetter()) { + push(new HInvokeStatic(<HInstruction>[pop()], HType.UNKNOWN)); + } + } + } else if (Elements.isInstanceSend(send, elements)) { + HInstruction receiver = generateInstanceSendReceiver(send); + generateInstanceGetterWithCompiledReceiver(send, receiver); + } else if (Elements.isStaticOrTopLevelFunction(element)) { + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + push(new HStatic(element.declaration)); + // TODO(ahe): This should be registered in codegen. + compiler.enqueuer.codegen.registerGetOfStaticFunction(element); + } else if (Elements.isErroneousElement(element)) { + // An erroneous element indicates an unresolved static getter. + generateThrowNoSuchMethod(send, + getTargetName(element, 'get'), + argumentNodes: const Link<Node>()); + } else { + stack.add(localsHandler.readLocal(element)); + } + } + + void generateInstanceSetterWithCompiledReceiver(Send send, + HInstruction receiver, + HInstruction value) { + assert(Elements.isInstanceSend(send, elements)); + Selector selector = elements.getSelector(send); + assert(selector.isSetter()); + SourceString setterName = selector.name; + bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector); + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + if (interceptedClasses != null) { + // If we're using an interceptor class, emit a call to the + // getInterceptor method and then the actual dynamic call on the + // interceptor object. + HInstruction instruction = + invokeInterceptor(interceptedClasses, receiver, send); + instruction = new HInvokeDynamicSetter( + selector, null, instruction, receiver, !hasSetter); + // Add the value as an argument to the setter call on the + // interceptor. + instruction.inputs.add(value); + addWithPosition(instruction, send); + } else { + addWithPosition( + new HInvokeDynamicSetter(selector, null, receiver, value, !hasSetter), + send); + } + stack.add(value); + } + + void generateSetter(SendSet send, Element element, HInstruction value) { + if (Elements.isStaticOrTopLevelField(element)) { + if (element.isSetter()) { + HStatic target = new HStatic(element); + add(target); + addWithPosition( + new HInvokeStatic(<HInstruction>[target, value], HType.UNKNOWN), + send); + } else { + value = potentiallyCheckType(value, element.computeType(compiler)); + addWithPosition(new HStaticStore(element, value), send); + } + stack.add(value); + } else if (element == null || Elements.isInstanceField(element)) { + HInstruction receiver = generateInstanceSendReceiver(send); + generateInstanceSetterWithCompiledReceiver(send, receiver, value); + } else if (Elements.isErroneousElement(element)) { + // An erroneous element indicates an unresolved static setter. + generateThrowNoSuchMethod(send, + getTargetName(element, 'set'), + argumentNodes: send.arguments); + } else { + stack.add(value); + // If the value does not already have a name, give it here. + if (value.sourceElement == null) { + value.sourceElement = element; + } + HInstruction checked = potentiallyCheckType( + value, element.computeType(compiler)); + if (!identical(checked, value)) { + pop(); + stack.add(checked); + } + localsHandler.updateLocal(element, checked); + } + } + + HInstruction invokeInterceptor(Set<ClassElement> intercepted, + HInstruction receiver, + Send send) { + HInterceptor interceptor = new HInterceptor(intercepted, receiver); + add(interceptor); + return interceptor; + } + + void pushInvokeHelper0(Element helper, HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper1(Element helper, HInstruction a0, HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper2(Element helper, + HInstruction a0, + HInstruction a1, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper3(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper4(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HInstruction a3, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper5(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HInstruction a3, + HInstruction a4, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3, a4]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + HForeign createForeign(String code, HType type, List<HInstruction> inputs) { + return new HForeign(new LiteralDartString(code), type, inputs); + } + + HInstruction getRuntimeTypeInfo(HInstruction target) { + pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), target, HType.UNKNOWN); + return pop(); + } + + // TODO(karlklose): change construction of the representations to be GVN'able + // (dartbug.com/7182). + List<HInstruction> buildTypeArgumentRepresentations(DartType type) { + HInstruction createForeignArray(String code, inputs) { + return createForeign(code, HType.READABLE_ARRAY, inputs); + } + HInstruction typeInfo; + + /// Helper to create an instruction that contains the runtime value of + /// the type variable [variable]. + HInstruction getTypeArgument(TypeVariableType variable) { + if (typeInfo == null) { + typeInfo = getRuntimeTypeInfo(localsHandler.readThis()); + } + int intIndex = RuntimeTypeInformation.getTypeVariableIndex(variable); + HInstruction index = graph.addConstantInt(intIndex, constantSystem); + return createForeignArray('#[#]', <HInstruction>[typeInfo, index]); + } + + // Compute the representation of the type arguments, including access + // to the runtime type information for type variables as instructions. + HInstruction representations; + if (type.element.isTypeVariable()) { + return <HInstruction>[getTypeArgument(type)]; + } else { + assert(type.element.isClass()); + List<HInstruction> arguments = <HInstruction>[]; + InterfaceType interface = type; + for (DartType argument in interface.typeArguments) { + List<HInstruction> inputs = <HInstruction>[]; + String template = rti.getTypeRepresentation(argument, (variable) { + HInstruction runtimeType = getTypeArgument(variable); + add(runtimeType); + inputs.add(runtimeType); + }); + HInstruction representation = createForeignArray(template, inputs); + add(representation); + arguments.add(representation); + } + return arguments; + } + } + + visitOperatorSend(node) { + Operator op = node.selector; + if (const SourceString("[]") == op.source) { + visitDynamicSend(node); + } else if (const SourceString("&&") == op.source || + const SourceString("||") == op.source) { + visitLogicalAndOr(node, op); + } else if (const SourceString("!") == op.source) { + visitLogicalNot(node); + } else if (node.argumentsNode is Prefix) { + visitUnary(node, op); + } else if (const SourceString("is") == op.source) { + visit(node.receiver); + HInstruction expression = pop(); + Node argument = node.arguments.head; + TypeAnnotation typeAnnotation = argument.asTypeAnnotation(); + bool isNot = false; + // TODO(ngeoffray): Duplicating pattern in resolver. We should + // add a new kind of node. + if (typeAnnotation == null) { + typeAnnotation = argument.asSend().receiver; + isNot = true; + } + DartType type = elements.getType(typeAnnotation); + if (type.isMalformed) { + String reasons = Types.fetchReasonsFromMalformedType(type); + if (compiler.enableTypeAssertions) { + generateMalformedSubtypeError(node, expression, type, reasons); + } else { + generateRuntimeError(node, '$type is malformed: $reasons'); + } + return; + } + if (type.element.isTypeVariable()) { + // TODO(karlklose): remove this check when the backend can deal with + // checks of the form [:o is T:] where [:T:] is a type variable. + stack.add(graph.addConstantBool(true, constantSystem)); + return; + } + + HInstruction instruction; + if (type.element.isTypeVariable() || + RuntimeTypeInformation.hasTypeArguments(type)) { + HInstruction typeInfo = getRuntimeTypeInfo(expression); + // TODO(karlklose): make isSubtype a HInstruction to enable + // optimizations? + Element helper = compiler.findHelper(const SourceString('isSubtype')); + HInstruction isSubtype = new HStatic(helper); + add(isSubtype); + // Build a list of representations for the type arguments. + List<HInstruction> representations = + buildTypeArgumentRepresentations(type); + // For each type argument, build a call to isSubtype, with the type + // argument as first and the representation of the tested type as + // second argument. + List<HInstruction> checks = <HInstruction>[]; + int index = 0; + representations.forEach((HInstruction representation) { + HInstruction position = graph.addConstantInt(index, constantSystem); + // Get the index'th type argument from the runtime type information. + HInstruction typeArgument = + createForeign('#[#]', HType.UNKNOWN, [typeInfo, position]); + add(typeArgument); + // Create the call to isSubtype. + List<HInstruction> inputs = + <HInstruction>[isSubtype, typeArgument, representation]; + HInstruction call = new HInvokeStatic(inputs, HType.BOOLEAN); + add(call); + checks.add(call); + index++; + }); + instruction = new HIs(type, <HInstruction>[expression]..addAll(checks)); + } else { + instruction = new HIs(type, <HInstruction>[expression]); + } + if (isNot) { + add(instruction); + instruction = new HNot(instruction); + } + push(instruction); + } else if (const SourceString("as") == op.source) { + visit(node.receiver); + HInstruction expression = pop(); + Node argument = node.arguments.head; + TypeAnnotation typeAnnotation = argument.asTypeAnnotation(); + DartType type = elements.getType(typeAnnotation); + HInstruction converted = expression.convertType( + compiler, type, HTypeConversion.CAST_TYPE_CHECK); + if (converted != expression) add(converted); + stack.add(converted); + } else { + visit(node.receiver); + visit(node.argumentsNode); + var right = pop(); + var left = pop(); + visitBinary(left, op, right, node); + } + } + + void addDynamicSendArgumentsToList(Send node, List<HInstruction> list) { + Selector selector = elements.getSelector(node); + if (selector.namedArgumentCount == 0) { + addGenericSendArgumentsToList(node.arguments, list); + } else { + // Visit positional arguments and add them to the list. + Link<Node> arguments = node.arguments; + int positionalArgumentCount = selector.positionalArgumentCount; + for (int i = 0; + i < positionalArgumentCount; + arguments = arguments.tail, i++) { + visit(arguments.head); + list.add(pop()); + } + + // Visit named arguments and add them into a temporary map. + Map<SourceString, HInstruction> instructions = + new Map<SourceString, HInstruction>(); + List<SourceString> namedArguments = selector.namedArguments; + int nameIndex = 0; + for (; !arguments.isEmpty; arguments = arguments.tail) { + visit(arguments.head); + instructions[namedArguments[nameIndex++]] = pop(); + } + + // Iterate through the named arguments to add them to the list + // of instructions, in an order that can be shared with + // selectors with the same named arguments. + List<SourceString> orderedNames = selector.getOrderedNamedArguments(); + for (SourceString name in orderedNames) { + list.add(instructions[name]); + } + } + } + + /** + * Returns true if the arguments were compatible with the function signature. + * + * Invariant: [element] must be an implementation element. + */ + bool addStaticSendArgumentsToList(Selector selector, + Link<Node> arguments, + FunctionElement element, + List<HInstruction> list) { + assert(invariant(element, element.isImplementation)); + + HInstruction compileArgument(Node argument) { + visit(argument); + return pop(); + } + + HInstruction handleConstant(Element parameter) { + Constant constant; + TreeElements calleeElements = + compiler.enqueuer.resolution.getCachedElements(element); + if (calleeElements.isParameterChecked(parameter)) { + constant = SentinelConstant.SENTINEL; + } else { + constant = compileConstant(parameter); + } + return graph.addConstant(constant); + } + + return selector.addArgumentsToList(arguments, + list, + element, + compileArgument, + handleConstant, + compiler); + } + + void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) { + for (; !link.isEmpty; link = link.tail) { + visit(link.head); + list.add(pop()); + } + } + + visitDynamicSend(Send node) { + Selector selector = elements.getSelector(node); + + SourceString dartMethodName; + bool isNotEquals = false; + if (node.isIndex && !node.arguments.tail.isEmpty) { + dartMethodName = Elements.constructOperatorName( + const SourceString('[]='), false); + } else if (node.selector.asOperator() != null) { + SourceString name = node.selector.asIdentifier().source; + isNotEquals = identical(name.stringValue, '!='); + dartMethodName = Elements.constructOperatorName( + name, node.argumentsNode is Prefix); + } else { + dartMethodName = node.selector.asIdentifier().source; + } + + Element element = elements[node]; + bool isClosureCall = false; + if (element != null && compiler.world.hasNoOverridingMember(element)) { + if (tryInlineMethod(element, selector, node.arguments, node)) { + if (element.isGetter()) { + // If the element is a getter, we are doing a closure call + // on what this getter returns. + assert(selector.isCall()); + isClosureCall = true; + } else { + return; + } + } + } + + List<HInstruction> inputs = <HInstruction>[]; + if (isClosureCall) inputs.add(pop()); + + HInstruction receiver; + if (!isClosureCall) { + if (node.receiver == null) { + receiver = localsHandler.readThis(); + } else { + visit(node.receiver); + receiver = pop(); + } + } + + addDynamicSendArgumentsToList(node, inputs); + + HInstruction invoke; + if (isClosureCall) { + Selector closureSelector = new Selector.callClosureFrom(selector); + invoke = new HInvokeClosure(closureSelector, inputs); + } else { + invoke = buildInvokeDynamic(node, selector, receiver, inputs); + } + + pushWithPosition(invoke, node); + + if (isNotEquals) { + HNot not = new HNot(popBoolified()); + push(not); + } + } + + visitClosureSend(Send node) { + Selector selector = elements.getSelector(node); + assert(node.receiver == null); + Element element = elements[node]; + HInstruction closureTarget; + if (element == null) { + visit(node.selector); + closureTarget = pop(); + } else { + assert(Elements.isLocal(element)); + closureTarget = localsHandler.readLocal(element); + } + var inputs = <HInstruction>[]; + inputs.add(closureTarget); + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + pushWithPosition(new HInvokeClosure(closureSelector, inputs), node); + } + + void handleForeignJs(Send node) { + Link<Node> link = node.arguments; + // If the invoke is on foreign code, don't visit the first + // argument, which is the type, and the second argument, + // which is the foreign code. + if (link.isEmpty || link.tail.isEmpty) { + compiler.cancel('At least two arguments expected', + node: node.argumentsNode); + } + List<HInstruction> inputs = <HInstruction>[]; + Node type = link.head; + Node code = link.tail.head; + addGenericSendArgumentsToList(link.tail.tail, inputs); + + native.NativeBehavior nativeBehavior = + compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); + HType ssaType = mapNativeBehaviorType(nativeBehavior); + if (code is StringNode) { + StringNode codeString = code; + if (!codeString.isInterpolation) { + // codeString may not be an interpolation, but may be a juxtaposition. + push(new HForeign(codeString.dartString, ssaType, inputs)); + return; + } + } + compiler.cancel('JS code must be a string literal', node: code); + } + + void handleForeignJsCurrentIsolate(Send node) { + if (!node.arguments.isEmpty) { + compiler.cancel( + 'Too many arguments to JS_CURRENT_ISOLATE', node: node); + } + + if (!compiler.hasIsolateSupport()) { + // If the isolate library is not used, we just generate code + // to fetch the Leg's current isolate. + String name = backend.namer.CURRENT_ISOLATE; + push(new HForeign(new DartString.literal(name), + HType.UNKNOWN, + <HInstruction>[])); + } else { + // Call a helper method from the isolate library. The isolate + // library uses its own isolate structure, that encapsulates + // Leg's isolate. + Element element = compiler.isolateHelperLibrary.find( + const SourceString('_currentIsolate')); + if (element == null) { + compiler.cancel( + 'Isolate library and compiler mismatch', node: node); + } + pushInvokeHelper0(element, HType.UNKNOWN); + } + } + + void handleForeignJsCallInIsolate(Send node) { + Link<Node> link = node.arguments; + if (!compiler.hasIsolateSupport()) { + // If the isolate library is not used, we just invoke the + // closure. + visit(link.tail.head); + Selector selector = new Selector.callClosure(0); + push(new HInvokeClosure(selector, <HInstruction>[pop()])); + } else { + // Call a helper method from the isolate library. + Element element = compiler.isolateHelperLibrary.find( + const SourceString('_callInIsolate')); + if (element == null) { + compiler.cancel( + 'Isolate library and compiler mismatch', node: node); + } + HStatic target = new HStatic(element); + add(target); + List<HInstruction> inputs = <HInstruction>[target]; + addGenericSendArgumentsToList(link, inputs); + push(new HInvokeStatic(inputs, HType.UNKNOWN)); + } + } + + FunctionSignature handleForeignRawFunctionRef(Send node, String name) { + if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { + compiler.cancel('"$name" requires exactly one argument', + node: node.argumentsNode); + } + Node closure = node.arguments.head; + Element element = elements[closure]; + if (!Elements.isStaticOrTopLevelFunction(element)) { + compiler.cancel( + '"$name" requires a static or top-level method', + node: closure); + } + FunctionElement function = element; + // TODO(johnniwinther): Try to eliminate the need to distinguish declaration + // and implementation signatures. Currently it is need because the + // signatures have different elements for parameters. + FunctionElement implementation = function.implementation; + FunctionSignature params = implementation.computeSignature(compiler); + if (params.optionalParameterCount != 0) { + compiler.cancel( + '"$name" does not handle closure with optional parameters', + node: closure); + } + visit(closure); + return params; + } + + void handleForeignDartClosureToJs(Send node, String name) { + FunctionSignature params = handleForeignRawFunctionRef(node, name); + List<HInstruction> inputs = <HInstruction>[pop()]; + String invocationName = backend.namer.invocationName( + new Selector.callClosure(params.requiredParameterCount)); + push(new HForeign(new DartString.literal('#.$invocationName'), + HType.UNKNOWN, + inputs)); + } + + void handleForeignSetCurrentIsolate(Send node) { + if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { + compiler.cancel('Exactly one argument required', + node: node.argumentsNode); + } + visit(node.arguments.head); + String isolateName = backend.namer.CURRENT_ISOLATE; + push(new HForeign(new DartString.literal("$isolateName = #"), + HType.UNKNOWN, + <HInstruction>[pop()])); + } + + void handleForeignCreateIsolate(Send node) { + if (!node.arguments.isEmpty) { + compiler.cancel('Too many arguments', + node: node.argumentsNode); + } + String constructorName = backend.namer.isolateName; + push(new HForeign(new DartString.literal("new $constructorName"), + HType.UNKNOWN, + <HInstruction>[])); + } + + visitForeignSend(Send node) { + Selector selector = elements.getSelector(node); + SourceString name = selector.name; + if (name == const SourceString('JS')) { + handleForeignJs(node); + } else if (name == const SourceString('JS_CURRENT_ISOLATE')) { + handleForeignJsCurrentIsolate(node); + } else if (name == const SourceString('JS_CALL_IN_ISOLATE')) { + handleForeignJsCallInIsolate(node); + } else if (name == const SourceString('DART_CLOSURE_TO_JS')) { + handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS'); + } else if (name == const SourceString('RAW_DART_FUNCTION_REF')) { + handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF'); + } else if (name == const SourceString('JS_SET_CURRENT_ISOLATE')) { + handleForeignSetCurrentIsolate(node); + } else if (name == const SourceString('JS_CREATE_ISOLATE')) { + handleForeignCreateIsolate(node); + } else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) { + stack.add(addConstantString(node, backend.namer.operatorIsPrefix())); + } else { + throw "Unknown foreign: ${selector}"; + } + } + + generateSuperNoSuchMethodSend(Send node) { + Selector selector = elements.getSelector(node); + SourceString name = selector.name; + + ClassElement cls = currentElement.getEnclosingClass(); + Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD); + if (element.enclosingElement.declaration != compiler.objectClass) { + // Register the call as dynamic if [:noSuchMethod:] on the super class + // is _not_ the default implementation from [:Object:]. + compiler.enqueuer.codegen.registerDynamicInvocation(name, selector); + } + HStatic target = new HStatic(element); + add(target); + HInstruction self = localsHandler.readThis(); + Constant nameConstant = constantSystem.createString( + new DartString.literal(name.slowToString()), node); + + String internalName = backend.namer.invocationName(selector); + Constant internalNameConstant = + constantSystem.createString(new DartString.literal(internalName), node); + + Element createInvocationMirror = + compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR); + + var arguments = new List<HInstruction>(); + if (node.argumentsNode != null) { + addGenericSendArgumentsToList(node.arguments, arguments); + } + var argumentsInstruction = new HLiteralList(arguments); + add(argumentsInstruction); + + var argumentNames = new List<HInstruction>(); + for (SourceString argumentName in selector.namedArguments) { + Constant argumentNameConstant = + constantSystem.createString(new DartString.literal( + argumentName.slowToString()), node); + argumentNames.add(graph.addConstant(argumentNameConstant)); + } + var argumentNamesInstruction = new HLiteralList(argumentNames); + add(argumentNamesInstruction); + + Constant kindConstant = + constantSystem.createInt(selector.invocationMirrorKind); + + pushInvokeHelper5(createInvocationMirror, + graph.addConstant(nameConstant), + graph.addConstant(internalNameConstant), + graph.addConstant(kindConstant), + argumentsInstruction, + argumentNamesInstruction, + HType.UNKNOWN); + + var inputs = <HInstruction>[ + target, + self, + pop()]; + push(new HInvokeSuper(inputs)); + } + + visitSend(Send node) { + Element element = elements[node]; + if (element != null && identical(element, currentElement)) { + graph.isRecursiveMethod = true; + } + super.visitSend(node); + } + + visitSuperSend(Send node) { + Selector selector = elements.getSelector(node); + Element element = elements[node]; + if (element == null) return generateSuperNoSuchMethodSend(node); + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + HInstruction target = new HStatic(element.declaration); + HInstruction context = localsHandler.readThis(); + add(target); + var inputs = <HInstruction>[target, context]; + if (node.isPropertyAccess) { + push(new HInvokeSuper(inputs)); + } else if (element.isFunction() || element.isGenerativeConstructor()) { + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + FunctionElement function = element.implementation; + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + function, inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, element, node.arguments); + } else { + push(new HInvokeSuper(inputs)); + } + } else { + target = new HInvokeSuper(inputs); + add(target); + inputs = <HInstruction>[target]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + push(new HInvokeClosure(closureSelector, inputs)); + } + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [argument] must not be malformed in checked mode. + */ + HInstruction analyzeTypeArgument(DartType argument, Node currentNode) { + assert(invariant(currentNode, + !compiler.enableTypeAssertions || !argument.isMalformed, + message: '$argument is malformed in checked mode')); + if (argument == compiler.types.dynamicType || argument.isMalformed) { + // Represent [dynamic] as [null]. + return graph.addConstantNull(constantSystem); + } + + // These variables are shared between invocations of the helper. + HInstruction typeInfo; + List<HInstruction> inputs = <HInstruction>[]; + + /** + * Helper to create an instruction that gets the value of a type variable. + */ + void addTypeVariableReference(TypeVariableType type) { + Element member = currentElement; + if (member.enclosingElement.isClosure()) { + ClosureClassElement closureClass = member.enclosingElement; + member = closureClass.methodElement; + member = member.getOutermostEnclosingMemberOrTopLevel(); + } + if (member.isFactoryConstructor()) { + // The type variable is stored in a parameter of the factory. + inputs.add(localsHandler.readLocal(type.element)); + } else if (member.isInstanceMember() + || member.isGenerativeConstructor()) { + // The type variable is stored in [this]. + if (typeInfo == null) { + pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), + localsHandler.readThis(), + HType.UNKNOWN); + typeInfo = pop(); + } + int index = RuntimeTypeInformation.getTypeVariableIndex(type); + HInstruction foreign = createForeign('#[$index]', HType.STRING, + <HInstruction>[typeInfo]); + add(foreign); + inputs.add(foreign); + } else { + // TODO(ngeoffray): Match the VM behavior and throw an + // exception at runtime. + compiler.cancel('Unimplemented unresolved type variable', + node: currentNode); + } + } + + String template = rti.getTypeRepresentation(argument, + addTypeVariableReference); + HInstruction result = createForeign(template, HType.STRING, inputs); + add(result); + return result; + } + + void handleListConstructor(InterfaceType type, + Node currentNode, + HInstruction newObject) { + if (!compiler.world.needsRti(type.element)) return; + List<HInstruction> inputs = <HInstruction>[]; + if (!type.isRaw) { + type.typeArguments.forEach((DartType argument) { + inputs.add(analyzeTypeArgument(argument, currentNode)); + }); + } + callSetRuntimeTypeInfo(type.element, inputs, newObject); + } + + void callSetRuntimeTypeInfo(ClassElement element, + List<HInstruction> rtiInputs, + HInstruction newObject) { + if (!compiler.world.needsRti(element) || element.typeVariables.isEmpty) { + return; + } + + HInstruction typeInfo = new HLiteralList(rtiInputs); + add(typeInfo); + + // Set the runtime type information on the object. + Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo(); + HInstruction typeInfoSetter = new HStatic(typeInfoSetterElement); + add(typeInfoSetter); + add(new HInvokeStatic( + <HInstruction>[typeInfoSetter, newObject, typeInfo], HType.UNKNOWN)); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [type] must not be malformed in checked mode. + */ + visitNewSend(Send node, InterfaceType type) { + assert(invariant(node, + !compiler.enableTypeAssertions || !type.isMalformed, + message: '$type is malformed in checked mode')); + bool isListConstructor = false; + computeType(element) { + Element originalElement = elements[node]; + if (identical(originalElement.getEnclosingClass(), compiler.listClass)) { + isListConstructor = true; + if (node.arguments.isEmpty) { + return HType.EXTENDABLE_ARRAY; + } else { + return HType.MUTABLE_ARRAY; + } + } else if (element.isGenerativeConstructor()) { + ClassElement cls = element.getEnclosingClass(); + return new HBoundedType.exact(cls.thisType); + } else { + return HType.UNKNOWN; + } + } + + Element constructor = elements[node]; + Selector selector = elements.getSelector(node); + if (compiler.enqueuer.resolution.getCachedElements(constructor) == null) { + compiler.internalError("Unresolved element: $constructor", node: node); + } + FunctionElement functionElement = constructor; + constructor = functionElement.redirectionTarget; + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + HInstruction target = new HStatic(constructor.declaration); + add(target); + var inputs = <HInstruction>[]; + inputs.add(target); + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + constructor.implementation, + inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, constructor, node.arguments); + return; + } + + ClassElement cls = constructor.getEnclosingClass(); + if (cls.isAbstract(compiler) && constructor.isGenerativeConstructor()) { + generateAbstractClassInstantiationError(node, cls.name.slowToString()); + return; + } + if (compiler.world.needsRti(cls)) { + Link<DartType> typeVariable = cls.typeVariables; + type.typeArguments.forEach((DartType argument) { + inputs.add(analyzeTypeArgument(argument, node)); + typeVariable = typeVariable.tail; + }); + // Also add null to non-provided type variables to call the + // constructor with the right number of arguments. + while (!typeVariable.isEmpty) { + inputs.add(graph.addConstantNull(constantSystem)); + typeVariable = typeVariable.tail; + } + } + + HType elementType = computeType(constructor); + HInstruction newInstance = new HInvokeStatic(inputs, elementType); + pushWithPosition(newInstance, node); + + // The List constructor forwards to a Dart static method that does + // not know about the type argument. Therefore we special case + // this constructor to have the setRuntimeTypeInfo called where + // the 'new' is done. + if (isListConstructor && compiler.world.needsRti(compiler.listClass)) { + handleListConstructor(type, node, newInstance); + } + } + + visitStaticSend(Send node) { + Selector selector = elements.getSelector(node); + Element element = elements[node]; + if (element.isForeign(compiler)) { + visitForeignSend(node); + return; + } + if (element.isErroneous()) { + generateThrowNoSuchMethod(node, + getTargetName(element), + argumentNodes: node.arguments); + return; + } + if (identical(element, compiler.assertMethod) + && !compiler.enableUserAssertions) { + stack.add(graph.addConstantNull(constantSystem)); + return; + } + compiler.ensure(!element.isGenerativeConstructor()); + if (element.isFunction()) { + bool isIdenticalFunction = element == compiler.identicalFunction; + + if (!isIdenticalFunction + && tryInlineMethod(element, selector, node.arguments, node)) { + return; + } + + HInstruction target = new HStatic(element); + add(target); + var inputs = <HInstruction>[target]; + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + element.implementation, + inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, element, node.arguments); + return; + } + + if (isIdenticalFunction) { + pushWithPosition(new HIdentity(inputs[1], inputs[2]), node); + return; + } + + HInvokeStatic instruction = new HInvokeStatic(inputs, HType.UNKNOWN); + // TODO(ngeoffray): Only do this if knowing the return type is + // useful. + HType returnType = + builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange( + currentElement, element); + if (returnType != null) instruction.guaranteedType = returnType; + pushWithPosition(instruction, node); + } else { + generateGetter(node, element); + List<HInstruction> inputs = <HInstruction>[pop()]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + pushWithPosition(new HInvokeClosure(closureSelector, inputs), node); + } + } + + HConstant addConstantString(Node node, String string) { + DartString dartString = new DartString.literal(string); + Constant constant = constantSystem.createString(dartString, node); + return graph.addConstant(constant); + } + + visitTypeReferenceSend(Send node) { + Element element = elements[node]; + if (element.isClass() || element.isTypedef()) { + // TODO(karlklose): add type representation + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + } else if (element.isTypeVariable()) { + // TODO(6248): implement support for type variables. + compiler.unimplemented('first class type for type variable', node: node); + } else { + internalError('unexpected element kind $element', node: node); + } + if (node.isCall) { + // This send is of the form 'e(...)', where e is resolved to a type + // reference. We create a regular closure call on the result of the type + // reference instead of creating a NoSuchMethodError to avoid pulling it + // in if it is not used (e.g., in a try/catch). + HInstruction target = pop(); + Selector selector = elements.getSelector(node); + List<HInstruction> inputs = <HInstruction>[target]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + push(new HInvokeClosure(closureSelector, inputs)); + } + } + + visitGetterSend(Send node) { + generateGetter(node, elements[node]); + } + + // TODO(antonm): migrate rest of SsaBuilder to internalError. + internalError(String reason, {Node node}) { + compiler.internalError(reason, node: node); + } + + void generateError(Node node, String message, Element helper) { + HInstruction errorMessage = addConstantString(node, message); + pushInvokeHelper1(helper, errorMessage, HType.UNKNOWN); + } + + void generateRuntimeError(Node node, String message) { + generateError(node, message, backend.getThrowRuntimeError()); + } + + void generateAbstractClassInstantiationError(Node node, String message) { + generateError(node, + message, + backend.getThrowAbstractClassInstantiationError()); + } + + void generateThrowNoSuchMethod(Node diagnosticNode, + String methodName, + {Link<Node> argumentNodes, + List<HInstruction> argumentValues, + List<String> existingArguments}) { + Element helper = + compiler.findHelper(const SourceString('throwNoSuchMethod')); + Constant receiverConstant = + constantSystem.createString(new DartString.empty(), diagnosticNode); + HInstruction receiver = graph.addConstant(receiverConstant); + DartString dartString = new DartString.literal(methodName); + Constant nameConstant = + constantSystem.createString(dartString, diagnosticNode); + HInstruction name = graph.addConstant(nameConstant); + if (argumentValues == null) { + argumentValues = <HInstruction>[]; + argumentNodes.forEach((argumentNode) { + visit(argumentNode); + HInstruction value = pop(); + argumentValues.add(value); + }); + } + HInstruction arguments = new HLiteralList(argumentValues); + add(arguments); + HInstruction existingNamesList; + if (existingArguments != null) { + List<HInstruction> existingNames = <HInstruction>[]; + for (String name in existingArguments) { + HInstruction nameConstant = + graph.addConstantString(new DartString.literal(name), + diagnosticNode, constantSystem); + existingNames.add(nameConstant); + } + existingNamesList = new HLiteralList(existingNames); + add(existingNamesList); + } else { + existingNamesList = graph.addConstantNull(constantSystem); + } + pushInvokeHelper4( + helper, receiver, name, arguments, existingNamesList, HType.UNKNOWN); + } + + /** + * Generate code to throw a [NoSuchMethodError] exception for calling a + * method with a wrong number of arguments or mismatching named optional + * arguments. + */ + void generateWrongArgumentCountError(Node diagnosticNode, + FunctionElement function, + Link<Node> argumentNodes) { + List<String> existingArguments = <String>[]; + FunctionSignature signature = function.computeSignature(compiler); + signature.forEachParameter((Element parameter) { + existingArguments.add(parameter.name.slowToString()); + }); + generateThrowNoSuchMethod(diagnosticNode, + function.name.slowToString(), + argumentNodes: argumentNodes, + existingArguments: existingArguments); + } + + void generateMalformedSubtypeError(Node node, HInstruction value, + DartType type, String reasons) { + HInstruction typeString = addConstantString(node, type.toString()); + HInstruction reasonsString = addConstantString(node, reasons); + Element helper = backend.getThrowMalformedSubtypeError(); + pushInvokeHelper3(helper, value, typeString, reasonsString, HType.UNKNOWN); + } + + visitNewExpression(NewExpression node) { + Element element = elements[node.send]; + if (!Elements.isErroneousElement(element)) { + FunctionElement function = element; + element = function.redirectionTarget; + } + if (Elements.isErroneousElement(element)) { + ErroneousElement error = element; + if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) { + generateThrowNoSuchMethod(node.send, + getTargetName(error, 'constructor'), + argumentNodes: node.send.arguments); + } else { + Message message = error.messageKind.message(error.messageArguments); + generateRuntimeError(node.send, message.toString()); + } + } else if (node.isConst()) { + // TODO(karlklose): add type representation + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + } else { + DartType type = elements.getType(node); + if (compiler.enableTypeAssertions && type.isMalformed) { + String reasons = Types.fetchReasonsFromMalformedType(type); + // TODO(johnniwinther): Change to resemble type errors from bounds check + // on type arguments. + generateRuntimeError(node, '$type is malformed: $reasons'); + } else { + // TODO(karlklose): move this type registration to the codegen. + compiler.codegenWorld.instantiatedTypes.add(type); + Send send = node.send; + Element constructor = elements[send]; + Selector selector = elements.getSelector(send); + if (!tryInlineMethod(constructor, selector, send.arguments, node)) { + visitNewSend(send, type); + } + } + } + } + + HInvokeDynamicMethod buildInvokeDynamic(Node node, + Selector selector, + HInstruction receiver, + List<HInstruction> arguments) { + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + List<HInstruction> inputs = <HInstruction>[]; + bool isIntercepted = interceptedClasses != null; + if (isIntercepted) { + assert(!interceptedClasses.isEmpty); + inputs.add(invokeInterceptor(interceptedClasses, receiver, node)); + } + inputs.add(receiver); + inputs.addAll(arguments); + return new HInvokeDynamicMethod(selector, inputs, isIntercepted); + } + + visitSendSet(SendSet node) { + Element element = elements[node]; + if (!Elements.isUnresolved(element) && element.impliesType()) { + Identifier selector = node.selector; + generateThrowNoSuchMethod(node, selector.source.slowToString(), + argumentNodes: node.arguments); + return; + } + Operator op = node.assignmentOperator; + if (node.isSuperCall) { + if (element == null) return generateSuperNoSuchMethodSend(node); + HInstruction target = new HStatic(element); + HInstruction context = localsHandler.readThis(); + add(target); + var inputs = <HInstruction>[target, context]; + addDynamicSendArgumentsToList(node, inputs); + if (!identical(node.assignmentOperator.source.stringValue, '=')) { + compiler.unimplemented('complex super assignment', + node: node.assignmentOperator); + } + push(new HInvokeSuper(inputs, isSetter: true)); + } else if (node.isIndex) { + if (const SourceString("=") == op.source) { + visitDynamicSend(node); + HInvokeDynamicMethod method = pop(); + // Push the value. + stack.add(method.inputs.last); + } else { + visit(node.receiver); + HInstruction receiver = pop(); + visit(node.argumentsNode); + HInstruction value; + HInstruction index; + // Compound assignments are considered as being prefix. + bool isCompoundAssignment = op.source.stringValue.endsWith('='); + bool isPrefix = !node.isPostfix; + Element getter = elements[node.selector]; + if (isCompoundAssignment) { + value = pop(); + index = pop(); + } else { + index = pop(); + value = graph.addConstantInt(1, constantSystem); + } + + HInvokeDynamicMethod left = buildInvokeDynamic( + node, new Selector.index(), receiver, [index]); + add(left); + visitBinary(left, op, value, node); + value = pop(); + HInvokeDynamicMethod assign = buildInvokeDynamic( + node, new Selector.indexSet(), receiver, [index, value]); + add(assign); + if (isPrefix) { + stack.add(value); + } else { + stack.add(left); + } + } + } else if (const SourceString("=") == op.source) { + Element element = elements[node]; + Link<Node> link = node.arguments; + assert(!link.isEmpty && link.tail.isEmpty); + visit(link.head); + HInstruction value = pop(); + generateSetter(node, element, value); + } else if (identical(op.source.stringValue, "is")) { + compiler.internalError("is-operator as SendSet", node: op); + } else { + assert(const SourceString("++") == op.source || + const SourceString("--") == op.source || + node.assignmentOperator.source.stringValue.endsWith("=")); + Element element = elements[node]; + bool isCompoundAssignment = !node.arguments.isEmpty; + bool isPrefix = !node.isPostfix; // Compound assignments are prefix. + + // [receiver] is only used if the node is an instance send. + HInstruction receiver = null; + Element selectorElement = elements[node]; + if (Elements.isInstanceSend(node, elements)) { + receiver = generateInstanceSendReceiver(node); + generateInstanceGetterWithCompiledReceiver(node, receiver); + } else { + generateGetter(node, elements[node.selector]); + } + HInstruction left = pop(); + HInstruction right; + if (isCompoundAssignment) { + visit(node.argumentsNode); + right = pop(); + } else { + right = graph.addConstantInt(1, constantSystem); + } + visitBinary(left, op, right, node); + HInstruction operation = pop(); + assert(operation != null); + if (Elements.isInstanceSend(node, elements)) { + assert(receiver != null); + generateInstanceSetterWithCompiledReceiver(node, receiver, operation); + } else { + assert(receiver == null); + generateSetter(node, element, operation); + } + if (!isPrefix) { + pop(); + stack.add(left); + } + } + } + + void visitLiteralInt(LiteralInt node) { + stack.add(graph.addConstantInt(node.value, constantSystem)); + } + + void visitLiteralDouble(LiteralDouble node) { + stack.add(graph.addConstantDouble(node.value, constantSystem)); + } + + void visitLiteralBool(LiteralBool node) { + stack.add(graph.addConstantBool(node.value, constantSystem)); + } + + void visitLiteralString(LiteralString node) { + stack.add(graph.addConstantString(node.dartString, node, constantSystem)); + } + + void visitStringJuxtaposition(StringJuxtaposition node) { + if (!node.isInterpolation) { + // This is a simple string with no interpolations. + stack.add(graph.addConstantString(node.dartString, node, constantSystem)); + return; + } + StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); + stringBuilder.visit(node); + stack.add(stringBuilder.result); + } + + void visitLiteralNull(LiteralNull node) { + stack.add(graph.addConstantNull(constantSystem)); + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + if (isAborted()) { + compiler.reportWarning(link.head, 'dead code'); + } else { + visit(link.head); + } + } + } + + void visitParenthesizedExpression(ParenthesizedExpression node) { + visit(node.expression); + } + + visitOperator(Operator node) { + // Operators are intercepted in their surrounding Send nodes. + compiler.internalError('visitOperator should not be called', node: node); + } + + visitCascade(Cascade node) { + visit(node.expression); + // Remove the result and reveal the duplicated receiver on the stack. + pop(); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + dup(); + } + + void handleInTryStatement() { + if (!inTryStatement) return; + HBasicBlock block = close(new HExitTry()); + HBasicBlock newBlock = graph.addNewBlock(); + block.addSuccessor(newBlock); + open(newBlock); + } + + visitReturn(Return node) { + if (identical(node.getBeginToken().stringValue, 'native')) { + native.handleSsaNative(this, node.expression); + return; + } + assert(invariant(node, !node.isRedirectingFactoryBody)); + HInstruction value; + if (node.expression == null) { + value = graph.addConstantNull(constantSystem); + } else { + visit(node.expression); + value = pop(); + value = potentiallyCheckType(value, returnType); + } + + handleInTryStatement(); + + if (!inliningStack.isEmpty) { + localsHandler.updateLocal(returnElement, value); + } else { + close(attachPosition(new HReturn(value), node)).addSuccessor(graph.exit); + } + } + + visitThrow(Throw node) { + if (node.expression == null) { + HInstruction exception = rethrowableException; + if (exception == null) { + exception = graph.addConstantNull(constantSystem); + compiler.internalError( + 'rethrowableException should not be null', node: node); + } + close(new HThrow(exception, isRethrow: true)); + } else { + visit(node.expression); + close(new HThrow(pop())); + } + } + + visitTypeAnnotation(TypeAnnotation node) { + compiler.internalError('visiting type annotation in SSA builder', + node: node); + } + + visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + if (definition is Identifier) { + HInstruction initialValue = graph.addConstantNull(constantSystem); + localsHandler.updateLocal(elements[definition], initialValue); + } else { + assert(definition is SendSet); + visitSendSet(definition); + pop(); // Discard value. + } + } + } + + visitLiteralList(LiteralList node) { + if (node.isConst()) { + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + return; + } + + List<HInstruction> inputs = <HInstruction>[]; + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + inputs.add(pop()); + } + push(new HLiteralList(inputs)); + } + + visitConditional(Conditional node) { + SsaBranchBuilder brancher = new SsaBranchBuilder(this, node); + brancher.handleConditional(() => visit(node.condition), + () => visit(node.thenExpression), + () => visit(node.elseExpression)); + } + + visitStringInterpolation(StringInterpolation node) { + StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); + stringBuilder.visit(node); + stack.add(stringBuilder.result); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + // The parts are iterated in visitStringInterpolation. + compiler.internalError('visitStringInterpolation should not be called', + node: node); + } + + visitEmptyStatement(EmptyStatement node) { + // Do nothing, empty statement. + } + + visitModifiers(Modifiers node) { + compiler.unimplemented('SsaBuilder.visitModifiers', node: node); + } + + visitBreakStatement(BreakStatement node) { + assert(!isAborted()); + handleInTryStatement(); + TargetElement target = elements[node]; + assert(target != null); + JumpHandler handler = jumpTargets[target]; + assert(handler != null); + if (node.target == null) { + handler.generateBreak(); + } else { + LabelElement label = elements[node.target]; + handler.generateBreak(label); + } + } + + visitContinueStatement(ContinueStatement node) { + handleInTryStatement(); + TargetElement target = elements[node]; + assert(target != null); + JumpHandler handler = jumpTargets[target]; + assert(handler != null); + if (node.target == null) { + handler.generateContinue(); + } else { + LabelElement label = elements[node.target]; + assert(label != null); + handler.generateContinue(label); + } + } + + /** + * Creates a [JumpHandler] for a statement. The node must be a jump + * target. If there are no breaks or continues targeting the statement, + * a special "null handler" is returned. + */ + JumpHandler createJumpHandler(Statement node) { + TargetElement element = elements[node]; + if (element == null || !identical(element.statement, node)) { + // No breaks or continues to this node. + return new NullJumpHandler(compiler); + } + return new JumpHandler(this, element); + } + + visitForIn(ForIn node) { + // Generate a structure equivalent to: + // Iterator<E> $iter = <iterable>.iterator; + // while ($iter.moveNext()) { + // E <declaredIdentifier> = $iter.current; + // <body> + // } + + // The iterator is shared between initializer, condition and body. + HInstruction iterator; + void buildInitializer() { + SourceString iteratorName = const SourceString("iterator"); + Selector selector = + new Selector.getter(iteratorName, currentElement.getLibrary()); + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + visit(node.expression); + HInstruction receiver = pop(); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + if (interceptedClasses == null) { + iterator = + new HInvokeDynamicGetter(selector, null, receiver, hasGetter); + } else { + HInterceptor interceptor = + invokeInterceptor(interceptedClasses, receiver, null); + iterator = + new HInvokeDynamicGetter(selector, null, interceptor, hasGetter); + // Add the receiver as an argument to the getter call on the + // interceptor. + iterator.inputs.add(receiver); + } + add(iterator); + } + HInstruction buildCondition() { + SourceString name = const SourceString('moveNext'); + Selector selector = new Selector.call( + name, currentElement.getLibrary(), 0); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + push(new HInvokeDynamicMethod(selector, <HInstruction>[iterator])); + return popBoolified(); + } + void buildBody() { + SourceString name = const SourceString('current'); + Selector call = new Selector.getter(name, currentElement.getLibrary()); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(call); + push(new HInvokeDynamicGetter(call, null, iterator, hasGetter)); + + Element variable; + if (node.declaredIdentifier.asSend() != null) { + variable = elements[node.declaredIdentifier]; + } else { + assert(node.declaredIdentifier.asVariableDefinitions() != null); + VariableDefinitions variableDefinitions = node.declaredIdentifier; + variable = elements[variableDefinitions.definitions.nodes.head]; + } + HInstruction oldVariable = pop(); + if (variable.isErroneous()) { + generateThrowNoSuchMethod(node, + getTargetName(variable, 'set'), + argumentValues: <HInstruction>[oldVariable]); + pop(); + } else { + localsHandler.updateLocal(variable, oldVariable); + } + + visit(node.body); + } + handleLoop(node, buildInitializer, buildCondition, () {}, buildBody); + } + + visitLabel(Label node) { + compiler.internalError('SsaBuilder.visitLabel', node: node); + } + + visitLabeledStatement(LabeledStatement node) { + Statement body = node.statement; + if (body is Loop || body is SwitchStatement) { + // Loops and switches handle their own labels. + visit(body); + return; + } + // Non-loop statements can only be break targets, not continue targets. + TargetElement targetElement = elements[body]; + if (targetElement == null || !identical(targetElement.statement, body)) { + // Labeled statements with no element on the body have no breaks. + // A different target statement only happens if the body is itself + // a break or continue for a different target. In that case, this + // label is also always unused. + visit(body); + return; + } + LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler); + assert(targetElement.isBreakTarget); + JumpHandler handler = new JumpHandler(this, targetElement); + // Introduce a new basic block. + HBasicBlock entryBlock = openNewBlock(); + visit(body); + SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock); + + HBasicBlock joinBlock = graph.addNewBlock(); + List<LocalsHandler> breakLocals = <LocalsHandler>[]; + handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { + breakInstruction.block.addSuccessor(joinBlock); + breakLocals.add(locals); + }); + bool hasBreak = breakLocals.length > 0; + if (!isAborted()) { + goto(current, joinBlock); + breakLocals.add(localsHandler); + } + open(joinBlock); + localsHandler = beforeLocals.mergeMultiple(breakLocals, joinBlock); + + if (hasBreak) { + // There was at least one reachable break, so the label is needed. + entryBlock.setBlockFlow( + new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph), + handler.labels()), + joinBlock); + } + handler.close(); + } + + visitLiteralMap(LiteralMap node) { + if (node.isConst()) { + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + return; + } + List<HInstruction> inputs = <HInstruction>[]; + for (Link<Node> link = node.entries.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + inputs.addLast(pop()); + inputs.addLast(pop()); + } + HLiteralList keyValuePairs = new HLiteralList(inputs); + add(keyValuePairs); + pushInvokeHelper1(backend.getMapMaker(), keyValuePairs, + new HType.fromBoundedType(compiler.mapClass.computeType(compiler), + compiler, + false)); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visit(node.value); + visit(node.key); + } + + visitNamedArgument(NamedArgument node) { + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + if (tryBuildConstantSwitch(node)) return; + + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + HBasicBlock startBlock = openNewBlock(); + visit(node.expression); + HInstruction expression = pop(); + if (node.cases.isEmpty) { + return; + } + + Link<Node> cases = node.cases.nodes; + JumpHandler jumpHandler = createJumpHandler(node); + + buildSwitchCases(cases, expression); + + HBasicBlock lastBlock = lastOpenedBlock; + + // Create merge block for break targets. + HBasicBlock joinBlock = new HBasicBlock(); + List<LocalsHandler> caseLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { + instruction.block.addSuccessor(joinBlock); + caseLocals.add(locals); + }); + if (!isAborted()) { + // The current flow is only aborted if the switch has a default that + // aborts (all previous cases must abort, and if there is no default, + // it's possible to miss all the cases). + caseLocals.add(localsHandler); + goto(current, joinBlock); + } + if (caseLocals.length != 0) { + graph.addBlock(joinBlock); + open(joinBlock); + if (caseLocals.length == 1) { + localsHandler = caseLocals[0]; + } else { + localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock); + } + } else { + // The joinblock is not used. + joinBlock = null; + } + startBlock.setBlockFlow( + new HLabeledBlockInformation.implicit( + new HSubGraphBlockInformation(new SubGraph(startBlock, lastBlock)), + elements[node]), + joinBlock); + jumpHandler.close(); + } + + bool tryBuildConstantSwitch(SwitchStatement node) { + Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>(); + // First check whether all case expressions are compile-time constants, + // and all have the same type that doesn't override operator==. + // TODO(lrn): Move the constant resolution to the resolver, so + // we can report an error before reaching the backend. + DartType firstConstantType = null; + bool failure = false; + for (SwitchCase switchCase in node.cases) { + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is CaseMatch) { + CaseMatch match = labelOrCase; + Constant constant = + compiler.constantHandler.tryCompileNodeWithDefinitions( + match.expression, elements); + if (constant == null) { + compiler.reportWarning(match.expression, + MessageKind.NOT_A_COMPILE_TIME_CONSTANT.error()); + failure = true; + continue; + } + if (firstConstantType == null) { + firstConstantType = constant.computeType(compiler); + if (nonPrimitiveTypeOverridesEquals(constant)) { + compiler.reportWarning(match.expression, + MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS.error()); + failure = true; + } + } else { + DartType constantType = + constant.computeType(compiler); + if (constantType != firstConstantType) { + compiler.reportWarning(match.expression, + MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL.error()); + failure = true; + } + } + constants[labelOrCase] = constant; + } else { + compiler.reportWarning(node, "Unsupported: Labels on cases"); + failure = true; + } + } + } + if (failure) { + return false; + } + + // TODO(ngeoffray): Handle switch-instruction in bailout code. + work.allowSpeculativeOptimization = false; + // Then build a switch structure. + HBasicBlock expressionStart = openNewBlock(); + visit(node.expression); + HInstruction expression = pop(); + if (node.cases.isEmpty) { + return true; + } + HBasicBlock expressionEnd = current; + + HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]); + HBasicBlock expressionBlock = close(switchInstruction); + JumpHandler jumpHandler = createJumpHandler(node); + LocalsHandler savedLocals = localsHandler; + + List<List<Constant>> matchExpressions = <List<Constant>>[]; + List<HStatementInformation> statements = <HStatementInformation>[]; + bool hasDefault = false; + Element getFallThroughErrorElement = + compiler.findHelper(const SourceString("getFallThroughError")); + HasNextIterator<Node> caseIterator = + new HasNextIterator<Node>(node.cases.iterator); + while (caseIterator.hasNext) { + SwitchCase switchCase = caseIterator.next(); + List<Constant> caseConstants = <Constant>[]; + HBasicBlock block = graph.addNewBlock(); + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is CaseMatch) { + Constant constant = constants[labelOrCase]; + caseConstants.add(constant); + HConstant hConstant = graph.addConstant(constant); + switchInstruction.inputs.add(hConstant); + hConstant.usedBy.add(switchInstruction); + expressionBlock.addSuccessor(block); + } + } + matchExpressions.add(caseConstants); + + if (switchCase.isDefaultCase) { + // An HSwitch has n inputs and n+1 successors, the last being the + // default case. + expressionBlock.addSuccessor(block); + hasDefault = true; + } + open(block); + localsHandler = new LocalsHandler.from(savedLocals); + visit(switchCase.statements); + if (!isAborted() && caseIterator.hasNext) { + pushInvokeHelper0(getFallThroughErrorElement, HType.UNKNOWN); + HInstruction error = pop(); + close(new HThrow(error)); + } + statements.add( + new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock))); + } + + // Add a join-block if necessary. + // We create [joinBlock] early, and then go through the cases that might + // want to jump to it. In each case, if we add [joinBlock] as a successor + // of another block, we also add an element to [caseLocals] that is used + // to create the phis in [joinBlock]. + // If we never jump to the join block, [caseLocals] will stay empty, and + // the join block is never added to the graph. + HBasicBlock joinBlock = new HBasicBlock(); + List<LocalsHandler> caseLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { + instruction.block.addSuccessor(joinBlock); + caseLocals.add(locals); + }); + if (!isAborted()) { + current.close(new HGoto()); + lastOpenedBlock.addSuccessor(joinBlock); + caseLocals.add(localsHandler); + } + if (!hasDefault) { + // The current flow is only aborted if the switch has a default that + // aborts (all previous cases must abort, and if there is no default, + // it's possible to miss all the cases). + expressionEnd.addSuccessor(joinBlock); + caseLocals.add(savedLocals); + } + assert(caseLocals.length == joinBlock.predecessors.length); + if (caseLocals.length != 0) { + graph.addBlock(joinBlock); + open(joinBlock); + if (caseLocals.length == 1) { + localsHandler = caseLocals[0]; + } else { + localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock); + } + } else { + // The joinblock is not used. + joinBlock = null; + } + + HSubExpressionBlockInformation expressionInfo = + new HSubExpressionBlockInformation(new SubExpression(expressionStart, + expressionEnd)); + expressionStart.setBlockFlow( + new HSwitchBlockInformation(expressionInfo, + matchExpressions, + statements, + hasDefault, + jumpHandler.target, + jumpHandler.labels()), + joinBlock); + + jumpHandler.close(); + return true; + } + + bool nonPrimitiveTypeOverridesEquals(Constant constant) { + // Function values override equals. Even static ones, since + // they inherit from [Function]. + if (constant.isFunction()) return true; + + // [Map] and [List] do not override equals. + // If constant is primitive, just return false. We know + // about the equals methods of num/String classes. + if (!constant.isConstructedObject()) return false; + + ConstructedConstant constructedConstant = constant; + DartType type = constructedConstant.type; + assert(type != null); + Element element = type.element; + // If the type is not a class, we'll just assume it overrides + // operator==. Typedefs do, since [Function] does. + if (!element.isClass()) return true; + ClassElement classElement = element; + return typeOverridesObjectEquals(classElement); + } + + bool typeOverridesObjectEquals(ClassElement classElement) { + Element operatorEq = + lookupOperator(classElement, const SourceString('==')); + if (operatorEq == null) return false; + // If the operator== declaration is in Object, it's not overridden. + return (operatorEq.getEnclosingClass() != compiler.objectClass); + } + + Element lookupOperator(ClassElement classElement, SourceString operatorName) { + SourceString dartMethodName = + Elements.constructOperatorName(operatorName, false); + return classElement.lookupMember(dartMethodName); + } + + + // Recursively build an if/else structure to match the cases. + void buildSwitchCases(Link<Node> cases, HInstruction expression, + [int encounteredCaseTypes = 0]) { + final int NO_TYPE = 0; + final int INT_TYPE = 1; + final int STRING_TYPE = 2; + final int CONFLICT_TYPE = 3; + int combine(int type1, int type2) => type1 | type2; + + SwitchCase node = cases.head; + // Called for the statements on all but the last case block. + // Ensures that a user expecting a fallthrough gets an error. + void visitStatementsAndAbort() { + visit(node.statements); + if (!isAborted()) { + compiler.reportWarning(node, 'Missing break at end of switch case'); + Element element = + compiler.findHelper(const SourceString("getFallThroughError")); + pushInvokeHelper0(element, HType.UNKNOWN); + HInstruction error = pop(); + close(new HThrow(error)); + } + } + + Link<Node> skipLabels(Link<Node> labelsAndCases) { + while (!labelsAndCases.isEmpty && labelsAndCases.head is Label) { + labelsAndCases = labelsAndCases.tail; + } + return labelsAndCases; + } + + Link<Node> labelsAndCases = skipLabels(node.labelsAndCases.nodes); + if (labelsAndCases.isEmpty) { + // Default case with no expressions. + if (!node.isDefaultCase) { + compiler.internalError("Case with no expression and not default", + node: node); + } + visit(node.statements); + // This must be the final case (otherwise "default" would be invalid), + // so we don't need to check for fallthrough. + return; + } + + // Recursively build the test conditions. Leaves the result on the + // expression stack. + void buildTests(Link<Node> remainingCases) { + // Build comparison for one case expression. + void left() { + CaseMatch match = remainingCases.head; + // TODO(lrn): Move the constant resolution to the resolver, so + // we can report an error before reaching the backend. + Constant constant = + compiler.constantHandler.tryCompileNodeWithDefinitions( + match.expression, elements); + if (constant != null) { + stack.add(graph.addConstant(constant)); + } else { + visit(match.expression); + } + push(new HIdentity(pop(), expression)); + } + + // If this is the last expression, just return it. + Link<Node> tail = skipLabels(remainingCases.tail); + if (tail.isEmpty) { + left(); + return; + } + + void right() { + buildTests(tail); + } + SsaBranchBuilder branchBuilder = + new SsaBranchBuilder(this, remainingCases.head); + branchBuilder.handleLogicalAndOr(left, right, isAnd: false); + } + + if (node.isDefaultCase) { + // Default case must be last. + assert(cases.tail.isEmpty); + // Perform the tests until one of them match, but then always execute the + // statements. + // TODO(lrn): Stop performing tests when all expressions are compile-time + // constant strings or integers. + handleIf(node, () { buildTests(labelsAndCases); }, (){}, null); + visit(node.statements); + } else { + if (cases.tail.isEmpty) { + handleIf(node, + () { buildTests(labelsAndCases); }, + () { visit(node.statements); }, + null); + } else { + handleIf(node, + () { buildTests(labelsAndCases); }, + () { visitStatementsAndAbort(); }, + () { buildSwitchCases(cases.tail, expression, + encounteredCaseTypes); }); + } + } + } + + visitSwitchCase(SwitchCase node) { + compiler.internalError('SsaBuilder.visitSwitchCase'); + } + + visitCaseMatch(CaseMatch node) { + compiler.internalError('SsaBuilder.visitCaseMatch'); + } + + visitTryStatement(TryStatement node) { + work.allowSpeculativeOptimization = false; + // Save the current locals. The catch block and the finally block + // must not reuse the existing locals handler. None of the variables + // that have been defined in the body-block will be used, but for + // loops we will add (unnecessary) phis that will reference the body + // variables. This makes it look as if the variables were used + // in a non-dominated block. + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + HBasicBlock enterBlock = openNewBlock(); + HTry tryInstruction = new HTry(); + close(tryInstruction); + bool oldInTryStatement = inTryStatement; + inTryStatement = true; + + HBasicBlock startTryBlock; + HBasicBlock endTryBlock; + HBasicBlock startCatchBlock; + HBasicBlock endCatchBlock; + HBasicBlock startFinallyBlock; + HBasicBlock endFinallyBlock; + + startTryBlock = graph.addNewBlock(); + open(startTryBlock); + visit(node.tryBlock); + if (!isAborted()) endTryBlock = close(new HGoto()); + SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock); + SubGraph catchGraph = null; + HLocalValue exception = null; + + if (!node.catchBlocks.isEmpty) { + localsHandler = new LocalsHandler.from(savedLocals); + startCatchBlock = graph.addNewBlock(); + open(startCatchBlock); + // TODO(kasperl): Bad smell. We shouldn't be constructing elements here. + // Note that the name of this element is irrelevant. + Element element = new ElementX(const SourceString('exception'), + ElementKind.PARAMETER, + currentElement); + exception = new HLocalValue(element); + add(exception); + HInstruction oldRethrowableException = rethrowableException; + rethrowableException = exception; + + pushInvokeHelper1( + backend.getExceptionUnwrapper(), exception, HType.UNKNOWN); + HInvokeStatic unwrappedException = pop(); + tryInstruction.exception = exception; + Link<Node> link = node.catchBlocks.nodes; + + void pushCondition(CatchBlock catchBlock) { + if (catchBlock.onKeyword != null) { + DartType type = elements.getType(catchBlock.type); + if (type == null) { + compiler.cancel('On with unresolved type', + node: catchBlock.type); + } + HInstruction condition = + new HIs(type, <HInstruction>[unwrappedException]); + push(condition); + } + else { + VariableDefinitions declaration = catchBlock.formals.nodes.head; + HInstruction condition = null; + if (declaration.type == null) { + condition = graph.addConstantBool(true, constantSystem); + stack.add(condition); + } else { + // TODO(aprelev@gmail.com): Once old catch syntax is removed + // "if" condition above and this "else" branch should be deleted as + // type of declared variable won't matter for the catch + // condition. + DartType type = elements.getType(declaration.type); + if (type == null) { + compiler.cancel('Catch with unresolved type', node: catchBlock); + } + condition = + new HIs(type, <HInstruction>[unwrappedException], nullOk: true); + push(condition); + } + } + } + + void visitThen() { + CatchBlock catchBlock = link.head; + link = link.tail; + if (catchBlock.exception != null) { + localsHandler.updateLocal(elements[catchBlock.exception], + unwrappedException); + } + Node trace = catchBlock.trace; + if (trace != null) { + pushInvokeHelper1( + backend.getTraceFromException(), exception, HType.UNKNOWN); + HInstruction traceInstruction = pop(); + localsHandler.updateLocal(elements[trace], traceInstruction); + } + visit(catchBlock); + } + + void visitElse() { + if (link.isEmpty) { + close(new HThrow(exception, isRethrow: true)); + } else { + CatchBlock newBlock = link.head; + handleIf(node, + () { pushCondition(newBlock); }, + visitThen, visitElse); + } + } + + CatchBlock firstBlock = link.head; + handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse); + if (!isAborted()) endCatchBlock = close(new HGoto()); + + rethrowableException = oldRethrowableException; + tryInstruction.catchBlock = startCatchBlock; + catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock); + } + + SubGraph finallyGraph = null; + if (node.finallyBlock != null) { + localsHandler = new LocalsHandler.from(savedLocals); + startFinallyBlock = graph.addNewBlock(); + open(startFinallyBlock); + visit(node.finallyBlock); + if (!isAborted()) endFinallyBlock = close(new HGoto()); + tryInstruction.finallyBlock = startFinallyBlock; + finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock); + } + + HBasicBlock exitBlock = graph.addNewBlock(); + + addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); } + addExitTrySuccessor(successor) { + if (successor == null) return; + // Iterate over all blocks created inside this try/catch, and + // attach successor information to blocks that end with + // [HExitTry]. + for (int i = startTryBlock.id; i < successor.id; i++) { + HBasicBlock block = graph.blocks[i]; + var last = block.last; + if (last is HExitTry) { + block.addSuccessor(successor); + } else if (last is HTry) { + // Skip all blocks inside this nested try/catch. + i = last.joinBlock.id; + } + } + } + + // Setup all successors. The entry block that contains the [HTry] + // has 1) the body, 2) the catch, 3) the finally, and 4) the exit + // blocks as successors. + enterBlock.addSuccessor(startTryBlock); + addOptionalSuccessor(enterBlock, startCatchBlock); + addOptionalSuccessor(enterBlock, startFinallyBlock); + enterBlock.addSuccessor(exitBlock); + + // The body has either the catch or the finally block as successor. + if (endTryBlock != null) { + assert(startCatchBlock != null || startFinallyBlock != null); + endTryBlock.addSuccessor( + startCatchBlock != null ? startCatchBlock : startFinallyBlock); + } + + // The catch block has either the finally or the exit block as + // successor. + if (endCatchBlock != null) { + endCatchBlock.addSuccessor( + startFinallyBlock != null ? startFinallyBlock : exitBlock); + } + + // The finally block has the exit block as successor. + if (endFinallyBlock != null) { + endFinallyBlock.addSuccessor(exitBlock); + } + + // If a block inside try/catch aborts (eg with a return statement), + // we explicitely mark this block a predecessor of the catch + // block and the finally block. + addExitTrySuccessor(startCatchBlock); + addExitTrySuccessor(startFinallyBlock); + + // Use the locals handler not altered by the catch and finally + // blocks. + localsHandler = savedLocals; + open(exitBlock); + enterBlock.setBlockFlow( + new HTryBlockInformation( + wrapStatementGraph(bodyGraph), + exception, + wrapStatementGraph(catchGraph), + wrapStatementGraph(finallyGraph)), + exitBlock); + inTryStatement = oldInTryStatement; + } + + visitScriptTag(ScriptTag node) { + compiler.unimplemented('SsaBuilder.visitScriptTag', node: node); + } + + visitCatchBlock(CatchBlock node) { + visit(node.block); + } + + visitTypedef(Typedef node) { + compiler.unimplemented('SsaBuilder.visitTypedef', node: node); + } + + visitTypeVariable(TypeVariable node) { + compiler.internalError('SsaBuilder.visitTypeVariable'); + } + + HType mapBaseType(BaseType baseType) { + if (!baseType.isClass()) return HType.UNKNOWN; + ClassBaseType classBaseType = baseType; + return new HType.fromBoundedType( + classBaseType.element.computeType(compiler), compiler, false); + } + + HType mapInferredType(ConcreteType concreteType) { + if (concreteType == null) return HType.UNKNOWN; + HType ssaType = HType.CONFLICTING; + for (BaseType baseType in concreteType.baseTypes) { + ssaType = ssaType.union(mapBaseType(baseType), compiler); + } + assert(!ssaType.isConflicting()); + return ssaType; + } + + HType mapNativeType(type) { + if (type == native.SpecialType.JsObject) { + return new HBoundedType.exact( + compiler.objectClass.computeType(compiler)); + } else if (type == native.SpecialType.JsArray) { + return HType.READABLE_ARRAY; + } else { + return new HType.fromBoundedType(type, compiler, false); + } + } + + HType mapNativeBehaviorType(native.NativeBehavior nativeBehavior) { + if (nativeBehavior.typesInstantiated.isEmpty) return HType.UNKNOWN; + + HType ssaType = HType.CONFLICTING; + for (final type in nativeBehavior.typesInstantiated) { + ssaType = ssaType.union(mapNativeType(type), compiler); + } + assert(!ssaType.isConflicting()); + return ssaType; + } +} + +/** + * Visitor that handles generation of string literals (LiteralString, + * StringInterpolation), and otherwise delegates to the given visitor for + * non-literal subexpressions. + * TODO(lrn): Consider whether to handle compile time constant int/boolean + * expressions as well. + */ +class StringBuilderVisitor extends Visitor { + final SsaBuilder builder; + final Node diagnosticNode; + + /** + * The string value generated so far. + */ + HInstruction result = null; + + StringBuilderVisitor(this.builder, this.diagnosticNode); + + void visit(Node node) { + node.accept(this); + } + + visitNode(Node node) { + builder.compiler.internalError('unexpected node', node: node); + } + + void visitExpression(Node node) { + node.accept(builder); + HInstruction expression = builder.pop(); + result = (result == null) ? expression : concat(result, expression); + } + + void visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + } + + void visitStringInterpolationPart(StringInterpolationPart node) { + visit(node.expression); + visit(node.string); + } + + void visitStringJuxtaposition(StringJuxtaposition node) { + node.visitChildren(this); + } + + void visitNodeList(NodeList node) { + node.visitChildren(this); + } + + HInstruction concat(HInstruction left, HInstruction right) { + HInstruction instruction = new HStringConcat(left, right, diagnosticNode); + builder.add(instruction); + return instruction; + } +} + +/** + * This class visits the method that is a candidate for inlining and + * finds whether it is too difficult to inline. + */ +class InlineWeeder extends Visitor { + final TreeElements elements; + bool seenReturn = false; + bool tooDifficult = false; + + InlineWeeder(this.elements); + + static bool canBeInlined(FunctionExpression functionExpression, + TreeElements elements) { + InlineWeeder weeder = new InlineWeeder(elements); + weeder.visit(functionExpression.body); + if (weeder.tooDifficult) return false; + return true; + } + + void visit(Node node) { + node.accept(this); + } + + void visitNode(Node node) { + if (seenReturn) { + tooDifficult = true; + } else { + node.visitChildren(this); + } + } + + void visitFunctionExpression(Node node) { + tooDifficult = true; + } + + void visitFunctionDeclaration(Node node) { + tooDifficult = true; + } + + void visitSend(Send node) { + if (node.isParameterCheck) { + tooDifficult = true; + return; + } + node.visitChildren(this); + } + + visitLoop(Node node) { + node.visitChildren(this); + if (seenReturn) tooDifficult = true; + } + + void visitReturn(Return node) { + if (seenReturn + || identical(node.getBeginToken().stringValue, 'native') + || node.isRedirectingFactoryBody) { + tooDifficult = true; + return; + } + node.visitChildren(this); + seenReturn = true; + } + + void visitTryStatement(Node node) { + tooDifficult = true; + } + + void visitThrow(Node node) { + tooDifficult = true; + } +} + +class InliningState { + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + final PartialFunctionElement function; + final Element oldReturnElement; + final DartType oldReturnType; + final TreeElements oldElements; + final List<HInstruction> oldStack; + + InliningState(this.function, + this.oldReturnElement, + this.oldReturnType, + this.oldElements, + this.oldStack) { + assert(function.isImplementation); + } +} + +class SsaBranch { + final SsaBranchBuilder branchBuilder; + final HBasicBlock block; + LocalsHandler startLocals; + LocalsHandler exitLocals; + SubGraph graph; + + SsaBranch(this.branchBuilder) : block = new HBasicBlock(); +} + +class SsaBranchBuilder { + final SsaBuilder builder; + final Node diagnosticNode; + + SsaBranchBuilder(this.builder, [this.diagnosticNode]); + + Compiler get compiler => builder.compiler; + + void checkNotAborted() { + if (builder.isAborted()) { + compiler.unimplemented("aborted control flow", node: diagnosticNode); + } + } + + void buildCondition(void visitCondition(), + SsaBranch conditionBranch, + SsaBranch thenBranch, + SsaBranch elseBranch) { + startBranch(conditionBranch); + visitCondition(); + checkNotAborted(); + assert(identical(builder.current, builder.lastOpenedBlock)); + HInstruction conditionValue = builder.popBoolified(); + HIf branch = new HIf(conditionValue); + HBasicBlock conditionExitBlock = builder.current; + builder.close(branch); + conditionBranch.exitLocals = builder.localsHandler; + conditionExitBlock.addSuccessor(thenBranch.block); + conditionExitBlock.addSuccessor(elseBranch.block); + bool conditionBranchLocalsCanBeReused = + mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true); + mergeLocals(conditionBranch, elseBranch, + mayReuseFromLocals: conditionBranchLocalsCanBeReused); + + conditionBranch.graph = + new SubExpression(conditionBranch.block, conditionExitBlock); + } + + /** + * Returns true if the locals of the [fromBranch] may be reused. A [:true:] + * return value implies that [mayReuseFromLocals] was set to [:true:]. + */ + bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch, + {bool mayReuseFromLocals}) { + LocalsHandler fromLocals = fromBranch.exitLocals; + if (toBranch.startLocals == null) { + if (mayReuseFromLocals) { + toBranch.startLocals = fromLocals; + return false; + } else { + toBranch.startLocals = new LocalsHandler.from(fromLocals); + return true; + } + } else { + toBranch.startLocals.mergeWith(fromLocals, toBranch.block); + return true; + } + } + + void startBranch(SsaBranch branch) { + builder.graph.addBlock(branch.block); + builder.localsHandler = branch.startLocals; + builder.open(branch.block); + } + + HInstruction buildBranch(SsaBranch branch, + void visitBranch(), + SsaBranch joinBranch, + bool isExpression) { + startBranch(branch); + visitBranch(); + branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock); + branch.exitLocals = builder.localsHandler; + if (!builder.isAborted()) { + builder.goto(builder.current, joinBranch.block); + mergeLocals(branch, joinBranch, mayReuseFromLocals: true); + } + if (isExpression) { + checkNotAborted(); + return builder.pop(); + } + return null; + } + + handleIf(void visitCondition(), void visitThen(), void visitElse()) { + if (visitElse == null) { + // Make sure to have an else part to avoid a critical edge. A + // critical edge is an edge that connects a block with multiple + // successors to a block with multiple predecessors. We avoid + // such edges because they prevent inserting copies during code + // generation of phi instructions. + visitElse = () {}; + } + + _handleDiamondBranch(visitCondition, visitThen, visitElse, false); + } + + handleConditional(void visitCondition(), void visitThen(), void visitElse()) { + assert(visitElse != null); + _handleDiamondBranch(visitCondition, visitThen, visitElse, true); + } + + void handleLogicalAndOr(void left(), void right(), {bool isAnd}) { + // x && y is transformed into: + // t0 = boolify(x); + // if (t0) { + // t1 = boolify(y); + // } + // result = phi(t1, false); + // + // x || y is transformed into: + // t0 = boolify(x); + // if (not(t0)) { + // t1 = boolify(y); + // } + // result = phi(t1, true); + HInstruction boolifiedLeft; + HInstruction boolifiedRight; + + void visitCondition() { + left(); + boolifiedLeft = builder.popBoolified(); + builder.stack.add(boolifiedLeft); + if (!isAnd) { + builder.push(new HNot(builder.pop())); + } + } + + void visitThen() { + right(); + boolifiedRight = builder.popBoolified(); + } + + handleIf(visitCondition, visitThen, null); + HConstant notIsAnd = + builder.graph.addConstantBool(!isAnd, builder.constantSystem); + HPhi result = new HPhi.manyInputs(null, + <HInstruction>[boolifiedRight, notIsAnd]); + builder.current.addPhi(result); + builder.stack.add(result); + } + + void handleLogicalAndOrWithLeftNode(Node left, + void visitRight(), + {bool isAnd}) { + // This method is similar to [handleLogicalAndOr] but optimizes the case + // where left is a logical "and" or logical "or". + // + // For example (x && y) && z is transformed into x && (y && z): + // t0 = boolify(x); + // if (t0) { + // t1 = boolify(y); + // if (t1) { + // t2 = boolify(z); + // } + // t3 = phi(t2, false); + // } + // result = phi(t3, false); + + Send send = left.asSend(); + if (send != null && + (isAnd ? send.isLogicalAnd : send.isLogicalOr)) { + Node newLeft = send.receiver; + Link<Node> link = send.argumentsNode.nodes; + assert(link.tail.isEmpty); + Node middle = link.head; + handleLogicalAndOrWithLeftNode( + newLeft, + () => handleLogicalAndOrWithLeftNode(middle, visitRight, + isAnd: isAnd), + isAnd: isAnd); + } else { + handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd); + } + } + + void _handleDiamondBranch(void visitCondition(), + void visitThen(), + void visitElse(), + bool isExpression) { + SsaBranch conditionBranch = new SsaBranch(this); + SsaBranch thenBranch = new SsaBranch(this); + SsaBranch elseBranch = new SsaBranch(this); + SsaBranch joinBranch = new SsaBranch(this); + + conditionBranch.startLocals = builder.localsHandler; + builder.goto(builder.current, conditionBranch.block); + + buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch); + HInstruction thenValue = + buildBranch(thenBranch, visitThen, joinBranch, isExpression); + HInstruction elseValue = + buildBranch(elseBranch, visitElse, joinBranch, isExpression); + + if (isExpression) { + assert(thenValue != null && elseValue != null); + HPhi phi = + new HPhi.manyInputs(null, <HInstruction>[thenValue, elseValue]); + joinBranch.block.addPhi(phi); + builder.stack.add(phi); + } + + HBasicBlock thenBlock = thenBranch.block; + HBasicBlock elseBlock = elseBranch.block; + HBasicBlock joinBlock; + // If at least one branch did not abort, open the joinBranch. + if (!joinBranch.block.predecessors.isEmpty) { + startBranch(joinBranch); + joinBlock = joinBranch.block; + } + + HIfBlockInformation info = + new HIfBlockInformation( + new HSubExpressionBlockInformation(conditionBranch.graph), + new HSubGraphBlockInformation(thenBranch.graph), + new HSubGraphBlockInformation(elseBranch.graph)); + + HBasicBlock conditionStartBlock = conditionBranch.block; + conditionStartBlock.setBlockFlow(info, joinBlock); + SubGraph conditionGraph = conditionBranch.graph; + HIf branch = conditionGraph.end.last; + assert(branch is HIf); + branch.blockInformation = conditionStartBlock.blockFlow; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart new file mode 100644 index 0000000..e5caac1 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart
@@ -0,0 +1,3006 @@ +// 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. + +part of ssa; + +class SsaCodeGeneratorTask extends CompilerTask { + + final JavaScriptBackend backend; + + SsaCodeGeneratorTask(JavaScriptBackend backend) + : this.backend = backend, + super(backend.compiler); + String get name => 'SSA code generator'; + NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter; + + + js.Fun buildJavaScriptFunction(FunctionElement element, + List<js.Parameter> parameters, + js.Block body) { + FunctionExpression expression = + element.implementation.parseNode(backend.compiler); + js.Fun result = new js.Fun(parameters, body); + // TODO(johnniwinther): remove the 'element.patch' hack. + Element sourceElement = element.patch == null ? element : element.patch; + SourceFile sourceFile = sourceElement.getCompilationUnit().script.file; + // TODO(podivilov): find the right sourceFile here and remove offset checks + // below. + if (expression.getBeginToken().charOffset < sourceFile.text.length) { + result.sourcePosition = new SourceFileLocation( + sourceFile, expression.getBeginToken()); + } + if (expression.getEndToken().charOffset < sourceFile.text.length) { + result.endSourcePosition = new SourceFileLocation( + sourceFile, expression.getEndToken()); + } + return result; + } + + CodeBuffer prettyPrint(js.Node node) { + var code = js.prettyPrint(node, compiler, allowVariableMinification: true); + return code; + } + + js.Expression generateCode(CodegenWorkItem work, HGraph graph) { + if (work.element.isField()) { + return generateLazyInitializer(work, graph); + } else { + return generateMethod(work, graph); + } + } + + js.Expression generateLazyInitializer(work, graph) { + return measure(() { + compiler.tracer.traceGraph("codegen", graph); + SsaOptimizedCodeGenerator codegen = + new SsaOptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + return new js.Fun(codegen.parameters, codegen.body); + }); + } + + js.Expression generateMethod(CodegenWorkItem work, HGraph graph) { + return measure(() { + compiler.tracer.traceGraph("codegen", graph); + SsaOptimizedCodeGenerator codegen = + new SsaOptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + + FunctionElement element = work.element; + js.Block body; + ClassElement enclosingClass = element.getEnclosingClass(); + + if (element.isInstanceMember() + && enclosingClass.isNative() + && native.isOverriddenMethod( + element, enclosingClass, nativeEmitter)) { + // Record that this method is overridden. In case of optional + // arguments, the emitter will generate stubs to handle them, + // and needs to know if the method is overridden. + nativeEmitter.overriddenMethods.add(element); + StringBuffer buffer = new StringBuffer(); + body = + nativeEmitter.generateMethodBodyWithPrototypeCheckForElement( + element, codegen.body, codegen.parameters); + } else { + body = codegen.body; + } + + return buildJavaScriptFunction(element, codegen.parameters, body); + }); + } + + js.Expression generateBailoutMethod(CodegenWorkItem work, HGraph graph) { + return measure(() { + compiler.tracer.traceGraph("codegen-bailout", graph); + + SsaUnoptimizedCodeGenerator codegen = + new SsaUnoptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + + js.Block body = new js.Block(<js.Statement>[]); + body.statements.add(codegen.body); + js.Fun fun = + buildJavaScriptFunction(work.element, codegen.newParameters, body); + return fun; + }); + } +} + +// Stop-gap until the core classes have such a class. +class OrderedSet<T> { + final LinkedHashMap<T, bool> map = new LinkedHashMap<T, bool>(); + + void add(T x) { + if (!map.containsKey(x)) { + map[x] = true; + } + } + + bool contains(T x) => map.containsKey(x); + + bool remove(T x) => map.remove(x) != null; + + bool get isEmpty => map.isEmpty; + + void forEach(f) => map.keys.forEach(f); + + T get first { + var iterator = map.keys.iterator; + if (!iterator.moveNext()) throw new StateError("No elements"); + return iterator.current; + } + + get length => map.length; +} + +typedef void ElementAction(Element element); + +abstract class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { + /** + * Returned by [expressionType] to tell how code can be generated for + * a subgraph. + * - [TYPE_STATEMENT] means that the graph must be generated as a statement, + * which is always possible. + * - [TYPE_EXPRESSION] means that the graph can be generated as an expression, + * or possibly several comma-separated expressions. + * - [TYPE_DECLARATION] means that the graph can be generated as an + * expression, and that it only generates expressions of the form + * variable = expression + * which are also valid as parts of a "var" declaration. + */ + static const int TYPE_STATEMENT = 0; + static const int TYPE_EXPRESSION = 1; + static const int TYPE_DECLARATION = 2; + + /** + * Whether we are currently generating expressions instead of statements. + * This includes declarations, which are generated as expressions. + */ + bool isGeneratingExpression = false; + + final JavaScriptBackend backend; + final CodegenWorkItem work; + final HTypeMap types; + + final Set<HInstruction> generateAtUseSite; + final Set<HInstruction> controlFlowOperators; + final Map<Element, ElementAction> breakAction; + final Map<Element, ElementAction> continueAction; + final List<js.Parameter> parameters; + + js.Block currentContainer; + js.Block get body => currentContainer; + List<js.Expression> expressionStack; + List<js.Block> oldContainerStack; + + /** + * Contains the names of the instructions, as well as the parallel + * copies to perform on block transitioning. + */ + VariableNames variableNames; + bool shouldGroupVarDeclarations = false; + + /** + * While generating expressions, we can't insert variable declarations. + * Instead we declare them at the start of the function. When minifying + * we do this most of the time, because it reduces the size unless there + * is only one variable. + */ + final OrderedSet<String> collectedVariableDeclarations; + + /** + * Set of variables and parameters that have already been declared. + */ + final Set<String> declaredLocals; + + int indent = 0; + HGraph currentGraph; + + // Records a block-information that is being handled specially. + // Used to break bad recursion. + HBlockInformation currentBlockInformation; + // The subgraph is used to delimit traversal for some constructions, e.g., + // if branches. + SubGraph subGraph; + + SsaCodeGenerator(this.backend, CodegenWorkItem work) + : this.work = work, + this.types = + (work.compilationContext as JavaScriptItemCompilationContext).types, + declaredLocals = new Set<String>(), + collectedVariableDeclarations = new OrderedSet<String>(), + currentContainer = new js.Block.empty(), + parameters = <js.Parameter>[], + expressionStack = <js.Expression>[], + oldContainerStack = <js.Block>[], + generateAtUseSite = new Set<HInstruction>(), + controlFlowOperators = new Set<HInstruction>(), + breakAction = new Map<Element, ElementAction>(), + continueAction = new Map<Element, ElementAction>(); + + Compiler get compiler => backend.compiler; + NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter; + CodegenEnqueuer get world => backend.compiler.enqueuer.codegen; + + bool isGenerateAtUseSite(HInstruction instruction) { + return generateAtUseSite.contains(instruction); + } + + bool isNonNegativeInt32Constant(HInstruction instruction) { + if (instruction.isConstantInteger()) { + HConstant constantInstruction = instruction; + PrimitiveConstant primitiveConstant = constantInstruction.constant; + int value = primitiveConstant.value; + if (value >= 0 && value < (1 << 31)) { + return true; + } + } + return false; + } + + bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) { + for (HInstruction user in instruction.usedBy) { + if (user is HPhi) { + if (!phiSet.contains(user)) { + phiSet.add(user); + if (hasNonBitOpUser(user, phiSet)) return true; + } + } else if (user is! HBitNot && user is! HBinaryBitOp) { + return true; + } + } + return false; + } + + // We want the outcome of bit-operations to be positive. However, if + // the result of a bit-operation is only used by other bit + // operations we do not have to convert to an unsigned + // integer. Also, if we are using & with a positive constant we know + // that the result is positive already and need no conversion. + bool requiresUintConversion(HInstruction instruction) { + if (instruction is HBitAnd) { + HBitAnd bitAnd = instruction; + if (isNonNegativeInt32Constant(bitAnd.left) || + isNonNegativeInt32Constant(bitAnd.right)) { + return false; + } + } + return hasNonBitOpUser(instruction, new Set<HPhi>()); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [statement]. + */ + void pushStatement(js.Statement statement, [HInstruction instruction]) { + assert(expressionStack.isEmpty); + if (instruction != null) { + attachLocation(statement, instruction); + } + currentContainer.statements.add(statement); + } + + void insertStatementAtStart(js.Statement statement) { + currentContainer.statements.insertRange(0, 1, statement); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [expression]. + */ + pushExpressionAsStatement(js.Expression expression, + [HInstruction instruction]) { + pushStatement(new js.ExpressionStatement(expression), instruction); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [expression]. + */ + push(js.Expression expression, [HInstruction instruction]) { + if (instruction != null) { + attachLocation(expression, instruction); + } + expressionStack.add(expression); + } + + js.Expression pop() { + return expressionStack.removeLast(); + } + + attachLocationToLast(HInstruction instruction) { + attachLocation(expressionStack.last, instruction); + } + + js.Node attachLocation(js.Node jsNode, HInstruction instruction) { + jsNode.sourcePosition = instruction.sourcePosition; + return jsNode; + } + + js.Node attachLocationRange(js.Node jsNode, + SourceFileLocation sourcePosition, + SourceFileLocation endSourcePosition) { + jsNode.sourcePosition = sourcePosition; + jsNode.endSourcePosition = endSourcePosition; + return jsNode; + } + + visitTypeGuard(HTypeGuard node); + visitBailoutTarget(HBailoutTarget node); + + beginGraph(HGraph graph); + endGraph(HGraph graph); + + preLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + startLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + endLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + + void preGenerateMethod(HGraph graph) { + new SsaInstructionMerger(types, generateAtUseSite).visitGraph(graph); + new SsaConditionMerger( + types, generateAtUseSite, controlFlowOperators).visitGraph(graph); + SsaLiveIntervalBuilder intervalBuilder = + new SsaLiveIntervalBuilder(compiler, generateAtUseSite); + intervalBuilder.visitGraph(graph); + SsaVariableAllocator allocator = new SsaVariableAllocator( + compiler, + intervalBuilder.liveInstructions, + intervalBuilder.liveIntervals, + generateAtUseSite); + allocator.visitGraph(graph); + variableNames = allocator.names; + shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1; + + // Don't register a return type for lazily initialized variables. + if (work.element is! FunctionElement) return; + + // Register return types to the backend. + graph.exit.predecessors.forEach((HBasicBlock block) { + HInstruction last = block.last; + assert(last is HGoto || last is HReturn); + if (last is HReturn) { + backend.registerReturnType(work.element, types[last.inputs[0]]); + } else { + backend.registerReturnType(work.element, HType.NULL); + } + }); + } + + void handleDelayedVariableDeclarations() { + // If we have only one variable declaration and the first statement is an + // assignment to that variable then we can merge the two. We count the + // number of variables in the variable allocator to try to avoid this issue, + // but it sometimes happens that the variable allocator introduces a + // temporary variable that it later eliminates. + if (!collectedVariableDeclarations.isEmpty) { + if (collectedVariableDeclarations.length == 1 && + currentContainer.statements.length >= 1 && + currentContainer.statements[0] is js.ExpressionStatement) { + String name = collectedVariableDeclarations.first; + js.ExpressionStatement statement = currentContainer.statements[0]; + if (statement.expression is js.Assignment) { + js.Assignment assignment = statement.expression; + if (!assignment.isCompound && + assignment.leftHandSide is js.VariableReference) { + js.VariableReference variableReference = assignment.leftHandSide; + if (variableReference.name == name) { + js.VariableDeclaration decl = new js.VariableDeclaration(name); + js.VariableInitialization initialization = + new js.VariableInitialization(decl, assignment.value); + currentContainer.statements[0] = new js.ExpressionStatement( + new js.VariableDeclarationList([initialization])); + return; + } + } + } + } + // If we can't merge the declaration with the first assignment then we + // just do it with a new var z,y,x; statement. + List<js.VariableInitialization> declarations = + <js.VariableInitialization>[]; + collectedVariableDeclarations.forEach((String name) { + declarations.add(new js.VariableInitialization( + new js.VariableDeclaration(name), null)); + }); + var declarationList = new js.VariableDeclarationList(declarations); + insertStatementAtStart(new js.ExpressionStatement(declarationList)); + } + } + + visitGraph(HGraph graph) { + preGenerateMethod(graph); + currentGraph = graph; + indent++; // We are already inside a function. + subGraph = new SubGraph(graph.entry, graph.exit); + HBasicBlock start = beginGraph(graph); + visitBasicBlock(start); + handleDelayedVariableDeclarations(); + endGraph(graph); + } + + void visitSubGraph(SubGraph newSubGraph) { + SubGraph oldSubGraph = subGraph; + subGraph = newSubGraph; + visitBasicBlock(subGraph.start); + subGraph = oldSubGraph; + } + + /** + * Check whether a sub-graph can be generated as an expression, or even + * as a declaration, or if it has to fall back to being generated as + * a statement. + * Expressions are anything that doesn't generate control flow constructs. + * Declarations must only generate assignments on the form "id = expression", + * and not, e.g., expressions where the value isn't assigned, or where it's + * assigned to something that's not a simple variable. + */ + int expressionType(HExpressionInformation info) { + // The only HExpressionInformation used as part of a HBlockInformation is + // current HSubExpressionBlockInformation, so it's the only one reaching + // here. If we start using the other HExpressionInformation types too, + // this code should be generalized. + assert(info is HSubExpressionBlockInformation); + HSubExpressionBlockInformation expressionInfo = info; + SubGraph limits = expressionInfo.subExpression; + + // Start assuming that we can generate declarations. If we find a + // counter-example, we degrade our assumption to either expression or + // statement, and in the latter case, we can return immediately since + // it can't get any worse. E.g., a function call where the return value + // isn't used can't be in a declaration. A bailout can't be in an + // expression. + int result = TYPE_DECLARATION; + HBasicBlock basicBlock = limits.start; + do { + HInstruction current = basicBlock.first; + while (current != basicBlock.last) { + // E.g, type guards. + if (current.isControlFlow()) { + return TYPE_STATEMENT; + } + // HFieldSet generates code on the form x.y = ..., which isn't + // valid in a declaration, but it also always have no uses, so + // it's caught by that test too. + assert(current is! HFieldSet || current.usedBy.isEmpty); + if (current.usedBy.isEmpty) { + result = TYPE_EXPRESSION; + } + current = current.next; + } + if (current is HGoto) { + basicBlock = basicBlock.successors[0]; + } else if (current is HConditionalBranch) { + if (generateAtUseSite.contains(current)) { + // Short-circuit control flow operator trickery. + // Check the second half, which will continue into the join. + // (The first half is [inputs[0]], the second half is [successors[0]], + // and [successors[1]] is the join-block). + basicBlock = basicBlock.successors[0]; + } else { + // We allow an expression to end on an HIf (a condition expression). + return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT; + } + } else { + // Expression-incompatible control flow. + return TYPE_STATEMENT; + } + } while (limits.contains(basicBlock)); + return result; + } + + bool isJSExpression(HExpressionInformation info) { + return !identical(expressionType(info), TYPE_STATEMENT); + } + + bool isJSDeclaration(HExpressionInformation info) { + return identical(expressionType(info), TYPE_DECLARATION); + } + + bool isJSCondition(HExpressionInformation info) { + HSubExpressionBlockInformation graph = info; + SubExpression limits = graph.subExpression; + return !identical(expressionType(info), TYPE_STATEMENT) && + (limits.end.last is HConditionalBranch); + } + + /** + * Generate statements from block information. + * If the block information contains expressions, generate only + * assignments, and if it ends in a conditional branch, don't generate + * the condition. + */ + void generateStatements(HBlockInformation block) { + if (block is HStatementInformation) { + block.accept(this); + } else { + HSubExpressionBlockInformation expression = block; + visitSubGraph(expression.subExpression); + } + } + + js.Block generateStatementsInNewBlock(HBlockInformation block) { + js.Block result = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = result; + generateStatements(block); + currentContainer = oldContainer; + return result; + } + + /** + * If the [block] only contains one statement returns that statement. If the + * that statement itself is a block, recursively calls this method. + * + * If the block is empty, returns a new instance of [js.NOP]. + */ + js.Statement unwrapStatement(js.Block block) { + int len = block.statements.length; + if (len == 0) return new js.EmptyStatement(); + if (len == 1) { + js.Statement result = block.statements[0]; + if (result is Block) return unwrapStatement(result); + return result; + } + return block; + } + + /** + * Generate expressions from block information. + */ + js.Expression generateExpression(HExpressionInformation expression) { + // Currently we only handle sub-expression graphs. + assert(expression is HSubExpressionBlockInformation); + + bool oldIsGeneratingExpression = isGeneratingExpression; + isGeneratingExpression = true; + List<js.Expression> oldExpressionStack = expressionStack; + List<js.Expression> sequenceElements = <js.Expression>[]; + expressionStack = sequenceElements; + HSubExpressionBlockInformation expressionSubGraph = expression; + visitSubGraph(expressionSubGraph.subExpression); + expressionStack = oldExpressionStack; + isGeneratingExpression = oldIsGeneratingExpression; + if (sequenceElements.isEmpty) { + // Happens when the initializer, condition or update of a loop is empty. + return null; + } else if (sequenceElements.length == 1) { + return sequenceElements[0]; + } else { + return new js.Sequence(sequenceElements); + } + } + + /** + * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET]. + */ + List<js.Expression> visitArguments(List<HInstruction> inputs) { + assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET); + List<js.Expression> result = <js.Expression>[]; + for (int i = HInvoke.ARGUMENTS_OFFSET; i < inputs.length; i++) { + use(inputs[i]); + result.add(pop()); + } + return result; + } + + bool isVariableDeclared(String variableName) { + return declaredLocals.contains(variableName) || + collectedVariableDeclarations.contains(variableName); + } + + js.Expression generateExpressionAssignment(String variableName, + js.Expression value) { + if (value is js.Binary) { + js.Binary binary = value; + String op = binary.op; + if (op == '+' || op == '-' || op == '/' || op == '*' || op == '%' || + op == '^' || op == '&' || op == '|') { + if (binary.left is js.VariableUse && + (binary.left as js.VariableUse).name == variableName) { + // We know now, that we can shorten x = x + y into x += y. + // Also check for the shortcut where y equals 1: x++ and x--. + if ((op == '+' || op == '-') && + binary.right is js.LiteralNumber && + (binary.right as js.LiteralNumber).value == "1") { + return new js.Prefix(op == '+' ? '++' : '--', binary.left); + } + return new js.Assignment.compound(binary.left, op, binary.right); + } + } + } + return new js.Assignment(new js.VariableUse(variableName), value); + } + + void assignVariable(String variableName, js.Expression value) { + if (isGeneratingExpression) { + // If we are in an expression then we can't declare the variable here. + // We have no choice, but to use it and then declare it separately. + if (!isVariableDeclared(variableName)) { + collectedVariableDeclarations.add(variableName); + } + push(generateExpressionAssignment(variableName, value)); + // Otherwise if we are trying to declare inline and we are in a statement + // then we declare (unless it was already declared). + } else if (!shouldGroupVarDeclarations && + !declaredLocals.contains(variableName)) { + // It may be necessary to remove it from the ones to be declared later. + collectedVariableDeclarations.remove(variableName); + declaredLocals.add(variableName); + js.VariableDeclaration decl = new js.VariableDeclaration(variableName); + js.VariableInitialization initialization = + new js.VariableInitialization(decl, value); + + pushExpressionAsStatement(new js.VariableDeclarationList( + <js.VariableInitialization>[initialization])); + } else { + // Otherwise we are just going to use it. If we have not already declared + // it then we make sure we will declare it later. + if (!declaredLocals.contains(variableName)) { + collectedVariableDeclarations.add(variableName); + } + pushExpressionAsStatement( + generateExpressionAssignment(variableName, value)); + } + } + + void define(HInstruction instruction) { + // For simple type checks like i = intTypeCheck(i), we don't have to + // emit an assignment, because the intTypeCheck just returns its + // argument. + bool needsAssignment = true; + if (instruction is HTypeConversion) { + String inputName = variableNames.getName(instruction.checkedInput); + if (variableNames.getName(instruction) == inputName) { + needsAssignment = false; + } + } + if (instruction is HLocalValue) { + needsAssignment = false; + } + + if (needsAssignment && + !instruction.isControlFlow() && variableNames.hasName(instruction)) { + visitExpression(instruction); + assignVariable(variableNames.getName(instruction), pop()); + return; + } + + if (isGeneratingExpression) { + visitExpression(instruction); + } else { + visitStatement(instruction); + } + } + + void use(HInstruction argument) { + if (isGenerateAtUseSite(argument)) { + visitExpression(argument); + } else if (argument is HCheck && argument.isControlFlow()) { + // A [HCheck] that has control flow can never be used as an + // expression and may not have a name. Therefore we just use the + // checked instruction. + HCheck check = argument; + use(check.checkedInput); + } else { + push(new js.VariableUse(variableNames.getName(argument))); + } + } + + visit(HInstruction node) { + node.accept(this); + } + + visitExpression(HInstruction node) { + bool oldIsGeneratingExpression = isGeneratingExpression; + isGeneratingExpression = true; + visit(node); + isGeneratingExpression = oldIsGeneratingExpression; + } + + visitStatement(HInstruction node) { + assert(!isGeneratingExpression); + visit(node); + if (!expressionStack.isEmpty) { + assert(expressionStack.length == 1); + pushExpressionAsStatement(pop()); + } + } + + void continueAsBreak(LabelElement target) { + pushStatement(new js.Break(backend.namer.continueLabelName(target))); + } + + void implicitContinueAsBreak(TargetElement target) { + pushStatement(new js.Break( + backend.namer.implicitContinueLabelName(target))); + } + + void implicitBreakWithLabel(TargetElement target) { + pushStatement(new js.Break(backend.namer.implicitBreakLabelName(target))); + } + + js.Statement wrapIntoLabels(js.Statement result, List<LabelElement> labels) { + for (LabelElement label in labels) { + if (label.isTarget) { + String breakLabelString = backend.namer.breakLabelName(label); + result = new js.LabeledStatement(breakLabelString, result); + } + } + return result; + } + + + // The regular [visitIf] method implements the needed logic. + bool visitIfInfo(HIfBlockInformation info) => false; + + bool visitSwitchInfo(HSwitchBlockInformation info) { + bool isExpression = isJSExpression(info.expression); + if (!isExpression) { + generateStatements(info.expression); + } + + if (isExpression) { + push(generateExpression(info.expression)); + } else { + use(info.expression.conditionExpression); + } + js.Expression key = pop(); + List<js.SwitchClause> cases = <js.SwitchClause>[]; + + js.Block oldContainer = currentContainer; + for (int i = 0; i < info.matchExpressions.length; i++) { + for (Constant constant in info.matchExpressions[i]) { + generateConstant(constant); + currentContainer = new js.Block.empty(); + cases.add(new js.Case(pop(), currentContainer)); + } + if (i == info.matchExpressions.length - 1 && info.hasDefault) { + currentContainer = new js.Block.empty(); + cases.add(new js.Default(currentContainer)); + } + generateStatements(info.statements[i]); + } + currentContainer = oldContainer; + + js.Statement result = new js.Switch(key, cases); + pushStatement(wrapIntoLabels(result, info.labels)); + return true; + } + + bool visitSequenceInfo(HStatementSequenceInformation info) { + return false; + } + + bool visitSubGraphInfo(HSubGraphBlockInformation info) { + visitSubGraph(info.subGraph); + return true; + } + + bool visitSubExpressionInfo(HSubExpressionBlockInformation info) { + return false; + } + + bool visitAndOrInfo(HAndOrBlockInformation info) { + return false; + } + + bool visitTryInfo(HTryBlockInformation info) { + js.Block body = generateStatementsInNewBlock(info.body); + js.Catch catchPart = null; + js.Block finallyPart = null; + if (info.catchBlock != null) { + HLocalValue exception = info.catchVariable; + String name = variableNames.getName(exception); + js.VariableDeclaration decl = new js.VariableDeclaration(name); + js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock); + catchPart = new js.Catch(decl, catchBlock); + } + if (info.finallyBlock != null) { + finallyPart = generateStatementsInNewBlock(info.finallyBlock); + } + pushStatement(new js.Try(body, catchPart, finallyPart)); + return true; + } + + void visitBodyIgnoreLabels(HLoopBlockInformation info) { + if (info.body.start.isLabeledBlock()) { + HBlockInformation oldInfo = currentBlockInformation; + currentBlockInformation = info.body.start.blockFlow.body; + generateStatements(info.body); + currentBlockInformation = oldInfo; + } else { + generateStatements(info.body); + } + } + + bool visitLoopInfo(HLoopBlockInformation info) { + HExpressionInformation condition = info.condition; + bool isConditionExpression = isJSCondition(condition); + + js.Loop loop; + + switch (info.kind) { + // Treate all three "test-first" loops the same way. + case HLoopBlockInformation.FOR_LOOP: + case HLoopBlockInformation.WHILE_LOOP: + case HLoopBlockInformation.FOR_IN_LOOP: + HBlockInformation initialization = info.initializer; + int initializationType = TYPE_STATEMENT; + if (initialization != null) { + initializationType = expressionType(initialization); + if (initializationType == TYPE_STATEMENT) { + generateStatements(initialization); + initialization = null; + } + } + if (isConditionExpression && + info.updates != null && isJSExpression(info.updates)) { + // If we have an updates graph, and it's expressible as an + // expression, generate a for-loop. + js.Expression jsInitialization = null; + if (initialization != null) { + int delayedVariablesCount = collectedVariableDeclarations.length; + jsInitialization = generateExpression(initialization); + if (!shouldGroupVarDeclarations && + delayedVariablesCount < collectedVariableDeclarations.length) { + // We just added a new delayed variable-declaration. See if we + // can put in a 'var' in front of the initialization to make it + // go away. + List<js.Expression> expressions; + if (jsInitialization is js.Sequence) { + expressions = jsInitialization.expressions; + } else { + expressions = <js.Expression>[jsInitialization]; + } + bool canTransformToVariableDeclaration = true; + for (js.Expression expression in expressions) { + bool expressionIsVariableAssignment = false; + if (expression is js.Assignment) { + js.Assignment assignment = expression; + if (assignment.leftHandSide is js.VariableUse && + assignment.compoundTarget == null) { + expressionIsVariableAssignment = true; + } + } + if (!expressionIsVariableAssignment) { + canTransformToVariableDeclaration = false; + break; + } + } + if (canTransformToVariableDeclaration) { + List<js.VariableInitialization> inits = + <js.VariableInitialization>[]; + for (js.Assignment assignment in expressions) { + String id = (assignment.leftHandSide as js.VariableUse).name; + js.Node declaration = new js.VariableDeclaration(id); + inits.add(new js.VariableInitialization(declaration, + assignment.value)); + collectedVariableDeclarations.remove(id); + } + jsInitialization = new js.VariableDeclarationList(inits); + } + } + } + js.Expression jsCondition = generateExpression(condition); + js.Expression jsUpdates = generateExpression(info.updates); + // The body might be labeled. Ignore this when recursing on the + // subgraph. + // TODO(lrn): Remove this extra labeling when handling all loops + // using subgraphs. + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + currentContainer = body; + visitBodyIgnoreLabels(info); + currentContainer = oldContainer; + body = unwrapStatement(body); + loop = new js.For(jsInitialization, jsCondition, jsUpdates, body); + } else { + // We have either no update graph, or it's too complex to + // put in an expression. + if (initialization != null) { + generateStatements(initialization); + } + js.Expression jsCondition; + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + if (isConditionExpression) { + jsCondition = generateExpression(condition); + currentContainer = body; + } else { + jsCondition = newLiteralBool(true); + currentContainer = body; + generateStatements(condition); + use(condition.conditionExpression); + js.Expression ifTest = new js.Prefix("!", pop()); + js.Break jsBreak = new js.Break(null); + pushStatement(new js.If.noElse(ifTest, jsBreak)); + } + if (info.updates != null) { + wrapLoopBodyForContinue(info); + generateStatements(info.updates); + } else { + visitBodyIgnoreLabels(info); + } + currentContainer = oldContainer; + body = unwrapStatement(body); + loop = new js.While(jsCondition, body); + } + break; + case HLoopBlockInformation.DO_WHILE_LOOP: + if (info.initializer != null) { + generateStatements(info.initializer); + } + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + // If there are phi copies in the block that jumps to the + // loop entry, we must emit the condition like this: + // do { + // body; + // if (condition) { + // phi updates; + // continue; + // } else { + // break; + // } + // } while (true); + HBasicBlock avoidEdge = info.end.successors[0]; + js.Block updateBody = new js.Block.empty(); + currentContainer = updateBody; + assignPhisOfSuccessors(avoidEdge); + bool hasPhiUpdates = !updateBody.statements.isEmpty; + currentContainer = body; + visitBodyIgnoreLabels(info); + if (info.updates != null) { + generateStatements(info.updates); + } + if (isConditionExpression) { + push(generateExpression(condition)); + } else { + generateStatements(condition); + use(condition.conditionExpression); + } + js.Expression jsCondition = pop(); + if (hasPhiUpdates) { + updateBody.statements.add(new js.Continue(null)); + body.statements.add( + new js.If(jsCondition, updateBody, new js.Break(null))); + jsCondition = newLiteralBool(true); + } + loop = new js.Do(unwrapStatement(body), jsCondition); + currentContainer = oldContainer; + break; + default: + compiler.internalError( + 'Unexpected loop kind: ${info.kind}', + instruction: condition.conditionExpression); + } + attachLocationRange(loop, info.sourcePosition, info.endSourcePosition); + pushStatement(wrapIntoLabels(loop, info.labels)); + return true; + } + + bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) { + preLabeledBlock(labeledBlockInfo); + Link<Element> continueOverrides = const Link<Element>(); + + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + js.Statement result = body; + + currentContainer = body; + + // If [labeledBlockInfo.isContinue], the block is an artificial + // block around the body of a loop with an update block, so that + // continues of the loop can be written as breaks of the body + // block. + if (labeledBlockInfo.isContinue) { + for (LabelElement label in labeledBlockInfo.labels) { + if (label.isContinueTarget) { + String labelName = backend.namer.continueLabelName(label); + result = new js.LabeledStatement(labelName, result); + continueAction[label] = continueAsBreak; + continueOverrides = continueOverrides.prepend(label); + } + } + // For handling unlabeled continues from the body of a loop. + // TODO(lrn): Consider recording whether the target is in fact + // a target of an unlabeled continue, and not generate this if it isn't. + TargetElement target = labeledBlockInfo.target; + String labelName = backend.namer.implicitContinueLabelName(target); + result = new js.LabeledStatement(labelName, result); + continueAction[target] = implicitContinueAsBreak; + continueOverrides = continueOverrides.prepend(target); + } else { + for (LabelElement label in labeledBlockInfo.labels) { + if (label.isBreakTarget) { + String labelName = backend.namer.breakLabelName(label); + result = new js.LabeledStatement(labelName, result); + } + } + TargetElement target = labeledBlockInfo.target; + if (target.isSwitch) { + // This is an extra block around a switch that is generated + // as a nested if/else chain. We add an extra break target + // so that case code can break. + String labelName = backend.namer.implicitBreakLabelName(target); + result = new js.LabeledStatement(labelName, result); + breakAction[target] = implicitBreakWithLabel; + } + } + + currentContainer = body; + startLabeledBlock(labeledBlockInfo); + generateStatements(labeledBlockInfo.body); + endLabeledBlock(labeledBlockInfo); + + if (labeledBlockInfo.isContinue) { + while (!continueOverrides.isEmpty) { + continueAction.remove(continueOverrides.head); + continueOverrides = continueOverrides.tail; + } + } else { + breakAction.remove(labeledBlockInfo.target); + } + + currentContainer = oldContainer; + pushStatement(result); + return true; + } + + // Wraps a loop body in a block to make continues have a target to break + // to (if necessary). + void wrapLoopBodyForContinue(HLoopBlockInformation info) { + TargetElement target = info.target; + if (target != null && target.isContinueTarget) { + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + currentContainer = body; + js.Statement result = body; + for (LabelElement label in info.labels) { + if (label.isContinueTarget) { + String labelName = backend.namer.continueLabelName(label); + result = new js.LabeledStatement(labelName, result); + continueAction[label] = continueAsBreak; + } + } + String labelName = backend.namer.implicitContinueLabelName(target); + result = new js.LabeledStatement(labelName, result); + continueAction[info.target] = implicitContinueAsBreak; + visitBodyIgnoreLabels(info); + continueAction.remove(info.target); + for (LabelElement label in info.labels) { + if (label.isContinueTarget) { + continueAction.remove(label); + } + } + currentContainer = oldContainer; + pushStatement(result); + } else { + // Loop body contains no continues, so we don't need a break target. + generateStatements(info.body); + } + } + + bool handleBlockFlow(HBlockFlow block) { + HBlockInformation info = block.body; + // If we reach here again while handling the attached information, + // e.g., because we call visitSubGraph on a subgraph starting on + // the same block, don't handle it again. + // When the structure graph is complete, we will be able to have + // different structures starting on the same basic block (e.g., an + // "if" and its condition). + if (identical(info, currentBlockInformation)) return false; + + HBlockInformation oldBlockInformation = currentBlockInformation; + currentBlockInformation = info; + bool success = info.accept(this); + currentBlockInformation = oldBlockInformation; + if (success) { + HBasicBlock continuation = block.continuation; + if (continuation != null) { + visitBasicBlock(continuation); + } + } + return success; + } + + void visitBasicBlock(HBasicBlock node) { + // Abort traversal if we are leaving the currently active sub-graph. + if (!subGraph.contains(node)) return; + + // If this node has block-structure based information attached, + // try using that to traverse from here. + if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) { + return; + } + iterateBasicBlock(node); + } + + void emitAssignment(String destination, String source) { + assignVariable(destination, new js.VariableUse(source)); + } + + /** + * Sequentialize a list of conceptually parallel copies. Parallel + * copies may contain cycles, that this method breaks. + */ + void sequentializeCopies(Iterable<Copy> copies, + String tempName, + void doAssignment(String target, String source)) { + // Map to keep track of the current location (ie the variable that + // holds the initial value) of a variable. + Map<String, String> currentLocation = new Map<String, String>(); + + // Map to keep track of the initial value of a variable. + Map<String, String> initialValue = new Map<String, String>(); + + // List of variables to assign a value. + List<String> worklist = <String>[]; + + // List of variables that we can assign a value to (ie are not + // being used anymore). + List<String> ready = <String>[]; + + // Prune [copies] by removing self-copies. + List<Copy> prunedCopies = <Copy>[]; + for (Copy copy in copies) { + if (copy.source != copy.destination) { + prunedCopies.add(copy); + } + } + copies = prunedCopies; + + + // For each copy, set the current location of the source to + // itself, and the initial value of the destination to the source. + // Add the destination to the list of copies to make. + for (Copy copy in copies) { + currentLocation[copy.source] = copy.source; + initialValue[copy.destination] = copy.source; + worklist.add(copy.destination); + } + + // For each copy, if the destination does not have a current + // location, then we can safely assign to it. + for (Copy copy in copies) { + if (currentLocation[copy.destination] == null) { + ready.add(copy.destination); + } + } + + while (!worklist.isEmpty) { + while (!ready.isEmpty) { + String destination = ready.removeLast(); + String source = initialValue[destination]; + // Since [source] might have been updated, use the current + // location of [source] + String copy = currentLocation[source]; + doAssignment(destination, copy); + // Now [destination] is the current location of [source]. + currentLocation[source] = destination; + // If [source] hasn't been updated and needs to have a value, + // add it to the list of variables that can be updated. Copies + // of [source] will now use [destination]. + if (source == copy && initialValue[source] != null) { + ready.add(source); + } + } + + // Check if we have a cycle. + String current = worklist.removeLast(); + // If [current] is used as a source, and the assignment has been + // done, we are done with this variable. Otherwise there is a + // cycle that we break by using a temporary name. + if (currentLocation[current] != null + && current != currentLocation[initialValue[current]]) { + doAssignment(tempName, current); + currentLocation[current] = tempName; + // [current] can now be safely updated. Copies of [current] + // will now use [tempName]. + ready.add(current); + } + } + } + + void assignPhisOfSuccessors(HBasicBlock node) { + CopyHandler handler = variableNames.getCopyHandler(node); + if (handler == null) return; + + // Map the instructions to strings. + Iterable<Copy> copies = handler.copies.map((Copy copy) { + return new Copy(variableNames.getName(copy.source), + variableNames.getName(copy.destination)); + }); + + sequentializeCopies(copies, variableNames.getSwapTemp(), emitAssignment); + + for (Copy copy in handler.assignments) { + String name = variableNames.getName(copy.destination); + use(copy.source); + assignVariable(name, pop()); + } + } + + void iterateBasicBlock(HBasicBlock node) { + HInstruction instruction = node.first; + while (!identical(instruction, node.last)) { + if (instruction is HTypeGuard || instruction is HBailoutTarget) { + visit(instruction); + } else if (!isGenerateAtUseSite(instruction)) { + define(instruction); + } + instruction = instruction.next; + } + assignPhisOfSuccessors(node); + visit(instruction); + } + + visitInvokeBinary(HInvokeBinary node, String op) { + use(node.left); + js.Expression jsLeft = pop(); + use(node.right); + push(new js.Binary(op, jsLeft, pop()), node); + } + + visitRelational(HRelational node, String op) => visitInvokeBinary(node, op); + + // We want the outcome of bit-operations to be positive. We use the unsigned + // shift operator to achieve this. + visitBitInvokeBinary(HBinaryBitOp node, String op) { + visitInvokeBinary(node, op); + if (requiresUintConversion(node)) { + push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node); + } + } + + visitInvokeUnary(HInvokeUnary node, String op) { + use(node.operand); + push(new js.Prefix(op, pop()), node); + } + + // We want the outcome of bit-operations to be positive. We use the unsigned + // shift operator to achieve this. + visitBitInvokeUnary(HInvokeUnary node, String op) { + visitInvokeUnary(node, op); + if (requiresUintConversion(node)) { + push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node); + } + } + + void emitIdentityComparison(HInstruction left, HInstruction right) { + String op = singleIdentityComparison(left, right, types); + if (op != null) { + use(left); + js.Expression jsLeft = pop(); + use(right); + push(new js.Binary(op, jsLeft, pop())); + } else { + assert(NullConstant.JsNull == 'null'); + use(left); + js.Binary leftEqualsNull = + new js.Binary("==", pop(), new js.LiteralNull()); + use(right); + js.Binary rightEqualsNull = + new js.Binary("==", pop(), new js.LiteralNull()); + use(right); + use(left); + js.Binary tripleEq = new js.Binary("===", pop(), pop()); + + push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq)); + } + } + + visitIdentity(HIdentity node) { + emitIdentityComparison(node.left, node.right); + } + + visitAdd(HAdd node) => visitInvokeBinary(node, '+'); + visitDivide(HDivide node) => visitInvokeBinary(node, '/'); + visitMultiply(HMultiply node) => visitInvokeBinary(node, '*'); + visitSubtract(HSubtract node) => visitInvokeBinary(node, '-'); + visitBitAnd(HBitAnd node) => visitBitInvokeBinary(node, '&'); + visitBitNot(HBitNot node) => visitBitInvokeUnary(node, '~'); + visitBitOr(HBitOr node) => visitBitInvokeBinary(node, '|'); + visitBitXor(HBitXor node) => visitBitInvokeBinary(node, '^'); + visitShiftLeft(HShiftLeft node) => visitBitInvokeBinary(node, '<<'); + + visitNegate(HNegate node) => visitInvokeUnary(node, '-'); + + visitLess(HLess node) => visitRelational(node, '<'); + visitLessEqual(HLessEqual node) => visitRelational(node, '<='); + visitGreater(HGreater node) => visitRelational(node, '>'); + visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>='); + + visitBoolify(HBoolify node) { + assert(node.inputs.length == 1); + use(node.inputs[0]); + push(new js.Binary('===', pop(), newLiteralBool(true)), node); + } + + visitExit(HExit node) { + // Don't do anything. + } + + visitGoto(HGoto node) { + HBasicBlock block = node.block; + assert(block.successors.length == 1); + List<HBasicBlock> dominated = block.dominatedBlocks; + // With the exception of the entry-node which dominates its successor + // and the exit node, no block finishing with a 'goto' can have more than + // one dominated block (since it has only one successor). + // If the successor is dominated by another block, then the other block + // is responsible for visiting the successor. + if (dominated.isEmpty) return; + if (dominated.length > 2) { + compiler.internalError('dominated.length = ${dominated.length}', + instruction: node); + } + if (dominated.length == 2 && block != currentGraph.entry) { + compiler.internalError('node.block != currentGraph.entry', + instruction: node); + } + assert(dominated[0] == block.successors[0]); + visitBasicBlock(dominated[0]); + } + + visitLoopBranch(HLoopBranch node) { + assert(node.block == subGraph.end); + // We are generating code for a loop condition. + // If we are generating the subgraph as an expression, the + // condition will be generated as the expression. + // Otherwise, we don't generate the expression, and leave that + // to the code that called [visitSubGraph]. + if (isGeneratingExpression) { + use(node.inputs[0]); + } + } + + /** + * Checks if [map] contains an [ElementAction] for [element], and + * if so calls that action and returns true. + * Otherwise returns false. + */ + bool tryCallAction(Map<Element, ElementAction> map, Element element) { + ElementAction action = map[element]; + if (action == null) return false; + action(element); + return true; + } + + visitBreak(HBreak node) { + assert(node.block.successors.length == 1); + if (node.label != null) { + LabelElement label = node.label; + if (!tryCallAction(breakAction, label)) { + pushStatement(new js.Break(backend.namer.breakLabelName(label)), node); + } + } else { + TargetElement target = node.target; + if (!tryCallAction(breakAction, target)) { + pushStatement(new js.Break(null), node); + } + } + } + + visitContinue(HContinue node) { + assert(node.block.successors.length == 1); + if (node.label != null) { + LabelElement label = node.label; + if (!tryCallAction(continueAction, label)) { + // TODO(floitsch): should this really be the breakLabelName? + pushStatement(new js.Continue(backend.namer.breakLabelName(label)), + node); + } + } else { + TargetElement target = node.target; + if (!tryCallAction(continueAction, target)) { + pushStatement(new js.Continue(null), node); + } + } + } + + visitExitTry(HExitTry node) { + // An [HExitTry] is used to represent the control flow graph of a + // try/catch block, ie the try body is always a predecessor + // of the catch and finally. Here, we continue visiting the try + // body by visiting the block that contains the user-level control + // flow instruction. + visitBasicBlock(node.bodyTrySuccessor); + } + + visitTry(HTry node) { + // We should never get here. Try/catch/finally is always handled using block + // information in [visitTryInfo], or not at all, in the case of the bailout + // generator. + compiler.internalError('visitTry should not be called', instruction: node); + } + + bool tryControlFlowOperation(HIf node) { + if (!controlFlowOperators.contains(node)) return false; + HPhi phi = node.joinBlock.phis.first; + bool atUseSite = isGenerateAtUseSite(phi); + // Don't generate a conditional operator in this situation: + // i = condition ? bar() : i; + // But generate this instead: + // if (condition) i = bar(); + // Usually, the variable name is longer than 'if' and it takes up + // more space to duplicate the name. + if (!atUseSite + && variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) { + return false; + } + if (!atUseSite) define(phi); + visitBasicBlock(node.joinBlock); + return true; + } + + void generateIf(HIf node, HIfBlockInformation info) { + use(node.inputs[0]); + js.Expression test = pop(); + + HStatementInformation thenGraph = info.thenGraph; + HStatementInformation elseGraph = info.elseGraph; + js.Statement thenPart = + unwrapStatement(generateStatementsInNewBlock(thenGraph)); + js.Statement elsePart = + unwrapStatement(generateStatementsInNewBlock(elseGraph)); + + pushStatement(new js.If(test, thenPart, elsePart), node); + } + + visitIf(HIf node) { + if (tryControlFlowOperation(node)) return; + + HInstruction condition = node.inputs[0]; + HIfBlockInformation info = node.blockInformation.body; + + if (condition.isConstant()) { + HConstant constant = condition; + if (constant.constant.isTrue()) { + generateStatements(info.thenGraph); + } else { + generateStatements(info.elseGraph); + } + } else { + generateIf(node, info); + } + + HBasicBlock joinBlock = node.joinBlock; + if (joinBlock != null && !identical(joinBlock.dominator, node.block)) { + // The join block is dominated by a block in one of the branches. + // The subgraph traversal never reached it, so we visit it here + // instead. + visitBasicBlock(joinBlock); + } + + // Visit all the dominated blocks that are not part of the then or else + // branches, and is not the join block. + // Depending on how the then/else branches terminate + // (e.g., return/throw/break) there can be any number of these. + List<HBasicBlock> dominated = node.block.dominatedBlocks; + for (int i = 2; i < dominated.length; i++) { + visitBasicBlock(dominated[i]); + } + } + + js.Call jsPropertyCall(js.Expression receiver, + String fieldName, + List<js.Expression> arguments) { + return new js.Call(new js.PropertyAccess.field(receiver, fieldName), + arguments); + } + + void visitInterceptor(HInterceptor node) { + backend.registerSpecializedGetInterceptor(node.interceptedClasses); + String name = backend.namer.getInterceptorName( + backend.getInterceptorMethod, node.interceptedClasses); + var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE); + use(node.receiver); + List<js.Expression> arguments = <js.Expression>[pop()]; + push(jsPropertyCall(isolate, name, arguments), node); + } + + visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + use(node.receiver); + js.Expression object = pop(); + SourceString name = node.selector.name; + String methodName; + List<js.Expression> arguments = visitArguments(node.inputs); + Element target = node.element; + + if (target != null) { + // Avoid adding the generative constructor name to the list of + // seen selectors. + if (target.isGenerativeConstructorBody()) { + methodName = name.slowToString(); + } else if (!node.isInterceptorCall) { + if (target == backend.jsArrayAdd) { + methodName = 'push'; + } else if (target == backend.jsArrayRemoveLast) { + methodName = 'pop'; + } else if (target == backend.jsStringSplit) { + methodName = 'split'; + // Split returns a List, so we make sure the backend knows the + // list class is instantiated. + world.registerInstantiatedClass(compiler.listClass); + } else if (target == backend.jsStringConcat) { + push(new js.Binary('+', object, arguments[0]), node); + return; + } + } + } + + if (methodName == null) { + methodName = backend.namer.invocationName(node.selector); + registerMethodInvoke(node); + } + push(jsPropertyCall(object, methodName, arguments), node); + } + + void visitOneShotInterceptor(HOneShotInterceptor node) { + List<js.Expression> arguments = visitArguments(node.inputs); + var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE); + Selector selector = node.selector; + String methodName = backend.namer.oneShotInterceptorName(selector); + push(jsPropertyCall(isolate, methodName, arguments), node); + backend.registerSpecializedGetInterceptor(node.interceptedClasses); + backend.addOneShotInterceptor(selector); + if (selector.isGetter()) { + registerGetter(node); + } else if (selector.isSetter()) { + registerSetter(node); + } else { + registerMethodInvoke(node); + } + } + + Selector getOptimizedSelectorFor(HInvokeDynamic node, + Selector defaultSelector) { + // If [JSInvocationMirror.invokeOn] has been called, we must not create a + // typed selector based on the receiver type. + if (node.element == null && // Invocation is not exact. + backend.compiler.enabledInvokeOn) { + return defaultSelector; + } + int receiverIndex = node.isInterceptorCall ? 1 : 0; + HType receiverHType = types[node.inputs[receiverIndex]]; + DartType receiverType = receiverHType.computeType(compiler); + if (receiverType != null && + !identical(receiverType.kind, TypeKind.MALFORMED_TYPE)) { + return new TypedSelector(receiverType, defaultSelector); + } else { + return defaultSelector; + } + } + + void registerInvoke(HInvokeDynamic node) { + bool inLoop = node.block.enclosingLoopHeader != null; + SourceString name = node.selector.name; + if (inLoop) { + Element target = node.element; + if (target != null) { + backend.builder.functionsCalledInLoop.add(target); + } else { + backend.builder.selectorsCalledInLoop[name] = node.selector; + } + } + + if (node.isInterceptorCall) { + backend.addInterceptedSelector(node.selector); + } + } + + void registerMethodInvoke(HInvokeDynamic node) { + Selector selector = getOptimizedSelectorFor(node, node.selector); + // Register this invocation to collect the types used at all call sites. + backend.registerDynamicInvocation(node, selector, types); + + // If we don't know what we're calling or if we are calling a getter, + // we need to register that fact that we may be calling a closure + // with the same arguments. + Element target = node.element; + if (target == null || target.isGetter()) { + // TODO(kasperl): If we have a typed selector for the call, we + // may know something about the types of closures that need + // the specific closure call method. + Selector call = new Selector.callClosureFrom(selector); + world.registerDynamicInvocation(call.name, call); + } + + if (target != null) { + // If we know we're calling a specific method, register that + // method only. + world.registerDynamicInvocationOf(target, selector); + } else { + SourceString name = node.selector.name; + world.registerDynamicInvocation(name, selector); + } + registerInvoke(node); + } + + void registerSetter(HInvokeDynamic node) { + Selector selector = getOptimizedSelectorFor(node, node.selector); + world.registerDynamicSetter(selector.name, selector); + HType valueType = node.isInterceptorCall + ? types[node.inputs[2]] + : types[node.inputs[1]]; + backend.addedDynamicSetter(selector, valueType); + registerInvoke(node); + } + + void registerGetter(HInvokeDynamic node) { + Selector getter = node.selector; + world.registerDynamicGetter( + getter.name, getOptimizedSelectorFor(node, getter)); + world.registerInstantiatedClass(compiler.functionClass); + registerInvoke(node); + } + + visitInvokeDynamicSetter(HInvokeDynamicSetter node) { + use(node.receiver); + Selector setter = node.selector; + String name = backend.namer.invocationName(setter); + push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node); + registerSetter(node); + } + + visitInvokeDynamicGetter(HInvokeDynamicGetter node) { + use(node.receiver); + Selector getter = node.selector; + String name = backend.namer.invocationName(getter); + push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node); + registerGetter(node); + } + + visitInvokeClosure(HInvokeClosure node) { + Selector call = new Selector.callClosureFrom(node.selector); + use(node.receiver); + push(jsPropertyCall(pop(), + backend.namer.invocationName(call), + visitArguments(node.inputs)), + node); + world.registerDynamicInvocation(call.name, call); + } + + visitInvokeStatic(HInvokeStatic node) { + if (node.typeCode() == HInstruction.INVOKE_STATIC_TYPECODE) { + // Register this invocation to collect the types used at all call sites. + backend.registerStaticInvocation(node, types); + } + use(node.target); + push(new js.Call(pop(), visitArguments(node.inputs)), node); + } + + visitInvokeSuper(HInvokeSuper node) { + Element superMethod = node.element; + Element superClass = superMethod.getEnclosingClass(); + if (superMethod.kind == ElementKind.FIELD) { + ClassElement currentClass = work.element.getEnclosingClass(); + if (currentClass.isClosure()) { + ClosureClassElement closure = currentClass; + currentClass = closure.methodElement.getEnclosingClass(); + } + String fieldName = currentClass.isShadowedByField(superMethod) + ? backend.namer.shadowedFieldName(superMethod) + : backend.namer.instanceFieldName(superMethod); + use(node.inputs[1]); + js.PropertyAccess access = + new js.PropertyAccess.field(pop(), fieldName); + if (node.isSetter) { + use(node.value); + push(new js.Assignment(access, pop()), node); + } else { + push(access, node); + } + } else { + String methodName = backend.namer.getName(superMethod); + String className = backend.namer.isolateAccess(superClass); + js.VariableUse classReference = new js.VariableUse(className); + js.PropertyAccess prototype = + new js.PropertyAccess.field(classReference, "prototype"); + js.PropertyAccess method = + new js.PropertyAccess.field(prototype, methodName); + push(jsPropertyCall(method, "call", visitArguments(node.inputs)), node); + } + world.registerStaticUse(superMethod); + } + + visitFieldGet(HFieldGet node) { + use(node.receiver); + if (node.element == backend.jsArrayLength + || node.element == backend.jsStringLength) { + // We're accessing a native JavaScript property called 'length' + // on a JS String or a JS array. Therefore, the name of that + // property should not be mangled. + push(new js.PropertyAccess.field(pop(), 'length'), node); + } else { + String name = _fieldPropertyName(node.element); + push(new js.PropertyAccess.field(pop(), name), node); + HType receiverHType = types[node.receiver]; + DartType type = receiverHType.computeType(compiler); + if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) { + world.registerFieldGetter( + node.element.name, node.element.getLibrary(), type); + } + } + } + + visitFieldSet(HFieldSet node) { + String name = _fieldPropertyName(node.element); + DartType type = types[node.receiver].computeType(compiler); + if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) { + // Field setters in the generative constructor body are handled in a + // step "SsaConstructionFieldTypes" in the ssa optimizer. + if (!work.element.isGenerativeConstructorBody()) { + world.registerFieldSetter( + node.element.name, node.element.getLibrary(), type); + backend.registerFieldSetter( + work.element, node.element, types[node.value]); + } + } + use(node.receiver); + js.Expression receiver = pop(); + use(node.value); + push(new js.Assignment(new js.PropertyAccess.field(receiver, name), pop()), + node); + } + + String _fieldPropertyName(Element element) => element.hasFixedBackendName() + ? element.fixedBackendName() + : backend.namer.getName(element); + + visitLocalGet(HLocalGet node) { + use(node.receiver); + } + + visitLocalSet(HLocalSet node) { + use(node.value); + assignVariable(variableNames.getName(node.receiver), pop()); + } + + void registerForeignType(HType type) { + DartType dartType = type.computeType(compiler); + if (dartType == null) { + assert(type == HType.UNKNOWN); + return; + } + world.registerInstantiatedClass(dartType.element); + } + + visitForeign(HForeign node) { + String code = node.code.slowToString(); + List<HInstruction> inputs = node.inputs; + if (node.isJsStatement()) { + if (!inputs.isEmpty) { + compiler.internalError("foreign statement with inputs: $code", + instruction: node); + } + pushStatement(new js.LiteralStatement(code), node); + } else { + List<js.Expression> data = <js.Expression>[]; + for (int i = 0; i < inputs.length; i++) { + use(inputs[i]); + data.add(pop()); + } + push(new js.LiteralExpression.withData(code, data), node); + } + registerForeignType(types[node]); + // TODO(sra): Tell world.nativeEnqueuer about the types created here. + } + + visitForeignNew(HForeignNew node) { + String jsClassReference = backend.namer.isolateAccess(node.element); + List<HInstruction> inputs = node.inputs; + // We can't use 'visitArguments', since our arguments start at input[0]. + List<js.Expression> arguments = <js.Expression>[]; + for (int i = 0; i < inputs.length; i++) { + use(inputs[i]); + arguments.add(pop()); + } + // TODO(floitsch): jsClassReference is an Access. We shouldn't treat it + // as if it was a string. + push(new js.New(new js.VariableUse(jsClassReference), arguments), node); + registerForeignType(types[node]); + } + + js.Expression newLiteralBool(bool value) { + if (compiler.enableMinification) { + // Use !0 for true, !1 for false. + return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1")); + } else { + return new js.LiteralBool(value); + } + } + + void generateConstant(Constant constant) { + if (constant.isFunction()) { + FunctionConstant function = constant; + world.registerStaticUse(function.element); + } + push(backend.emitter.constantReference(constant)); + } + + visitConstant(HConstant node) { + assert(isGenerateAtUseSite(node)); + generateConstant(node.constant); + DartType type = node.constant.computeType(compiler); + if (node.constant is ConstructedConstant) { + ConstantHandler handler = compiler.constantHandler; + handler.registerCompileTimeConstant(node.constant); + } + world.registerInstantiatedClass(type.element); + } + + visitNot(HNot node) { + assert(node.inputs.length == 1); + generateNot(node.inputs[0]); + attachLocationToLast(node); + } + + void generateNot(HInstruction input) { + bool canGenerateOptimizedComparison(HInstruction instruction) { + if (instruction is !HRelational) return false; + HRelational relational = instruction; + HInstruction left = relational.left; + HInstruction right = relational.right; + // This optimization doesn't work for NaN, so we only do it if the + // type is known to be an integer. + return types[left].isUseful() && left.isInteger(types) + && types[right].isUseful() && right.isInteger(types); + } + + if (input is HBoolify && isGenerateAtUseSite(input)) { + use(input.inputs[0]); + push(new js.Binary("!==", pop(), newLiteralBool(true)), input); + } else if (canGenerateOptimizedComparison(input) && + isGenerateAtUseSite(input)) { + Map<String, String> inverseOperator = const <String, String>{ + "==" : "!=", + "!=" : "==", + "===": "!==", + "!==": "===", + "<" : ">=", + "<=" : ">", + ">" : "<=", + ">=" : "<" + }; + HRelational relational = input; + BinaryOperation operation = relational.operation(backend.constantSystem); + visitRelational(input, inverseOperator[operation.name.stringValue]); + } else { + use(input); + push(new js.Prefix("!", pop())); + } + } + + visitParameterValue(HParameterValue node) { + assert(!isGenerateAtUseSite(node)); + String name = variableNames.getName(node); + parameters.add(new js.Parameter(name)); + declaredLocals.add(name); + } + + visitLocalValue(HLocalValue node) { + assert(!isGenerateAtUseSite(node)); + String name = variableNames.getName(node); + collectedVariableDeclarations.add(name); + } + + visitPhi(HPhi node) { + // This method is only called for phis that are generated at use + // site. A phi can be generated at use site only if it is the + // result of a control flow operation. + HBasicBlock ifBlock = node.block.dominator; + assert(controlFlowOperators.contains(ifBlock.last)); + HInstruction input = ifBlock.last.inputs[0]; + if (input.isConstantFalse()) { + use(node.inputs[1]); + } else if (input.isConstantTrue()) { + use(node.inputs[0]); + } else if (node.inputs[1].isConstantBoolean()) { + String operation = node.inputs[1].isConstantFalse() ? '&&' : '||'; + if (operation == '||') { + if (input is HNot) { + use(input.inputs[0]); + } else { + generateNot(input); + } + } else { + use(input); + } + js.Expression left = pop(); + use(node.inputs[0]); + push(new js.Binary(operation, left, pop())); + } else { + use(input); + js.Expression test = pop(); + use(node.inputs[0]); + js.Expression then = pop(); + use(node.inputs[1]); + push(new js.Conditional(test, then, pop())); + } + } + + visitReturn(HReturn node) { + assert(node.inputs.length == 1); + HInstruction input = node.inputs[0]; + if (input.isConstantNull()) { + pushStatement(new js.Return(null), node); + } else { + use(node.inputs[0]); + pushStatement(new js.Return(pop()), node); + } + } + + visitThis(HThis node) { + push(new js.This()); + } + + visitThrow(HThrow node) { + if (node.isRethrow) { + use(node.inputs[0]); + pushStatement(new js.Throw(pop()), node); + } else { + generateThrowWithHelper(r'$throw', node.inputs[0]); + } + } + + visitRangeConversion(HRangeConversion node) { + // Range conversion instructions are removed by the value range + // analyzer. + assert(false); + } + + visitBoundsCheck(HBoundsCheck node) { + // TODO(ngeoffray): Separate the two checks of the bounds check, so, + // e.g., the zero checks can be shared if possible. + + // If the checks always succeeds, we would have removed the bounds check + // completely. + assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE); + if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) { + js.Expression under; + js.Expression over; + if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) { + use(node.index); + under = new js.Binary("<", pop(), new js.LiteralNumber("0")); + } + if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) { + var index = node.index; + use(index); + js.Expression jsIndex = pop(); + use(node.length); + over = new js.Binary(">=", jsIndex, pop()); + } + assert(over != null || under != null); + js.Expression underOver = under == null + ? over + : over == null + ? under + : new js.Binary("||", under, over); + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + generateThrowWithHelper('ioore', node.index); + currentContainer = oldContainer; + thenBody = unwrapStatement(thenBody); + pushStatement(new js.If.noElse(underOver, thenBody), node); + } else { + generateThrowWithHelper('ioore', node.index); + } + } + + visitIntegerCheck(HIntegerCheck node) { + if (!node.alwaysFalse) { + checkInt(node.value, '!=='); + js.Expression test = pop(); + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + generateThrowWithHelper('iae', node.value); + currentContainer = oldContainer; + thenBody = unwrapStatement(thenBody); + pushStatement(new js.If.noElse(test, thenBody), node); + } else { + generateThrowWithHelper('iae', node.value); + } + } + + void generateThrowWithHelper(String helperName, HInstruction argument) { + Element helper = compiler.findHelper(new SourceString(helperName)); + world.registerStaticUse(helper); + js.VariableUse jsHelper = + new js.VariableUse(backend.namer.isolateAccess(helper)); + js.Call value = new js.Call(jsHelper, visitArguments([null, argument])); + attachLocation(value, argument); + // BUG(4906): Using throw here adds to the size of the generated code + // but it has the advantage of explicitly telling the JS engine that + // this code path will terminate abruptly. Needs more work. + pushStatement(new js.Throw(value)); + } + + void visitSwitch(HSwitch node) { + // Switches are handled using [visitSwitchInfo]. + } + + void visitStatic(HStatic node) { + // Check whether this static is used for anything else than as a target in + // a static call. + node.usedBy.forEach((HInstruction instr) { + if (instr is !HInvokeStatic) { + backend.registerNonCallStaticUse(node); + if (node.element.isFunction()) { + world.registerInstantiatedClass(compiler.functionClass); + } + } else if (instr.target != node) { + backend.registerNonCallStaticUse(node); + } + }); + Element element = node.element; + world.registerStaticUse(element); + ClassElement cls = element.getEnclosingClass(); + if (element.isGenerativeConstructor() + || (element.isFactoryConstructor() && cls == compiler.listClass)) { + world.registerInstantiatedClass(cls); + } + push(new js.VariableUse(backend.namer.isolateAccess(node.element))); + } + + void visitLazyStatic(HLazyStatic node) { + Element element = node.element; + world.registerStaticUse(element); + String lazyGetter = backend.namer.isolateLazyInitializerAccess(element); + js.VariableUse target = new js.VariableUse(lazyGetter); + js.Call call = new js.Call(target, <js.Expression>[]); + push(call, node); + } + + void visitStaticStore(HStaticStore node) { + world.registerStaticUse(node.element); + js.VariableUse variableUse = + new js.VariableUse(backend.namer.isolateAccess(node.element)); + use(node.inputs[0]); + push(new js.Assignment(variableUse, pop()), node); + } + + void visitStringConcat(HStringConcat node) { + if (isEmptyString(node.left)) { + useStringified(node.right); + } else if (isEmptyString(node.right)) { + useStringified(node.left); + } else { + useStringified(node.left); + js.Expression left = pop(); + useStringified(node.right); + push(new js.Binary("+", left, pop()), node); + } + } + + bool isEmptyString(HInstruction node) { + if (!node.isConstantString()) return false; + HConstant constant = node; + StringConstant string = constant.constant; + return string.value.length == 0; + } + + void useStringified(HInstruction node) { + if (node.isString(types)) { + use(node); + } else { + Element convertToString = compiler.findHelper(const SourceString("S")); + world.registerStaticUse(convertToString); + js.VariableUse variableUse = + new js.VariableUse(backend.namer.isolateAccess(convertToString)); + use(node); + push(new js.Call(variableUse, <js.Expression>[pop()]), node); + } + } + + void visitLiteralList(HLiteralList node) { + world.registerInstantiatedClass(compiler.listClass); + generateArrayLiteral(node); + } + + void generateArrayLiteral(HLiteralList node) { + int len = node.inputs.length; + List<js.ArrayElement> elements = <js.ArrayElement>[]; + for (int i = 0; i < len; i++) { + use(node.inputs[i]); + elements.add(new js.ArrayElement(i, pop())); + } + push(new js.ArrayInitializer(len, elements), node); + } + + void visitIndex(HIndex node) { + use(node.receiver); + js.Expression receiver = pop(); + use(node.index); + push(new js.PropertyAccess(receiver, pop()), node); + } + + void visitIndexAssign(HIndexAssign node) { + use(node.receiver); + js.Expression receiver = pop(); + use(node.index); + js.Expression index = pop(); + use(node.value); + push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()), + node); + } + + void checkInt(HInstruction input, String cmp) { + use(input); + js.Expression left = pop(); + use(input); + js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0")); + push(new js.Binary(cmp, left, or0)); + } + + void checkBigInt(HInstruction input, String cmp) { + use(input); + js.Expression left = pop(); + use(input); + js.Expression right = pop(); + // TODO(4984): Deal with infinity and -0.0. + push(new js.LiteralExpression.withData('Math.floor(#) === #', + <js.Expression>[left, right])); + } + + void checkTypeOf(HInstruction input, String cmp, String typeName) { + use(input); + js.Expression typeOf = new js.Prefix("typeof", pop()); + push(new js.Binary(cmp, typeOf, js.string(typeName))); + } + + void checkNum(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'number'); + + void checkDouble(HInstruction input, String cmp) => checkNum(input, cmp); + + void checkString(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'string'); + + void checkBool(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'boolean'); + + void checkObject(HInstruction input, String cmp) { + assert(NullConstant.JsNull == 'null'); + if (cmp == "===") { + checkTypeOf(input, '===', 'object'); + js.Expression left = pop(); + use(input); + js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull()); + push(new js.Binary("&&", left, notNull)); + } else { + assert(cmp == "!=="); + checkTypeOf(input, '!==', 'object'); + js.Expression left = pop(); + use(input); + js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull()); + push(new js.Binary("||", left, eqNull)); + } + } + + void checkArray(HInstruction input, String cmp) { + use(input); + js.PropertyAccess constructor = + new js.PropertyAccess.field(pop(), 'constructor'); + push(new js.Binary(cmp, constructor, new js.VariableUse('Array'))); + } + + void checkFieldExists(HInstruction input, String fieldName) { + use(input); + js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName); + // Double negate to boolify the result. + push(new js.Prefix('!', new js.Prefix('!', field))); + } + + void checkImmutableArray(HInstruction input) { + checkFieldExists(input, 'immutable\$list'); + } + + void checkExtendableArray(HInstruction input) { + checkFieldExists(input, 'fixed\$length'); + } + + void checkFixedArray(HInstruction input) { + checkFieldExists(input, 'fixed\$length'); + } + + void checkNull(HInstruction input) { + use(input); + push(new js.Binary('==', pop(), new js.LiteralNull())); + } + + void checkNonNull(HInstruction input) { + use(input); + push(new js.Binary('!=', pop(), new js.LiteralNull())); + } + + void checkFunction(HInstruction input, DartType type) { + checkTypeOf(input, '===', 'function'); + js.Expression functionTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + functionTest, + new js.Binary('&&', objectTest, pop()))); + } + + void checkType(HInstruction input, DartType type, {bool negative: false}) { + assert(invariant(input, !type.isMalformed, + message: 'Attempt to check malformed type $type')); + world.registerIsCheck(type); + Element element = type.element; + use(input); + js.PropertyAccess field = + new js.PropertyAccess.field(pop(), backend.namer.operatorIs(element)); + if (backend.emitter.nativeEmitter.requiresNativeIsCheck(element)) { + push(new js.Call(field, <js.Expression>[])); + if (negative) push(new js.Prefix('!', pop())); + } else { + // We always negate at least once so that the result is boolified. + push(new js.Prefix('!', field)); + // If the result is not negated, put another '!' in front. + if (!negative) push(new js.Prefix('!', pop())); + } + } + + void handleNumberOrStringSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.listClass) + && !Elements.isListSupertype(type.element, compiler) + && !Elements.isStringOnlySupertype(type.element, compiler)); + checkNum(input, '==='); + js.Expression numberTest = pop(); + checkString(input, '==='); + js.Expression stringTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + new js.Binary('||', numberTest, stringTest), + new js.Binary('&&', objectTest, pop()))); + } + + void handleStringSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.listClass) + && !Elements.isListSupertype(type.element, compiler) + && !Elements.isNumberOrStringSupertype(type.element, compiler)); + checkString(input, '==='); + js.Expression stringTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + stringTest, + new js.Binary('&&', objectTest, pop()))); + } + + void handleListOrSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.stringClass) + && !Elements.isStringOnlySupertype(type.element, compiler) + && !Elements.isNumberOrStringSupertype(type.element, compiler)); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkArray(input, '==='); + js.Expression arrayTest = pop(); + checkType(input, type); + push(new js.Binary('&&', + objectTest, + new js.Binary('||', arrayTest, pop()))); + } + + void visitIs(HIs node) { + DartType type = node.typeExpression; + world.registerIsCheck(type); + Element element = type.element; + if (identical(element.kind, ElementKind.TYPE_VARIABLE)) { + compiler.unimplemented("visitIs for type variables", + instruction: node.expression); + } + LibraryElement coreLibrary = compiler.coreLibrary; + ClassElement objectClass = compiler.objectClass; + HInstruction input = node.expression; + + if (identical(element, objectClass) || + identical(element, compiler.dynamicClass)) { + // The constant folder also does this optimization, but we make + // it safe by assuming it may have not run. + push(newLiteralBool(true), node); + } else if (element == compiler.stringClass) { + checkString(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.doubleClass) { + checkDouble(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.numClass) { + checkNum(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.boolClass) { + checkBool(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.functionClass) { + checkFunction(input, type); + attachLocationToLast(node); + } else if (element == compiler.intClass) { + // The is check in the code tells us that it might not be an + // int. So we do a typeof first to avoid possible + // deoptimizations on the JS engine due to the Math.floor check. + checkNum(input, '==='); + js.Expression numTest = pop(); + checkBigInt(input, '==='); + push(new js.Binary('&&', numTest, pop()), node); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + handleNumberOrStringSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + handleStringSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (identical(element, compiler.listClass) + || Elements.isListSupertype(element, compiler)) { + handleListOrSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (element.isTypedef()) { + checkNonNull(input); + js.Expression nullTest = pop(); + checkType(input, type); + push(new js.Binary('&&', nullTest, pop())); + attachLocationToLast(node); + } else if (types[input].canBePrimitive() || types[input].canBeNull()) { + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('&&', objectTest, pop()), node); + } else { + checkType(input, type); + attachLocationToLast(node); + } + if (node.hasArgumentChecks()) { + InterfaceType interfaceType = type; + ClassElement cls = type.element; + Link<DartType> arguments = interfaceType.typeArguments; + js.Expression result = pop(); + for (int i = 0; i < node.checkCount; i++) { + use(node.getCheck(i)); + result = new js.Binary('&&', result, pop()); + } + push(result, node); + } + if (node.nullOk) { + checkNull(input); + push(new js.Binary('||', pop(), pop()), node); + } + } + + // TODO(johnniwinther): Refactor this method. + void visitTypeConversion(HTypeConversion node) { + Map<String, SourceString> castNames = const <String, SourceString> { + "stringTypeCheck": + const SourceString("stringTypeCast"), + "doubleTypeCheck": + const SourceString("doubleTypeCast"), + "numTypeCheck": + const SourceString("numTypeCast"), + "boolTypeCheck": + const SourceString("boolTypeCast"), + "functionTypeCheck": + const SourceString("functionTypeCast"), + "intTypeCheck": + const SourceString("intTypeCast"), + "numberOrStringSuperNativeTypeCheck": + const SourceString("numberOrStringSuperNativeTypeCast"), + "numberOrStringSuperTypeCheck": + const SourceString("numberOrStringSuperTypeCast"), + "stringSuperNativeTypeCheck": + const SourceString("stringSuperNativeTypeCast"), + "stringSuperTypeCheck": + const SourceString("stringSuperTypeCast"), + "listTypeCheck": + const SourceString("listTypeCast"), + "listSuperNativeTypeCheck": + const SourceString("listSuperNativeTypeCast"), + "listSuperTypeCheck": + const SourceString("listSuperTypeCast"), + "callTypeCheck": + const SourceString("callTypeCast"), + "propertyTypeCheck": + const SourceString("propertyTypeCast"), + // TODO(johnniwinther): Add a malformedTypeCast which produces a TypeError + // with another message. + "malformedTypeCheck": + const SourceString("malformedTypeCheck") + }; + + if (node.isChecked) { + DartType type = node.type.computeType(compiler); + Element element = type.element; + world.registerIsCheck(type); + + if (node.isArgumentTypeCheck) { + if (element == backend.jsIntClass) { + checkInt(node.checkedInput, '!=='); + } else { + assert(element == backend.jsNumberClass); + checkNum(node.checkedInput, '!=='); + } + js.Expression test = pop(); + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + currentContainer = body; + generateThrowWithHelper('iae', node.checkedInput); + currentContainer = oldContainer; + body = unwrapStatement(body); + pushStatement(new js.If.noElse(test, body), node); + return; + } + assert(node.isCheckedModeCheck || node.isCastTypeCheck); + + SourceString helper; + if (node.isBooleanConversionCheck) { + helper = const SourceString('boolConversionCheck'); + } else { + helper = backend.getCheckedModeHelper(type); + if (node.isCastTypeCheck) { + helper = castNames[helper.stringValue]; + } + } + FunctionElement helperElement = compiler.findHelper(helper); + world.registerStaticUse(helperElement); + List<js.Expression> arguments = <js.Expression>[]; + use(node.checkedInput); + arguments.add(pop()); + int parameterCount = + helperElement.computeSignature(compiler).parameterCount; + if (parameterCount == 2) { + // 2 arguments implies that the method is either [propertyTypeCheck] + // or [propertyTypeCast]. + assert(!type.isMalformed); + String additionalArgument = backend.namer.operatorIs(element); + arguments.add(js.string(additionalArgument)); + } else if (parameterCount == 3) { + // 3 arguments implies that the method is [malformedTypeCheck]. + assert(type.isMalformed); + String reasons = Types.fetchReasonsFromMalformedType(type); + arguments.add(js.string('$type')); + // TODO(johnniwinther): Handle escaping correctly. + arguments.add(js.string(reasons)); + } else { + assert(!type.isMalformed); + } + String helperName = backend.namer.isolateAccess(helperElement); + push(new js.Call(new js.VariableUse(helperName), arguments)); + } else { + use(node.checkedInput); + } + } +} + +class SsaOptimizedCodeGenerator extends SsaCodeGenerator { + SsaOptimizedCodeGenerator(backend, work) : super(backend, work); + + HBasicBlock beginGraph(HGraph graph) { + return graph.entry; + } + + void endGraph(HGraph graph) {} + + // Called by visitTypeGuard to generate the actual bailout call, something + // like "return $.foo$bailout(t0, t1);" + js.Statement bailout(HTypeGuard guard, String reason) { + HBailoutTarget target = guard.bailoutTarget; + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(new js.LiteralNumber("${guard.state}")); + + for (int i = 0; i < target.inputs.length; i++) { + HInstruction parameter = target.inputs[i]; + for (int pad = target.padding[i]; pad != 0; pad--) { + // This argument will not be used by the bailout function, because + // of the control flow (controlled by the state argument passed + // above). We need to pass it to get later arguments in the right + // position. + arguments.add(new js.LiteralNumber('0')); + } + use(parameter); + arguments.add(pop()); + } + // Don't bother emitting the rest of the pending nulls. Doing so might make + // the function invocation a little faster by having the call site and + // function defintion have the same number of arguments, but it would be + // more verbose and we don't expect the calls to bailout functions to be + // hot. + + Element method = work.element; + js.Expression bailoutTarget; // Receiver of the bailout call. + Namer namer = backend.namer; + if (method.isInstanceMember()) { + String bailoutName = namer.getBailoutName(method); + bailoutTarget = new js.PropertyAccess.field(new js.This(), bailoutName); + } else { + assert(!method.isField()); + bailoutTarget = new js.VariableUse(namer.isolateBailoutAccess(method)); + } + js.Call call = new js.Call(bailoutTarget, arguments); + attachLocation(call, guard); + return new js.Return(call); + } + + // Generate a type guard, something like "if (typeof t0 == 'number')" and the + // corresponding bailout call, something like "return $.foo$bailout(t0, t1);" + void visitTypeGuard(HTypeGuard node) { + HInstruction input = node.guarded; + DartType indexingBehavior = + backend.jsIndexingBehaviorInterface.computeType(compiler); + if (node.isInteger(types)) { + // if (input is !int) bailout + checkInt(input, '!=='); + js.Statement then = bailout(node, 'Not an integer'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isNumber(types)) { + // if (input is !num) bailout + checkNum(input, '!=='); + js.Statement then = bailout(node, 'Not a number'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isBoolean(types)) { + // if (input is !bool) bailout + checkBool(input, '!=='); + js.Statement then = bailout(node, 'Not a boolean'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isString(types)) { + // if (input is !string) bailout + checkString(input, '!=='); + js.Statement then = bailout(node, 'Not a string'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isExtendableArray(types)) { + // if (input is !Object || input is !Array || input.isFixed) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkFixedArray(input); + js.Binary test = new js.Binary('||', objectTest, arrayTest); + test = new js.Binary('||', test, pop()); + js.Statement then = bailout(node, 'Not an extendable array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isMutableArray(types)) { + // if (input is !Object + // || ((input is !Array || input.isImmutable) + // && input is !JsIndexingBehavior)) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkImmutableArray(input); + js.Binary notArrayOrImmutable = new js.Binary('||', arrayTest, pop()); + checkType(input, indexingBehavior, negative: true); + js.Binary notIndexing = new js.Binary('&&', notArrayOrImmutable, pop()); + js.Binary test = new js.Binary('||', objectTest, notIndexing); + js.Statement then = bailout(node, 'Not a mutable array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isReadableArray(types)) { + // if (input is !Object + // || (input is !Array && input is !JsIndexingBehavior)) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkType(input, indexingBehavior, negative: true); + js.Expression notIndexing = new js.Binary('&&', arrayTest, pop()); + js.Binary test = new js.Binary('||', objectTest, notIndexing); + js.Statement then = bailout(node, 'Not an array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isIndexablePrimitive(types)) { + // if (input is !String + // && (input is !Object + // || (input is !Array && input is !JsIndexingBehavior))) bailout + checkString(input, '!=='); + js.Expression stringTest = pop(); + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkType(input, indexingBehavior, negative: true); + js.Binary notIndexingTest = new js.Binary('&&', arrayTest, pop()); + js.Binary notObjectOrIndexingTest = + new js.Binary('||', objectTest, notIndexingTest); + js.Binary test = + new js.Binary('&&', stringTest, notObjectOrIndexingTest); + js.Statement then = bailout(node, 'Not a string or array'); + pushStatement(new js.If.noElse(test, then), node); + } else { + compiler.internalError('Unexpected type guard', instruction: input); + } + } + + void visitBailoutTarget(HBailoutTarget target) { + // Do nothing. Bailout targets are only used in the non-optimized version. + } + + void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } + + void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } + + void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } +} + +class SsaUnoptimizedCodeGenerator extends SsaCodeGenerator { + + js.Switch currentBailoutSwitch; + final List<js.Switch> oldBailoutSwitches; + final List<js.Parameter> newParameters; + final List<String> labels; + int labelId = 0; + /** + * Keeps track if a bailout switch already used its [:default::] clause. New + * bailout-switches just push [:false:] on the stack and replace it when + * they used the [:default::] clause. + */ + final List<bool> defaultClauseUsedInBailoutStack; + + SsaBailoutPropagator propagator; + HInstruction savedFirstInstruction; + + SsaUnoptimizedCodeGenerator(backend, work) + : super(backend, work), + oldBailoutSwitches = <js.Switch>[], + newParameters = <js.Parameter>[], + labels = <String>[], + defaultClauseUsedInBailoutStack = <bool>[]; + + String pushLabel() { + String label = 'L${labelId++}'; + labels.addLast(label); + return label; + } + + String popLabel() { + return labels.removeLast(); + } + + String currentLabel() { + return labels.last; + } + + js.VariableUse generateStateUse() + => new js.VariableUse(variableNames.stateName); + + HBasicBlock beginGraph(HGraph graph) { + propagator = new SsaBailoutPropagator(compiler, variableNames); + propagator.visitGraph(graph); + // TODO(ngeoffray): We could avoid generating the state at the + // call site for non-complex bailout methods. + newParameters.add(new js.Parameter(variableNames.stateName)); + + List<String> names = new List<String>(propagator.bailoutArity); + for (String variable in propagator.parameterNames.keys) { + int index = propagator.parameterNames[variable]; + assert(names[index] == null); + names[index] = variable; + } + for (int i = 0; i < names.length; i++) { + declaredLocals.add(names[i]); + newParameters.add(new js.Parameter(names[i])); + } + + if (propagator.hasComplexBailoutTargets) { + startBailoutSwitch(); + + return graph.entry; + } else { + // We change the first instruction of the first guard to be the + // bailout target. We will change it back in the call to [endGraph]. + HBasicBlock block = propagator.firstBailoutTarget.block; + savedFirstInstruction = block.first; + block.first = propagator.firstBailoutTarget; + return block; + } + } + + // If argument is a [HCheck] and it does not have a name, we try to + // find the name of its checked input. Note that there must be a + // name, otherwise the instruction would not be in the live + // environment. + HInstruction unwrap(HInstruction argument) { + while (argument is HCheck && !variableNames.hasName(argument)) { + argument = argument.checkedInput; + } + assert(variableNames.hasName(argument)); + return argument; + } + + void endGraph(HGraph graph) { + if (propagator.hasComplexBailoutTargets) { + endBailoutSwitch(); + } else { + // Put back the original first instruction of the block. + propagator.firstBailoutTarget.block.first = savedFirstInstruction; + } + } + + visitParameterValue(HParameterValue node) { + // Nothing to do, parameters are dealt with specially in a bailout + // method. + } + + bool visitAndOrInfo(HAndOrBlockInformation info) => false; + + visitLoopBranch(HLoopBranch node) { + if (node.computeLoopHeader().hasBailoutTargets()) { + // The graph visitor in [visitLoopInfo] does not handle the + // condition. We must instead manually emit it here. + handleLoopCondition(node); + // We must also visit the body from here. + // For a do while loop, the body has already been visited. + if (!node.isDoWhile()) { + visitBasicBlock(node.block.dominatedBlocks[0]); + } + } else { + super.visitLoopBranch(node); + } + } + + + bool visitIfInfo(HIfBlockInformation info) { + if (info.thenGraph.start.hasBailoutTargets()) return false; + if (info.elseGraph.start.hasBailoutTargets()) return false; + return super.visitIfInfo(info); + } + + bool visitLoopInfo(HLoopBlockInformation info) { + // Always emit with block flow traversal. + if (info.loopHeader.hasBailoutTargets()) { + // If there are any bailout targets in the loop, we cannot use + // the pretty [SsaCodeGenerator.visitLoopInfo] printer. + if (info.initializer != null) { + generateStatements(info.initializer); + } + beginLoop(info.loopHeader); + if (!info.isDoWhile()) { + generateStatements(info.condition); + } + generateStatements(info.body); + if (info.isDoWhile()) { + generateStatements(info.condition); + } + if (info.updates != null) { + generateStatements(info.updates); + } + endLoop(info.end); + return true; + } + return super.visitLoopInfo(info); + } + + bool visitTryInfo(HTryBlockInformation info) => false; + bool visitSequenceInfo(HStatementSequenceInformation info) => false; + + void visitTypeGuard(HTypeGuard node) { + // Do nothing. Type guards are only used in the optimized version. + } + + void visitBailoutTarget(HBailoutTarget node) { + if (propagator.hasComplexBailoutTargets) { + js.Block nextBlock = new js.Block.empty(); + js.Case clause = new js.Case(new js.LiteralNumber('${node.state}'), + nextBlock); + currentBailoutSwitch.cases.add(clause); + currentContainer = nextBlock; + pushExpressionAsStatement(new js.Assignment(generateStateUse(), + new js.LiteralNumber('0'))); + } + // Here we need to rearrange the inputs of the bailout target, so that they + // are output in the correct order, perhaps with interspersed nulls, to + // match the order in the bailout function, which is of course common to all + // the bailout points. + var newInputs = new List<HInstruction>(propagator.bailoutArity); + for (HInstruction input in node.inputs) { + int index = propagator.parameterNames[variableNames.getName(input)]; + newInputs[index] = input; + } + // We record the count of unused arguments instead of just filling in the + // inputs list with dummy arguments because it is useful to be able easily + // to distinguish between a dummy argument (eg 0 or null) and a real + // argument that happens to have the same value. The dummy arguments are + // not going to be accessed by the bailout function due to the control flow + // implied by the state argument, so we can put anything there, including + // just not emitting enough arguments and letting the JS engine insert + // undefined for the trailing arguments. + node.padding = new List<int>(node.inputs.length); + int j = 0; + int pendingUnusedArguments = 0; + for (int i = 0; i < newInputs.length; i++) { + HInstruction input = newInputs[i]; + if (input == null) { + pendingUnusedArguments++; + } else { + node.padding[j] = pendingUnusedArguments; + pendingUnusedArguments = 0; + node.updateInput(j, input); + j++; + } + } + assert(j == node.inputs.length); + } + + void startBailoutCase(List<HBailoutTarget> bailouts1, + [List<HBailoutTarget> bailouts2 = const []]) { + if (!defaultClauseUsedInBailoutStack.last && + bailouts1.length + bailouts2.length >= 2) { + currentContainer = new js.Block.empty(); + currentBailoutSwitch.cases.add(new js.Default(currentContainer)); + int len = defaultClauseUsedInBailoutStack.length; + defaultClauseUsedInBailoutStack[len - 1] = true; + } else { + _handleBailoutCase(bailouts1); + _handleBailoutCase(bailouts2); + currentContainer = currentBailoutSwitch.cases.last.body; + } + } + + void _handleBailoutCase(List<HBailoutTarget> targets) { + for (int i = 0, len = targets.length; i < len; i++) { + js.LiteralNumber expr = new js.LiteralNumber('${targets[i].state}'); + currentBailoutSwitch.cases.add(new js.Case(expr, new js.Block.empty())); + } + } + + void startBailoutSwitch() { + defaultClauseUsedInBailoutStack.add(false); + oldBailoutSwitches.add(currentBailoutSwitch); + List<js.SwitchClause> cases = <js.SwitchClause>[]; + js.Block firstBlock = new js.Block.empty(); + cases.add(new js.Case(new js.LiteralNumber("0"), firstBlock)); + currentBailoutSwitch = new js.Switch(generateStateUse(), cases); + pushStatement(currentBailoutSwitch); + oldContainerStack.add(currentContainer); + currentContainer = firstBlock; + } + + js.Switch endBailoutSwitch() { + js.Switch result = currentBailoutSwitch; + currentBailoutSwitch = oldBailoutSwitches.removeLast(); + defaultClauseUsedInBailoutStack.removeLast(); + currentContainer = oldContainerStack.removeLast(); + return result; + } + + void beginLoop(HBasicBlock block) { + String loopLabel = pushLabel(); + if (block.hasBailoutTargets()) { + startBailoutCase(block.bailoutTargets); + } + oldContainerStack.add(currentContainer); + currentContainer = new js.Block.empty(); + if (block.hasBailoutTargets()) { + startBailoutSwitch(); + HLoopInformation loopInformation = block.loopInformation; + if (loopInformation.target != null) { + breakAction[loopInformation.target] = (TargetElement target) { + pushStatement(new js.Break(loopLabel)); + }; + } + } + } + + void endLoop(HBasicBlock block) { + String loopLabel = popLabel(); + + HBasicBlock header = block.isLoopHeader() ? block : block.parentLoopHeader; + HLoopInformation info = header.loopInformation; + if (header.hasBailoutTargets()) { + endBailoutSwitch(); + if (info.target != null) breakAction.remove(info.target); + } + + js.Statement body = unwrapStatement(currentContainer); + currentContainer = oldContainerStack.removeLast(); + + js.Statement result = new js.While(newLiteralBool(true), body); + attachLocationRange(result, + info.loopBlockInformation.sourcePosition, + info.loopBlockInformation.endSourcePosition); + result = new js.LabeledStatement(loopLabel, result); + result = wrapIntoLabels(result, info.labels); + pushStatement(result); + } + + void handleLoopCondition(HLoopBranch node) { + use(node.inputs[0]); + js.Expression test = new js.Prefix('!', pop()); + js.Statement then = new js.Break(currentLabel()); + pushStatement(new js.If.noElse(test, then), node); + } + + void generateIf(HIf node, HIfBlockInformation info) { + HStatementInformation thenGraph = info.thenGraph; + HStatementInformation elseGraph = info.elseGraph; + bool thenHasGuards = thenGraph.start.hasBailoutTargets(); + bool elseHasGuards = elseGraph.start.hasBailoutTargets(); + bool hasGuards = thenHasGuards || elseHasGuards; + if (!hasGuards) { + super.generateIf(node, info); + return; + } + + startBailoutCase(thenGraph.start.bailoutTargets, + elseGraph.start.bailoutTargets); + + use(node.inputs[0]); + js.Binary stateEquals0 = + new js.Binary('===', generateStateUse(), new js.LiteralNumber('0')); + js.Expression condition = new js.Binary('&&', stateEquals0, pop()); + // TODO(ngeoffray): Put the condition initialization in the + // arguments? + List<HBailoutTarget> targets = node.thenBlock.bailoutTargets; + for (int i = 0, len = targets.length; i < len; i++) { + js.VariableUse stateRef = generateStateUse(); + js.Expression targetState = new js.LiteralNumber('${targets[i].state}'); + js.Binary stateTest = new js.Binary('===', stateRef, targetState); + condition = new js.Binary('||', stateTest, condition); + } + + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + if (thenHasGuards) startBailoutSwitch(); + generateStatements(thenGraph); + if (thenHasGuards) endBailoutSwitch(); + thenBody = unwrapStatement(thenBody); + + js.Statement elseBody = null; + elseBody = new js.Block.empty(); + currentContainer = elseBody; + if (elseHasGuards) startBailoutSwitch(); + generateStatements(elseGraph); + if (elseHasGuards) endBailoutSwitch(); + elseBody = unwrapStatement(elseBody); + + currentContainer = oldContainer; + pushStatement(new js.If(condition, thenBody, elseBody), node); + } + + void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + indent--; + startBailoutCase(labeledBlockInfo.body.start.bailoutTargets); + indent++; + } + } + + void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + startBailoutSwitch(); + } + } + + void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + endBailoutSwitch(); + } + } +} + +String singleIdentityComparison(HInstruction left, + HInstruction right, + HTypeMap propagatedTypes) { + // Returns the single identity comparison (== or ===) or null if a more + // complex expression is required. + if ((left.isConstant() && left.isConstantSentinel()) || + (right.isConstant() && right.isConstantSentinel())) return '==='; + HType leftType = propagatedTypes[left]; + HType rightType = propagatedTypes[right]; + if (leftType.canBeNull() && rightType.canBeNull()) { + if (left.isConstantNull() || right.isConstantNull() || + (leftType.isPrimitive() && leftType == rightType)) { + return '=='; + } + return null; + } else { + return '==='; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart new file mode 100644 index 0000000..625eafb --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart
@@ -0,0 +1,380 @@ +// 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. + +part of ssa; + +/** + * Instead of emitting each SSA instruction with a temporary variable + * mark instructions that can be emitted at their use-site. + * For example, in: + * t0 = 4; + * t1 = 3; + * t2 = add(t0, t1); + * t0 and t1 would be marked and the resulting code would then be: + * t2 = add(4, 3); + */ +class SsaInstructionMerger extends HBaseVisitor { + HTypeMap types; + /** + * List of [HInstruction] that the instruction merger expects in + * order when visiting the inputs of an instruction. + */ + List<HInstruction> expectedInputs; + /** + * Set of pure [HInstruction] that the instruction merger expects to + * find. The order of pure instructions do not matter, as they will + * not be affected by side effects. + */ + Set<HInstruction> pureInputs; + Set<HInstruction> generateAtUseSite; + + void markAsGenerateAtUseSite(HInstruction instruction) { + assert(!instruction.isJsStatement()); + generateAtUseSite.add(instruction); + } + + SsaInstructionMerger(this.types, this.generateAtUseSite); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void analyzeInputs(HInstruction user, int start) { + List<HInstruction> inputs = user.inputs; + for (int i = start; i < inputs.length; i++) { + HInstruction input = inputs[i]; + if (!generateAtUseSite.contains(input) + && !input.isCodeMotionInvariant() + && input.usedBy.length == 1 + && input is !HPhi + && input is !HLocalValue + && !input.isJsStatement()) { + if (input.isPure()) { + // Only consider a pure input if it is in the same loop. + // Otherwise, we might move GVN'ed instruction back into the + // loop. + if (user.hasSameLoopHeaderAs(input)) { + // Move it closer to [user], so that instructions in + // between do not prevent making it generate at use site. + input.moveBefore(user); + pureInputs.add(input); + // Visit the pure input now so that the expected inputs + // are after the expected inputs of [user]. + input.accept(this); + } + } else { + expectedInputs.add(input); + } + } + } + } + + void visitInstruction(HInstruction instruction) { + // A code motion invariant instruction is dealt before visiting it. + assert(!instruction.isCodeMotionInvariant()); + analyzeInputs(instruction, 0); + } + + // The codegen might use the input multiple times, so it must not be + // set generate at use site. + void visitIs(HIs instruction) {} + + // A bounds check method must not have its first input generated at use site, + // because it's using it twice. + void visitBoundsCheck(HBoundsCheck instruction) { + analyzeInputs(instruction, 1); + } + + // An integer check method must not have its input generated at use site, + // because it's using it twice. + void visitIntegerCheck(HIntegerCheck instruction) {} + + // A type guard should not generate its input at use site, otherwise + // they would not be alive. + void visitTypeGuard(HTypeGuard instruction) {} + + // An identity operation must only have its inputs generated at use site if + // does not require an expression with multiple uses (because of null / + // undefined). + void visitIdentity(HIdentity instruction) { + HInstruction left = instruction.left; + HInstruction right = instruction.right; + if (singleIdentityComparison(left, right, types) != null) { + super.visitIdentity(instruction); + } + // Do nothing. + } + + void visitTypeConversion(HTypeConversion instruction) { + if (!instruction.isChecked) { + markAsGenerateAtUseSite(instruction); + } else if (!instruction.isArgumentTypeCheck) { + assert(instruction.isCheckedModeCheck || instruction.isCastTypeCheck); + // Checked mode checks and cast checks compile to code that + // only use their input once, so we can safely visit them + // and try to merge the input. + visitInstruction(instruction); + } + } + + void tryGenerateAtUseSite(HInstruction instruction) { + if (instruction.isControlFlow()) return; + markAsGenerateAtUseSite(instruction); + } + + bool isBlockSinglePredecessor(HBasicBlock block) { + return block.successors.length == 1 + && block.successors[0].predecessors.length == 1; + } + + void visitBasicBlock(HBasicBlock block) { + // Compensate from not merging blocks: if the block is the + // single predecessor of its single successor, let the successor + // visit it. + if (isBlockSinglePredecessor(block)) return; + + tryMergingExpressions(block); + } + + void tryMergingExpressions(HBasicBlock block) { + // Visit each instruction of the basic block in last-to-first order. + // Keep a list of expected inputs of the current "expression" being + // merged. If instructions occur in the expected order, they are + // included in the expression. + + // The expectedInputs list holds non-trivial instructions that may + // be generated at their use site, if they occur in the correct order. + if (expectedInputs == null) expectedInputs = new List<HInstruction>(); + if (pureInputs == null) pureInputs = new Set<HInstruction>(); + + // Pop instructions from expectedInputs until instruction is found. + // Return true if it is found, or false if not. + bool findInInputsAndPopNonMatching(HInstruction instruction) { + assert(!instruction.isPure()); + while (!expectedInputs.isEmpty) { + HInstruction nextInput = expectedInputs.removeLast(); + assert(!generateAtUseSite.contains(nextInput)); + assert(nextInput.usedBy.length == 1); + if (identical(nextInput, instruction)) { + return true; + } + } + return false; + } + + block.last.accept(this); + bool dontVisitPure = false; + for (HInstruction instruction = block.last.previous; + instruction != null; + instruction = instruction.previous) { + if (generateAtUseSite.contains(instruction)) { + continue; + } + if (instruction.isCodeMotionInvariant()) { + markAsGenerateAtUseSite(instruction); + continue; + } + if (instruction.isJsStatement()) { + expectedInputs.clear(); + } + if (instruction.isPure()) { + if (pureInputs.contains(instruction)) { + tryGenerateAtUseSite(instruction); + } else { + // If the input is not in the [pureInputs] set, it has not + // been visited. + instruction.accept(this); + } + } else { + if (findInInputsAndPopNonMatching(instruction)) { + // The current instruction is the next non-trivial + // expected input. + tryGenerateAtUseSite(instruction); + } else { + assert(expectedInputs.isEmpty); + } + instruction.accept(this); + } + } + + if (block.predecessors.length == 1 + && isBlockSinglePredecessor(block.predecessors[0])) { + assert(block.phis.isEmpty); + tryMergingExpressions(block.predecessors[0]); + } else { + expectedInputs = null; + pureInputs = null; + } + } +} + +/** + * Detect control flow arising from short-circuit logical and + * conditional operators, and prepare the program to be generated + * using these operators instead of nested ifs and boolean variables. + */ +class SsaConditionMerger extends HGraphVisitor { + final HTypeMap types; + Set<HInstruction> generateAtUseSite; + Set<HInstruction> controlFlowOperators; + + void markAsGenerateAtUseSite(HInstruction instruction) { + assert(!instruction.isJsStatement()); + generateAtUseSite.add(instruction); + } + + SsaConditionMerger(this.types, + this.generateAtUseSite, + this.controlFlowOperators); + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + } + + /** + * Check if a block has at least one statement other than + * [instruction]. + */ + bool hasAnyStatement(HBasicBlock block, HInstruction instruction) { + // If [instruction] is not in [block], then if the block is not + // empty, we know there will be a statement to emit. + if (!identical(instruction.block, block)) return !identical(block.last, block.first); + + // If [instruction] is not the last instruction of the block + // before the control flow instruction, or the last instruction, + // then we will have to emit a statement for that last instruction. + if (instruction != block.last + && !identical(instruction, block.last.previous)) return true; + + // If one of the instructions in the block until [instruction] is + // not generated at use site, then we will have to emit a + // statement for it. + // TODO(ngeoffray): we could generate a comma separated + // list of expressions. + for (HInstruction temp = block.first; + !identical(temp, instruction); + temp = temp.next) { + if (!generateAtUseSite.contains(temp)) return true; + } + + return false; + } + + bool isSafeToGenerateAtUseSite(HInstruction user, HInstruction input) { + // A [HForeign] instruction uses operators and if we generate + // [input] at use site, the precedence might be wrong. + if (user is HForeign) return false; + // A [HCheck] instruction with control flow uses its input + // multiple times, so we avoid generating it at use site. + if (user is HCheck && user.isControlFlow()) return false; + // A [HIs] instruction uses its input multiple times, so we + // avoid generating it at use site. + if (user is HIs) return false; + return true; + } + + void visitBasicBlock(HBasicBlock block) { + if (block.last is !HIf) return; + HIf startIf = block.last; + HBasicBlock end = startIf.joinBlock; + + // We check that the structure is the following: + // If + // / \ + // / \ + // 1 expr goto + // goto / + // \ / + // \ / + // phi(expr, true|false) + // + // and the same for nested nodes: + // + // If + // / \ + // / \ + // 1 expr1 \ + // If \ + // / \ \ + // / \ goto + // 1 expr2 | + // goto goto | + // \ / | + // \ / | + // phi1(expr2, true|false) + // \ | + // \ | + // phi(phi1, true|false) + + if (end == null) return; + if (end.phis.isEmpty) return; + if (!identical(end.phis.first, end.phis.last)) return; + HBasicBlock elseBlock = startIf.elseBlock; + + if (!identical(end.predecessors[1], elseBlock)) return; + HPhi phi = end.phis.first; + HInstruction thenInput = phi.inputs[0]; + HInstruction elseInput = phi.inputs[1]; + if (thenInput.isJsStatement() || elseInput.isJsStatement()) return; + + if (hasAnyStatement(elseBlock, elseInput)) return; + assert(elseBlock.successors.length == 1); + assert(end.predecessors.length == 2); + + HBasicBlock thenBlock = startIf.thenBlock; + // Skip trivial goto blocks. + while (thenBlock.successors[0] != end && thenBlock.first is HGoto) { + thenBlock = thenBlock.successors[0]; + } + + // If the [thenBlock] is already a control flow operation, and does not + // have any statement and its join block is [end], we can emit a + // sequence of control flow operation. + if (controlFlowOperators.contains(thenBlock.last)) { + HIf otherIf = thenBlock.last; + if (!identical(otherIf.joinBlock, end)) { + // This could be a join block that just feeds into our join block. + HBasicBlock otherJoin = otherIf.joinBlock; + if (otherJoin.first != otherJoin.last) return; + if (otherJoin.successors.length != 1) return; + if (otherJoin.successors[0] != end) return; + if (otherJoin.phis.isEmpty) return; + if (!identical(otherJoin.phis.first, otherJoin.phis.last)) return; + HPhi otherPhi = otherJoin.phis.first; + if (thenInput != otherPhi) return; + if (elseInput != otherPhi.inputs[1]) return; + } + if (hasAnyStatement(thenBlock, otherIf)) return; + } else { + if (!identical(end.predecessors[0], thenBlock)) return; + if (hasAnyStatement(thenBlock, thenInput)) return; + assert(thenBlock.successors.length == 1); + } + + // From now on, we have recognized a control flow operation built from + // the builder. Mark the if instruction as such. + controlFlowOperators.add(startIf); + + // If the operation is only used by the first instruction + // of its block and is safe to be generated at use site, mark it + // so. + if (phi.usedBy.length == 1 + && identical(phi.usedBy[0], phi.block.first) + && isSafeToGenerateAtUseSite(phi.usedBy[0], phi)) { + markAsGenerateAtUseSite(phi); + } + + if (identical(elseInput.block, elseBlock)) { + assert(elseInput.usedBy.length == 1); + markAsGenerateAtUseSite(elseInput); + } + + // If [thenInput] is defined in the first predecessor, then it is only used + // by [phi] and can be generated at use site. + if (identical(thenInput.block, end.predecessors[0])) { + assert(thenInput.usedBy.length == 1); + markAsGenerateAtUseSite(thenInput); + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart new file mode 100644 index 0000000..375f617 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
@@ -0,0 +1,620 @@ +// Copyright (c) 2013, 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. + +part of ssa; + +/** + * [InvokeDynamicSpecializer] and its subclasses are helpers to + * optimize intercepted dynamic calls. It knows what input types + * would be beneficial for performance, and how to change a invoke + * dynamic to a builtin instruction (e.g. HIndex, HBitNot). + */ +class InvokeDynamicSpecializer { + const InvokeDynamicSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + return null; + } + + Operation operation(ConstantSystem constantSystem) => null; + + static InvokeDynamicSpecializer lookupSpecializer(Selector selector) { + if (selector.kind == SelectorKind.INDEX) { + return selector.name == const SourceString('[]') + ? const IndexSpecializer() + : const IndexAssignSpecializer(); + } else if (selector.kind == SelectorKind.OPERATOR) { + if (selector.name == const SourceString('unary-')) { + return const UnaryNegateSpecializer(); + } else if (selector.name == const SourceString('~')) { + return const BitNotSpecializer(); + } else if (selector.name == const SourceString('+')) { + return const AddSpecializer(); + } else if (selector.name == const SourceString('-')) { + return const SubtractSpecializer(); + } else if (selector.name == const SourceString('*')) { + return const MultiplySpecializer(); + } else if (selector.name == const SourceString('/')) { + return const DivideSpecializer(); + } else if (selector.name == const SourceString('~/')) { + return const TruncatingDivideSpecializer(); + } else if (selector.name == const SourceString('%')) { + return const ModuloSpecializer(); + } else if (selector.name == const SourceString('>>')) { + return const ShiftRightSpecializer(); + } else if (selector.name == const SourceString('<<')) { + return const ShiftLeftSpecializer(); + } else if (selector.name == const SourceString('&')) { + return const BitAndSpecializer(); + } else if (selector.name == const SourceString('|')) { + return const BitOrSpecializer(); + } else if (selector.name == const SourceString('^')) { + return const BitXorSpecializer(); + } else if (selector.name == const SourceString('==')) { + return const EqualsSpecializer(); + } else if (selector.name == const SourceString('<')) { + return const LessSpecializer(); + } else if (selector.name == const SourceString('<=')) { + return const LessEqualSpecializer(); + } else if (selector.name == const SourceString('>')) { + return const GreaterSpecializer(); + } else if (selector.name == const SourceString('>=')) { + return const GreaterEqualSpecializer(); + } + } + return const InvokeDynamicSpecializer(); + } +} + +class IndexAssignSpecializer extends InvokeDynamicSpecializer { + const IndexAssignSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction index = instruction.inputs[2]; + if (input == instruction.inputs[1] && + (index.isTypeUnknown(types) || index.isNumber(types))) { + return HType.MUTABLE_ARRAY; + } + // The index should be an int when the receiver is a string or array. + // However it turns out that inserting an integer check in the optimized + // version is cheaper than having another bailout case. This is true, + // because the integer check will simply throw if it fails. + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (instruction.inputs[1].isMutableArray(types)) { + return new HIndexAssign(instruction.inputs[1], + instruction.inputs[2], + instruction.inputs[3]); + } + return null; + } +} + +class IndexSpecializer extends InvokeDynamicSpecializer { + const IndexSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction index = instruction.inputs[2]; + if (input == instruction.inputs[1] && + (index.isTypeUnknown(types) || index.isNumber(types))) { + return HType.INDEXABLE_PRIMITIVE; + } + // The index should be an int when the receiver is a string or array. + // However it turns out that inserting an integer check in the optimized + // version is cheaper than having another bailout case. This is true, + // because the integer check will simply throw if it fails. + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (instruction.inputs[1].isIndexablePrimitive(types)) { + return new HIndex(instruction.inputs[1], instruction.inputs[2]); + } + return null; + } +} + +class BitNotSpecializer extends InvokeDynamicSpecializer { + const BitNotSpecializer(); + + UnaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitNot; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[1]) { + HType propagatedType = types[instruction]; + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.INTEGER; + } + } + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + // All bitwise operations on primitive types either produce an + // integer or throw an error. + if (instruction.inputs[1].isPrimitive(types)) return HType.INTEGER; + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction input = instruction.inputs[1]; + if (input.isNumber(types)) return new HBitNot(input); + return null; + } +} + +class UnaryNegateSpecializer extends InvokeDynamicSpecializer { + const UnaryNegateSpecializer(); + + UnaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.negate; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[1]) { + HType propagatedType = types[instruction]; + // If the outgoing type should be a number (integer, double or both) we + // want the outgoing type to be the input too. + // If we don't know the outgoing type we try to make it a number. + if (propagatedType.isNumber()) return propagatedType; + if (propagatedType.isUnknown()) return HType.NUMBER; + } + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + HType operandType = types[instruction.inputs[1]]; + if (operandType.isNumber()) return operandType; + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction input = instruction.inputs[1]; + if (input.isNumber(types)) return new HNegate(input); + return null; + } +} + +abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer { + const BinaryArithmeticSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER; + if (left.isNumber(types)) { + if (left.isDouble(types) || right.isDouble(types)) return HType.DOUBLE; + return HType.NUMBER; + } + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + + HType propagatedType = types[instruction]; + // If the desired output type should be an integer we want to get two + // integers as arguments. + if (propagatedType.isInteger()) return HType.INTEGER; + // If the outgoing type should be a number we can get that if both inputs + // are numbers. If we don't know the outgoing type we try to make it a + // number. + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.NUMBER; + } + // Even if the desired outgoing type is not a number we still want the + // second argument to be a number if the first one is a number. This will + // not help for the outgoing type, but at least the binary arithmetic + // operation will not have type problems. + // TODO(floitsch): normally we shouldn't request a number, but simply + // throw an ArgumentError if it isn't. This would be similar + // to the array case. + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (input == right && left.isNumber(types)) return HType.NUMBER; + return HType.UNKNOWN; + } + + bool isBuiltin(HInvokeDynamic instruction, HTypeMap types) { + return instruction.inputs[1].isNumber(types) + && instruction.inputs[2].isNumber(types); + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (isBuiltin(instruction, types)) { + HInstruction builtin = + newBuiltinVariant(instruction.inputs[1], instruction.inputs[2]); + if (builtin != null) return builtin; + // Even if there is no builtin equivalent instruction, we know + // the instruction does not have any side effect, and that it + // can be GVN'ed. + instruction.clearAllSideEffects(); + instruction.clearAllDependencies(); + instruction.setUseGvn(); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right); +} + +class AddSpecializer extends BinaryArithmeticSpecializer { + const AddSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.add; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HAdd(left, right); + } +} + +class DivideSpecializer extends BinaryArithmeticSpecializer { + const DivideSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.divide; + } + + HType computeTypeFromInputTypes(HInstruction instruction, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + if (left.isNumber(types)) return HType.DOUBLE; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInstruction instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + // A division can never return an integer. So don't ask for integer inputs. + if (instruction.isInteger(types)) return HType.UNKNOWN; + return super.computeDesiredTypeForInput( + instruction, input, types, compiler); + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HDivide(left, right); + } +} + +class ModuloSpecializer extends BinaryArithmeticSpecializer { + const ModuloSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.modulo; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Modulo cannot be mapped to the native operator (different semantics). + return null; + } +} + +class MultiplySpecializer extends BinaryArithmeticSpecializer { + const MultiplySpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.multiply; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HMultiply(left, right); + } +} + +class SubtractSpecializer extends BinaryArithmeticSpecializer { + const SubtractSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.subtract; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HSubtract(left, right); + } +} + +class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer { + const TruncatingDivideSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.truncatingDivide; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Truncating divide does not have a JS equivalent. + return null; + } +} + +abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer { + const BinaryBitOpSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + // All bitwise operations on primitive types either produce an + // integer or throw an error. + HInstruction left = instruction.inputs[1]; + if (left.isPrimitive(types)) return HType.INTEGER; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + HType propagatedType = types[instruction]; + // If the outgoing type should be a number we can get that only if both + // inputs are integers. If we don't know the outgoing type we try to make + // it an integer. + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.INTEGER; + } + return HType.UNKNOWN; + } +} + +class ShiftLeftSpecializer extends BinaryBitOpSpecializer { + const ShiftLeftSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.shiftLeft; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (!left.isNumber(types) || !right.isConstantInteger()) return null; + HConstant rightConstant = right; + IntConstant intConstant = rightConstant.constant; + int count = intConstant.value; + if (count >= 0 && count <= 31) { + return newBuiltinVariant(left, right); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HShiftLeft(left, right); + } +} + +class ShiftRightSpecializer extends BinaryBitOpSpecializer { + const ShiftRightSpecializer(); + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Shift right cannot be mapped to the native operator easily. + return null; + } + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.shiftRight; + } +} + +class BitOrSpecializer extends BinaryBitOpSpecializer { + const BitOrSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitOr; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitOr(left, right); + } +} + +class BitAndSpecializer extends BinaryBitOpSpecializer { + const BitAndSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitAnd; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitAnd(left, right); + } +} + +class BitXorSpecializer extends BinaryBitOpSpecializer { + const BitXorSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitXor; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitXor(left, right); + } +} + +abstract class RelationalSpecializer extends InvokeDynamicSpecializer { + const RelationalSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + if (types[instruction.inputs[1]].isPrimitiveOrNull()) return HType.BOOLEAN; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + HType propagatedType = types[instruction]; + // For all relational operations except HIdentity, we expect to get numbers + // only. With numbers the outgoing type is a boolean. If something else + // is desired, then numbers are incorrect, though. + if (propagatedType.isUnknown() || propagatedType.isBoolean()) { + HInstruction left = instruction.inputs[1]; + if (left.isTypeUnknown(types) || left.isNumber(types)) { + return HType.NUMBER; + } + } + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isNumber(types) && right.isNumber(types)) { + return newBuiltinVariant(left, right); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right); +} + +class EqualsSpecializer extends RelationalSpecializer { + const EqualsSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + HType propagatedType = types[instruction]; + if (input == left && types[right].isUseful()) { + // All our useful types have 'identical' semantics. But we don't want to + // speculatively test for all possible types. Therefore we try to match + // the two types. That is, if we see x == 3, then we speculatively test + // if x is a number and bailout if it isn't. + // If right is a number we don't need more than a number (no need to match + // the exact type of right). + if (right.isNumber(types)) return HType.NUMBER; + return types[right]; + } + // String equality testing is much more common than array equality testing. + if (input == left && left.isIndexablePrimitive(types)) { + return HType.READABLE_ARRAY; + } + // String equality testing is much more common than array equality testing. + if (input == right && right.isIndexablePrimitive(types)) { + return HType.STRING; + } + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (types[left].isPrimitiveOrNull() || right.isConstantNull()) { + return newBuiltinVariant(left, right); + } + return null; + } + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.equal; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HIdentity(left, right); + } +} + +class LessSpecializer extends RelationalSpecializer { + const LessSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.less; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HLess(left, right); + } +} + +class GreaterSpecializer extends RelationalSpecializer { + const GreaterSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.greater; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HGreater(left, right); + } +} + +class GreaterEqualSpecializer extends RelationalSpecializer { + const GreaterEqualSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.greaterEqual; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HGreaterEqual(left, right); + } +} + +class LessEqualSpecializer extends RelationalSpecializer { + const LessEqualSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.lessEqual; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HLessEqual(left, right); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart new file mode 100644 index 0000000..afbd2e1 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart
@@ -0,0 +1,2711 @@ +// 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. + +part of ssa; + +abstract class HVisitor<R> { + R visitAdd(HAdd node); + R visitBailoutTarget(HBailoutTarget node); + R visitBitAnd(HBitAnd node); + R visitBitNot(HBitNot node); + R visitBitOr(HBitOr node); + R visitBitXor(HBitXor node); + R visitBoolify(HBoolify node); + R visitBoundsCheck(HBoundsCheck node); + R visitBreak(HBreak node); + R visitConstant(HConstant node); + R visitContinue(HContinue node); + R visitDivide(HDivide node); + R visitExit(HExit node); + R visitExitTry(HExitTry node); + R visitFieldGet(HFieldGet node); + R visitFieldSet(HFieldSet node); + R visitForeign(HForeign node); + R visitForeignNew(HForeignNew node); + R visitGoto(HGoto node); + R visitGreater(HGreater node); + R visitGreaterEqual(HGreaterEqual node); + R visitIdentity(HIdentity node); + R visitIf(HIf node); + R visitIndex(HIndex node); + R visitIndexAssign(HIndexAssign node); + R visitIntegerCheck(HIntegerCheck node); + R visitInterceptor(HInterceptor node); + R visitInvokeClosure(HInvokeClosure node); + R visitInvokeDynamicGetter(HInvokeDynamicGetter node); + R visitInvokeDynamicMethod(HInvokeDynamicMethod node); + R visitInvokeDynamicSetter(HInvokeDynamicSetter node); + R visitInvokeStatic(HInvokeStatic node); + R visitInvokeSuper(HInvokeSuper node); + R visitIs(HIs node); + R visitLazyStatic(HLazyStatic node); + R visitLess(HLess node); + R visitLessEqual(HLessEqual node); + R visitLiteralList(HLiteralList node); + R visitLocalGet(HLocalGet node); + R visitLocalSet(HLocalSet node); + R visitLocalValue(HLocalValue node); + R visitLoopBranch(HLoopBranch node); + R visitMultiply(HMultiply node); + R visitNegate(HNegate node); + R visitNot(HNot node); + R visitOneShotInterceptor(HOneShotInterceptor); + R visitParameterValue(HParameterValue node); + R visitPhi(HPhi node); + R visitRangeConversion(HRangeConversion node); + R visitReturn(HReturn node); + R visitShiftLeft(HShiftLeft node); + R visitStatic(HStatic node); + R visitStaticStore(HStaticStore node); + R visitStringConcat(HStringConcat node); + R visitSubtract(HSubtract node); + R visitSwitch(HSwitch node); + R visitThis(HThis node); + R visitThrow(HThrow node); + R visitTry(HTry node); + R visitTypeGuard(HTypeGuard node); + R visitTypeConversion(HTypeConversion node); +} + +abstract class HGraphVisitor { + visitDominatorTree(HGraph graph) { + void visitBasicBlockAndSuccessors(HBasicBlock block) { + visitBasicBlock(block); + List dominated = block.dominatedBlocks; + for (int i = 0; i < dominated.length; i++) { + visitBasicBlockAndSuccessors(dominated[i]); + } + } + + visitBasicBlockAndSuccessors(graph.entry); + } + + visitPostDominatorTree(HGraph graph) { + void visitBasicBlockAndSuccessors(HBasicBlock block) { + List dominated = block.dominatedBlocks; + for (int i = dominated.length - 1; i >= 0; i--) { + visitBasicBlockAndSuccessors(dominated[i]); + } + visitBasicBlock(block); + } + + visitBasicBlockAndSuccessors(graph.entry); + } + + visitBasicBlock(HBasicBlock block); +} + +abstract class HInstructionVisitor extends HGraphVisitor { + HBasicBlock currentBlock; + + visitInstruction(HInstruction node); + + visitBasicBlock(HBasicBlock node) { + void visitInstructionList(HInstructionList list) { + HInstruction instruction = list.first; + while (instruction != null) { + visitInstruction(instruction); + instruction = instruction.next; + assert(instruction != list.first); + } + } + + currentBlock = node; + visitInstructionList(node); + } +} + +class HGraph { + HBasicBlock entry; + HBasicBlock exit; + HThis thisInstruction; + bool isRecursiveMethod = false; + bool calledInLoop = false; + final List<HBasicBlock> blocks; + + // We canonicalize all constants used within a graph so we do not + // have to worry about them for global value numbering. + Map<Constant, HConstant> constants; + + HGraph() + : blocks = new List<HBasicBlock>(), + constants = new Map<Constant, HConstant>() { + entry = addNewBlock(); + // The exit block will be added later, so it has an id that is + // after all others in the system. + exit = new HBasicBlock(); + } + + void addBlock(HBasicBlock block) { + int id = blocks.length; + block.id = id; + blocks.add(block); + assert(identical(blocks[id], block)); + } + + HBasicBlock addNewBlock() { + HBasicBlock result = new HBasicBlock(); + addBlock(result); + return result; + } + + HBasicBlock addNewLoopHeaderBlock(TargetElement target, + List<LabelElement> labels) { + HBasicBlock result = addNewBlock(); + result.loopInformation = + new HLoopInformation(result, target, labels); + return result; + } + + static HType mapConstantTypeToSsaType(Constant constant) { + if (constant.isNull()) return HType.NULL; + if (constant.isBool()) return HType.BOOLEAN; + if (constant.isInt()) return HType.INTEGER; + if (constant.isDouble()) return HType.DOUBLE; + if (constant.isString()) return HType.STRING; + if (constant.isList()) return HType.READABLE_ARRAY; + if (constant.isFunction()) return HType.UNKNOWN; + if (constant.isSentinel()) return HType.UNKNOWN; + ObjectConstant objectConstant = constant; + return new HBoundedType.exact(objectConstant.type); + } + + HConstant addConstant(Constant constant) { + HConstant result = constants[constant]; + if (result == null) { + HType type = mapConstantTypeToSsaType(constant); + result = new HConstant.internal(constant, type); + entry.addAtExit(result); + constants[constant] = result; + } else if (result.block == null) { + // The constant was not used anymore. + entry.addAtExit(result); + } + return result; + } + + HConstant addConstantInt(int i, ConstantSystem constantSystem) { + return addConstant(constantSystem.createInt(i)); + } + + HConstant addConstantDouble(double d, ConstantSystem constantSystem) { + return addConstant(constantSystem.createDouble(d)); + } + + HConstant addConstantString(DartString str, + Node diagnosticNode, + ConstantSystem constantSystem) { + return addConstant(constantSystem.createString(str, diagnosticNode)); + } + + HConstant addConstantBool(bool value, ConstantSystem constantSystem) { + return addConstant(constantSystem.createBool(value)); + } + + HConstant addConstantNull(ConstantSystem constantSystem) { + return addConstant(constantSystem.createNull()); + } + + void finalize() { + addBlock(exit); + exit.open(); + exit.close(new HExit()); + assignDominators(); + } + + void assignDominators() { + // Run through the blocks in order of increasing ids so we are + // guaranteed that we have computed dominators for all blocks + // higher up in the dominator tree. + for (int i = 0, length = blocks.length; i < length; i++) { + HBasicBlock block = blocks[i]; + List<HBasicBlock> predecessors = block.predecessors; + if (block.isLoopHeader()) { + block.assignCommonDominator(predecessors[0]); + } else { + for (int j = predecessors.length - 1; j >= 0; j--) { + block.assignCommonDominator(predecessors[j]); + } + } + } + } + + bool isValid() { + HValidator validator = new HValidator(); + validator.visitGraph(this); + return validator.isValid; + } +} + +class HBaseVisitor extends HGraphVisitor implements HVisitor { + HBasicBlock currentBlock; + + visitBasicBlock(HBasicBlock node) { + currentBlock = node; + + HInstruction instruction = node.first; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.next; + } + } + + visitInstruction(HInstruction instruction) {} + + visitBinaryArithmetic(HBinaryArithmetic node) => visitInvokeBinary(node); + visitBinaryBitOp(HBinaryBitOp node) => visitBinaryArithmetic(node); + visitInvoke(HInvoke node) => visitInstruction(node); + visitInvokeBinary(HInvokeBinary node) => visitInstruction(node); + visitInvokeDynamic(HInvokeDynamic node) => visitInvoke(node); + visitInvokeDynamicField(HInvokeDynamicField node) => visitInvokeDynamic(node); + visitInvokeUnary(HInvokeUnary node) => visitInstruction(node); + visitConditionalBranch(HConditionalBranch node) => visitControlFlow(node); + visitControlFlow(HControlFlow node) => visitInstruction(node); + visitFieldAccess(HFieldAccess node) => visitInstruction(node); + visitRelational(HRelational node) => visitInvokeBinary(node); + + visitAdd(HAdd node) => visitBinaryArithmetic(node); + visitBailoutTarget(HBailoutTarget node) => visitInstruction(node); + visitBitAnd(HBitAnd node) => visitBinaryBitOp(node); + visitBitNot(HBitNot node) => visitInvokeUnary(node); + visitBitOr(HBitOr node) => visitBinaryBitOp(node); + visitBitXor(HBitXor node) => visitBinaryBitOp(node); + visitBoolify(HBoolify node) => visitInstruction(node); + visitBoundsCheck(HBoundsCheck node) => visitCheck(node); + visitBreak(HBreak node) => visitJump(node); + visitContinue(HContinue node) => visitJump(node); + visitCheck(HCheck node) => visitInstruction(node); + visitConstant(HConstant node) => visitInstruction(node); + visitDivide(HDivide node) => visitBinaryArithmetic(node); + visitExit(HExit node) => visitControlFlow(node); + visitExitTry(HExitTry node) => visitControlFlow(node); + visitFieldGet(HFieldGet node) => visitFieldAccess(node); + visitFieldSet(HFieldSet node) => visitFieldAccess(node); + visitForeign(HForeign node) => visitInstruction(node); + visitForeignNew(HForeignNew node) => visitForeign(node); + visitGoto(HGoto node) => visitControlFlow(node); + visitGreater(HGreater node) => visitRelational(node); + visitGreaterEqual(HGreaterEqual node) => visitRelational(node); + visitIdentity(HIdentity node) => visitRelational(node); + visitIf(HIf node) => visitConditionalBranch(node); + visitIndex(HIndex node) => visitInstruction(node); + visitIndexAssign(HIndexAssign node) => visitInstruction(node); + visitIntegerCheck(HIntegerCheck node) => visitCheck(node); + visitInterceptor(HInterceptor node) => visitInstruction(node); + visitInvokeClosure(HInvokeClosure node) + => visitInvokeDynamic(node); + visitInvokeDynamicMethod(HInvokeDynamicMethod node) + => visitInvokeDynamic(node); + visitInvokeDynamicGetter(HInvokeDynamicGetter node) + => visitInvokeDynamicField(node); + visitInvokeDynamicSetter(HInvokeDynamicSetter node) + => visitInvokeDynamicField(node); + visitInvokeStatic(HInvokeStatic node) => visitInvoke(node); + visitInvokeSuper(HInvokeSuper node) => visitInvoke(node); + visitJump(HJump node) => visitControlFlow(node); + visitLazyStatic(HLazyStatic node) => visitInstruction(node); + visitLess(HLess node) => visitRelational(node); + visitLessEqual(HLessEqual node) => visitRelational(node); + visitLiteralList(HLiteralList node) => visitInstruction(node); + visitLocalGet(HLocalGet node) => visitFieldAccess(node); + visitLocalSet(HLocalSet node) => visitFieldAccess(node); + visitLocalValue(HLocalValue node) => visitInstruction(node); + visitLoopBranch(HLoopBranch node) => visitConditionalBranch(node); + visitNegate(HNegate node) => visitInvokeUnary(node); + visitNot(HNot node) => visitInstruction(node); + visitOneShotInterceptor(HOneShotInterceptor node) + => visitInvokeDynamic(node); + visitPhi(HPhi node) => visitInstruction(node); + visitMultiply(HMultiply node) => visitBinaryArithmetic(node); + visitParameterValue(HParameterValue node) => visitLocalValue(node); + visitRangeConversion(HRangeConversion node) => visitCheck(node); + visitReturn(HReturn node) => visitControlFlow(node); + visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node); + visitSubtract(HSubtract node) => visitBinaryArithmetic(node); + visitSwitch(HSwitch node) => visitControlFlow(node); + visitStatic(HStatic node) => visitInstruction(node); + visitStaticStore(HStaticStore node) => visitInstruction(node); + visitStringConcat(HStringConcat node) => visitInstruction(node); + visitThis(HThis node) => visitParameterValue(node); + visitThrow(HThrow node) => visitControlFlow(node); + visitTry(HTry node) => visitControlFlow(node); + visitTypeGuard(HTypeGuard node) => visitCheck(node); + visitIs(HIs node) => visitInstruction(node); + visitTypeConversion(HTypeConversion node) => visitCheck(node); +} + +class SubGraph { + // The first and last block of the sub-graph. + final HBasicBlock start; + final HBasicBlock end; + + const SubGraph(this.start, this.end); + + bool contains(HBasicBlock block) { + assert(start != null); + assert(end != null); + assert(block != null); + return start.id <= block.id && block.id <= end.id; + } +} + +class SubExpression extends SubGraph { + const SubExpression(HBasicBlock start, HBasicBlock end) + : super(start, end); + + /** Find the condition expression if this sub-expression is a condition. */ + HInstruction get conditionExpression { + HInstruction last = end.last; + if (last is HConditionalBranch || last is HSwitch) return last.inputs[0]; + return null; + } +} + +class HInstructionList { + HInstruction first = null; + HInstruction last = null; + + bool get isEmpty { + return first == null; + } + + void internalAddAfter(HInstruction cursor, HInstruction instruction) { + if (cursor == null) { + assert(isEmpty); + first = last = instruction; + } else if (identical(cursor, last)) { + last.next = instruction; + instruction.previous = last; + last = instruction; + } else { + instruction.previous = cursor; + instruction.next = cursor.next; + cursor.next.previous = instruction; + cursor.next = instruction; + } + } + + void internalAddBefore(HInstruction cursor, HInstruction instruction) { + if (cursor == null) { + assert(isEmpty); + first = last = instruction; + } else if (identical(cursor, first)) { + first.previous = instruction; + instruction.next = first; + first = instruction; + } else { + instruction.next = cursor; + instruction.previous = cursor.previous; + cursor.previous.next = instruction; + cursor.previous = instruction; + } + } + + void detach(HInstruction instruction) { + assert(contains(instruction)); + assert(instruction.isInBasicBlock()); + if (instruction.previous == null) { + first = instruction.next; + } else { + instruction.previous.next = instruction.next; + } + if (instruction.next == null) { + last = instruction.previous; + } else { + instruction.next.previous = instruction.previous; + } + instruction.previous = null; + instruction.next = null; + } + + void remove(HInstruction instruction) { + assert(instruction.usedBy.isEmpty); + detach(instruction); + } + + /** Linear search for [instruction]. */ + bool contains(HInstruction instruction) { + HInstruction cursor = first; + while (cursor != null) { + if (identical(cursor, instruction)) return true; + cursor = cursor.next; + } + return false; + } +} + +class HBasicBlock extends HInstructionList { + // The [id] must be such that any successor's id is greater than + // this [id]. The exception are back-edges. + int id; + + static const int STATUS_NEW = 0; + static const int STATUS_OPEN = 1; + static const int STATUS_CLOSED = 2; + int status = STATUS_NEW; + + HInstructionList phis; + + HLoopInformation loopInformation = null; + HBlockFlow blockFlow = null; + HBasicBlock parentLoopHeader = null; + List<HBailoutTarget> bailoutTargets; + + final List<HBasicBlock> predecessors; + List<HBasicBlock> successors; + + HBasicBlock dominator = null; + final List<HBasicBlock> dominatedBlocks; + + HBasicBlock() : this.withId(null); + HBasicBlock.withId(this.id) + : phis = new HInstructionList(), + predecessors = <HBasicBlock>[], + successors = const <HBasicBlock>[], + dominatedBlocks = <HBasicBlock>[], + bailoutTargets = <HBailoutTarget>[]; + + int get hashCode => id; + + bool isNew() => status == STATUS_NEW; + bool isOpen() => status == STATUS_OPEN; + bool isClosed() => status == STATUS_CLOSED; + + bool isLoopHeader() { + return loopInformation != null; + } + + void setBlockFlow(HBlockInformation blockInfo, HBasicBlock continuation) { + blockFlow = new HBlockFlow(blockInfo, continuation); + } + + bool isLabeledBlock() => + blockFlow != null && + blockFlow.body is HLabeledBlockInformation; + + HBasicBlock get enclosingLoopHeader { + if (isLoopHeader()) return this; + return parentLoopHeader; + } + + bool hasBailoutTargets() => !bailoutTargets.isEmpty; + + void open() { + assert(isNew()); + status = STATUS_OPEN; + } + + void close(HControlFlow end) { + assert(isOpen()); + addAfter(last, end); + status = STATUS_CLOSED; + } + + void addAtEntry(HInstruction instruction) { + assert(instruction is !HPhi); + internalAddBefore(first, instruction); + instruction.notifyAddedToBlock(this); + } + + void addAtExit(HInstruction instruction) { + assert(isClosed()); + assert(last is HControlFlow); + assert(instruction is !HPhi); + internalAddBefore(last, instruction); + instruction.notifyAddedToBlock(this); + } + + void moveAtExit(HInstruction instruction) { + assert(instruction is !HPhi); + assert(instruction.isInBasicBlock()); + assert(isClosed()); + assert(last is HControlFlow); + internalAddBefore(last, instruction); + instruction.block = this; + assert(isValid()); + } + + void add(HInstruction instruction) { + assert(instruction is !HControlFlow); + assert(instruction is !HPhi); + internalAddAfter(last, instruction); + instruction.notifyAddedToBlock(this); + } + + void addPhi(HPhi phi) { + phis.internalAddAfter(phis.last, phi); + phi.notifyAddedToBlock(this); + } + + void removePhi(HPhi phi) { + phis.remove(phi); + assert(phi.block == this); + phi.notifyRemovedFromBlock(); + } + + void addAfter(HInstruction cursor, HInstruction instruction) { + assert(cursor is !HPhi); + assert(instruction is !HPhi); + assert(isOpen() || isClosed()); + internalAddAfter(cursor, instruction); + instruction.notifyAddedToBlock(this); + } + + void addBefore(HInstruction cursor, HInstruction instruction) { + assert(cursor is !HPhi); + assert(instruction is !HPhi); + assert(isOpen() || isClosed()); + internalAddBefore(cursor, instruction); + instruction.notifyAddedToBlock(this); + } + + void remove(HInstruction instruction) { + assert(isOpen() || isClosed()); + assert(instruction is !HPhi); + super.remove(instruction); + assert(instruction.block == this); + instruction.notifyRemovedFromBlock(); + } + + void addSuccessor(HBasicBlock block) { + if (successors.isEmpty) { + successors = [block]; + } else { + successors.add(block); + } + block.predecessors.add(this); + } + + void postProcessLoopHeader() { + assert(isLoopHeader()); + // Only the first entry into the loop is from outside the + // loop. All other entries must be back edges. + for (int i = 1, length = predecessors.length; i < length; i++) { + loopInformation.addBackEdge(predecessors[i]); + } + } + + /** + * Rewrites all uses of the [from] instruction to using the [to] + * instruction instead. + */ + void rewrite(HInstruction from, HInstruction to) { + for (HInstruction use in from.usedBy) { + use.rewriteInput(from, to); + } + to.usedBy.addAll(from.usedBy); + from.usedBy.clear(); + } + + /** + * Rewrites all uses of the [from] instruction to using either the + * [to] instruction, or a [HCheck] instruction that has better type + * information on [to], and that dominates the user. + */ + void rewriteWithBetterUser(HInstruction from, HInstruction to) { + Link<HCheck> better = const Link<HCheck>(); + for (HInstruction user in to.usedBy) { + if (user is HCheck && identical((user as HCheck).checkedInput, to)) { + better = better.prepend(user); + } + } + + if (better.isEmpty) return rewrite(from, to); + + L1: for (HInstruction user in from.usedBy) { + for (HCheck check in better) { + if (check.dominates(user)) { + user.rewriteInput(from, check); + check.usedBy.add(user); + continue L1; + } + } + user.rewriteInput(from, to); + to.usedBy.add(user); + } + from.usedBy.clear(); + } + + bool isExitBlock() { + return identical(first, last) && first is HExit; + } + + void addDominatedBlock(HBasicBlock block) { + assert(isClosed()); + assert(id != null && block.id != null); + assert(dominatedBlocks.indexOf(block) < 0); + // Keep the list of dominated blocks sorted such that if there are two + // succeeding blocks in the list, the predecessor is before the successor. + // Assume that we add the dominated blocks in the right order. + int index = dominatedBlocks.length; + while (index > 0 && dominatedBlocks[index - 1].id > block.id) { + index--; + } + if (index == dominatedBlocks.length) { + dominatedBlocks.add(block); + } else { + dominatedBlocks.insertRange(index, 1, block); + } + assert(block.dominator == null); + block.dominator = this; + } + + void removeDominatedBlock(HBasicBlock block) { + assert(isClosed()); + assert(id != null && block.id != null); + int index = dominatedBlocks.indexOf(block); + assert(index >= 0); + if (index == dominatedBlocks.length - 1) { + dominatedBlocks.removeLast(); + } else { + dominatedBlocks.removeRange(index, 1); + } + assert(identical(block.dominator, this)); + block.dominator = null; + } + + void assignCommonDominator(HBasicBlock predecessor) { + assert(isClosed()); + if (dominator == null) { + // If this basic block doesn't have a dominator yet we use the + // given predecessor as the dominator. + predecessor.addDominatedBlock(this); + } else if (predecessor.dominator != null) { + // If the predecessor has a dominator and this basic block has a + // dominator, we find a common parent in the dominator tree and + // use that as the dominator. + HBasicBlock block0 = dominator; + HBasicBlock block1 = predecessor; + while (!identical(block0, block1)) { + if (block0.id > block1.id) { + block0 = block0.dominator; + } else { + block1 = block1.dominator; + } + assert(block0 != null && block1 != null); + } + if (!identical(dominator, block0)) { + dominator.removeDominatedBlock(this); + block0.addDominatedBlock(this); + } + } + } + + void forEachPhi(void f(HPhi phi)) { + HPhi current = phis.first; + while (current != null) { + HInstruction saved = current.next; + f(current); + current = saved; + } + } + + void forEachInstruction(void f(HInstruction instruction)) { + HInstruction current = first; + while (current != null) { + HInstruction saved = current.next; + f(current); + current = saved; + } + } + + bool isValid() { + assert(isClosed()); + HValidator validator = new HValidator(); + validator.visitBasicBlock(this); + return validator.isValid; + } + + // TODO(ngeoffray): Cache the information if this method ends up + // being hot. + bool dominates(HBasicBlock other) { + do { + if (identical(this, other)) return true; + other = other.dominator; + } while (other != null && other.id >= id); + return false; + } +} + + +abstract class HInstruction implements Spannable { + Element sourceElement; + SourceFileLocation sourcePosition; + + final int id; + static int idCounter; + + final List<HInstruction> inputs; + final List<HInstruction> usedBy; + + HBasicBlock block; + HInstruction previous = null; + HInstruction next = null; + int flags = 0; + + // Changes flags. + static const int FLAG_CHANGES_INDEX = 0; + static const int FLAG_CHANGES_INSTANCE_PROPERTY = FLAG_CHANGES_INDEX + 1; + static const int FLAG_CHANGES_STATIC_PROPERTY + = FLAG_CHANGES_INSTANCE_PROPERTY + 1; + static const int FLAG_CHANGES_COUNT = FLAG_CHANGES_STATIC_PROPERTY + 1; + + // Depends flags (one for each changes flag). + static const int FLAG_DEPENDS_ON_INDEX_STORE = FLAG_CHANGES_COUNT; + static const int FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE = + FLAG_DEPENDS_ON_INDEX_STORE + 1; + static const int FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE = + FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE + 1; + static const int FLAG_DEPENDS_ON_COUNT = + FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE + 1; + + // Other flags. + static const int FLAG_USE_GVN = FLAG_DEPENDS_ON_COUNT; + + // Type codes. + static const int UNDEFINED_TYPECODE = -1; + static const int BOOLIFY_TYPECODE = 0; + static const int TYPE_GUARD_TYPECODE = 1; + static const int BOUNDS_CHECK_TYPECODE = 2; + static const int INTEGER_CHECK_TYPECODE = 3; + static const int INTERCEPTOR_TYPECODE = 4; + static const int ADD_TYPECODE = 5; + static const int DIVIDE_TYPECODE = 6; + static const int MULTIPLY_TYPECODE = 7; + static const int SUBTRACT_TYPECODE = 8; + static const int SHIFT_LEFT_TYPECODE = 9; + static const int BIT_OR_TYPECODE = 10; + static const int BIT_AND_TYPECODE = 11; + static const int BIT_XOR_TYPECODE = 12; + static const int NEGATE_TYPECODE = 13; + static const int BIT_NOT_TYPECODE = 14; + static const int NOT_TYPECODE = 15; + static const int IDENTITY_TYPECODE = 16; + static const int GREATER_TYPECODE = 17; + static const int GREATER_EQUAL_TYPECODE = 18; + static const int LESS_TYPECODE = 19; + static const int LESS_EQUAL_TYPECODE = 20; + static const int STATIC_TYPECODE = 21; + static const int STATIC_STORE_TYPECODE = 22; + static const int FIELD_GET_TYPECODE = 23; + static const int TYPE_CONVERSION_TYPECODE = 24; + static const int BAILOUT_TARGET_TYPECODE = 25; + static const int INVOKE_STATIC_TYPECODE = 26; + static const int INDEX_TYPECODE = 27; + static const int IS_TYPECODE = 28; + static const int INVOKE_DYNAMIC_TYPECODE = 29; + + HInstruction(this.inputs) : id = idCounter++, usedBy = <HInstruction>[]; + + int get hashCode => id; + + bool getFlag(int position) => (flags & (1 << position)) != 0; + void setFlag(int position) { flags |= (1 << position); } + void clearFlag(int position) { flags &= ~(1 << position); } + + static int computeDependsOnFlags(int flags) => flags << FLAG_CHANGES_COUNT; + + int getChangesFlags() => flags & ((1 << FLAG_CHANGES_COUNT) - 1); + int getDependsOnFlags() { + return (flags & ((1 << FLAG_DEPENDS_ON_COUNT) - 1)) >> FLAG_CHANGES_COUNT; + } + + bool hasSideEffects() => getChangesFlags() != 0; + bool dependsOnSomething() => getDependsOnFlags() != 0; + + void setAllSideEffects() { flags |= ((1 << FLAG_CHANGES_COUNT) - 1); } + void clearAllSideEffects() { flags &= ~((1 << FLAG_CHANGES_COUNT) - 1); } + + void setDependsOnSomething() { + int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT; + flags |= (((1 << count) - 1) << FLAG_CHANGES_COUNT); + } + void clearAllDependencies() { + int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT; + flags &= ~(((1 << count) - 1) << FLAG_CHANGES_COUNT); + } + + bool dependsOnStaticPropertyStore() { + return getFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE); + } + void setDependsOnStaticPropertyStore() { + setFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE); + } + void setChangesStaticProperty() { setFlag(FLAG_CHANGES_STATIC_PROPERTY); } + + bool dependsOnIndexStore() => getFlag(FLAG_DEPENDS_ON_INDEX_STORE); + void setDependsOnIndexStore() { setFlag(FLAG_DEPENDS_ON_INDEX_STORE); } + void setChangesIndex() { setFlag(FLAG_CHANGES_INDEX); } + + bool dependsOnInstancePropertyStore() { + return getFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE); + } + void setDependsOnInstancePropertyStore() { + setFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE); + } + void setChangesInstanceProperty() { setFlag(FLAG_CHANGES_INSTANCE_PROPERTY); } + + bool useGvn() => getFlag(FLAG_USE_GVN); + void setUseGvn() { setFlag(FLAG_USE_GVN); } + + void updateInput(int i, HInstruction insn) { + inputs[i] = insn; + } + + /** + * A pure instruction is an instruction that does not have any side + * effect, nor any dependency. They can be moved anywhere in the + * graph. + */ + bool isPure() => !hasSideEffects() && !dependsOnSomething() && !canThrow(); + + // Can this node throw an exception? + bool canThrow() => false; + + // Does this node potentially affect control flow. + bool isControlFlow() => false; + + // All isFunctions work on the propagated types. + bool isArray(HTypeMap types) => types[this].isArray(); + bool isReadableArray(HTypeMap types) => types[this].isReadableArray(); + bool isMutableArray(HTypeMap types) => types[this].isMutableArray(); + bool isExtendableArray(HTypeMap types) => types[this].isExtendableArray(); + bool isFixedArray(HTypeMap types) => types[this].isFixedArray(); + bool isBoolean(HTypeMap types) => types[this].isBoolean(); + bool isInteger(HTypeMap types) => types[this].isInteger(); + bool isDouble(HTypeMap types) => types[this].isDouble(); + bool isNumber(HTypeMap types) => types[this].isNumber(); + bool isNumberOrNull(HTypeMap types) => types[this].isNumberOrNull(); + bool isString(HTypeMap types) => types[this].isString(); + bool isTypeUnknown(HTypeMap types) => types[this].isUnknown(); + bool isIndexablePrimitive(HTypeMap types) + => types[this].isIndexablePrimitive(); + bool isPrimitive(HTypeMap types) => types[this].isPrimitive(); + bool canBePrimitive(HTypeMap types) => types[this].canBePrimitive(); + bool canBeNull(HTypeMap types) => types[this].canBeNull(); + + /** + * This is the type the instruction is guaranteed to have. It does not + * take any propagation into account. + */ + HType guaranteedType = HType.UNKNOWN; + bool hasGuaranteedType() => !guaranteedType.isUnknown(); + + /** + * Some instructions have a good idea of their return type, but cannot + * guarantee the type. The computed does not need to be more specialized + * than the provided type for [this]. + * + * Examples: the likely type of [:x == y:] is a boolean. In most cases this + * cannot be guaranteed, but when merging types we still want to use this + * information. + * + * Similarily the [HAdd] instruction is likely a number. Note that, even if + * the incoming type is already set to integer, the likely type might still + * just return the number type. + */ + HType computeLikelyType(HTypeMap types, Compiler compiler) => types[this]; + + /** + * Compute the type of the instruction by propagating the input types through + * the instruction. + * + * By default just copy the guaranteed type. + */ + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return guaranteedType; + } + + /** + * Compute the desired type for the the given [input]. Aside from using + * other inputs to compute the desired type one should also use + * the given [types] which, during the invocation of this method, + * represents the desired type of [this]. + */ + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + bool isInBasicBlock() => block != null; + + String inputsToString() { + void addAsCommaSeparated(StringBuffer buffer, List<HInstruction> list) { + for (int i = 0; i < list.length; i++) { + if (i != 0) buffer.add(', '); + buffer.add("@${list[i].id}"); + } + } + + StringBuffer buffer = new StringBuffer(); + buffer.add('('); + addAsCommaSeparated(buffer, inputs); + buffer.add(') - used at ['); + addAsCommaSeparated(buffer, usedBy); + buffer.add(']'); + return buffer.toString(); + } + + bool gvnEquals(HInstruction other) { + assert(useGvn() && other.useGvn()); + // Check that the type and the flags match. + bool hasSameType = typeEquals(other); + assert(hasSameType == (typeCode() == other.typeCode())); + if (!hasSameType) return false; + if (flags != other.flags) return false; + // Check that the inputs match. + final int inputsLength = inputs.length; + final List<HInstruction> otherInputs = other.inputs; + if (inputsLength != otherInputs.length) return false; + for (int i = 0; i < inputsLength; i++) { + if (!identical(inputs[i], otherInputs[i])) return false; + } + // Check that the data in the instruction matches. + return dataEquals(other); + } + + int gvnHashCode() { + int result = typeCode(); + int length = inputs.length; + for (int i = 0; i < length; i++) { + result = (result * 19) + (inputs[i].id) + (result >> 7); + } + return result; + } + + // These methods should be overwritten by instructions that + // participate in global value numbering. + int typeCode() => HInstruction.UNDEFINED_TYPECODE; + bool typeEquals(HInstruction other) => false; + bool dataEquals(HInstruction other) => false; + + accept(HVisitor visitor); + + void notifyAddedToBlock(HBasicBlock targetBlock) { + assert(!isInBasicBlock()); + assert(block == null); + // Add [this] to the inputs' uses. + for (int i = 0; i < inputs.length; i++) { + assert(inputs[i].isInBasicBlock()); + inputs[i].usedBy.add(this); + } + block = targetBlock; + assert(isValid()); + } + + void notifyRemovedFromBlock() { + assert(isInBasicBlock()); + assert(usedBy.isEmpty); + + // Remove [this] from the inputs' uses. + for (int i = 0; i < inputs.length; i++) { + inputs[i].removeUser(this); + } + this.block = null; + assert(isValid()); + } + + void rewriteInput(HInstruction from, HInstruction to) { + for (int i = 0; i < inputs.length; i++) { + if (identical(inputs[i], from)) inputs[i] = to; + } + } + + /** Removes all occurrences of [user] from [usedBy]. */ + void removeUser(HInstruction user) { + List<HInstruction> users = usedBy; + int length = users.length; + for (int i = 0; i < length; i++) { + if (identical(users[i], user)) { + users[i] = users[length - 1]; + length--; + } + } + users.length = length; + } + + // Change all uses of [oldInput] by [this] to [newInput]. Also + // updates the [usedBy] of [oldInput] and [newInput]. + void changeUse(HInstruction oldInput, HInstruction newInput) { + for (int i = 0; i < inputs.length; i++) { + if (identical(inputs[i], oldInput)) { + inputs[i] = newInput; + newInput.usedBy.add(this); + } + } + List<HInstruction> oldInputUsers = oldInput.usedBy; + int i = 0; + while (i < oldInputUsers.length) { + if (oldInputUsers[i] == this) { + oldInputUsers[i] = oldInputUsers[oldInput.usedBy.length - 1]; + oldInputUsers.length--; + } else { + i++; + } + } + } + + // Compute the set of users of this instruction that is dominated by + // [other]. If [other] is a user of [this], it is included in the + // returned set. + Set<HInstruction> dominatedUsers(HInstruction other) { + // Keep track of all instructions that we have to deal with later + // and count the number of them that are in the current block. + Set<HInstruction> users = new Set<HInstruction>(); + int usersInCurrentBlock = 0; + + // Run through all the users and see if they are dominated or + // potentially dominated by [other]. + HBasicBlock otherBlock = other.block; + for (int i = 0, length = usedBy.length; i < length; i++) { + HInstruction current = usedBy[i]; + if (otherBlock.dominates(current.block)) { + if (identical(current.block, otherBlock)) usersInCurrentBlock++; + users.add(current); + } + } + + // Run through all the phis in the same block as [other] and remove them + // from the users set. + if (usersInCurrentBlock > 0) { + for (HPhi phi = otherBlock.phis.first; phi != null; phi = phi.next) { + if (users.contains(phi)) { + users.remove(phi); + if (--usersInCurrentBlock == 0) break; + } + } + } + + // Run through all the instructions before [other] and remove them + // from the users set. + if (usersInCurrentBlock > 0) { + HInstruction current = otherBlock.first; + while (!identical(current, other)) { + if (users.contains(current)) { + users.remove(current); + if (--usersInCurrentBlock == 0) break; + } + current = current.next; + } + } + + return users; + } + + void moveBefore(HInstruction other) { + assert(this is !HControlFlow); + assert(this is !HPhi); + assert(other is !HPhi); + block.detach(this); + other.block.internalAddBefore(other, this); + block = other.block; + } + + bool isConstant() => false; + bool isConstantBoolean() => false; + bool isConstantNull() => false; + bool isConstantNumber() => false; + bool isConstantInteger() => false; + bool isConstantString() => false; + bool isConstantList() => false; + bool isConstantMap() => false; + bool isConstantFalse() => false; + bool isConstantTrue() => false; + bool isConstantSentinel() => false; + + bool isValid() { + HValidator validator = new HValidator(); + validator.currentBlock = block; + validator.visitInstruction(this); + return validator.isValid; + } + + /** + * The code for computing a bailout environment, and the code + * generation must agree on what does not need to be captured, + * so should always be generated at use site. + */ + bool isCodeMotionInvariant() => false; + + bool isJsStatement() => false; + + bool dominates(HInstruction other) { + // An instruction does not dominates itself. + if (this == other) return false; + if (block != other.block) return block.dominates(other.block); + + HInstruction current = this.next; + while (current != null) { + if (current == other) return true; + current = current.next; + } + return false; + } + + + HInstruction convertType(Compiler compiler, DartType type, int kind) { + if (type == null) return this; + if (identical(type.element, compiler.dynamicClass)) return this; + if (identical(type.element, compiler.objectClass)) return this; + + // If the original can't be null, type conversion also can't produce null. + bool canBeNull = this.guaranteedType.canBeNull(); + HType convertedType = + new HType.fromBoundedType(type, compiler, canBeNull); + + // No need to convert if we know the instruction has + // [convertedType] as a bound. + if (this.guaranteedType == convertedType) { + return this; + } + + return new HTypeConversion(convertedType, this, kind); + } + + /** + * Return whether the instructions do not belong to a loop or + * belong to the same loop. + */ + bool hasSameLoopHeaderAs(HInstruction other) { + return block.enclosingLoopHeader == other.block.enclosingLoopHeader; + } +} + +class HBoolify extends HInstruction { + HBoolify(HInstruction value) : super(<HInstruction>[value]) { + assert(!hasSideEffects()); + setUseGvn(); + } + + HType get guaranteedType => HType.BOOLEAN; + + accept(HVisitor visitor) => visitor.visitBoolify(this); + int typeCode() => HInstruction.BOOLIFY_TYPECODE; + bool typeEquals(other) => other is HBoolify; + bool dataEquals(HInstruction other) => true; +} + +/** + * A [HCheck] instruction is an instruction that might do a dynamic + * check at runtime on another instruction. To have proper instruction + * dependencies in the graph, instructions that depend on the check + * being done reference the [HCheck] instruction instead of the + * instruction itself. + */ +abstract class HCheck extends HInstruction { + HCheck(inputs) : super(inputs) { + assert(!hasSideEffects()); + setUseGvn(); + } + HInstruction get checkedInput => inputs[0]; + bool isJsStatement() => true; + bool canThrow() => true; +} + +class HBailoutTarget extends HInstruction { + final int state; + bool isEnabled = true; + // For each argument we record how many dummy (unused) arguments should + // precede it, to make sure it lands in the correctly named parameter in the + // bailout function. + List<int> padding; + HBailoutTarget(this.state) : super(<HInstruction>[]) { + assert(!hasSideEffects()); + setUseGvn(); + } + + bool isControlFlow() => isEnabled; + bool isJsStatement() => isEnabled; + + accept(HVisitor visitor) => visitor.visitBailoutTarget(this); + int typeCode() => HInstruction.BAILOUT_TARGET_TYPECODE; + bool typeEquals(other) => other is HBailoutTarget; + bool dataEquals(HBailoutTarget other) => other.state == state; +} + +class HTypeGuard extends HCheck { + final HType guardedType; + bool isEnabled = false; + + HTypeGuard(this.guardedType, HInstruction guarded, HInstruction bailoutTarget) + : super(<HInstruction>[guarded, bailoutTarget]); + + HInstruction get guarded => inputs[0]; + HInstruction get checkedInput => guarded; + HBailoutTarget get bailoutTarget => inputs[1]; + int get state => bailoutTarget.state; + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return isEnabled ? guardedType : types[guarded]; + } + + HType get guaranteedType => isEnabled ? guardedType : HType.UNKNOWN; + + bool isControlFlow() => true; + bool isJsStatement() => isEnabled; + bool canThrow() => isEnabled; + + accept(HVisitor visitor) => visitor.visitTypeGuard(this); + int typeCode() => HInstruction.TYPE_GUARD_TYPECODE; + bool typeEquals(other) => other is HTypeGuard; + bool dataEquals(HTypeGuard other) => guardedType == other.guardedType; +} + +class HBoundsCheck extends HCheck { + static const int ALWAYS_FALSE = 0; + static const int FULL_CHECK = 1; + static const int ALWAYS_ABOVE_ZERO = 2; + static const int ALWAYS_BELOW_LENGTH = 3; + static const int ALWAYS_TRUE = 4; + /** + * Details which tests have been done statically during compilation. + * Default is that all checks must be performed dynamically. + */ + int staticChecks = FULL_CHECK; + + HBoundsCheck(length, index) : super(<HInstruction>[length, index]); + + HInstruction get length => inputs[1]; + HInstruction get index => inputs[0]; + bool isControlFlow() => true; + + HType get guaranteedType => HType.INTEGER; + + accept(HVisitor visitor) => visitor.visitBoundsCheck(this); + int typeCode() => HInstruction.BOUNDS_CHECK_TYPECODE; + bool typeEquals(other) => other is HBoundsCheck; + bool dataEquals(HInstruction other) => true; +} + +class HIntegerCheck extends HCheck { + bool alwaysFalse = false; + + HIntegerCheck(value) : super(<HInstruction>[value]); + + HInstruction get value => inputs[0]; + bool isControlFlow() => true; + + HType get guaranteedType => HType.INTEGER; + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + // If the desired type of the input is already a number, we want + // to specialize it to an integer. + return input.isNumber(types) + ? HType.INTEGER + : super.computeDesiredTypeForInput(input, types, compiler); + } + + accept(HVisitor visitor) => visitor.visitIntegerCheck(this); + int typeCode() => HInstruction.INTEGER_CHECK_TYPECODE; + bool typeEquals(other) => other is HIntegerCheck; + bool dataEquals(HInstruction other) => true; +} + +abstract class HConditionalBranch extends HControlFlow { + HConditionalBranch(inputs) : super(inputs); + HInstruction get condition => inputs[0]; + HBasicBlock get trueBranch => block.successors[0]; + HBasicBlock get falseBranch => block.successors[1]; +} + +abstract class HControlFlow extends HInstruction { + HControlFlow(inputs) : super(inputs); + bool isControlFlow() => true; + bool isJsStatement() => true; +} + +abstract class HInvoke extends HInstruction { + /** + * The first argument must be the target: either an [HStatic] node, or + * the receiver of a method-call. The remaining inputs are the arguments + * to the invocation. + */ + HInvoke(List<HInstruction> inputs) : super(inputs) { + setAllSideEffects(); + setDependsOnSomething(); + } + static const int ARGUMENTS_OFFSET = 1; + bool canThrow() => true; +} + +abstract class HInvokeDynamic extends HInvoke { + final InvokeDynamicSpecializer specializer; + final Selector selector; + Element element; + + HInvokeDynamic(Selector selector, + this.element, + List<HInstruction> inputs, + [bool isIntercepted = false]) + : super(inputs), + this.selector = selector, + specializer = isIntercepted + ? InvokeDynamicSpecializer.lookupSpecializer(selector) + : const InvokeDynamicSpecializer(); + toString() => 'invoke dynamic: $selector'; + HInstruction get receiver => inputs[0]; + + bool get isInterceptorCall { + // We know it's a selector call if it follows the interceptor + // calling convention, which adds the actual receiver as a + // parameter to the call. + return inputs.length - 2 == selector.argumentCount; + } + + int typeCode() => HInstruction.INVOKE_DYNAMIC_TYPECODE; + bool typeEquals(other) => other is HInvokeDynamic; + bool dataEquals(HInvokeDynamic other) { + return selector == other.selector + && element == other.element; + } + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return specializer.computeDesiredTypeForInput(this, input, types, compiler); + } + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return specializer.computeTypeFromInputTypes(this, types, compiler); + } +} + +class HInvokeClosure extends HInvokeDynamic { + HInvokeClosure(Selector selector, List<HInstruction> inputs) + : super(selector, null, inputs) { + assert(selector.isClosureCall()); + } + accept(HVisitor visitor) => visitor.visitInvokeClosure(this); +} + +class HInvokeDynamicMethod extends HInvokeDynamic { + HInvokeDynamicMethod(Selector selector, + List<HInstruction> inputs, + [bool isIntercepted = false]) + : super(selector, null, inputs, isIntercepted); + + String toString() => 'invoke dynamic method: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicMethod(this); + + bool isIndexOperatorOnIndexablePrimitive(HTypeMap types) { + return isInterceptorCall + && selector.kind == SelectorKind.INDEX + && selector.name == const SourceString('[]') + && inputs[1].isIndexablePrimitive(types); + } +} + +abstract class HInvokeDynamicField extends HInvokeDynamic { + final bool isSideEffectFree; + HInvokeDynamicField( + Selector selector, Element element, List<HInstruction> inputs, + this.isSideEffectFree) + : super(selector, element, inputs); + toString() => 'invoke dynamic field: $selector'; +} + +class HInvokeDynamicGetter extends HInvokeDynamicField { + HInvokeDynamicGetter(selector, element, receiver, isSideEffectFree) + : super(selector, element, [receiver], isSideEffectFree) { + clearAllSideEffects(); + if (isSideEffectFree) { + setUseGvn(); + setDependsOnInstancePropertyStore(); + } else { + setDependsOnSomething(); + setAllSideEffects(); + } + } + toString() => 'invoke dynamic getter: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicGetter(this); +} + +class HInvokeDynamicSetter extends HInvokeDynamicField { + HInvokeDynamicSetter(selector, element, receiver, value, isSideEffectFree) + : super(selector, element, [receiver, value], isSideEffectFree) { + clearAllSideEffects(); + if (isSideEffectFree) { + setChangesInstanceProperty(); + } else { + setAllSideEffects(); + setDependsOnSomething(); + } + } + toString() => 'invoke dynamic setter: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicSetter(this); +} + +class HInvokeStatic extends HInvoke { + /** The first input must be the target. */ + HInvokeStatic(inputs, HType type) : super(inputs) { + guaranteedType = type; + } + + toString() => 'invoke static: ${element.name}'; + accept(HVisitor visitor) => visitor.visitInvokeStatic(this); + int typeCode() => HInstruction.INVOKE_STATIC_TYPECODE; + Element get element => target.element; + HStatic get target => inputs[0]; +} + +class HInvokeSuper extends HInvokeStatic { + final bool isSetter; + HInvokeSuper(inputs, {this.isSetter: false}) : super(inputs, HType.UNKNOWN); + toString() => 'invoke super: ${element.name}'; + accept(HVisitor visitor) => visitor.visitInvokeSuper(this); + + HInstruction get value { + assert(isSetter); + // Index 0: the element, index 1: 'this'. + return inputs[2]; + } +} + +abstract class HFieldAccess extends HInstruction { + final Element element; + + HFieldAccess(Element element, List<HInstruction> inputs) + : this.element = element, super(inputs); + + HInstruction get receiver => inputs[0]; +} + +class HFieldGet extends HFieldAccess { + final bool isAssignable; + + HFieldGet(Element element, HInstruction receiver, {bool isAssignable}) + : this.isAssignable = (isAssignable != null) + ? isAssignable + : element.isAssignable(), + super(element, <HInstruction>[receiver]) { + clearAllSideEffects(); + setUseGvn(); + if (this.isAssignable) { + setDependsOnInstancePropertyStore(); + } + } + + // TODO(ngeoffray): Only if input can be null. + bool canThrow() => true; + + accept(HVisitor visitor) => visitor.visitFieldGet(this); + + int typeCode() => HInstruction.FIELD_GET_TYPECODE; + bool typeEquals(other) => other is HFieldGet; + bool dataEquals(HFieldGet other) => element == other.element; + String toString() => "FieldGet $element"; +} + +class HFieldSet extends HFieldAccess { + HFieldSet(Element element, + HInstruction receiver, + HInstruction value) + : super(element, <HInstruction>[receiver, value]) { + clearAllSideEffects(); + setChangesInstanceProperty(); + } + + // TODO(ngeoffray): Only if input can be null. + bool canThrow() => true; + + HInstruction get value => inputs[1]; + accept(HVisitor visitor) => visitor.visitFieldSet(this); + + bool isJsStatement() => true; + String toString() => "FieldSet $element"; +} + +class HLocalGet extends HFieldAccess { + // No need to use GVN for a [HLocalGet], it is just a local + // access. + HLocalGet(Element element, HLocalValue local) + : super(element, <HInstruction>[local]); + + accept(HVisitor visitor) => visitor.visitLocalGet(this); + + HLocalValue get local => inputs[0]; +} + +class HLocalSet extends HFieldAccess { + HLocalSet(Element element, HLocalValue local, HInstruction value) + : super(element, <HInstruction>[local, value]); + + accept(HVisitor visitor) => visitor.visitLocalSet(this); + + HLocalValue get local => inputs[0]; + HInstruction get value => inputs[1]; + bool isJsStatement() => true; +} + +class HForeign extends HInstruction { + final DartString code; + final HType type; + final bool isStatement; + + HForeign(this.code, + this.type, + List<HInstruction> inputs, + {this.isStatement: false}) + : super(inputs) { + setAllSideEffects(); + setDependsOnSomething(); + } + + HForeign.statement(code, List<HInstruction> inputs) + : this(code, HType.UNKNOWN, inputs, isStatement: true); + + accept(HVisitor visitor) => visitor.visitForeign(this); + + HType get guaranteedType => type; + + bool isJsStatement() => isStatement; + bool canThrow() => true; +} + +class HForeignNew extends HForeign { + ClassElement element; + HForeignNew(this.element, HType type, List<HInstruction> inputs) + : super(const LiteralDartString("new"), type, inputs); + accept(HVisitor visitor) => visitor.visitForeignNew(this); +} + +abstract class HInvokeBinary extends HInstruction { + HInvokeBinary(HInstruction left, HInstruction right) + : super(<HInstruction>[left, right]) { + clearAllSideEffects(); + setUseGvn(); + } + + HInstruction get left => inputs[0]; + HInstruction get right => inputs[1]; + + BinaryOperation operation(ConstantSystem constantSystem); +} + +abstract class HBinaryArithmetic extends HInvokeBinary { + HBinaryArithmetic(HInstruction left, HInstruction right) : super(left, right); + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER; + if (left.isDouble(types)) return HType.DOUBLE; + return HType.NUMBER; + } + + BinaryOperation operation(ConstantSystem constantSystem); +} + +class HAdd extends HBinaryArithmetic { + HAdd(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitAdd(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.add; + int typeCode() => HInstruction.ADD_TYPECODE; + bool typeEquals(other) => other is HAdd; + bool dataEquals(HInstruction other) => true; +} + +class HDivide extends HBinaryArithmetic { + HDivide(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitDivide(this); + + HType get guaranteedType => HType.DOUBLE; + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.divide; + int typeCode() => HInstruction.DIVIDE_TYPECODE; + bool typeEquals(other) => other is HDivide; + bool dataEquals(HInstruction other) => true; +} + +class HMultiply extends HBinaryArithmetic { + HMultiply(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitMultiply(this); + + BinaryOperation operation(ConstantSystem operations) + => operations.multiply; + int typeCode() => HInstruction.MULTIPLY_TYPECODE; + bool typeEquals(other) => other is HMultiply; + bool dataEquals(HInstruction other) => true; +} + +class HSubtract extends HBinaryArithmetic { + HSubtract(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitSubtract(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.subtract; + int typeCode() => HInstruction.SUBTRACT_TYPECODE; + bool typeEquals(other) => other is HSubtract; + bool dataEquals(HInstruction other) => true; +} + +/** + * An [HSwitch] instruction has one input for the incoming + * value, and one input per constant that it can switch on. + * Its block has one successor per constant, and one for the default. + */ +class HSwitch extends HControlFlow { + HSwitch(List<HInstruction> inputs) : super(inputs); + + HConstant constant(int index) => inputs[index + 1]; + HInstruction get expression => inputs[0]; + + /** + * Provides the target to jump to if none of the constants match + * the expression. If the switch had no default case, this is the + * following join-block. + */ + HBasicBlock get defaultTarget => block.successors.last; + + accept(HVisitor visitor) => visitor.visitSwitch(this); + + String toString() => "HSwitch cases = $inputs"; +} + +// TODO(floitsch): Should HBinaryArithmetic really be the super class of +// HBinaryBitOp? +abstract class HBinaryBitOp extends HBinaryArithmetic { + HBinaryBitOp(HInstruction left, HInstruction right) : super(left, right); + HType get guaranteedType => HType.INTEGER; +} + +class HShiftLeft extends HBinaryBitOp { + HShiftLeft(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitShiftLeft(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.shiftLeft; + int typeCode() => HInstruction.SHIFT_LEFT_TYPECODE; + bool typeEquals(other) => other is HShiftLeft; + bool dataEquals(HInstruction other) => true; +} + +class HBitOr extends HBinaryBitOp { + HBitOr(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitOr(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitOr; + int typeCode() => HInstruction.BIT_OR_TYPECODE; + bool typeEquals(other) => other is HBitOr; + bool dataEquals(HInstruction other) => true; +} + +class HBitAnd extends HBinaryBitOp { + HBitAnd(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitAnd(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitAnd; + int typeCode() => HInstruction.BIT_AND_TYPECODE; + bool typeEquals(other) => other is HBitAnd; + bool dataEquals(HInstruction other) => true; +} + +class HBitXor extends HBinaryBitOp { + HBitXor(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitXor(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitXor; + int typeCode() => HInstruction.BIT_XOR_TYPECODE; + bool typeEquals(other) => other is HBitXor; + bool dataEquals(HInstruction other) => true; +} + +abstract class HInvokeUnary extends HInstruction { + HInvokeUnary(HInstruction input) : super(<HInstruction>[input]) { + clearAllSideEffects(); + setUseGvn(); + } + + HInstruction get operand => inputs[0]; + + UnaryOperation operation(ConstantSystem constantSystem); +} + +class HNegate extends HInvokeUnary { + HNegate(HInstruction input) : super(input); + accept(HVisitor visitor) => visitor.visitNegate(this); + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return types[operand]; + } + + UnaryOperation operation(ConstantSystem constantSystem) + => constantSystem.negate; + int typeCode() => HInstruction.NEGATE_TYPECODE; + bool typeEquals(other) => other is HNegate; + bool dataEquals(HInstruction other) => true; +} + +class HBitNot extends HInvokeUnary { + HBitNot(HInstruction input) : super(input); + accept(HVisitor visitor) => visitor.visitBitNot(this); + + HType get guaranteedType => HType.INTEGER; + UnaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitNot; + int typeCode() => HInstruction.BIT_NOT_TYPECODE; + bool typeEquals(other) => other is HBitNot; + bool dataEquals(HInstruction other) => true; +} + +class HExit extends HControlFlow { + HExit() : super(const <HInstruction>[]); + toString() => 'exit'; + accept(HVisitor visitor) => visitor.visitExit(this); +} + +class HGoto extends HControlFlow { + HGoto() : super(const <HInstruction>[]); + toString() => 'goto'; + accept(HVisitor visitor) => visitor.visitGoto(this); +} + +abstract class HJump extends HControlFlow { + final TargetElement target; + final LabelElement label; + HJump(this.target) : label = null, super(const <HInstruction>[]); + HJump.toLabel(LabelElement label) + : label = label, target = label.target, super(const <HInstruction>[]); +} + +class HBreak extends HJump { + HBreak(TargetElement target) : super(target); + HBreak.toLabel(LabelElement label) : super.toLabel(label); + toString() => (label != null) ? 'break ${label.labelName}' : 'break'; + accept(HVisitor visitor) => visitor.visitBreak(this); +} + +class HContinue extends HJump { + HContinue(TargetElement target) : super(target); + HContinue.toLabel(LabelElement label) : super.toLabel(label); + toString() => (label != null) ? 'continue ${label.labelName}' : 'continue'; + accept(HVisitor visitor) => visitor.visitContinue(this); +} + +class HTry extends HControlFlow { + HLocalValue exception; + HBasicBlock catchBlock; + HBasicBlock finallyBlock; + HTry() : super(const <HInstruction>[]); + toString() => 'try'; + accept(HVisitor visitor) => visitor.visitTry(this); + HBasicBlock get joinBlock => this.block.successors.last; +} + +// An [HExitTry] control flow node is used when the body of a try or +// the body of a catch contains a return, break or continue. To build +// the control flow graph, we explicitly mark the body that +// leads to one of this instruction a predecessor of catch and +// finally. +class HExitTry extends HControlFlow { + HExitTry() : super(const <HInstruction>[]); + toString() => 'exit try'; + accept(HVisitor visitor) => visitor.visitExitTry(this); + HBasicBlock get bodyTrySuccessor => block.successors[0]; +} + +class HIf extends HConditionalBranch { + HBlockFlow blockInformation = null; + HIf(HInstruction condition) : super(<HInstruction>[condition]); + toString() => 'if'; + accept(HVisitor visitor) => visitor.visitIf(this); + + HBasicBlock get thenBlock { + assert(identical(block.dominatedBlocks[0], block.successors[0])); + return block.successors[0]; + } + + HBasicBlock get elseBlock { + assert(identical(block.dominatedBlocks[1], block.successors[1])); + return block.successors[1]; + } + + HBasicBlock get joinBlock => blockInformation.continuation; +} + +class HLoopBranch extends HConditionalBranch { + static const int CONDITION_FIRST_LOOP = 0; + static const int DO_WHILE_LOOP = 1; + + final int kind; + HLoopBranch(HInstruction condition, [this.kind = CONDITION_FIRST_LOOP]) + : super(<HInstruction>[condition]); + toString() => 'loop-branch'; + accept(HVisitor visitor) => visitor.visitLoopBranch(this); + + bool isDoWhile() { + return identical(kind, DO_WHILE_LOOP); + } + + HBasicBlock computeLoopHeader() { + HBasicBlock result; + if (isDoWhile()) { + // In case of a do/while, the successor is a block that avoids + // a critical edge and branchs to the loop header. + result = block.successors[0].successors[0]; + } else { + // For other loops, the loop header might be up the dominator + // tree if the loop condition has control flow. + result = block; + while (!result.isLoopHeader()) result = result.dominator; + } + + assert(result.isLoopHeader()); + return result; + } +} + +class HConstant extends HInstruction { + final Constant constant; + final HType constantType; + HConstant.internal(this.constant, HType this.constantType) + : super(<HInstruction>[]); + + toString() => 'literal: $constant'; + accept(HVisitor visitor) => visitor.visitConstant(this); + + HType get guaranteedType => constantType; + + bool isConstant() => true; + bool isConstantBoolean() => constant.isBool(); + bool isConstantNull() => constant.isNull(); + bool isConstantNumber() => constant.isNum(); + bool isConstantInteger() => constant.isInt(); + bool isConstantString() => constant.isString(); + bool isConstantList() => constant.isList(); + bool isConstantMap() => constant.isMap(); + bool isConstantFalse() => constant.isFalse(); + bool isConstantTrue() => constant.isTrue(); + bool isConstantSentinel() => constant.isSentinel(); + + // Maybe avoid this if the literal is big? + bool isCodeMotionInvariant() => true; +} + +class HNot extends HInstruction { + HNot(HInstruction value) : super(<HInstruction>[value]) { + setUseGvn(); + } + + HType get guaranteedType => HType.BOOLEAN; + + // 'Not' only works on booleans. That's what we want as input. + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.BOOLEAN; + } + + accept(HVisitor visitor) => visitor.visitNot(this); + int typeCode() => HInstruction.NOT_TYPECODE; + bool typeEquals(other) => other is HNot; + bool dataEquals(HInstruction other) => true; +} + +/** + * An [HLocalValue] represents a local. Unlike [HParameterValue]s its + * first use must be in an HLocalSet. That is, [HParameterValue]s have a + * value from the start, whereas [HLocalValue]s need to be initialized first. + */ +class HLocalValue extends HInstruction { + HLocalValue(Element element) : super(<HInstruction>[]) { + sourceElement = element; + } + + toString() => 'local ${sourceElement.name}'; + accept(HVisitor visitor) => visitor.visitLocalValue(this); +} + +class HParameterValue extends HLocalValue { + HParameterValue(Element element) : super(element); + + toString() => 'parameter ${sourceElement.name.slowToString()}'; + accept(HVisitor visitor) => visitor.visitParameterValue(this); +} + +class HThis extends HParameterValue { + HThis(Element element, [HType type = HType.UNKNOWN]) : super(element) { + guaranteedType = type; + } + toString() => 'this'; + accept(HVisitor visitor) => visitor.visitThis(this); + bool isCodeMotionInvariant() => true; +} + +class HPhi extends HInstruction { + static const IS_NOT_LOGICAL_OPERATOR = 0; + static const IS_AND = 1; + static const IS_OR = 2; + + int logicalOperatorType = IS_NOT_LOGICAL_OPERATOR; + + // The order of the [inputs] must correspond to the order of the + // predecessor-edges. That is if an input comes from the first predecessor + // of the surrounding block, then the input must be the first in the [HPhi]. + HPhi(Element element, List<HInstruction> inputs) : super(inputs) { + sourceElement = element; + } + HPhi.noInputs(Element element) : this(element, <HInstruction>[]); + HPhi.singleInput(Element element, HInstruction input) + : this(element, <HInstruction>[input]); + HPhi.manyInputs(Element element, List<HInstruction> inputs) + : this(element, inputs); + + void addInput(HInstruction input) { + assert(isInBasicBlock()); + inputs.add(input); + input.usedBy.add(this); + } + + // Compute the (shared) type of the inputs if any. If all inputs + // have the same known type return it. If any two inputs have + // different known types, we'll return a conflict -- otherwise we'll + // simply return an unknown type. + HType computeInputsType(bool ignoreUnknowns, + HTypeMap types, + Compiler compiler) { + HType candidateType = HType.CONFLICTING; + for (int i = 0, length = inputs.length; i < length; i++) { + HType inputType = types[inputs[i]]; + if (ignoreUnknowns && inputType.isUnknown()) continue; + // Phis need to combine the incoming types using the union operation. + // For example, if one incoming edge has type integer and the other has + // type double, then the phi is either an integer or double and thus has + // type number. + candidateType = candidateType.union(inputType, compiler); + if (candidateType.isUnknown()) return HType.UNKNOWN; + } + return candidateType; + } + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + HType inputsType = computeInputsType(false, types, compiler); + if (inputsType.isConflicting()) return HType.UNKNOWN; + return inputsType; + } + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + HType propagatedType = types[this]; + // Best case scenario for a phi is, when all inputs have the same type. If + // there is no desired outgoing type we therefore try to unify the input + // types (which is basically the [likelyType]). + if (propagatedType.isUnknown()) return computeLikelyType(types, compiler); + // When the desired outgoing type is conflicting we don't need to give any + // requirements on the inputs. + if (propagatedType.isConflicting()) return HType.UNKNOWN; + // Otherwise the input type must match the desired outgoing type. + return propagatedType; + } + + HType computeLikelyType(HTypeMap types, Compiler compiler) { + HType agreedType = computeInputsType(true, types, compiler); + if (agreedType.isConflicting()) return HType.UNKNOWN; + // Don't be too restrictive. If the agreed type is integer or double just + // say that the likely type is number. If more is expected the type will be + // propagated back. + if (agreedType.isNumber()) return HType.NUMBER; + return agreedType; + } + + bool isLogicalOperator() => logicalOperatorType != IS_NOT_LOGICAL_OPERATOR; + + String logicalOperator() { + assert(isLogicalOperator()); + if (logicalOperatorType == IS_AND) return "&&"; + assert(logicalOperatorType == IS_OR); + return "||"; + } + + toString() => 'phi'; + accept(HVisitor visitor) => visitor.visitPhi(this); +} + +abstract class HRelational extends HInvokeBinary { + bool usesBoolifiedInterceptor = false; + HRelational(HInstruction left, HInstruction right) : super(left, right); + HType get guaranteedType => HType.BOOLEAN; +} + +class HIdentity extends HRelational { + HIdentity(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitIdentity(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.identity; + int typeCode() => HInstruction.IDENTITY_TYPECODE; + bool typeEquals(other) => other is HIdentity; + bool dataEquals(HInstruction other) => true; +} + +class HGreater extends HRelational { + HGreater(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitGreater(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.greater; + int typeCode() => HInstruction.GREATER_TYPECODE; + bool typeEquals(other) => other is HGreater; + bool dataEquals(HInstruction other) => true; +} + +class HGreaterEqual extends HRelational { + HGreaterEqual(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitGreaterEqual(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.greaterEqual; + int typeCode() => HInstruction.GREATER_EQUAL_TYPECODE; + bool typeEquals(other) => other is HGreaterEqual; + bool dataEquals(HInstruction other) => true; +} + +class HLess extends HRelational { + HLess(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitLess(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.less; + int typeCode() => HInstruction.LESS_TYPECODE; + bool typeEquals(other) => other is HLess; + bool dataEquals(HInstruction other) => true; +} + +class HLessEqual extends HRelational { + HLessEqual(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitLessEqual(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.lessEqual; + int typeCode() => HInstruction.LESS_EQUAL_TYPECODE; + bool typeEquals(other) => other is HLessEqual; + bool dataEquals(HInstruction other) => true; +} + +class HReturn extends HControlFlow { + HReturn(value) : super(<HInstruction>[value]); + toString() => 'return'; + accept(HVisitor visitor) => visitor.visitReturn(this); +} + +class HThrow extends HControlFlow { + final bool isRethrow; + HThrow(value, {this.isRethrow: false}) : super(<HInstruction>[value]); + toString() => 'throw'; + accept(HVisitor visitor) => visitor.visitThrow(this); +} + +class HStatic extends HInstruction { + final Element element; + HStatic(this.element) : super(<HInstruction>[]) { + assert(element != null); + assert(invariant(this, element.isDeclaration)); + clearAllSideEffects(); + if (element.isAssignable()) { + setDependsOnStaticPropertyStore(); + } + setUseGvn(); + } + toString() => 'static ${element.name}'; + accept(HVisitor visitor) => visitor.visitStatic(this); + + int gvnHashCode() => super.gvnHashCode() ^ element.hashCode; + int typeCode() => HInstruction.STATIC_TYPECODE; + bool typeEquals(other) => other is HStatic; + bool dataEquals(HStatic other) => element == other.element; + bool isCodeMotionInvariant() => !element.isAssignable(); +} + +class HInterceptor extends HInstruction { + Set<ClassElement> interceptedClasses; + HInterceptor(this.interceptedClasses, HInstruction receiver) + : super(<HInstruction>[receiver]) { + clearAllSideEffects(); + setUseGvn(); + } + String toString() => 'interceptor on $interceptedClasses'; + accept(HVisitor visitor) => visitor.visitInterceptor(this); + HInstruction get receiver => inputs[0]; + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + if (interceptedClasses.length != 1) return HType.UNKNOWN; + // If the only class being intercepted is of type number, we + // make this interceptor call say it wants that class as input. + Element interceptor = interceptedClasses.toList()[0]; + JavaScriptBackend backend = compiler.backend; + if (interceptor == backend.jsNumberClass) { + return HType.NUMBER; + } else if (interceptor == backend.jsIntClass) { + return HType.INTEGER; + } else if (interceptor == backend.jsDoubleClass) { + return HType.DOUBLE; + } + return HType.UNKNOWN; + } + + int typeCode() => HInstruction.INTERCEPTOR_TYPECODE; + bool typeEquals(other) => other is HInterceptor; + bool dataEquals(HInterceptor other) { + return interceptedClasses == other.interceptedClasses + || (interceptedClasses.length == other.interceptedClasses.length + && interceptedClasses.containsAll(other.interceptedClasses)); + } +} + +/** + * A "one-shot" interceptor is a call to a synthetized method that + * will fetch the interceptor of its first parameter, and make a call + * on a given selector with the remaining parameters. + * + * In order to share the same optimizations with regular interceptor + * calls, this class extends [HInvokeDynamic] and also has the null + * constant as the first input. + */ +class HOneShotInterceptor extends HInvokeDynamic { + Set<ClassElement> interceptedClasses; + HOneShotInterceptor(Selector selector, + List<HInstruction> inputs, + this.interceptedClasses) + : super(selector, null, inputs, true) { + assert(inputs[0] is HConstant); + assert(inputs[0].guaranteedType == HType.NULL); + } + + String toString() => 'one shot interceptor on $selector'; + accept(HVisitor visitor) => visitor.visitOneShotInterceptor(this); +} + +/** An [HLazyStatic] is a static that is initialized lazily at first read. */ +class HLazyStatic extends HInstruction { + final Element element; + HLazyStatic(this.element) : super(<HInstruction>[]) { + // TODO(4931): The first access has side-effects, but we afterwards we + // should be able to GVN. + setAllSideEffects(); + setDependsOnSomething(); + } + + toString() => 'lazy static ${element.name}'; + accept(HVisitor visitor) => visitor.visitLazyStatic(this); + + int typeCode() => 30; + // TODO(4931): can we do better here? + bool isCodeMotionInvariant() => false; + bool canThrow() => true; +} + +class HStaticStore extends HInstruction { + Element element; + HStaticStore(this.element, HInstruction value) + : super(<HInstruction>[value]) { + clearAllSideEffects(); + setChangesStaticProperty(); + } + toString() => 'static store ${element.name}'; + accept(HVisitor visitor) => visitor.visitStaticStore(this); + + int typeCode() => HInstruction.STATIC_STORE_TYPECODE; + bool typeEquals(other) => other is HStaticStore; + bool dataEquals(HStaticStore other) => element == other.element; + bool isJsStatement() => true; +} + +class HLiteralList extends HInstruction { + HLiteralList(inputs) : super(inputs); + toString() => 'literal list'; + accept(HVisitor visitor) => visitor.visitLiteralList(this); + + HType get guaranteedType => HType.EXTENDABLE_ARRAY; +} + +/** + * The primitive array indexing operation. Note that this instruction + * does not throw because we generate the checks explicitly. + */ +class HIndex extends HInstruction { + HIndex(HInstruction receiver, HInstruction index) + : super(<HInstruction>[receiver, index]) { + clearAllSideEffects(); + setDependsOnIndexStore(); + setUseGvn(); + } + + String toString() => 'index operator'; + accept(HVisitor visitor) => visitor.visitIndex(this); + + HInstruction get receiver => inputs[0]; + HInstruction get index => inputs[1]; + + int typeCode() => HInstruction.INDEX_TYPECODE; + bool typeEquals(HInstruction other) => other is HIndex; + bool dataEquals(HIndex other) => true; +} + +/** + * The primitive array assignment operation. Note that this instruction + * does not throw because we generate the checks explicitly. + */ +class HIndexAssign extends HInstruction { + HIndexAssign(HInstruction receiver, + HInstruction index, + HInstruction value) + : super(<HInstruction>[receiver, index, value]) { + clearAllSideEffects(); + setChangesIndex(); + } + String toString() => 'index assign operator'; + accept(HVisitor visitor) => visitor.visitIndexAssign(this); + + HInstruction get receiver => inputs[0]; + HInstruction get index => inputs[1]; + HInstruction get value => inputs[2]; +} + +class HIs extends HInstruction { + final DartType typeExpression; + final bool nullOk; + + HIs(this.typeExpression, List<HInstruction> inputs, {this.nullOk: false}) + : super(inputs) { + setUseGvn(); + } + + HInstruction get expression => inputs[0]; + HInstruction getCheck(int index) => inputs[index + 1]; + int get checkCount => inputs.length - 1; + + bool hasArgumentChecks() => inputs.length > 1; + + HType get guaranteedType => HType.BOOLEAN; + + accept(HVisitor visitor) => visitor.visitIs(this); + + toString() => "$expression is $typeExpression"; + + int typeCode() => HInstruction.IS_TYPECODE; + bool typeEquals(HInstruction other) => other is HIs; + bool dataEquals(HIs other) { + return typeExpression == other.typeExpression + && nullOk == other.nullOk; + } +} + +class HTypeConversion extends HCheck { + HType type; + final int kind; + + static const int NO_CHECK = 0; + static const int CHECKED_MODE_CHECK = 1; + static const int ARGUMENT_TYPE_CHECK = 2; + static const int CAST_TYPE_CHECK = 3; + static const int BOOLEAN_CONVERSION_CHECK = 4; + + HTypeConversion(this.type, HInstruction input, [this.kind = NO_CHECK]) + : super(<HInstruction>[input]) { + sourceElement = input.sourceElement; + } + HTypeConversion.checkedModeCheck(HType type, HInstruction input) + : this(type, input, CHECKED_MODE_CHECK); + HTypeConversion.argumentTypeCheck(HType type, HInstruction input) + : this(type, input, ARGUMENT_TYPE_CHECK); + HTypeConversion.castCheck(HType type, HInstruction input) + : this(type, input, CAST_TYPE_CHECK); + + + bool get isChecked => kind != NO_CHECK; + bool get isCheckedModeCheck { + return kind == CHECKED_MODE_CHECK || kind == BOOLEAN_CONVERSION_CHECK; + } + bool get isArgumentTypeCheck => kind == ARGUMENT_TYPE_CHECK; + bool get isCastTypeCheck => kind == CAST_TYPE_CHECK; + bool get isBooleanConversionCheck => kind == BOOLEAN_CONVERSION_CHECK; + + HType get guaranteedType => type; + + accept(HVisitor visitor) => visitor.visitTypeConversion(this); + + bool isJsStatement() => kind == ARGUMENT_TYPE_CHECK; + bool isControlFlow() => kind == ARGUMENT_TYPE_CHECK; + bool canThrow() => isChecked; + + int typeCode() => HInstruction.TYPE_CONVERSION_TYPECODE; + bool typeEquals(HInstruction other) => other is HTypeConversion; + bool dataEquals(HTypeConversion other) { + return type == other.type && kind == other.kind; + } +} + +class HRangeConversion extends HCheck { + HRangeConversion(HInstruction input) : super(<HInstruction>[input]) { + sourceElement = input.sourceElement; + } + accept(HVisitor visitor) => visitor.visitRangeConversion(this); + + // We currently only do range analysis for integers. + HType get guaranteedType => HType.INTEGER; +} + +class HStringConcat extends HInstruction { + final Node node; + HStringConcat(HInstruction left, HInstruction right, this.node) + : super(<HInstruction>[left, right]) { + setAllSideEffects(); + setDependsOnSomething(); + } + HType get guaranteedType => HType.STRING; + + HInstruction get left => inputs[0]; + HInstruction get right => inputs[1]; + + accept(HVisitor visitor) => visitor.visitStringConcat(this); + toString() => "string concat"; +} + +/** Non-block-based (aka. traditional) loop information. */ +class HLoopInformation { + final HBasicBlock header; + final List<HBasicBlock> blocks; + final List<HBasicBlock> backEdges; + final List<LabelElement> labels; + final TargetElement target; + + /** Corresponding block information for the loop. */ + HLoopBlockInformation loopBlockInformation; + + HLoopInformation(this.header, this.target, this.labels) + : blocks = new List<HBasicBlock>(), + backEdges = new List<HBasicBlock>(); + + void addBackEdge(HBasicBlock predecessor) { + backEdges.add(predecessor); + addBlock(predecessor); + } + + // Adds a block and transitively all its predecessors in the loop as + // loop blocks. + void addBlock(HBasicBlock block) { + if (identical(block, header)) return; + HBasicBlock parentHeader = block.parentLoopHeader; + if (identical(parentHeader, header)) { + // Nothing to do in this case. + } else if (parentHeader != null) { + addBlock(parentHeader); + } else { + block.parentLoopHeader = header; + blocks.add(block); + for (int i = 0, length = block.predecessors.length; i < length; i++) { + addBlock(block.predecessors[i]); + } + } + } + + HBasicBlock getLastBackEdge() { + int maxId = -1; + HBasicBlock result = null; + for (int i = 0, length = backEdges.length; i < length; i++) { + HBasicBlock current = backEdges[i]; + if (current.id > maxId) { + maxId = current.id; + result = current; + } + } + return result; + } +} + + +/** + * Embedding of a [HBlockInformation] for block-structure based traversal + * in a dominator based flow traversal by attaching it to a basic block. + * To go back to dominator-based traversal, a [HSubGraphBlockInformation] + * structure can be added in the block structure. + */ +class HBlockFlow { + final HBlockInformation body; + final HBasicBlock continuation; + HBlockFlow(this.body, this.continuation); +} + + +/** + * Information about a syntactic-like structure. + */ +abstract class HBlockInformation { + HBasicBlock get start; + HBasicBlock get end; + bool accept(HBlockInformationVisitor visitor); +} + + +/** + * Information about a statement-like structure. + */ +abstract class HStatementInformation extends HBlockInformation { + bool accept(HStatementInformationVisitor visitor); +} + + +/** + * Information about an expression-like structure. + */ +abstract class HExpressionInformation extends HBlockInformation { + bool accept(HExpressionInformationVisitor visitor); + HInstruction get conditionExpression; +} + + +abstract class HStatementInformationVisitor { + bool visitLabeledBlockInfo(HLabeledBlockInformation info); + bool visitLoopInfo(HLoopBlockInformation info); + bool visitIfInfo(HIfBlockInformation info); + bool visitTryInfo(HTryBlockInformation info); + bool visitSwitchInfo(HSwitchBlockInformation info); + bool visitSequenceInfo(HStatementSequenceInformation info); + // Pseudo-structure embedding a dominator-based traversal into + // the block-structure traversal. This will eventually go away. + bool visitSubGraphInfo(HSubGraphBlockInformation info); +} + + +abstract class HExpressionInformationVisitor { + bool visitAndOrInfo(HAndOrBlockInformation info); + bool visitSubExpressionInfo(HSubExpressionBlockInformation info); +} + + +abstract class HBlockInformationVisitor + implements HStatementInformationVisitor, HExpressionInformationVisitor { +} + + +/** + * Generic class wrapping a [SubGraph] as a block-information until + * all structures are handled properly. + */ +class HSubGraphBlockInformation implements HStatementInformation { + final SubGraph subGraph; + HSubGraphBlockInformation(this.subGraph); + + HBasicBlock get start => subGraph.start; + HBasicBlock get end => subGraph.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSubGraphInfo(this); +} + + +/** + * Generic class wrapping a [SubExpression] as a block-information until + * expressions structures are handled properly. + */ +class HSubExpressionBlockInformation implements HExpressionInformation { + final SubExpression subExpression; + HSubExpressionBlockInformation(this.subExpression); + + HBasicBlock get start => subExpression.start; + HBasicBlock get end => subExpression.end; + + HInstruction get conditionExpression => subExpression.conditionExpression; + + bool accept(HExpressionInformationVisitor visitor) => + visitor.visitSubExpressionInfo(this); +} + + +/** A sequence of separate statements. */ +class HStatementSequenceInformation implements HStatementInformation { + final List<HStatementInformation> statements; + HStatementSequenceInformation(this.statements); + + HBasicBlock get start => statements[0].start; + HBasicBlock get end => statements.last.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSequenceInfo(this); +} + + +class HLabeledBlockInformation implements HStatementInformation { + final HStatementInformation body; + final List<LabelElement> labels; + final TargetElement target; + final bool isContinue; + + HLabeledBlockInformation(this.body, + List<LabelElement> labels, + {this.isContinue: false}) : + this.labels = labels, this.target = labels[0].target; + + HLabeledBlockInformation.implicit(this.body, + this.target, + {this.isContinue: false}) + : this.labels = const<LabelElement>[]; + + HBasicBlock get start => body.start; + HBasicBlock get end => body.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitLabeledBlockInfo(this); +} + +class LoopTypeVisitor extends Visitor { + const LoopTypeVisitor(); + int visitNode(Node node) => HLoopBlockInformation.NOT_A_LOOP; + int visitWhile(While node) => HLoopBlockInformation.WHILE_LOOP; + int visitFor(For node) => HLoopBlockInformation.FOR_LOOP; + int visitDoWhile(DoWhile node) => HLoopBlockInformation.DO_WHILE_LOOP; + int visitForIn(ForIn node) => HLoopBlockInformation.FOR_IN_LOOP; +} + +class HLoopBlockInformation implements HStatementInformation { + static const int WHILE_LOOP = 0; + static const int FOR_LOOP = 1; + static const int DO_WHILE_LOOP = 2; + static const int FOR_IN_LOOP = 3; + static const int NOT_A_LOOP = -1; + + final int kind; + final HExpressionInformation initializer; + final HExpressionInformation condition; + final HStatementInformation body; + final HExpressionInformation updates; + final TargetElement target; + final List<LabelElement> labels; + final SourceFileLocation sourcePosition; + final SourceFileLocation endSourcePosition; + + HLoopBlockInformation(this.kind, + this.initializer, + this.condition, + this.body, + this.updates, + this.target, + this.labels, + this.sourcePosition, + this.endSourcePosition) { + assert( + (kind == DO_WHILE_LOOP ? body.start : condition.start).isLoopHeader()); + } + + HBasicBlock get start { + if (initializer != null) return initializer.start; + if (kind == DO_WHILE_LOOP) { + return body.start; + } + return condition.start; + } + + HBasicBlock get loopHeader { + return kind == DO_WHILE_LOOP ? body.start : condition.start; + } + + HBasicBlock get end { + if (updates != null) return updates.end; + if (kind == DO_WHILE_LOOP && condition != null) { + return condition.end; + } + return body.end; + } + + static int loopType(Node node) { + return node.accept(const LoopTypeVisitor()); + } + + bool isDoWhile() => kind == DO_WHILE_LOOP; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitLoopInfo(this); +} + +class HIfBlockInformation implements HStatementInformation { + final HExpressionInformation condition; + final HStatementInformation thenGraph; + final HStatementInformation elseGraph; + HIfBlockInformation(this.condition, + this.thenGraph, + this.elseGraph); + + HBasicBlock get start => condition.start; + HBasicBlock get end => elseGraph == null ? thenGraph.end : elseGraph.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitIfInfo(this); +} + +class HAndOrBlockInformation implements HExpressionInformation { + final bool isAnd; + final HExpressionInformation left; + final HExpressionInformation right; + HAndOrBlockInformation(this.isAnd, + this.left, + this.right); + + HBasicBlock get start => left.start; + HBasicBlock get end => right.end; + + // We don't currently use HAndOrBlockInformation. + HInstruction get conditionExpression { + return null; + } + bool accept(HExpressionInformationVisitor visitor) => + visitor.visitAndOrInfo(this); +} + +class HTryBlockInformation implements HStatementInformation { + final HStatementInformation body; + final HLocalValue catchVariable; + final HStatementInformation catchBlock; + final HStatementInformation finallyBlock; + HTryBlockInformation(this.body, + this.catchVariable, + this.catchBlock, + this.finallyBlock); + + HBasicBlock get start => body.start; + HBasicBlock get end => + finallyBlock == null ? catchBlock.end : finallyBlock.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitTryInfo(this); +} + + + +class HSwitchBlockInformation implements HStatementInformation { + final HExpressionInformation expression; + final List<List<Constant>> matchExpressions; + final List<HStatementInformation> statements; + // If the switch has a default, it's the last statement block, which + // may or may not have other expresions. + final bool hasDefault; + final TargetElement target; + final List<LabelElement> labels; + + HSwitchBlockInformation(this.expression, + this.matchExpressions, + this.statements, + this.hasDefault, + this.target, + this.labels); + + HBasicBlock get start => expression.start; + HBasicBlock get end { + // We don't create a switch block if there are no cases. + assert(!statements.isEmpty); + return statements.last.end; + } + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSwitchInfo(this); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart new file mode 100644 index 0000000..35ba2cb --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart
@@ -0,0 +1,1545 @@ +// 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. + +part of ssa; + +abstract class OptimizationPhase { + String get name; + void visitGraph(HGraph graph); +} + +class SsaOptimizerTask extends CompilerTask { + final JavaScriptBackend backend; + SsaOptimizerTask(JavaScriptBackend backend) + : this.backend = backend, + super(backend.compiler); + String get name => 'SSA optimizer'; + Compiler get compiler => backend.compiler; + + void runPhases(HGraph graph, List<OptimizationPhase> phases) { + for (OptimizationPhase phase in phases) { + runPhase(graph, phase); + } + } + + void runPhase(HGraph graph, OptimizationPhase phase) { + phase.visitGraph(graph); + compiler.tracer.traceGraph(phase.name, graph); + assert(graph.isValid()); + } + + void optimize(CodegenWorkItem work, HGraph graph, bool speculative) { + ConstantSystem constantSystem = compiler.backend.constantSystem; + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + measure(() { + List<OptimizationPhase> phases = <OptimizationPhase>[ + // Run trivial constant folding first to optimize + // some patterns useful for type conversion. + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaTypeConversionInserter(compiler), + new SsaTypePropagator(compiler, types), + new SsaConstantFolder(constantSystem, backend, work, types), + // The constant folder affects the types of instructions, so + // we run the type propagator again. Note that this would + // not be necessary if types were directly stored on + // instructions. + new SsaTypePropagator(compiler, types), + new SsaCheckInserter(backend, work, types, context.boundsChecked), + new SsaRedundantPhiEliminator(), + new SsaDeadPhiEliminator(), + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaTypePropagator(compiler, types), + new SsaReceiverSpecialization(compiler), + new SsaGlobalValueNumberer(compiler, types), + new SsaCodeMotion(), + new SsaValueRangeAnalyzer(constantSystem, types, work), + // Previous optimizations may have generated new + // opportunities for constant folding. + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaSimplifyInterceptors(constantSystem), + new SsaDeadCodeEliminator(types)]; + runPhases(graph, phases); + if (!speculative) { + runPhase(graph, new SsaConstructionFieldTypes(backend, work, types)); + } + }); + } + + bool trySpeculativeOptimizations(CodegenWorkItem work, HGraph graph) { + if (work.element.isField()) { + // Lazy initializers may not have bailout methods. + return false; + } + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + return measure(() { + // Run the phases that will generate type guards. + List<OptimizationPhase> phases = <OptimizationPhase>[ + new SsaSpeculativeTypePropagator(compiler, types), + new SsaTypeGuardInserter(compiler, work, types), + new SsaEnvironmentBuilder(compiler), + // Change the propagated types back to what they were before we + // speculatively propagated, so that we can generate the bailout + // version. + // Note that we do this even if there were no guards inserted. If a + // guard is not beneficial enough we don't emit one, but there might + // still be speculative types on the instructions. + new SsaTypePropagator(compiler, types), + // Then run the [SsaCheckInserter] because the type propagator also + // propagated types non-speculatively. For example, it might have + // propagated the type array for a call to the List constructor. + new SsaCheckInserter(backend, work, types, context.boundsChecked)]; + runPhases(graph, phases); + return !work.guards.isEmpty; + }); + } + + void prepareForSpeculativeOptimizations(CodegenWorkItem work, HGraph graph) { + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + measure(() { + // In order to generate correct code for the bailout version, we did not + // propagate types from the instruction to the type guard. We do it + // now to be able to optimize further. + work.guards.forEach((HTypeGuard guard) { + guard.bailoutTarget.isEnabled = false; + guard.isEnabled = true; + }); + // We also need to insert range and integer checks for the type + // guards. Now that they claim to have a certain type, some + // depending instructions might become builtin (like native array + // accesses) and need to be checked. + // Also run the type propagator, to please the codegen in case + // no other optimization is run. + runPhases(graph, <OptimizationPhase>[ + new SsaCheckInserter(backend, work, types, context.boundsChecked), + new SsaTypePropagator(compiler, types)]); + }); + } +} + +/** + * If both inputs to known operations are available execute the operation at + * compile-time. + */ +class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase { + final String name = "SsaConstantFolder"; + final JavaScriptBackend backend; + final CodegenWorkItem work; + final ConstantSystem constantSystem; + final HTypeMap types; + HGraph graph; + Compiler get compiler => backend.compiler; + + SsaConstantFolder(this.constantSystem, this.backend, this.work, this.types); + + void visitGraph(HGraph visitee) { + graph = visitee; + visitDominatorTree(visitee); + } + + visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + HInstruction replacement = instruction.accept(this); + if (replacement != instruction) { + block.rewrite(instruction, replacement); + + // If we can replace [instruction] with [replacement], then + // [replacement]'s type can be narrowed. + types[replacement] = + types[replacement].intersection(types[instruction], compiler); + + // If the replacement instruction does not know its + // source element, use the source element of the + // instruction. + if (replacement.sourceElement == null) { + replacement.sourceElement = instruction.sourceElement; + } + if (replacement.sourcePosition == null) { + replacement.sourcePosition = instruction.sourcePosition; + } + if (!replacement.isInBasicBlock()) { + // The constant folding can return an instruction that is already + // part of the graph (like an input), so we only add the replacement + // if necessary. + block.addAfter(instruction, replacement); + // Visit the replacement as the next instruction in case it + // can also be constant folded away. + next = replacement; + } + block.remove(instruction); + } + instruction = next; + } + } + + HInstruction visitInstruction(HInstruction node) { + return node; + } + + HInstruction visitBoolify(HBoolify node) { + List<HInstruction> inputs = node.inputs; + assert(inputs.length == 1); + HInstruction input = inputs[0]; + HType type = types[input]; + if (type.isBoolean()) return input; + // All values !== true are boolified to false. + if (!type.isBooleanOrNull() && !type.isUnknown()) { + return graph.addConstantBool(false, constantSystem); + } + return node; + } + + HInstruction visitNot(HNot node) { + List<HInstruction> inputs = node.inputs; + assert(inputs.length == 1); + HInstruction input = inputs[0]; + if (input is HConstant) { + HConstant constant = input; + bool isTrue = constant.constant.isTrue(); + return graph.addConstantBool(!isTrue, constantSystem); + } else if (input is HNot) { + return input.inputs[0]; + } + return node; + } + + HInstruction visitInvokeUnary(HInvokeUnary node) { + HInstruction folded = + foldUnary(node.operation(constantSystem), node.operand); + return folded != null ? folded : node; + } + + HInstruction foldUnary(UnaryOperation operation, HInstruction operand) { + if (operand is HConstant) { + HConstant receiver = operand; + Constant folded = operation.fold(receiver.constant); + if (folded != null) return graph.addConstant(folded); + } + return null; + } + + HInstruction optimizeLengthInterceptedGetter(HInvokeDynamic node) { + HInstruction actualReceiver = node.inputs[1]; + if (actualReceiver.isIndexablePrimitive(types)) { + if (actualReceiver.isConstantString()) { + HConstant constantInput = actualReceiver; + StringConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } else if (actualReceiver.isConstantList()) { + HConstant constantInput = actualReceiver; + ListConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } + Element element; + bool isAssignable; + if (actualReceiver.isString(types)) { + element = backend.jsStringLength; + isAssignable = false; + } else { + element = backend.jsArrayLength; + isAssignable = !actualReceiver.isFixedArray(types); + } + HFieldGet result = new HFieldGet( + element, actualReceiver, isAssignable: isAssignable); + result.guaranteedType = HType.INTEGER; + types[result] = HType.INTEGER; + return result; + } else if (actualReceiver.isConstantMap()) { + HConstant constantInput = actualReceiver; + MapConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } + return node; + } + + HInstruction handleInterceptorCall(HInvokeDynamic node) { + // We only optimize for intercepted method calls in this method. + Selector selector = node.selector; + + // Try constant folding the instruction. + Operation operation = node.specializer.operation(constantSystem); + if (operation != null) { + HInstruction instruction = node.inputs.length == 2 + ? foldUnary(operation, node.inputs[1]) + : foldBinary(operation, node.inputs[1], node.inputs[2]); + if (instruction != null) return instruction; + } + + // Try converting the instruction to a builtin instruction. + HInstruction instruction = + node.specializer.tryConvertToBuiltin(node, types); + if (instruction != null) return instruction; + + // Check if this call does not need to be intercepted. + HInstruction input = node.inputs[1]; + HType type = types[input]; + var interceptor = node.inputs[0]; + + if (interceptor.isConstant() && selector.isCall()) { + DartType type = types[interceptor].computeType(compiler); + ClassElement cls = type.element; + node.element = cls.lookupSelector(selector); + } + + if (interceptor is !HThis && !type.canBePrimitive()) { + // If the type can be null, and the intercepted method can be in + // the object class, keep the interceptor. + if (type.canBeNull()) { + Set<ClassElement> interceptedClasses; + if (interceptor is HInterceptor) { + interceptedClasses = interceptor.interceptedClasses; + } else if (node is HOneShotInterceptor) { + var oneShotInterceptor = node; + interceptedClasses = oneShotInterceptor.interceptedClasses; + } + if (interceptedClasses.contains(compiler.objectClass)) return node; + } + if (selector.isGetter()) { + // Change the call to a regular invoke dynamic call. + return new HInvokeDynamicGetter(selector, null, input, false); + } else if (selector.isSetter()) { + return new HInvokeDynamicSetter( + selector, null, input, node.inputs[2], false); + } else { + // Change the call to a regular invoke dynamic call. + return new HInvokeDynamicMethod( + selector, node.inputs.getRange(1, node.inputs.length - 1)); + } + } + + if (selector.isCall()) { + Element target; + if (input.isExtendableArray(types)) { + if (selector.applies(backend.jsArrayRemoveLast, compiler)) { + target = backend.jsArrayRemoveLast; + } else if (selector.applies(backend.jsArrayAdd, compiler)) { + // The codegen special cases array calls, but does not + // inline argument type checks. + if (!compiler.enableTypeAssertions) { + target = backend.jsArrayAdd; + } + } + } else if (input.isString(types)) { + if (selector.applies(backend.jsStringSplit, compiler)) { + if (node.inputs[2].isString(types)) { + target = backend.jsStringSplit; + } + } else if (selector.applies(backend.jsStringConcat, compiler)) { + if (node.inputs[2].isString(types)) { + target = backend.jsStringConcat; + } + } else if (selector.applies(backend.jsStringToString, compiler)) { + return input; + } + } + if (target != null) { + // TODO(ngeoffray): There is a strong dependency between codegen + // and this optimization that the dynamic invoke does not need an + // interceptor. We currently need to keep a + // HInvokeDynamicMethod and not create a HForeign because + // HForeign is too opaque for the SssaCheckInserter (that adds a + // bounds check on removeLast). Once we start inlining, the + // bounds check will become explicit, so we won't need this + // optimization. + HInvokeDynamicMethod result = new HInvokeDynamicMethod( + node.selector, node.inputs.getRange(1, node.inputs.length - 1)); + result.element = target; + return result; + } + } else if (selector.isGetter()) { + if (selector.applies(backend.jsArrayLength, compiler)) { + return optimizeLengthInterceptedGetter(node); + } + } + return node; + } + + bool isFixedSizeListConstructor(HInvokeStatic node) { + Element element = node.target.element; + if (backend.fixedLengthListConstructor == null) { + backend.fixedLengthListConstructor = + compiler.listClass.lookupConstructor( + new Selector.callConstructor(const SourceString("fixedLength"), + compiler.listClass.getLibrary())); + } + // TODO(ngeoffray): checking if the second input is an integer + // should not be necessary but it currently makes it easier for + // other optimizations to reason on a fixed length constructor + // that we know takes an int. + return element == backend.fixedLengthListConstructor + && node.inputs[1].isInteger(types); + } + + HInstruction visitInvokeStatic(HInvokeStatic node) { + if (isFixedSizeListConstructor(node)) { + node.guaranteedType = HType.FIXED_ARRAY; + } + return node; + } + + HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + HType receiverType = types[node.receiver]; + if (receiverType.isExact()) { + HBoundedType type = receiverType; + Element element = type.lookupMember(node.selector.name); + // TODO(ngeoffray): Also fold if it's a getter or variable. + if (element != null && element.isFunction()) { + if (node.selector.applies(element, compiler)) { + FunctionElement method = element; + FunctionSignature parameters = method.computeSignature(compiler); + if (parameters.optionalParameterCount == 0) { + node.element = element; + } + // TODO(ngeoffray): If the method has optional parameters, + // we should pass the default values here. + } + } + } + return node; + } + + HInstruction visitIntegerCheck(HIntegerCheck node) { + HInstruction value = node.value; + if (value.isInteger(types)) return value; + if (value.isConstant()) { + HConstant constantInstruction = value; + assert(!constantInstruction.constant.isInt()); + if (!constantSystem.isInt(constantInstruction.constant)) { + // -0.0 is a double but will pass the runtime integer check. + node.alwaysFalse = true; + } + } + return node; + } + + HInstruction foldBinary(BinaryOperation operation, + HInstruction left, + HInstruction right) { + if (left is HConstant && right is HConstant) { + HConstant op1 = left; + HConstant op2 = right; + Constant folded = operation.fold(op1.constant, op2.constant); + if (folded != null) return graph.addConstant(folded); + } + return null; + } + + HInstruction visitInvokeBinary(HInvokeBinary node) { + HInstruction left = node.left; + HInstruction right = node.right; + BinaryOperation operation = node.operation(constantSystem); + HConstant folded = foldBinary(operation, left, right); + if (folded != null) return folded; + return node; + } + + bool allUsersAreBoolifies(HInstruction instruction) { + List<HInstruction> users = instruction.usedBy; + int length = users.length; + for (int i = 0; i < length; i++) { + if (users[i] is! HBoolify) return false; + } + return true; + } + + HInstruction visitRelational(HRelational node) { + if (allUsersAreBoolifies(node)) { + // TODO(ngeoffray): Call a boolified selector. + // This node stays the same, but the Boolify node will go away. + } + // Note that we still have to call [super] to make sure that we end up + // in the remaining optimizations. + return super.visitRelational(node); + } + + HInstruction handleIdentityCheck(HRelational node) { + HInstruction left = node.left; + HInstruction right = node.right; + HType leftType = types[left]; + HType rightType = types[right]; + + // We don't optimize on numbers to preserve the runtime semantics. + if (!(left.isNumberOrNull(types) && right.isNumberOrNull(types)) && + leftType.intersection(rightType, compiler).isConflicting()) { + return graph.addConstantBool(false, constantSystem); + } + + if (left.isConstantBoolean() && right.isBoolean(types)) { + HConstant constant = left; + if (constant.constant.isTrue()) { + return right; + } else { + return new HNot(right); + } + } + + if (right.isConstantBoolean() && left.isBoolean(types)) { + HConstant constant = right; + if (constant.constant.isTrue()) { + return left; + } else { + return new HNot(left); + } + } + + return null; + } + + HInstruction visitIdentity(HIdentity node) { + HInstruction newInstruction = handleIdentityCheck(node); + return newInstruction == null ? super.visitIdentity(node) : newInstruction; + } + + HInstruction visitTypeGuard(HTypeGuard node) { + HInstruction value = node.guarded; + // If the intersection of the types is still the incoming type then + // the incoming type was a subtype of the guarded type, and no check + // is required. + HType combinedType = types[value].intersection(node.guardedType, compiler); + return (combinedType == types[value]) ? value : node; + } + + HInstruction visitIs(HIs node) { + DartType type = node.typeExpression; + Element element = type.element; + if (element.isTypeVariable()) { + compiler.unimplemented("visitIs for type variables"); + } if (element.isTypedef()) { + return node; + } + + HType expressionType = types[node.expression]; + if (identical(element, compiler.objectClass) + || identical(element, compiler.dynamicClass)) { + return graph.addConstantBool(true, constantSystem); + } else if (expressionType.isInteger()) { + if (identical(element, compiler.intClass) + || identical(element, compiler.numClass) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else if (identical(element, compiler.doubleClass)) { + // We let the JS semantics decide for that check. Currently + // the code we emit will always return true. + return node; + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isDouble()) { + if (identical(element, compiler.doubleClass) + || identical(element, compiler.numClass) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else if (identical(element, compiler.intClass)) { + // We let the JS semantics decide for that check. Currently + // the code we emit will return true for a double that can be + // represented as a 31-bit integer and for -0.0. + return node; + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isNumber()) { + if (identical(element, compiler.numClass)) { + return graph.addConstantBool(true, constantSystem); + } + // We cannot just return false, because the expression may be of + // type int or double. + } else if (expressionType.isString()) { + if (identical(element, compiler.stringClass) + || Elements.isStringOnlySupertype(element, compiler) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isArray()) { + if (identical(element, compiler.listClass) + || Elements.isListSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else { + return graph.addConstantBool(false, constantSystem); + } + // TODO(karlklose): remove the hasTypeArguments check. + } else if (expressionType.isUseful() + && !expressionType.canBeNull() + && !RuntimeTypeInformation.hasTypeArguments(type)) { + DartType receiverType = expressionType.computeType(compiler); + if (receiverType != null) { + if (!receiverType.isMalformed && + !type.isMalformed && + compiler.types.isSubtype(receiverType, type)) { + return graph.addConstantBool(true, constantSystem); + } else if (expressionType.isExact()) { + return graph.addConstantBool(false, constantSystem); + } + } + } + return node; + } + + HInstruction visitTypeConversion(HTypeConversion node) { + HInstruction value = node.inputs[0]; + DartType type = types[node].computeType(compiler); + if (identical(type.element, compiler.dynamicClass) + || identical(type.element, compiler.objectClass)) { + return value; + } + if (types[value].canBeNull() && node.isBooleanConversionCheck) { + return node; + } + HType combinedType = types[value].intersection(types[node], compiler); + return (combinedType == types[value]) ? value : node; + } + + Element findConcreteFieldForDynamicAccess(HInstruction receiver, + Selector selector) { + HType receiverType = types[receiver]; + if (!receiverType.isUseful()) return null; + if (receiverType.canBeNull()) return null; + DartType type = receiverType.computeType(compiler); + if (type == null) return null; + return compiler.world.locateSingleField(type, selector); + } + + HInstruction visitFieldGet(HFieldGet node) { + if (node.element == backend.jsArrayLength) { + if (node.receiver is HInvokeStatic) { + // Try to recognize the length getter with input + // [:new List.fixedLength(int):]. + HInvokeStatic call = node.receiver; + if (isFixedSizeListConstructor(call)) { + return call.inputs[1]; + } + } + } + return node; + } + + HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + + Element field = + findConcreteFieldForDynamicAccess(node.receiver, node.selector); + if (field == null) return node; + + Modifiers modifiers = field.modifiers; + bool isFinalOrConst = modifiers.isFinal() || modifiers.isConst(); + if (!compiler.resolverWorld.hasInvokedSetter(field, compiler)) { + // If no setter is ever used for this field it is only initialized in the + // initializer list. + isFinalOrConst = true; + } + HFieldGet result = new HFieldGet( + field, node.inputs[0], isAssignable: !isFinalOrConst); + HType type = backend.optimisticFieldType(field); + if (type != null) { + result.guaranteedType = type; + backend.registerFieldTypesOptimization( + work.element, field, result.guaranteedType); + } + return result; + } + + HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + + Element field = + findConcreteFieldForDynamicAccess(node.receiver, node.selector); + if (field == null || !field.isAssignable()) return node; + HInstruction value = node.inputs[1]; + if (compiler.enableTypeAssertions) { + HInstruction other = value.convertType( + compiler, + field.computeType(compiler), + HTypeConversion.CHECKED_MODE_CHECK); + if (other != value) { + node.block.addBefore(node, other); + value = other; + } + } + return new HFieldSet(field, node.inputs[0], value); + } + + HInstruction visitStringConcat(HStringConcat node) { + DartString folded = const LiteralDartString(""); + for (int i = 0; i < node.inputs.length; i++) { + HInstruction part = node.inputs[i]; + if (!part.isConstant()) return node; + HConstant constant = part; + if (!constant.constant.isPrimitive()) return node; + PrimitiveConstant primitive = constant.constant; + folded = new DartString.concat(folded, primitive.toDartString()); + } + return graph.addConstant(constantSystem.createString(folded, node.node)); + } + + HInstruction visitInterceptor(HInterceptor node) { + if (node.isConstant()) return node; + HInstruction constant = tryComputeConstantInterceptor( + node.inputs[0], node.interceptedClasses); + if (constant == null) return node; + return constant; + } + + HInstruction tryComputeConstantInterceptor(HInstruction input, + Set<ClassElement> intercepted) { + HType type = types[input]; + ClassElement constantInterceptor; + if (type.isInteger()) { + constantInterceptor = backend.jsIntClass; + } else if (type.isDouble()) { + constantInterceptor = backend.jsDoubleClass; + } else if (type.isBoolean()) { + constantInterceptor = backend.jsBoolClass; + } else if (type.isString()) { + constantInterceptor = backend.jsStringClass; + } else if (type.isArray()) { + constantInterceptor = backend.jsArrayClass; + } else if (type.isNull()) { + constantInterceptor = backend.jsNullClass; + } else if (type.isNumber()) { + // If the method being intercepted is not defined in [int] or + // [double] we can safely use the number interceptor. + if (!intercepted.contains(compiler.intClass) + && !intercepted.contains(compiler.doubleClass)) { + constantInterceptor = backend.jsNumberClass; + } + } + + if (constantInterceptor == null) return null; + if (constantInterceptor == work.element.getEnclosingClass()) { + return graph.thisInstruction; + } + + Constant constant = new ConstructedConstant( + constantInterceptor.computeType(compiler), <Constant>[]); + return graph.addConstant(constant); + } + + HInstruction visitOneShotInterceptor(HOneShotInterceptor node) { + HInstruction newInstruction = handleInterceptorCall(node); + if (newInstruction != node) return newInstruction; + + HInstruction constant = tryComputeConstantInterceptor( + node.inputs[1], node.interceptedClasses); + + if (constant == null) return node; + + Selector selector = node.selector; + // TODO(ngeoffray): make one shot interceptors know whether + // they have side effects. + if (selector.isGetter()) { + HInstruction res = new HInvokeDynamicGetter( + selector, node.element, constant, false); + res.inputs.add(node.inputs[1]); + return res; + } else if (node.selector.isSetter()) { + HInstruction res = new HInvokeDynamicSetter( + selector, node.element, constant, node.inputs[1], false); + res.inputs.add(node.inputs[2]); + return res; + } else { + List<HInstruction> inputs = new List<HInstruction>.from(node.inputs); + inputs[0] = constant; + return new HInvokeDynamicMethod(selector, inputs, true); + } + } +} + +class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase { + final HTypeMap types; + final Set<HInstruction> boundsChecked; + final CodegenWorkItem work; + final JavaScriptBackend backend; + final String name = "SsaCheckInserter"; + HGraph graph; + + SsaCheckInserter(this.backend, + this.work, + this.types, + this.boundsChecked); + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + instruction = instruction.accept(this); + instruction = next; + } + } + + HBoundsCheck insertBoundsCheck(HInstruction node, + HInstruction receiver, + HInstruction index) { + bool isAssignable = !receiver.isFixedArray(types); + HFieldGet length = new HFieldGet( + backend.jsArrayLength, receiver, isAssignable: isAssignable); + length.guaranteedType = HType.INTEGER; + types[length] = HType.INTEGER; + node.block.addBefore(node, length); + + HBoundsCheck check = new HBoundsCheck(index, length); + node.block.addBefore(node, check); + boundsChecked.add(node); + return check; + } + + HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) { + HIntegerCheck check = new HIntegerCheck(value); + node.block.addBefore(node, check); + Set<HInstruction> dominatedUsers = value.dominatedUsers(node); + for (HInstruction user in dominatedUsers) { + user.changeUse(value, check); + } + return check; + } + + void visitIndex(HIndex node) { + if (boundsChecked.contains(node)) return; + HInstruction index = node.index; + if (!node.index.isInteger(types)) { + index = insertIntegerCheck(node, index); + } + index = insertBoundsCheck(node, node.receiver, index); + node.changeUse(node.index, index); + } + + void visitIndexAssign(HIndexAssign node) { + if (!node.receiver.isMutableArray(types)) return; + if (boundsChecked.contains(node)) return; + HInstruction index = node.index; + if (!node.index.isInteger(types)) { + index = insertIntegerCheck(node, index); + } + index = insertBoundsCheck(node, node.receiver, index); + node.changeUse(node.index, index); + } + + void visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + Element element = node.element; + if (node.isInterceptorCall) return; + if (element != backend.jsArrayRemoveLast) return; + if (boundsChecked.contains(node)) return; + insertBoundsCheck( + node, node.receiver, graph.addConstantInt(0, backend.constantSystem)); + } +} + +class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase { + final HTypeMap types; + final String name = "SsaDeadCodeEliminator"; + + SsaDeadCodeEliminator(this.types); + + bool isDeadCode(HInstruction instruction) { + return !instruction.hasSideEffects() + && !instruction.canThrow() + && instruction.usedBy.isEmpty + && instruction is !HTypeGuard + && instruction is !HParameterValue + && instruction is !HLocalSet + && !instruction.isControlFlow(); + } + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.last; + while (instruction != null) { + var previous = instruction.previous; + if (isDeadCode(instruction)) block.remove(instruction); + instruction = previous; + } + } +} + +class SsaDeadPhiEliminator implements OptimizationPhase { + final String name = "SsaDeadPhiEliminator"; + + void visitGraph(HGraph graph) { + final List<HPhi> worklist = <HPhi>[]; + // A set to keep track of the live phis that we found. + final Set<HPhi> livePhis = new Set<HPhi>(); + + // Add to the worklist all live phis: phis referenced by non-phi + // instructions. + for (final block in graph.blocks) { + block.forEachPhi((HPhi phi) { + for (final user in phi.usedBy) { + if (user is !HPhi) { + worklist.add(phi); + livePhis.add(phi); + break; + } + } + }); + } + + // Process the worklist by propagating liveness to phi inputs. + while (!worklist.isEmpty) { + HPhi phi = worklist.removeLast(); + for (final input in phi.inputs) { + if (input is HPhi && !livePhis.contains(input)) { + worklist.add(input); + livePhis.add(input); + } + } + } + + // Remove phis that are not live. + // Traverse in reverse order to remove phis with no uses before the + // phis that they might use. + // NOTICE: Doesn't handle circular references, but we don't currently + // create any. + List<HBasicBlock> blocks = graph.blocks; + for (int i = blocks.length - 1; i >= 0; i--) { + HBasicBlock block = blocks[i]; + HPhi current = block.phis.first; + HPhi next = null; + while (current != null) { + next = current.next; + if (!livePhis.contains(current) + // TODO(ahe): Not sure the following is correct. + && current.usedBy.isEmpty) { + block.removePhi(current); + } + current = next; + } + } + } +} + +class SsaRedundantPhiEliminator implements OptimizationPhase { + final String name = "SsaRedundantPhiEliminator"; + + void visitGraph(HGraph graph) { + final List<HPhi> worklist = <HPhi>[]; + + // Add all phis in the worklist. + for (final block in graph.blocks) { + block.forEachPhi((HPhi phi) => worklist.add(phi)); + } + + while (!worklist.isEmpty) { + HPhi phi = worklist.removeLast(); + + // If the phi has already been processed, continue. + if (!phi.isInBasicBlock()) continue; + + // Find if the inputs of the phi are the same instruction. + // The builder ensures that phi.inputs[0] cannot be the phi + // itself. + assert(!identical(phi.inputs[0], phi)); + HInstruction candidate = phi.inputs[0]; + for (int i = 1; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + // If the input is the phi, the phi is still candidate for + // elimination. + if (!identical(input, candidate) && !identical(input, phi)) { + candidate = null; + break; + } + } + + // If the inputs are not the same, continue. + if (candidate == null) continue; + + // Because we're updating the users of this phi, we may have new + // phis candidate for elimination. Add phis that used this phi + // to the worklist. + for (final user in phi.usedBy) { + if (user is HPhi) worklist.add(user); + } + phi.block.rewrite(phi, candidate); + phi.block.removePhi(phi); + } + } +} + +class SsaGlobalValueNumberer implements OptimizationPhase { + final String name = "SsaGlobalValueNumberer"; + final Compiler compiler; + final HTypeMap types; + final Set<int> visited; + + List<int> blockChangesFlags; + List<int> loopChangesFlags; + + SsaGlobalValueNumberer(this.compiler, this.types) : visited = new Set<int>(); + + void visitGraph(HGraph graph) { + computeChangesFlags(graph); + moveLoopInvariantCode(graph); + visitBasicBlock(graph.entry, new ValueSet()); + } + + void moveLoopInvariantCode(HGraph graph) { + for (int i = graph.blocks.length - 1; i >= 0; i--) { + HBasicBlock block = graph.blocks[i]; + if (block.isLoopHeader()) { + int changesFlags = loopChangesFlags[block.id]; + HLoopInformation info = block.loopInformation; + // Iterate over all blocks of this loop. Note that blocks in + // inner loops are not visited here, but we know they + // were visited before because we are iterating in post-order. + // So instructions that are GVN'ed in an inner loop are in their + // loop entry, and [info.blocks] contains this loop entry. + for (HBasicBlock other in info.blocks) { + moveLoopInvariantCodeFromBlock(other, block, changesFlags); + } + } + } + } + + void moveLoopInvariantCodeFromBlock(HBasicBlock block, + HBasicBlock loopHeader, + int changesFlags) { + assert(block.parentLoopHeader == loopHeader); + HBasicBlock preheader = loopHeader.predecessors[0]; + int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags); + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + if (instruction.useGvn() + && (instruction is !HCheck) + && (instruction.flags & dependsFlags) == 0) { + bool loopInvariantInputs = true; + List<HInstruction> inputs = instruction.inputs; + for (int i = 0, length = inputs.length; i < length; i++) { + if (isInputDefinedAfterDominator(inputs[i], preheader)) { + loopInvariantInputs = false; + break; + } + } + + // If the inputs are loop invariant, we can move the + // instruction from the current block to the pre-header block. + if (loopInvariantInputs) { + block.detach(instruction); + preheader.moveAtExit(instruction); + } + } + int oldChangesFlags = changesFlags; + changesFlags |= instruction.getChangesFlags(); + if (oldChangesFlags != changesFlags) { + dependsFlags = HInstruction.computeDependsOnFlags(changesFlags); + } + instruction = next; + } + } + + bool isInputDefinedAfterDominator(HInstruction input, + HBasicBlock dominator) { + return input.block.id > dominator.id; + } + + void visitBasicBlock(HBasicBlock block, ValueSet values) { + HInstruction instruction = block.first; + if (block.isLoopHeader()) { + int flags = loopChangesFlags[block.id]; + values.kill(flags); + } + while (instruction != null) { + HInstruction next = instruction.next; + int flags = instruction.getChangesFlags(); + assert(flags == 0 || !instruction.useGvn()); + values.kill(flags); + if (instruction.useGvn()) { + HInstruction other = values.lookup(instruction); + if (other != null) { + assert(other.gvnEquals(instruction) && instruction.gvnEquals(other)); + block.rewriteWithBetterUser(instruction, other); + block.remove(instruction); + } else { + values.add(instruction); + } + } + instruction = next; + } + + List<HBasicBlock> dominatedBlocks = block.dominatedBlocks; + for (int i = 0, length = dominatedBlocks.length; i < length; i++) { + HBasicBlock dominated = dominatedBlocks[i]; + // No need to copy the value set for the last child. + ValueSet successorValues = (i == length - 1) ? values : values.copy(); + // If we have no values in our set, we do not have to kill + // anything. Also, if the range of block ids from the current + // block to the dominated block is empty, there is no blocks on + // any path from the current block to the dominated block so we + // don't have to do anything either. + assert(block.id < dominated.id); + if (!successorValues.isEmpty && block.id + 1 < dominated.id) { + visited.clear(); + int changesFlags = getChangesFlagsForDominatedBlock(block, dominated); + successorValues.kill(changesFlags); + } + visitBasicBlock(dominated, successorValues); + } + } + + void computeChangesFlags(HGraph graph) { + // Create the changes flags lists. Make sure to initialize the + // loop changes flags list to zero so we can use bitwise or when + // propagating loop changes upwards. + final int length = graph.blocks.length; + blockChangesFlags = new List<int>.fixedLength(length); + loopChangesFlags = new List<int>.fixedLength(length); + for (int i = 0; i < length; i++) loopChangesFlags[i] = 0; + + // Run through all the basic blocks in the graph and fill in the + // changes flags lists. + for (int i = length - 1; i >= 0; i--) { + final HBasicBlock block = graph.blocks[i]; + final int id = block.id; + + // Compute block changes flags for the block. + int changesFlags = 0; + HInstruction instruction = block.first; + while (instruction != null) { + changesFlags |= instruction.getChangesFlags(); + instruction = instruction.next; + } + assert(blockChangesFlags[id] == null); + blockChangesFlags[id] = changesFlags; + + // Loop headers are part of their loop, so update the loop + // changes flags accordingly. + if (block.isLoopHeader()) { + loopChangesFlags[id] |= changesFlags; + } + + // Propagate loop changes flags upwards. + HBasicBlock parentLoopHeader = block.parentLoopHeader; + if (parentLoopHeader != null) { + loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader()) + ? loopChangesFlags[id] + : changesFlags; + } + } + } + + int getChangesFlagsForDominatedBlock(HBasicBlock dominator, + HBasicBlock dominated) { + int changesFlags = 0; + List<HBasicBlock> predecessors = dominated.predecessors; + for (int i = 0, length = predecessors.length; i < length; i++) { + HBasicBlock block = predecessors[i]; + int id = block.id; + // If the current predecessor block is on the path from the + // dominator to the dominated, it must have an id that is in the + // range from the dominator to the dominated. + if (dominator.id < id && id < dominated.id && !visited.contains(id)) { + visited.add(id); + changesFlags |= blockChangesFlags[id]; + // Loop bodies might not be on the path from dominator to dominated, + // but they can invalidate values. + changesFlags |= loopChangesFlags[id]; + changesFlags |= getChangesFlagsForDominatedBlock(dominator, block); + } + } + return changesFlags; + } +} + +// This phase merges equivalent instructions on different paths into +// one instruction in a dominator block. It runs through the graph +// post dominator order and computes a ValueSet for each block of +// instructions that can be moved to a dominator block. These +// instructions are the ones that: +// 1) can be used for GVN, and +// 2) do not use definitions of their own block. +// +// A basic block looks at its sucessors and finds the intersection of +// these computed ValueSet. It moves all instructions of the +// intersection into its own list of instructions. +class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase { + final String name = "SsaCodeMotion"; + + List<ValueSet> values; + + void visitGraph(HGraph graph) { + values = new List<ValueSet>.fixedLength(graph.blocks.length); + for (int i = 0; i < graph.blocks.length; i++) { + values[graph.blocks[i].id] = new ValueSet(); + } + visitPostDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + List<HBasicBlock> successors = block.successors; + + // Phase 1: get the ValueSet of all successors (if there are more than one), + // compute the intersection and move the instructions of the intersection + // into this block. + if (successors.length > 1) { + ValueSet instructions = values[successors[0].id]; + for (int i = 1; i < successors.length; i++) { + ValueSet other = values[successors[i].id]; + instructions = instructions.intersection(other); + } + + if (!instructions.isEmpty) { + List<HInstruction> list = instructions.toList(); + for (HInstruction instruction in list) { + // Move the instruction to the current block. + instruction.block.detach(instruction); + block.moveAtExit(instruction); + // Go through all successors and rewrite their instruction + // to the shared one. + for (final successor in successors) { + HInstruction toRewrite = values[successor.id].lookup(instruction); + if (toRewrite != instruction) { + successor.rewriteWithBetterUser(toRewrite, instruction); + successor.remove(toRewrite); + } + } + } + } + } + + // Don't try to merge instructions to a dominator if we have + // multiple predecessors. + if (block.predecessors.length != 1) return; + + // Phase 2: Go through all instructions of this block and find + // which instructions can be moved to a dominator block. + ValueSet set_ = values[block.id]; + HInstruction instruction = block.first; + int flags = 0; + while (instruction != null) { + int dependsFlags = HInstruction.computeDependsOnFlags(flags); + flags |= instruction.getChangesFlags(); + + HInstruction current = instruction; + instruction = instruction.next; + + // TODO(ngeoffray): this check is needed because we currently do + // not have flags to express 'Gvn'able', but not movable. + if (current is HCheck) continue; + if (!current.useGvn()) continue; + if ((current.flags & dependsFlags) != 0) continue; + + bool canBeMoved = true; + for (final HInstruction input in current.inputs) { + if (input.block == block) { + canBeMoved = false; + break; + } + } + if (!canBeMoved) continue; + + // This is safe because we are running after GVN. + // TODO(ngeoffray): ensure GVN has been run. + set_.add(current); + } + } +} + +class SsaTypeConversionInserter extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaTypeconversionInserter"; + final Compiler compiler; + + SsaTypeConversionInserter(this.compiler); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + + // Update users of [input] that are dominated by [:dominator.first:] + // to use [newInput] instead. + void changeUsesDominatedBy(HBasicBlock dominator, + HInstruction input, + HType convertedType) { + Set<HInstruction> dominatedUsers = input.dominatedUsers(dominator.first); + if (dominatedUsers.isEmpty) return; + + HTypeConversion newInput = new HTypeConversion(convertedType, input); + dominator.addBefore(dominator.first, newInput); + dominatedUsers.forEach((HInstruction user) { + user.changeUse(input, newInput); + }); + } + + void visitIs(HIs instruction) { + HInstruction input = instruction.expression; + HType convertedType = + new HType.fromBoundedType(instruction.typeExpression, compiler); + + List<HInstruction> ifUsers = <HInstruction>[]; + List<HInstruction> notIfUsers = <HInstruction>[]; + + for (HInstruction user in instruction.usedBy) { + if (user is HIf) { + ifUsers.add(user); + } else if (user is HNot) { + for (HInstruction notUser in user.usedBy) { + if (notUser is HIf) notIfUsers.add(notUser); + } + } + } + + if (ifUsers.isEmpty && notIfUsers.isEmpty) return; + + for (HIf ifUser in ifUsers) { + changeUsesDominatedBy(ifUser.thenBlock, input, convertedType); + // TODO(ngeoffray): Also change uses for the else block on a HType + // that knows it is not of a specific Type. + } + + for (HIf ifUser in notIfUsers) { + changeUsesDominatedBy(ifUser.elseBlock, input, convertedType); + // TODO(ngeoffray): Also change uses for the then block on a HType + // that knows it is not of a specific Type. + } + } +} + + +// Analyze the constructors to see if some fields will always have a specific +// type after construction. If this is the case we can ignore the type given +// by the field initializer. This is especially useful when the field +// initializer is initializing the field to null. +class SsaConstructionFieldTypes + extends HBaseVisitor implements OptimizationPhase { + final JavaScriptBackend backend; + final CodegenWorkItem work; + final HTypeMap types; + final String name = "SsaConstructionFieldTypes"; + final Set<HInstruction> thisUsers; + final Set<Element> allSetters; + final Map<HBasicBlock, Map<Element, HType>> blockFieldSetters; + bool thisExposed = false; + HGraph currentGraph; + Map<Element, HType> currentFieldSetters; + + SsaConstructionFieldTypes(JavaScriptBackend this.backend, + CodegenWorkItem this.work, + HTypeMap this.types) + : thisUsers = new Set<HInstruction>(), + allSetters = new Set<Element>(), + blockFieldSetters = new Map<HBasicBlock, Map<Element, HType>>(); + + void visitGraph(HGraph graph) { + currentGraph = graph; + if (!work.element.isGenerativeConstructorBody() && + !work.element.isGenerativeConstructor()) return; + visitDominatorTree(graph); + if (work.element.isGenerativeConstructor()) { + backend.registerConstructor(work.element); + } + } + + visitBasicBlock(HBasicBlock block) { + if (block.predecessors.length == 0) { + // Create a new empty map for the first block. + currentFieldSetters = new Map<Element, HType>(); + } else { + // Build a map which intersects the fields from all predecessors. For + // each field in this intersection it unions the types. + currentFieldSetters = + new Map.from(blockFieldSetters[block.predecessors[0]]); + // Loop headers are the only nodes with back edges. + if (!block.isLoopHeader()) { + for (int i = 1; i < block.predecessors.length; i++) { + Map<Element, HType> predecessorsFieldSetters = + blockFieldSetters[block.predecessors[i]]; + Map<Element, HType> newFieldSetters = new Map<Element, HType>(); + predecessorsFieldSetters.forEach((Element element, HType type) { + HType currentType = currentFieldSetters[element]; + if (currentType != null) { + newFieldSetters[element] = + currentType.union(type, backend.compiler); + } + }); + currentFieldSetters = newFieldSetters; + } + } else { + assert(block.predecessors.length <= 2); + } + } + block.forEachPhi((HPhi phi) => phi.accept(this)); + block.forEachInstruction( + (HInstruction instruction) => instruction.accept(this)); + assert(currentFieldSetters != null); + blockFieldSetters[block] = currentFieldSetters; + } + + visitInstruction(HInstruction instruction) { + // All instructions not explicitly handled below will flag the this + // exposure if using this. + thisExposed = thisExposed || thisUsers.contains(instruction); + } + + visitPhi(HPhi phi) { + if (thisUsers.contains(phi)) { + thisUsers.addAll(phi.usedBy); + } + } + + visitThis(HThis instruction) { + // Collect all users of this in a set to make the this exposed check simple + // and cheap. + thisUsers.addAll(instruction.usedBy); + } + + visitFieldGet(HInstruction _) { + // The field get instruction is allowed to use this. + } + + visitForeignNew(HForeignNew node) { + // The HForeignNew instruction is used in the generative constructor to + // initialize all fields in newly created objects. The fields are + // initialized to the value present in the initializer list or set to null + // if not otherwise initialized. + // Here we handle members in superclasses as well, as the handling of + // the generative constructor bodies will ensure, that the initializer + // type will not be used if the field is in any of these. + int j = 0; + node.element.forEachInstanceField( + (ClassElement enclosingClass, Element element) { + backend.registerFieldInitializer(element, types[node.inputs[j]]); + j++; + }, + includeBackendMembers: false, + includeSuperMembers: true); + } + + visitFieldSet(HFieldSet node) { + Element field = node.element; + HInstruction value = node.value; + HType type = types[value]; + // [HFieldSet] is also used for variables in try/catch. + if (field.isField()) allSetters.add(field); + // Don't handle fields defined in superclasses. Given that the field is + // always added to the [allSetters] set, setting a field defined in a + // superclass will get an inferred type of UNKNOWN. + if (identical(work.element.getEnclosingClass(), field.getEnclosingClass()) && + value.hasGuaranteedType()) { + currentFieldSetters[field] = type; + } + } + + visitExit(HExit node) { + // If this has been exposed then we cannot say anything about types after + // construction. + if (!thisExposed) { + // Register the known field types. + currentFieldSetters.forEach((Element element, HType type) { + backend.registerFieldConstructor(element, type); + allSetters.remove(element); + }); + } + + // For other fields having setters in the generative constructor body, set + // the type to UNKNOWN to avoid relying on the type set in the initializer + // list. + allSetters.forEach((Element element) { + backend.registerFieldConstructor(element, HType.UNKNOWN); + }); + } +} + +/** + * This phase specializes dominated uses of a call, where the call + * can give us some type information of what the receiver might be. + * For example, after a call to [:a.foo():], if [:foo:] is only + * in class [:A:], a can be of type [:A:]. + */ +class SsaReceiverSpecialization extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaReceiverSpecialization"; + final Compiler compiler; + + SsaReceiverSpecialization(this.compiler); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void visitInterceptor(HInterceptor interceptor) { + HInstruction receiver = interceptor.receiver; + JavaScriptBackend backend = compiler.backend; + for (var user in receiver.usedBy) { + if (user is HInterceptor && interceptor.dominates(user)) { + Set<ClassElement> otherIntercepted = user.interceptedClasses; + // If the dominated interceptor intercepts the int class or + // the double class, we make sure these classes are also being + // intercepted by the dominating interceptor. Otherwise, the + // dominating interceptor could just intercept the number + // class and therefore not implement the methods in the int or + // double class. + if (otherIntercepted.contains(backend.jsIntClass) + || otherIntercepted.contains(backend.jsDoubleClass)) { + interceptor.interceptedClasses.addAll(user.interceptedClasses); + } + user.interceptedClasses = interceptor.interceptedClasses; + } + } + } + + // TODO(ngeoffray): Also implement it for non-intercepted calls. +} + +/** + * This phase replaces all interceptors that are used only once with + * one-shot interceptors. It saves code size and makes the receiver of + * an intercepted call a candidate for being generated at use site. + */ +class SsaSimplifyInterceptors extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaSimplifyInterceptors"; + final ConstantSystem constantSystem; + HGraph graph; + + SsaSimplifyInterceptors(this.constantSystem); + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + } + + void visitInterceptor(HInterceptor node) { + if (node.usedBy.length != 1) return; + // [HBailoutTarget] instructions might have the interceptor as + // input. In such situation we let the dead code analyzer find out + // the interceptor is not needed. + if (node.usedBy[0] is !HInvokeDynamic) return; + + HInvokeDynamic user = node.usedBy[0]; + + // If [node] was loop hoisted, we keep the interceptor. + if (!user.hasSameLoopHeaderAs(node)) return; + + // Replace the user with a [HOneShotInterceptor]. + HConstant nullConstant = graph.addConstantNull(constantSystem); + List<HInstruction> inputs = new List<HInstruction>.from(user.inputs); + inputs[0] = nullConstant; + HOneShotInterceptor interceptor = new HOneShotInterceptor( + user.selector, inputs, node.interceptedClasses); + interceptor.sourcePosition = user.sourcePosition; + interceptor.sourceElement = user.sourceElement; + + HBasicBlock block = user.block; + block.addAfter(user, interceptor); + block.rewrite(user, interceptor); + block.remove(user); + + // The interceptor will be removed in the dead code elimination + // phase. Note that removing it here would not work because of how + // the [visitBasicBlock] is implemented. + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart new file mode 100644 index 0000000..13de8b6 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart
@@ -0,0 +1,43 @@ +// 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. + +library ssa; + +import 'dart:collection'; + +import '../closure.dart'; +import '../js/js.dart' as js; +import '../dart2jslib.dart' hide Selector; +import '../dart_types.dart'; +import '../source_file.dart'; +import '../source_map_builder.dart'; +import '../elements/elements.dart'; +import '../js_backend/js_backend.dart'; +import '../native_handler.dart' as native; +import '../tree/tree.dart'; +import '../types/types.dart'; +import '../universe/universe.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +import '../scanner/scannerlib.dart' + show PartialFunctionElement, Token, PLUS_TOKEN; + +import '../elements/modelx.dart' + show ElementX, + ConstructorBodyElementX; + +part 'bailout.dart'; +part 'builder.dart'; +part 'codegen.dart'; +part 'codegen_helpers.dart'; +part 'invoke_dynamic_specializers.dart'; +part 'nodes.dart'; +part 'optimize.dart'; +part 'types.dart'; +part 'types_propagation.dart'; +part 'validate.dart'; +part 'variable_allocator.dart'; +part 'value_range_analyzer.dart'; +part 'value_set.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart new file mode 100644 index 0000000..7a8f6bc --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart
@@ -0,0 +1,563 @@ +// 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. + +library tracer; + +import 'dart:io'; +import 'ssa.dart'; +import '../js_backend/js_backend.dart'; +import '../dart2jslib.dart'; + +const bool GENERATE_SSA_TRACE = false; +const String SSA_TRACE_FILTER = null; + +class HTracer extends HGraphVisitor implements Tracer { + JavaScriptItemCompilationContext context; + int indent = 0; + final RandomAccessFile output; + final bool enabled = GENERATE_SSA_TRACE; + bool traceActive = false; + + HTracer([String path = "dart.cfg"]) + : output = GENERATE_SSA_TRACE ? new File(path).openSync(FileMode.WRITE) + : null; + + void close() { + if (enabled) output.closeSync(); + } + + void traceCompilation(String methodName, + JavaScriptItemCompilationContext compilationContext) { + if (!enabled) return; + this.context = compilationContext; + traceActive = + SSA_TRACE_FILTER == null || methodName.contains(SSA_TRACE_FILTER); + if (!traceActive) return; + tag("compilation", () { + printProperty("name", methodName); + printProperty("method", methodName); + printProperty("date", new DateTime.now().millisecondsSinceEpoch); + }); + } + + void traceGraph(String name, HGraph graph) { + if (!traceActive) return; + tag("cfg", () { + printProperty("name", name); + visitDominatorTree(graph); + }); + } + + void addPredecessors(HBasicBlock block) { + if (block.predecessors.isEmpty) { + printEmptyProperty("predecessors"); + } else { + addIndent(); + add("predecessors"); + for (HBasicBlock predecessor in block.predecessors) { + add(' "B${predecessor.id}"'); + } + add("\n"); + } + } + + void addSuccessors(HBasicBlock block) { + if (block.successors.isEmpty) { + printEmptyProperty("successors"); + } else { + addIndent(); + add("successors"); + for (HBasicBlock successor in block.successors) { + add(' "B${successor.id}"'); + } + add("\n"); + } + } + + void addInstructions(HInstructionStringifier stringifier, + HInstructionList list) { + HTypeMap types = context.types; + for (HInstruction instruction = list.first; + instruction != null; + instruction = instruction.next) { + int bci = 0; + int uses = instruction.usedBy.length; + String changes = instruction.hasSideEffects() ? '!' : ' '; + String depends = instruction.dependsOnSomething() ? '?' : ''; + addIndent(); + String temporaryId = stringifier.temporaryId(instruction); + String instructionString = stringifier.visit(instruction); + add("$bci $uses $temporaryId $instructionString $changes $depends <|@\n"); + } + } + + void visitBasicBlock(HBasicBlock block) { + HInstructionStringifier stringifier = + new HInstructionStringifier(context, block); + assert(block.id != null); + tag("block", () { + printProperty("name", "B${block.id}"); + printProperty("from_bci", -1); + printProperty("to_bci", -1); + addPredecessors(block); + addSuccessors(block); + printEmptyProperty("xhandlers"); + printEmptyProperty("flags"); + if (block.dominator != null) { + printProperty("dominator", "B${block.dominator.id}"); + } + tag("states", () { + tag("locals", () { + printProperty("size", 0); + printProperty("method", "None"); + block.forEachPhi((phi) { + String phiId = stringifier.temporaryId(phi); + StringBuffer inputIds = new StringBuffer(); + for (int i = 0; i < phi.inputs.length; i++) { + inputIds.add(stringifier.temporaryId(phi.inputs[i])); + inputIds.add(" "); + } + println("${phi.id} $phiId [ $inputIds]"); + }); + }); + }); + tag("HIR", () { + addInstructions(stringifier, block.phis); + addInstructions(stringifier, block); + }); + }); + } + + void tag(String tagName, Function f) { + println("begin_$tagName"); + indent++; + f(); + indent--; + println("end_$tagName"); + } + + void println(String string) { + addIndent(); + add(string); + add("\n"); + } + + void printEmptyProperty(String propertyName) { + println(propertyName); + } + + void printProperty(String propertyName, var value) { + if (value is num) { + println("$propertyName $value"); + } else { + println('$propertyName "$value"'); + } + } + + void add(String string) { + output.writeStringSync(string); + } + + void addIndent() { + for (int i = 0; i < indent; i++) { + add(" "); + } + } +} + +class HInstructionStringifier implements HVisitor<String> { + JavaScriptItemCompilationContext context; + HBasicBlock currentBlock; + + HInstructionStringifier(this.context, this.currentBlock); + + visit(HInstruction node) => node.accept(this); + + String temporaryId(HInstruction instruction) { + String prefix; + HType type = context.types[instruction]; + if (!type.isPrimitive()) { + prefix = 'U'; + } else { + if (type == HType.MUTABLE_ARRAY) { + prefix = 'm'; + } else if (type == HType.READABLE_ARRAY) { + prefix = 'a'; + } else if (type == HType.EXTENDABLE_ARRAY) { + prefix = 'e'; + } else if (type == HType.BOOLEAN) { + prefix = 'b'; + } else if (type == HType.INTEGER) { + prefix = 'i'; + } else if (type == HType.DOUBLE) { + prefix = 'd'; + } else if (type == HType.NUMBER) { + prefix = 'n'; + } else if (type == HType.STRING) { + prefix = 's'; + } else if (type == HType.UNKNOWN) { + prefix = 'v'; + } else if (type == HType.CONFLICTING) { + prefix = 'c'; + } else if (type == HType.INDEXABLE_PRIMITIVE) { + prefix = 'r'; + } else if (type == HType.NULL) { + prefix = 'u'; + } else { + prefix = 'x'; + } + } + return "$prefix${instruction.id}"; + } + + String visitBailoutTarget(HBailoutTarget node) { + StringBuffer envBuffer = new StringBuffer(); + List<HInstruction> inputs = node.inputs; + for (int i = 0; i < inputs.length; i++) { + envBuffer.add(" ${temporaryId(inputs[i])}"); + } + String on = node.isEnabled ? "enabled" : "disabled"; + return "BailoutTarget($on): id: ${node.state} env: $envBuffer"; + } + + String visitBoolify(HBoolify node) { + return "Boolify: ${temporaryId(node.inputs[0])}"; + } + + String handleInvokeBinary(HInvokeBinary node, String op) { + String left = temporaryId(node.left); + String right= temporaryId(node.right); + return '$left $op $right'; + } + + String visitAdd(HAdd node) => handleInvokeBinary(node, '+'); + + String visitBitAnd(HBitAnd node) => handleInvokeBinary(node, '&'); + + String visitBitNot(HBitNot node) { + String operand = temporaryId(node.operand); + return "~$operand"; + } + + String visitBitOr(HBitOr node) => handleInvokeBinary(node, '|'); + + String visitBitXor(HBitXor node) => handleInvokeBinary(node, '^'); + + String visitBoundsCheck(HBoundsCheck node) { + String lengthId = temporaryId(node.length); + String indexId = temporaryId(node.index); + return "Bounds check: length = $lengthId, index = $indexId"; + } + + String visitBreak(HBreak node) { + HBasicBlock target = currentBlock.successors[0]; + if (node.label != null) { + return "Break ${node.label.labelName}: (B${target.id})"; + } + return "Break: (B${target.id})"; + } + + String visitConstant(HConstant constant) => "Constant ${constant.constant}"; + + String visitContinue(HContinue node) { + HBasicBlock target = currentBlock.successors[0]; + if (node.label != null) { + return "Continue ${node.label.labelName}: (B${target.id})"; + } + return "Continue: (B${target.id})"; + } + + String visitDivide(HDivide node) => handleInvokeBinary(node, '/'); + + String visitExit(HExit node) => "exit"; + + String visitFieldGet(HFieldGet node) { + String fieldName = node.element.name.slowToString(); + return 'field get ${temporaryId(node.receiver)}.$fieldName'; + } + + String visitFieldSet(HFieldSet node) { + String valueId = temporaryId(node.value); + String fieldName = node.element.name.slowToString(); + return 'field set ${temporaryId(node.receiver)}.$fieldName to $valueId'; + } + + String visitLocalGet(HLocalGet node) { + String localName = node.element.name.slowToString(); + return 'local get ${temporaryId(node.local)}.$localName'; + } + + String visitLocalSet(HLocalSet node) { + String valueId = temporaryId(node.value); + String localName = node.element.name.slowToString(); + return 'local set ${temporaryId(node.local)}.$localName to $valueId'; + } + + String visitGoto(HGoto node) { + HBasicBlock target = currentBlock.successors[0]; + return "Goto: (B${target.id})"; + } + + String visitGreater(HGreater node) => handleInvokeBinary(node, '>'); + String visitGreaterEqual(HGreaterEqual node) { + handleInvokeBinary(node, '>='); + } + String visitIdentity(HIdentity node) => handleInvokeBinary(node, '==='); + + String visitIf(HIf node) { + HBasicBlock thenBlock = currentBlock.successors[0]; + HBasicBlock elseBlock = currentBlock.successors[1]; + String conditionId = temporaryId(node.inputs[0]); + return "If ($conditionId): (B${thenBlock.id}) else (B${elseBlock.id})"; + } + + String visitGenericInvoke(String invokeType, String functionName, + List<HInstruction> arguments) { + StringBuffer argumentsString = new StringBuffer(); + for (int i = 0; i < arguments.length; i++) { + if (i != 0) argumentsString.add(", "); + argumentsString.add(temporaryId(arguments[i])); + } + return "$invokeType: $functionName($argumentsString)"; + } + + String visitIndex(HIndex node) { + String receiver = temporaryId(node.receiver); + String index = temporaryId(node.index); + return "Index: $receiver[$index]"; + } + + String visitIndexAssign(HIndexAssign node) { + String receiver = temporaryId(node.receiver); + String index = temporaryId(node.index); + String value = temporaryId(node.value); + return "IndexAssign: $receiver[$index] = $value"; + } + + String visitIntegerCheck(HIntegerCheck node) { + String value = temporaryId(node.value); + return "Integer check: $value"; + } + + String visitInterceptor(HInterceptor node) { + String value = temporaryId(node.inputs[0]); + return "Intercept: $value"; + } + + String visitInvokeClosure(HInvokeClosure node) + => visitInvokeDynamic(node, "closure"); + + String visitInvokeDynamic(HInvokeDynamic invoke, String kind) { + String receiver = temporaryId(invoke.receiver); + String name = invoke.selector.name.slowToString(); + String target = "($kind) $receiver.$name"; + int offset = HInvoke.ARGUMENTS_OFFSET; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke", target, arguments); + } + + String visitInvokeDynamicMethod(HInvokeDynamicMethod node) + => visitInvokeDynamic(node, "method"); + String visitInvokeDynamicGetter(HInvokeDynamicGetter node) + => visitInvokeDynamic(node, "get"); + String visitInvokeDynamicSetter(HInvokeDynamicSetter node) + => visitInvokeDynamic(node, "set"); + + String visitInvokeStatic(HInvokeStatic invoke) { + String target = temporaryId(invoke.target); + int offset = HInvoke.ARGUMENTS_OFFSET; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke", target, arguments); + } + + String visitInvokeSuper(HInvokeSuper invoke) { + String target = temporaryId(invoke.target); + int offset = HInvoke.ARGUMENTS_OFFSET + 1; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke super", target, arguments); + } + + String visitForeign(HForeign foreign) { + return visitGenericInvoke("Foreign", "${foreign.code}", foreign.inputs); + } + + String visitForeignNew(HForeignNew node) { + return visitGenericInvoke("New", + "${node.element.name.slowToString()}", + node.inputs); + } + + String visitLess(HLess node) => handleInvokeBinary(node, '<'); + String visitLessEqual(HLessEqual node) => handleInvokeBinary(node, '<='); + + String visitLiteralList(HLiteralList node) { + StringBuffer elementsString = new StringBuffer(); + for (int i = 0; i < node.inputs.length; i++) { + if (i != 0) elementsString.add(", "); + elementsString.add(temporaryId(node.inputs[i])); + } + return "Literal list: [$elementsString]"; + } + + String visitLoopBranch(HLoopBranch branch) { + HBasicBlock bodyBlock = currentBlock.successors[0]; + HBasicBlock exitBlock = currentBlock.successors[1]; + String conditionId = temporaryId(branch.inputs[0]); + return "While ($conditionId): (B${bodyBlock.id}) then (B${exitBlock.id})"; + } + + String visitMultiply(HMultiply node) => handleInvokeBinary(node, '*'); + + String visitNegate(HNegate node) { + String operand = temporaryId(node.operand); + return "-$operand"; + } + + String visitNot(HNot node) => "Not: ${temporaryId(node.inputs[0])}"; + + String visitParameterValue(HParameterValue node) { + return "p${node.sourceElement.name.slowToString()}"; + } + + String visitLocalValue(HLocalValue node) { + return "l${node.sourceElement.name.slowToString()}"; + } + + String visitPhi(HPhi phi) { + StringBuffer buffer = new StringBuffer(); + buffer.add("Phi("); + for (int i = 0; i < phi.inputs.length; i++) { + if (i > 0) buffer.add(", "); + buffer.add(temporaryId(phi.inputs[i])); + } + buffer.add(")"); + return buffer.toString(); + } + + String visitReturn(HReturn node) => "Return ${temporaryId(node.inputs[0])}"; + + String visitShiftLeft(HShiftLeft node) => handleInvokeBinary(node, '<<'); + + String visitStatic(HStatic node) + => "Static ${node.element.name.slowToString()}"; + + String visitLazyStatic(HLazyStatic node) + => "LazyStatic ${node.element.name.slowToString()}"; + + String visitOneShotInterceptor(HOneShotInterceptor node) + => visitInvokeDynamic(node, "one shot interceptor"); + + String visitStaticStore(HStaticStore node) { + String lhs = node.element.name.slowToString(); + return "Static $lhs = ${temporaryId(node.inputs[0])}"; + } + + String visitStringConcat(HStringConcat node) { + var leftId = temporaryId(node.left); + var rightId = temporaryId(node.right); + return "StringConcat: $leftId + $rightId"; + } + + String visitSubtract(HSubtract node) => handleInvokeBinary(node, '-'); + + String visitSwitch(HSwitch node) { + StringBuffer buf = new StringBuffer(); + buf.add("Switch: ("); + buf.add(temporaryId(node.inputs[0])); + buf.add(") "); + for (int i = 1; i < node.inputs.length; i++) { + buf.add(temporaryId(node.inputs[i])); + buf.add(": B"); + buf.add(node.block.successors[i - 1].id); + buf.add(", "); + } + buf.add("default: B"); + buf.add(node.block.successors.last.id); + return buf.toString(); + } + + String visitThis(HThis node) => "this"; + + String visitThrow(HThrow node) => "Throw ${temporaryId(node.inputs[0])}"; + + String visitExitTry(HExitTry node) { + return "Exit try"; + } + + String visitTry(HTry node) { + List<HBasicBlock> successors = currentBlock.successors; + String tryBlock = 'B${successors[0].id}'; + String catchBlock = 'none'; + if (node.catchBlock != null) { + catchBlock = 'B${successors[1].id}'; + } + + String finallyBlock = 'none'; + if (node.finallyBlock != null) { + finallyBlock = 'B${node.finallyBlock.id}'; + } + + return "Try: $tryBlock, Catch: $catchBlock, Finally: $finallyBlock, " + "Join: B${successors.last.id}"; + } + + String visitTypeGuard(HTypeGuard node) { + String type; + HType guardedType = node.guardedType; + if (guardedType == HType.MUTABLE_ARRAY) { + type = "mutable_array"; + } else if (guardedType == HType.READABLE_ARRAY) { + type = "readable_array"; + } else if (guardedType == HType.EXTENDABLE_ARRAY) { + type = "extendable_array"; + } else if (guardedType == HType.BOOLEAN) { + type = "bool"; + } else if (guardedType == HType.INTEGER) { + type = "integer"; + } else if (guardedType == HType.DOUBLE) { + type = "double"; + } else if (guardedType == HType.NUMBER) { + type = "number"; + } else if (guardedType == HType.STRING) { + type = "string"; + } else if (guardedType == HType.INDEXABLE_PRIMITIVE) { + type = "string_or_array"; + } else if (guardedType == HType.UNKNOWN) { + type = 'unknown'; + } else { + throw new CompilerCancelledException('Unexpected type guard: $type'); + } + HInstruction guarded = node.guarded; + HInstruction bailoutTarget = node.bailoutTarget; + StringBuffer envBuffer = new StringBuffer(); + List<HInstruction> inputs = node.inputs; + assert(inputs.length >= 2); + assert(inputs[0] == guarded); + assert(inputs[1] == bailoutTarget); + for (int i = 2; i < inputs.length; i++) { + envBuffer.add(" ${temporaryId(inputs[i])}"); + } + String on = node.isEnabled ? "enabled" : "disabled"; + String guardedId = temporaryId(node.guarded); + String bailoutId = temporaryId(node.bailoutTarget); + return "TypeGuard($on): $guardedId is $type bailout: $bailoutId " + "env: $envBuffer"; + } + + String visitIs(HIs node) { + String type = node.typeExpression.toString(); + return "TypeTest: ${temporaryId(node.expression)} is $type"; + } + + String visitTypeConversion(HTypeConversion node) { + return "TypeConversion: ${temporaryId(node.checkedInput)} to ${node.type}"; + } + + String visitRangeConversion(HRangeConversion node) { + return "RangeConversion: ${node.checkedInput}"; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart new file mode 100644 index 0000000..b6d4746 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart
@@ -0,0 +1,997 @@ +// 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. + +part of ssa; + +abstract class HType { + const HType(); + + /** + * Returns an [HType] that represents [type] and all types that have + * [type] as supertype. + */ + factory HType.fromBoundedType(DartType type, + Compiler compiler, + [bool canBeNull = false]) { + Element element = type.element; + if (element.kind == ElementKind.TYPE_VARIABLE) { + // TODO(ngeoffray): Replace object type with [type]. + return new HBoundedPotentialPrimitiveType( + compiler.objectClass.computeType(compiler), canBeNull, true); + } + + if (element == compiler.intClass) { + return canBeNull ? HType.INTEGER_OR_NULL : HType.INTEGER; + } else if (element == compiler.numClass) { + return canBeNull ? HType.NUMBER_OR_NULL : HType.NUMBER; + } else if (element == compiler.doubleClass) { + return canBeNull ? HType.DOUBLE_OR_NULL : HType.DOUBLE; + } else if (element == compiler.stringClass) { + return canBeNull ? HType.STRING_OR_NULL : HType.STRING; + } else if (element == compiler.boolClass) { + return canBeNull ? HType.BOOLEAN_OR_NULL : HType.BOOLEAN; + } else if (element == compiler.nullClass) { + return HType.NULL; + } else if (element == compiler.listClass + || Elements.isListSupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveArray(type, canBeNull); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveNumberOrString(type, canBeNull); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveString(type, canBeNull); + } else if (element == compiler.objectClass) { + return new HBoundedPotentialPrimitiveType( + compiler.objectClass.computeType(compiler), canBeNull, true); + } else { + return canBeNull ? new HBoundedType.withNull(type) + : new HBoundedType.nonNull(type); + } + } + + static const HType CONFLICTING = const HConflictingType(); + static const HType UNKNOWN = const HUnknownType(); + static const HType BOOLEAN = const HBooleanType(); + static const HType NUMBER = const HNumberType(); + static const HType INTEGER = const HIntegerType(); + static const HType DOUBLE = const HDoubleType(); + static const HType INDEXABLE_PRIMITIVE = const HIndexablePrimitiveType(); + static const HType STRING = const HStringType(); + static const HType READABLE_ARRAY = const HReadableArrayType(); + static const HType MUTABLE_ARRAY = const HMutableArrayType(); + static const HType FIXED_ARRAY = const HFixedArrayType(); + static const HType EXTENDABLE_ARRAY = const HExtendableArrayType(); + static const HType NULL = const HNullType(); + + static const HType BOOLEAN_OR_NULL = const HBooleanOrNullType(); + static const HType NUMBER_OR_NULL = const HNumberOrNullType(); + static const HType INTEGER_OR_NULL = const HIntegerOrNullType(); + static const HType DOUBLE_OR_NULL = const HDoubleOrNullType(); + static const HType STRING_OR_NULL = const HStringOrNullType(); + + bool isConflicting() => identical(this, CONFLICTING); + bool isUnknown() => identical(this, UNKNOWN); + bool isNull() => false; + bool isBoolean() => false; + bool isNumber() => false; + bool isInteger() => false; + bool isDouble() => false; + bool isString() => false; + bool isBooleanOrNull() => false; + bool isNumberOrNull() => false; + bool isIntegerOrNull() => false; + bool isDoubleOrNull() => false; + bool isStringOrNull() => false; + bool isIndexablePrimitive() => false; + bool isFixedArray() => false; + bool isReadableArray() => false; + bool isMutableArray() => false; + bool isExtendableArray() => false; + bool isPrimitive() => false; + bool isExact() => false; + bool isPrimitiveOrNull() => false; + bool isTop() => false; + + bool canBePrimitive() => false; + bool canBeNull() => false; + + /** A type is useful it is not unknown, not conflicting, and not null. */ + bool isUseful() => !isUnknown() && !isConflicting() && !isNull(); + /** Alias for isReadableArray. */ + bool isArray() => isReadableArray(); + + DartType computeType(Compiler compiler); + + /** + * The intersection of two types is the intersection of its values. For + * example: + * * INTEGER.intersect(NUMBER) => INTEGER. + * * DOUBLE.intersect(INTEGER) => CONFLICTING. + * * MUTABLE_ARRAY.intersect(READABLE_ARRAY) => MUTABLE_ARRAY. + * + * When there is no predefined type to represent the intersection returns + * [CONFLICTING]. + * + * An intersection with [UNKNOWN] returns the non-UNKNOWN type. An + * intersection with [CONFLICTING] returns [CONFLICTING]. + */ + HType intersection(HType other, Compiler compiler); + + /** + * The union of two types is the union of its values. For example: + * * INTEGER.union(NUMBER) => NUMBER. + * * DOUBLE.union(INTEGER) => NUMBER. + * * MUTABLE_ARRAY.union(READABLE_ARRAY) => READABLE_ARRAY. + * + * When there is no predefined type to represent the union returns + * [UNKNOWN]. + * + * A union with [UNKNOWN] returns [UNKNOWN]. + * A union of [CONFLICTING] with any other types returns the other type. + */ + HType union(HType other, Compiler compiler); +} + +/** Used to represent [HType.UNKNOWN] and [HType.CONFLICTING]. */ +abstract class HAnalysisType extends HType { + final String name; + const HAnalysisType(this.name); + String toString() => name; + + DartType computeType(Compiler compiler) => null; +} + +class HUnknownType extends HAnalysisType { + const HUnknownType() : super("unknown"); + bool canBePrimitive() => true; + bool canBeNull() => true; + + HType union(HType other, Compiler compiler) => this; + HType intersection(HType other, Compiler compiler) => other; +} + +class HConflictingType extends HAnalysisType { + const HConflictingType() : super("conflicting"); + bool canBePrimitive() => true; + bool canBeNull() => true; + + HType union(HType other, Compiler compiler) => other; + HType intersection(HType other, Compiler compiler) => this; +} + +abstract class HPrimitiveType extends HType { + const HPrimitiveType(); + bool isPrimitive() => true; + bool canBePrimitive() => true; + bool isPrimitiveOrNull() => true; +} + +class HNullType extends HPrimitiveType { + const HNullType(); + bool canBeNull() => true; + bool isNull() => true; + String toString() => 'null'; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNullClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING_OR_NULL; + if (other.isInteger()) return HType.INTEGER_OR_NULL; + if (other.isDouble()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN_OR_NULL; + // TODO(ngeoffray): Deal with the type of null more generally. + if (other.isReadableArray()) return other.union(this, compiler); + if (!other.canBeNull()) return HType.UNKNOWN; + return other; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isUnknown()) return HType.NULL; + if (other.isConflicting()) return HType.CONFLICTING; + if (!other.canBeNull()) return HType.CONFLICTING; + return HType.NULL; + } +} + +abstract class HPrimitiveOrNullType extends HType { + const HPrimitiveOrNullType(); + bool canBePrimitive() => true; + bool canBeNull() => true; + bool isPrimitiveOrNull() => true; +} + +class HBooleanOrNullType extends HPrimitiveOrNullType { + const HBooleanOrNullType(); + String toString() => "boolean or null"; + bool isBooleanOrNull() => true; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsBoolClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.BOOLEAN_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN_OR_NULL; + if (other.isNull()) return HType.BOOLEAN_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.BOOLEAN_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.BOOLEAN; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HBooleanType extends HPrimitiveType { + const HBooleanType(); + bool isBoolean() => true; + bool isBooleanOrNull() => true; + String toString() => "boolean"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsBoolClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.BOOLEAN; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isBoolean()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isNull()) return HType.BOOLEAN_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN; + if (other.isBoolean()) return HType.BOOLEAN; + return HType.CONFLICTING; + } +} + +class HNumberOrNullType extends HPrimitiveOrNullType { + const HNumberOrNullType(); + bool isNumberOrNull() => true; + String toString() => "number or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNumberClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NUMBER_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.NUMBER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.NUMBER_OR_NULL; + if (other.isInteger()) return HType.INTEGER; + if (other.isDouble()) return HType.DOUBLE; + if (other.isNumber()) return HType.NUMBER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.NUMBER; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HNumberType extends HPrimitiveType { + const HNumberType(); + bool isNumber() => true; + bool isNumberOrNull() => true; + String toString() => "number"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNumberClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NUMBER; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.NUMBER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.NUMBER; + if (other.isNumber()) return other; + if (other.isIntegerOrNull()) return HType.INTEGER; + if (other.isDoubleOrNull()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.NUMBER; + return HType.CONFLICTING; + } +} + +class HIntegerOrNullType extends HNumberOrNullType { + const HIntegerOrNullType(); + bool isIntegerOrNull() => true; + String toString() => "integer or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsIntClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INTEGER_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isInteger()) return HType.INTEGER_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.INTEGER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INTEGER_OR_NULL; + if (other.isInteger()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isDouble()) return HType.CONFLICTING; + if (other.isDoubleOrNull()) return HType.NULL; + if (other.isNumber()) return HType.INTEGER; + if (other.isNumberOrNull()) return HType.INTEGER_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.INTEGER; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HIntegerType extends HNumberType { + const HIntegerType(); + bool isInteger() => true; + bool isIntegerOrNull() => true; + String toString() => "integer"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsIntClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INTEGER; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isInteger()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.INTEGER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER; + if (other.isInteger()) return HType.INTEGER; + if (other.isDouble()) return HType.CONFLICTING; + if (other.isDoubleOrNull()) return HType.CONFLICTING; + if (other.isNumber()) return HType.INTEGER; + if (other.isNumberOrNull()) return HType.INTEGER; + return HType.CONFLICTING; + } +} + +class HDoubleOrNullType extends HNumberOrNullType { + const HDoubleOrNullType(); + bool isDoubleOrNull() => true; + String toString() => "double or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsDoubleClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.DOUBLE_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isDouble()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.DOUBLE_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.DOUBLE_OR_NULL; + if (other.isInteger()) return HType.CONFLICTING; + if (other.isIntegerOrNull()) return HType.NULL; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.DOUBLE; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HDoubleType extends HNumberType { + const HDoubleType(); + bool isDouble() => true; + bool isDoubleOrNull() => true; + String toString() => "double"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsDoubleClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.DOUBLE; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.DOUBLE_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.DOUBLE; + if (other.isIntegerOrNull()) return HType.CONFLICTING; + if (other.isInteger()) return HType.CONFLICTING; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE; + if (other.isNumber()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.DOUBLE; + return HType.CONFLICTING; + } +} + +class HIndexablePrimitiveType extends HPrimitiveType { + const HIndexablePrimitiveType(); + bool isIndexablePrimitive() => true; + String toString() => "indexable"; + + DartType computeType(Compiler compiler) { + // TODO(ngeoffray): Represent union types. + return null; + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INDEXABLE_PRIMITIVE; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveString) { + // TODO(ngeoffray): Represent union types. + return HType.UNKNOWN; + } + if (other is HBoundedPotentialPrimitiveArray) { + // TODO(ngeoffray): Represent union types. + return HType.UNKNOWN; + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INDEXABLE_PRIMITIVE; + if (other.isIndexablePrimitive()) return other; + if (other is HBoundedPotentialPrimitiveString) return HType.STRING; + if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HStringOrNullType extends HPrimitiveOrNullType { + const HStringOrNullType(); + bool isStringOrNull() => true; + String toString() => "String or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsStringClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.STRING_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING_OR_NULL; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isIndexablePrimitive()) { + // We don't have a type that represents the nullable indexable + // primitive. + return HType.UNKNOWN; + } + if (other is HBoundedPotentialPrimitiveString) { + if (other.canBeNull()) { + return other; + } else { + HBoundedType boundedType = other; + return new HBoundedPotentialPrimitiveString(boundedType.type, true); + } + } + if (other.isNull()) return HType.STRING_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.STRING_OR_NULL; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + if (other is HBoundedPotentialPrimitiveString) { + return other.canBeNull() ? HType.STRING_OR_NULL : HType.STRING; + } + if (other.isTop()) { + return other.canBeNull() ? this : HType.STRING; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HStringType extends HIndexablePrimitiveType { + const HStringType(); + bool isString() => true; + bool isStringOrNull() => true; + String toString() => "String"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsStringClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.STRING; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveString) return other; + if (other.isNull()) return HType.STRING_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.STRING; + if (other.isString()) return HType.STRING; + if (other.isArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING; + if (other is HBoundedPotentialPrimitiveString) return HType.STRING; + return HType.CONFLICTING; + } +} + +class HReadableArrayType extends HIndexablePrimitiveType { + const HReadableArrayType(); + bool isReadableArray() => true; + String toString() => "readable array"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsArrayClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.READABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be readable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.READABLE_ARRAY; + if (other.isString()) return HType.CONFLICTING; + if (other.isReadableArray()) return other; + if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HMutableArrayType extends HReadableArrayType { + const HMutableArrayType(); + bool isMutableArray() => true; + String toString() => "mutable array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.MUTABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be mutable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.MUTABLE_ARRAY; + if (other.isMutableArray()) return other; + if (other.isString()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.MUTABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.MUTABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HFixedArrayType extends HMutableArrayType { + const HFixedArrayType(); + bool isFixedArray() => true; + String toString() => "fixed array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.FIXED_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isFixedArray()) return HType.FIXED_ARRAY; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be fixed array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.FIXED_ARRAY; + if (other.isFixedArray()) return HType.FIXED_ARRAY; + if (other.isExtendableArray()) return HType.CONFLICTING; + if (other.isString()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.FIXED_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.FIXED_ARRAY; + return HType.CONFLICTING; + } +} + +class HExtendableArrayType extends HMutableArrayType { + const HExtendableArrayType(); + bool isExtendableArray() => true; + String toString() => "extendable array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.EXTENDABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be extendable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.EXTENDABLE_ARRAY; + if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY; + if (other.isString()) return HType.CONFLICTING; + if (other.isFixedArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.EXTENDABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.EXTENDABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HBoundedType extends HType { + final DartType type; + final bool _canBeNull; + final bool _isExact; + + String toString() { + return 'BoundedType($type, canBeNull: $_canBeNull, isExact: $_isExact)'; + } + + bool canBeNull() => _canBeNull; + + bool isExact() => _isExact; + + const HBoundedType(DartType this.type, + [bool canBeNull = false, isExact = false]) + : _canBeNull = canBeNull, _isExact = isExact; + const HBoundedType.exact(DartType type) : this(type, false, true); + const HBoundedType.withNull(DartType type) : this(type, true, false); + const HBoundedType.nonNull(DartType type) : this(type); + + DartType computeType(Compiler compiler) => type; + + Element lookupMember(SourceString name) { + if (!isExact()) return null; + ClassElement classElement = type.element; + return classElement.lookupMember(name); + } + + HType intersection(HType other, Compiler compiler) { + assert(!(isExact() && canBeNull())); + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isNull()) return canBeNull() ? HType.NULL : HType.CONFLICTING; + + if (other is HBoundedType) { + HBoundedType temp = other; + if (identical(this.type, temp.type)) { + // If the types are the same, we return the [HBoundedType] + // that has the most restrictive representation: if it's exact + // (eg cannot be a subtype), and if it cannot be null. + if (isExact()) { + return this; + } else if (other.isExact()) { + return other; + } else if (canBeNull()) { + return other; + } else { + return this; + } + // If one type is a subtype of the other, we return the former, + // which is the narrower type. + } else if (!type.isMalformed && !other.type.isMalformed) { + if (compiler.types.isSubtype(type, other.type)) { + return this; + } else if (compiler.types.isSubtype(other.type, type)) { + return other; + } + } + } + if (other.isUnknown()) return this; + if (other.canBeNull() && canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } + + bool operator ==(HType other) { + if (other is !HBoundedType) return false; + HBoundedType bounded = other; + return (identical(type, bounded.type) + && identical(canBeNull(), bounded.canBeNull()) + && identical(isExact(), other.isExact())); + } + + HType union(HType other, Compiler compiler) { + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedType.withNull(type); + } + } + if (other is HBoundedType) { + HBoundedType temp = other; + if (!identical(type, temp.type)) return HType.UNKNOWN; + if (isExact()) return other; + if (other.isExact()) return this; + return canBeNull() ? this : other; + } + if (other.isConflicting()) return this; + return HType.UNKNOWN; + } +} + +class HBoundedPotentialPrimitiveType extends HBoundedType { + final bool _isObject; + const HBoundedPotentialPrimitiveType(DartType type, + bool canBeNull, + this._isObject) + : super(type, canBeNull, false); + + String toString() { + return 'BoundedPotentialPrimitiveType($type, canBeNull: $_canBeNull)'; + } + + bool canBePrimitive() => true; + bool isTop() => _isObject; + + HType union(HType other, Compiler compiler) { + if (isTop()) { + // The union of the top type and another type is the top type. + if (!canBeNull() && other.canBeNull()) { + return new HBoundedPotentialPrimitiveType(type, true, true); + } else { + return this; + } + } else { + return super.union(other, compiler); + } + } + + HType intersection(HType other, Compiler compiler) { + if (isTop()) { + // The intersection of the top type and any other type is the other type. + // TODO(ngeoffray): Also update the canBeNull information. + return other; + } else { + return super.intersection(other, compiler); + } + } +} + +class HBoundedPotentialPrimitiveNumberOrString + extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveNumberOrString(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + HType union(HType other, Compiler compiler) { + if (other.isNumber()) return this; + if (other.isNumberOrNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + if (other.isString()) return this; + if (other.isStringOrNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + if (other.isNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isNumber()) return other; + if (other.isNumberOrNull()) { + if (!canBeNull()) return HType.NUMBER; + return other; + } + if (other.isString()) return other; + if (other.isStringOrNull()) { + if (!canBeNull()) return HType.STRING; + return other; + } + return super.intersection(other, compiler); + } +} + +class HBoundedPotentialPrimitiveArray extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveArray(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + HType union(HType other, Compiler compiler) { + if (other.isString()) return HType.UNKNOWN; + if (other.isReadableArray()) return this; + // TODO(ngeoffray): implement union types. + if (other.isIndexablePrimitive()) return HType.UNKNOWN; + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveArray(type, true); + } + } + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isString()) return HType.CONFLICTING; + if (other.isReadableArray()) return other; + if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY; + return super.intersection(other, compiler); + } +} + +class HBoundedPotentialPrimitiveString extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveString(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + bool isPrimitiveOrNull() => true; + + HType union(HType other, Compiler compiler) { + if (other.isString()) return this; + if (other.isStringOrNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveString(type, true); + } + } + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveString(type, true); + } + } + // TODO(ngeoffray): implement union types. + if (other.isIndexablePrimitive()) return HType.UNKNOWN; + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) { + return canBeNull() ? HType.STRING_OR_NULL : HType.STRING; + } + if (other.isReadableArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + return super.intersection(other, compiler); + } +} + +class HTypeMap { + // Approximately 85% of methods in the sample "swarm" have less than + // 32 instructions. + static const int INITIAL_SIZE = 32; + + List<HType> _list = new List<HType>()..length = INITIAL_SIZE; + + operator [](HInstruction instruction) { + HType result; + if (instruction.id < _list.length) result = _list[instruction.id]; + if (result == null) return instruction.guaranteedType; + return result; + } + + operator []=(HInstruction instruction, HType value) { + int length = _list.length; + int id = instruction.id; + if (length <= id) { + if (id + 1 < length * 2) { + _list.length = length * 2; + } else { + _list.length = id + 1; + } + } + _list[id] = value; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart new file mode 100644 index 0000000..ee0f1a1 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart
@@ -0,0 +1,210 @@ +// 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. + +part of ssa; + +class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase { + + final Map<int, HInstruction> workmap; + final List<int> worklist; + final Map<HInstruction, Function> pendingOptimizations; + final HTypeMap types; + + final Compiler compiler; + String get name => 'type propagator'; + + SsaTypePropagator(this.compiler, this.types) + : workmap = new Map<int, HInstruction>(), + worklist = new List<int>(), + pendingOptimizations = new Map<HInstruction, Function>(); + + HType computeType(HInstruction instruction) { + if (instruction.hasGuaranteedType()) return instruction.guaranteedType; + return instruction.computeTypeFromInputTypes(types, compiler); + } + + // Re-compute and update the type of the instruction. Returns + // whether or not the type was changed. + bool updateType(HInstruction instruction) { + // The [updateType] method is invoked when one of the inputs of + // the instruction changes its type. That gives us a new + // opportunity to consider this instruction for optimizations. + considerForArgumentTypeOptimization(instruction); + // Compute old and new types. + HType oldType = types[instruction]; + HType newType = computeType(instruction); + // We unconditionally replace the propagated type with the new type. The + // computeType must make sure that we eventually reach a stable state. + types[instruction] = newType; + return oldType != newType; + } + + void considerForArgumentTypeOptimization(HInstruction instruction) { + // Update the pending optimizations map based on the potentially + // new types of the operands. If the operand types no longer allow + // us to optimize, we remove the pending optimization. + if (instruction is !HInvokeDynamicMethod) return; + HInvokeDynamicMethod invoke = instruction; + if (instruction.specializer is !BinaryArithmeticSpecializer) return; + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isNumber(types) && !right.isNumber(types)) { + pendingOptimizations[instruction] = () { + // This callback function is invoked after we're done + // propagating types. The types shouldn't have changed. + assert(left.isNumber(types) && !right.isNumber(types)); + convertInput(instruction, right, HType.NUMBER); + }; + } else { + pendingOptimizations.remove(instruction); + } + } + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + processWorklist(); + } + + visitBasicBlock(HBasicBlock block) { + if (block.isLoopHeader()) { + block.forEachPhi((HPhi phi) { + // Set the initial type for the phi. We're not using the type + // the phi thinks it has because new optimizations may imply + // changing it. + // In theory we would need to mark + // the type of all other incoming edges as "unitialized" and take this + // into account when doing the propagation inside the phis. Just + // setting the propagated type is however easier. + types[phi] = types[phi.inputs[0]]; + addToWorkList(phi); + }); + } else { + block.forEachPhi((HPhi phi) { + if (updateType(phi)) { + addDependentInstructionsToWorkList(phi); + } + }); + } + + HInstruction instruction = block.first; + while (instruction != null) { + if (updateType(instruction)) { + addDependentInstructionsToWorkList(instruction); + } + instruction = instruction.next; + } + } + + void processWorklist() { + do { + while (!worklist.isEmpty) { + int id = worklist.removeLast(); + HInstruction instruction = workmap[id]; + assert(instruction != null); + workmap.remove(id); + if (updateType(instruction)) { + addDependentInstructionsToWorkList(instruction); + } + } + // While processing the optimizable arithmetic instructions, we + // may discover better type information for dominated users of + // replaced operands, so we may need to take another stab at + // emptying the worklist afterwards. + processPendingOptimizations(); + } while (!worklist.isEmpty); + } + + void addDependentInstructionsToWorkList(HInstruction instruction) { + for (int i = 0, length = instruction.usedBy.length; i < length; i++) { + // The non-speculative type propagator only propagates types forward. We + // thus only need to add the users of the [instruction] to the list. + addToWorkList(instruction.usedBy[i]); + } + } + + void addToWorkList(HInstruction instruction) { + final int id = instruction.id; + if (!workmap.containsKey(id)) { + worklist.add(id); + workmap[id] = instruction; + } + } + + void processPendingOptimizations() { + pendingOptimizations.forEach((instruction, action) => action()); + pendingOptimizations.clear(); + } + + void convertInput(HInstruction instruction, HInstruction input, HType type) { + HTypeConversion converted = + new HTypeConversion.argumentTypeCheck(type, input); + instruction.block.addBefore(instruction, converted); + Set<HInstruction> dominatedUsers = input.dominatedUsers(instruction); + for (HInstruction user in dominatedUsers) { + user.changeUse(input, converted); + addToWorkList(user); + } + } +} + +class SsaSpeculativeTypePropagator extends SsaTypePropagator { + final String name = 'speculative type propagator'; + SsaSpeculativeTypePropagator(Compiler compiler, HTypeMap types) + : super(compiler, types); + + void addDependentInstructionsToWorkList(HInstruction instruction) { + // The speculative type propagator propagates types forward and backward. + // Not only do we need to add the users of the [instruction] to the list. + // We also need to add the inputs fo the [instruction], since they might + // want to propagate the desired outgoing type. + for (int i = 0, length = instruction.usedBy.length; i < length; i++) { + addToWorkList(instruction.usedBy[i]); + } + for (int i = 0, length = instruction.inputs.length; i < length; i++) { + addToWorkList(instruction.inputs[i]); + } + } + + HType computeDesiredType(HInstruction instruction) { + HType desiredType = HType.UNKNOWN; + for (final user in instruction.usedBy) { + HType userType = + user.computeDesiredTypeForInput(instruction, types, compiler); + // Mainly due to the "if (true)" added by hackAroundPossiblyAbortingBody + // in builder.dart uninitialized variables will propagate a type of null + // which will result in a conflicting type when combined with a primitive + // type. Avoid this to improve generated code. + // TODO(sgjesse): Reconcider this when hackAroundPossiblyAbortingBody + // has been removed. + if (desiredType.isPrimitive() && userType == HType.NULL) continue; + desiredType = desiredType.intersection(userType, compiler); + // No need to continue if two users disagree on the type. + if (desiredType.isConflicting()) break; + } + return desiredType; + } + + HType computeType(HInstruction instruction) { + // Once we are in a conflicting state don't update the type anymore. + HType oldType = types[instruction]; + if (oldType.isConflicting()) return oldType; + + HType newType = super.computeType(instruction); + // [computeDesiredType] goes to all usedBys and lets them compute their + // desired type. By setting the [newType] here we give them more context to + // work with. + types[instruction] = newType; + HType desiredType = computeDesiredType(instruction); + // If the desired type is conflicting just return the computed type. + if (desiredType.isConflicting()) return newType; + // TODO(ngeoffray): Allow speculative optimizations on + // non-primitive types? + if (!desiredType.isPrimitive()) return newType; + return newType.intersection(desiredType, compiler); + } + + // Do not use speculative argument type optimization for now. + void considerForArgumentTypeOptimization(HInstruction instruction) { } + +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart new file mode 100644 index 0000000..5e9598e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart
@@ -0,0 +1,181 @@ +// Copyright (c) 2011, 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. + +part of ssa; + +class HValidator extends HInstructionVisitor { + bool isValid = true; + HGraph graph; + + void visitGraph(HGraph visitee) { + graph = visitee; + visitDominatorTree(visitee); + } + + void markInvalid(String reason) { + print(reason); + isValid = false; + } + + // Note that during construction of the Ssa graph the basic blocks are + // not required to be valid yet. + void visitBasicBlock(HBasicBlock block) { + currentBlock = block; + if (!isValid) return; // Don't need to continue if we are already invalid. + + // Test that the last instruction is a branching instruction and that the + // basic block contains the branch-target. + if (block.first == null || block.last == null) { + markInvalid("empty block"); + } + if (block.last is !HControlFlow) { + markInvalid("block ends with non-tail node."); + } + if (block.last is HIf && block.successors.length != 2) { + markInvalid("If node without two successors"); + } + if (block.last is HConditionalBranch && block.successors.length != 2) { + markInvalid("Conditional node without two successors"); + } + if (block.last is HGoto && block.successors.length != 1) { + markInvalid("Goto node with not exactly one successor"); + } + if (block.last is HJump && block.successors.length != 1) { + markInvalid("Break or continue node without one successor"); + } + if (block.last is HReturn && + (block.successors.length != 1 || !block.successors[0].isExitBlock())) { + markInvalid("Return node with > 1 succesor or not going to exit-block"); + } + if (block.last is HExit && !block.successors.isEmpty) { + markInvalid("Exit block with successor"); + } + if (block.last is HThrow && !block.successors.isEmpty) { + markInvalid("Throw block with successor"); + } + + if (block.successors.isEmpty && + block.last is !HThrow && + !block.isExitBlock()) { + markInvalid("Non-exit or throw block without successor"); + } + + // Check that successors ids are always higher than the current one. + // TODO(floitsch): this is, of course, not true for back-branches. + if (block.id == null) markInvalid("block without id"); + for (HBasicBlock successor in block.successors) { + if (!isValid) break; + if (successor.id == null) markInvalid("successor without id"); + if (successor.id <= block.id && !successor.isLoopHeader()) { + markInvalid("successor with lower id, but not a loop-header"); + } + } + + // Check that the entries in the dominated-list are sorted. + int lastId = 0; + for (HBasicBlock dominated in block.dominatedBlocks) { + if (!isValid) break; + if (!identical(dominated.dominator, block)) { + markInvalid("dominated block not pointing back"); + } + if (dominated.id == null || dominated.id <= lastId) { + markInvalid("dominated.id == null or dominated has <= id"); + } + lastId = dominated.id; + } + + if (!isValid) return; + block.forEachPhi(visitInstruction); + + // Check that the blocks of the parameters of a phi are dominating the + // corresponding predecessor block. Note that a block dominates + // itself. + block.forEachPhi((HPhi phi) { + for (int i = 0; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + if (!input.block.dominates(block.predecessors[i])) { + markInvalid("Definition does not dominate use"); + } + } + }); + + // Check that the blocks of the inputs of an instruction dominate the + // instruction's block. + block.forEachInstruction((HInstruction instruction) { + for (HInstruction input in instruction.inputs) { + if (!input.block.dominates(block)) { + markInvalid("Definition does not dominate use"); + } + } + }); + + super.visitBasicBlock(block); + } + + /** Returns how often [instruction] is contained in [instructions]. */ + static int countInstruction(List<HInstruction> instructions, + HInstruction instruction) { + int result = 0; + for (int i = 0; i < instructions.length; i++) { + if (identical(instructions[i], instruction)) result++; + } + return result; + } + + /** + * Returns true if the predicate returns true for every instruction in the + * list. The argument to [f] is an instruction with the count of how often + * it appeared in the list [instructions]. + */ + static bool everyInstruction(List<HInstruction> instructions, Function f) { + var copy = new List<HInstruction>.from(instructions); + // TODO(floitsch): there is currently no way to sort HInstructions before + // we have assigned an ID. The loop is therefore O(n^2) for now. + for (int i = 0; i < copy.length; i++) { + var current = copy[i]; + if (current == null) continue; + int count = 1; + for (int j = i + 1; j < copy.length; j++) { + if (identical(copy[j], current)) { + copy[j] = null; + count++; + } + } + if (!f(current, count)) return false; + } + return true; + } + + void visitInstruction(HInstruction instruction) { + // Verifies that we are in the use list of our inputs. + bool hasCorrectInputs() { + bool inBasicBlock = instruction.isInBasicBlock(); + return everyInstruction(instruction.inputs, (input, count) { + if (inBasicBlock) { + return countInstruction(input.usedBy, instruction) == count; + } else { + return countInstruction(input.usedBy, instruction) == 0; + } + }); + } + + // Verifies that all our uses have us in their inputs. + bool hasCorrectUses() { + if (!instruction.isInBasicBlock()) return true; + return everyInstruction(instruction.usedBy, (use, count) { + return countInstruction(use.inputs, instruction) == count; + }); + } + + if (!identical(instruction.block, currentBlock)) { + markInvalid("Instruction in wrong block"); + } + if (!hasCorrectInputs()) { + markInvalid("Incorrect inputs"); + } + if (!hasCorrectUses()) { + markInvalid("Incorrect uses"); + } + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart new file mode 100644 index 0000000..c28d5c4 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
@@ -0,0 +1,996 @@ +// 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. + +part of ssa; + + +class ValueRangeInfo { + final ConstantSystem constantSystem; + + IntValue intZero; + IntValue intOne; + + ValueRangeInfo(this.constantSystem) { + intZero = newIntValue(0); + intOne = newIntValue(1); + } + + Value newIntValue(int value) { + return new IntValue(value, this); + } + + Value newInstructionValue(HInstruction instruction) { + return new InstructionValue(instruction, this); + } + + Value newLengthValue(HInstruction instruction) { + return new LengthValue(instruction, this); + } + + Value newAddValue(Value left, Value right) { + return new AddValue(left, right, this); + } + + Value newSubtractValue(Value left, Value right) { + return new SubtractValue(left, right, this); + } + + Value newNegateValue(Value value) { + return new NegateValue(value, this); + } + + Range newRange(Value low, Value up) { + return new Range(low, up, this); + } + + Range newUnboundRange() { + return new Range.unbound(this); + } + + Range newNormalizedRange(Value low, Value up) { + return new Range.normalize(low, up, this); + } +} + +/** + * A [Value] represents both symbolic values like the value of a + * parameter, or the length of an array, and concrete values, like + * constants. + */ +abstract class Value { + final ValueRangeInfo info; + const Value([this.info = null]); + + Value operator +(Value other) => const UnknownValue(); + Value operator -(Value other) => const UnknownValue(); + Value operator -() => const UnknownValue(); + Value operator &(Value other) => const UnknownValue(); + + Value min(Value other) { + if (this == other) return this; + if (other == const MinIntValue()) return other; + if (other == const MaxIntValue()) return this; + Value value = this - other; + if (value.isPositive) return other; + if (value.isNegative) return this; + return const UnknownValue(); + } + + Value max(Value other) { + if (this == other) return this; + if (other == const MinIntValue()) return this; + if (other == const MaxIntValue()) return other; + Value value = this - other; + if (value.isPositive) return this; + if (value.isNegative) return other; + return const UnknownValue(); + } + + bool get isNegative => false; + bool get isPositive => false; + bool get isZero => false; +} + +/** + * An [IntValue] contains a constant integer value. + */ +class IntValue extends Value { + final int value; + + const IntValue(this.value, info) : super(info); + + Value operator +(other) { + if (other.isZero) return this; + if (other is !IntValue) return other + this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.add.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator -(other) { + if (other.isZero) return this; + if (other is !IntValue) return -other + this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.subtract.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator -() { + if (isZero) return this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.negate.fold( + constantSystem.createInt(value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator &(other) { + if (other is !IntValue) return const UnknownValue(); + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.bitAnd.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + return info.newIntValue(constant.value); + } + + Value min(other) { + if (other is !IntValue) return other.min(this); + return this.value < other.value ? this : other; + } + + Value max(other) { + if (other is !IntValue) return other.max(this); + return this.value < other.value ? other : this; + } + + bool operator ==(other) { + if (other is !IntValue) return false; + return this.value == other.value; + } + + String toString() => 'IntValue $value'; + bool get isNegative => value < 0; + bool get isPositive => value >= 0; + bool get isZero => value == 0; +} + +/** + * The [MaxIntValue] represents the maximum value an integer can have, + * which is currently +infinity. + */ +class MaxIntValue extends Value { + const MaxIntValue() : super(null); + Value operator +(Value other) => this; + Value operator -(Value other) => this; + Value operator -() => const MinIntValue(); + Value min(Value other) => other; + Value max(Value other) => this; + String toString() => 'Max'; + bool get isNegative => false; + bool get isPositive => true; +} + +/** + * The [MinIntValue] represents the minimum value an integer can have, + * which is currently -infinity. + */ +class MinIntValue extends Value { + const MinIntValue() : super(null); + Value operator +(Value other) => this; + Value operator -(Value other) => this; + Value operator -() => const MaxIntValue(); + Value min(Value other) => this; + Value max(Value other) => other; + String toString() => 'Min'; + bool get isNegative => true; + bool get isPositive => false; +} + +/** + * The [UnknownValue] is the sentinel in our analysis to mark an + * operation that could not be done because of too much complexity. + */ +class UnknownValue extends Value { + const UnknownValue() : super(null); + Value operator +(Value other) => const UnknownValue(); + Value operator -(Value other) => const UnknownValue(); + Value operator -() => const UnknownValue(); + Value min(Value other) => const UnknownValue(); + Value max(Value other) => const UnknownValue(); + bool get isNegative => false; + bool get isPositive => false; + String toString() => 'Unknown'; +} + +/** + * A symbolic value representing an [HInstruction]. + */ +class InstructionValue extends Value { + final HInstruction instruction; + InstructionValue(this.instruction, info) : super(info); + + bool operator ==(other) { + if (other is !InstructionValue) return false; + return this.instruction == other.instruction; + } + + Value operator +(Value other) { + if (other.isZero) return this; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(this, -other); + } + return info.newAddValue(this, other); + } + if (other is InstructionValue) { + return info.newAddValue(this, other); + } + return other + this; + } + + Value operator -(Value other) { + if (other.isZero) return this; + if (this == other) return info.intZero; + if (other is IntValue) { + if (other.isNegative) { + return info.newAddValue(this, -other); + } + return info.newSubtractValue(this, other); + } + if (other is InstructionValue) { + return info.newSubtractValue(this, other); + } + return -other + this; + } + + Value operator -() { + return info.newNegateValue(this); + } + + bool get isNegative => false; + bool get isPositive => false; + + String toString() => 'Instruction: $instruction'; +} + +/** + * Special value for instructions that represent the length of an + * array. The difference with an [InstructionValue] is that we know + * the value is positive. + */ +class LengthValue extends InstructionValue { + LengthValue(HInstruction instruction, info) : super(instruction, info); + bool get isPositive => true; + String toString() => 'Length: $instruction'; +} + +/** + * Represents a binary operation on two [Value], where the operation + * did not yield a canonical value. + */ +class BinaryOperationValue extends Value { + final Value left; + final Value right; + BinaryOperationValue(this.left, this.right, info) : super(info); +} + +class AddValue extends BinaryOperationValue { + AddValue(left, right, info) : super(left, right, info); + + bool operator ==(other) { + if (other is !AddValue) return false; + return (left == other.left && right == other.right) + || (left == other.right && right == other.left); + } + + Value operator -() => -left - right; + + Value operator +(Value other) { + if (other.isZero) return this; + Value value = left + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value + right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + Value operator -(Value other) { + if (other.isZero) return this; + Value value = left - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value + right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + bool get isNegative => left.isNegative && right.isNegative; + bool get isPositive => left.isPositive && right.isPositive; + String toString() => '$left + $right'; +} + +class SubtractValue extends BinaryOperationValue { + SubtractValue(left, right, info) : super(left, right, info); + + bool operator ==(other) { + if (other is !SubtractValue) return false; + return left == other.left && right == other.right; + } + + Value operator -() => right - left; + + Value operator +(Value other) { + if (other.isZero) return this; + Value value = left + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value - right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = other - right; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + Value operator -(Value other) { + if (other.isZero) return this; + Value value = left - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value - right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left - value; + } + return const UnknownValue(); + } + + bool get isNegative => left.isNegative && right.isPositive; + bool get isPositive => left.isPositive && right.isNegative; + String toString() => '$left - $right'; +} + +class NegateValue extends Value { + final Value value; + NegateValue(this.value, info) : super(info); + + bool operator ==(other) { + if (other is !NegateValue) return false; + return value == other.value; + } + + Value operator +(other) { + if (other.isZero) return this; + if (other == value) return info.intZero; + if (other is NegateValue) return this - other.value; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(this, -other); + } + return info.newSubtractValue(other, value); + } + if (other is InstructionValue) { + return info.newSubtractValue(other, value); + } + return other - value; + } + + Value operator &(Value other) => const UnknownValue(); + + Value operator -(other) { + if (other.isZero) return this; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(-other, value); + } + return info.newSubtractValue(this, other); + } + if (other is InstructionValue) { + return info.newSubtractValue(this, other); + } + if (other is NegateValue) return this + other.value; + return -other - value; + } + + Value operator -() => value; + + bool get isNegative => value.isPositive; + bool get isPositive => value.isNegative; + String toString() => '-$value'; +} + +/** + * A [Range] represents the possible integer values an instruction + * can have, from its [lower] bound to its [upper] bound, both + * included. + */ +class Range { + final Value lower; + final Value upper; + final ValueRangeInfo info; + Range(this.lower, this.upper, this.info); + + Range.unbound(info) : this(const MinIntValue(), const MaxIntValue(), info); + + /** + * Checks if the given values are unknown, and creates a + * range that does not have any unknown values. + */ + Range.normalize(Value low, Value up, info) : this( + low == const UnknownValue() ? const MinIntValue() : low, + up == const UnknownValue() ? const MaxIntValue() : up, + info); + + Range union(Range other) { + return info.newNormalizedRange( + lower.min(other.lower), upper.max(other.upper)); + } + + intersection(Range other) { + Value low = lower.max(other.lower); + Value up = upper.min(other.upper); + // If we could not compute max or min, pick a value in the two + // ranges, with priority to [IntValue]s because they are simpler. + if (low == const UnknownValue()) { + if (lower is IntValue) low = lower; + else if (other.lower is IntValue) low = other.lower; + else low = lower; + } + if (up == const UnknownValue()) { + if (upper is IntValue) up = upper; + else if (other.upper is IntValue) up = other.upper; + else up = upper; + } + return info.newRange(low, up); + } + + Range operator +(Range other) { + return info.newNormalizedRange(lower + other.lower, upper + other.upper); + } + + Range operator -(Range other) { + return info.newNormalizedRange(lower - other.upper, upper - other.lower); + } + + Range operator -() { + return info.newNormalizedRange(-upper, -lower); + } + + Range operator &(Range other) { + if (isSingleValue + && other.isSingleValue + && lower is IntValue + && other.lower is IntValue) { + return info.newRange(lower & other.lower, upper & other.upper); + } + if (isPositive && other.isPositive) { + Value up = upper.min(other.upper); + if (up == const UnknownValue()) { + // If we could not find a trivial bound, just try to use the + // one that is an int. + up = upper is IntValue ? upper : other.upper; + // Make sure we get the same upper bound, whether it's a & b + // or b & a. + if (up is! IntValue && upper != other.upper) up = const MaxIntValue(); + } + return info.newRange(info.intZero, up); + } else if (isPositive) { + return info.newRange(info.intZero, upper); + } else if (other.isPositive) { + return info.newRange(info.intZero, other.upper); + } else { + return info.newUnboundRange(); + } + } + + bool operator ==(other) { + if (other is! Range) return false; + return other.lower == lower && other.upper == upper; + } + + bool operator <(Range other) { + return upper != other.lower && upper.min(other.lower) == upper; + } + + bool operator >(Range other) { + return lower != other.upper && lower.max(other.upper) == lower; + } + + bool operator <=(Range other) { + return upper.min(other.lower) == upper; + } + + bool operator >=(Range other) { + return lower.max(other.upper) == lower; + } + + bool get isNegative => upper.isNegative; + bool get isPositive => lower.isPositive; + bool get isSingleValue => lower == upper; + + String toString() => '[$lower, $upper]'; +} + +/** + * Visits the graph in dominator order, and computes value ranges for + * integer instructions. While visiting the graph, this phase also + * removes unnecessary bounds checks, and comparisons that are proven + * to be true or false. + */ +class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase { + String get name => 'SSA value range builder'; + + /** + * List of [HRangeConversion] instructions created by the phase. We + * save them here in order to remove them once the phase is done. + */ + final List<HRangeConversion> conversions = <HRangeConversion>[]; + + /** + * Value ranges for integer instructions. This map gets populated by + * the dominator tree visit. + */ + final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>(); + + final ConstantSystem constantSystem; + final HTypeMap types; + final ValueRangeInfo info; + + CodegenWorkItem work; + HGraph graph; + + SsaValueRangeAnalyzer(constantSystem, this.types, this.work) + : info = new ValueRangeInfo(constantSystem), + this.constantSystem = constantSystem; + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + // We remove the range conversions after visiting the graph so + // that the graph does not get polluted with these instructions + // only necessary for this phase. + removeRangeConversion(); + } + + void removeRangeConversion() { + conversions.forEach((HRangeConversion instruction) { + instruction.block.rewrite(instruction, instruction.inputs[0]);; + instruction.block.remove(instruction); + }); + } + + void visitBasicBlock(HBasicBlock block) { + + void visit(HInstruction instruction) { + Range range = instruction.accept(this); + if (instruction.isInteger(types)) { + assert(range != null); + ranges[instruction] = range; + } + } + + block.forEachPhi(visit); + block.forEachInstruction(visit); + } + + Range visitInstruction(HInstruction instruction) { + return info.newUnboundRange(); + } + + Range visitParameterValue(HParameterValue parameter) { + if (!parameter.isInteger(types)) return info.newUnboundRange(); + Value value = info.newInstructionValue(parameter); + return info.newRange(value, value); + } + + Range visitPhi(HPhi phi) { + if (!phi.isInteger(types)) return info.newUnboundRange(); + if (phi.block.isLoopHeader()) { + Range range = tryInferLoopPhiRange(phi); + if (range == null) return info.newUnboundRange(); + return range; + } + + Range range = ranges[phi.inputs[0]]; + for (int i = 1; i < phi.inputs.length; i++) { + range = range.union(ranges[phi.inputs[i]]); + } + return range; + } + + Range tryInferLoopPhiRange(HPhi phi) { + HInstruction update = phi.inputs[1]; + return update.accept(new LoopUpdateRecognizer(phi, ranges, types, info)); + } + + Range visitConstant(HConstant constant) { + if (!constant.isInteger(types)) return info.newUnboundRange(); + IntConstant constantInt = constant.constant; + Value value = info.newIntValue(constantInt.value); + return info.newRange(value, value); + } + + Range visitFieldGet(HFieldGet fieldGet) { + if (!fieldGet.isInteger(types)) return info.newUnboundRange(); + if (!fieldGet.receiver.isIndexablePrimitive(types)) { + return visitInstruction(fieldGet); + } + LengthValue value = info.newLengthValue(fieldGet); + // We know this range is above zero. To simplify the analysis, we + // put the zero value as the lower bound of this range. This + // allows to easily remove the second bound check in the following + // expression: a[1] + a[0]. + return info.newRange(info.intZero, value); + } + + Range visitBoundsCheck(HBoundsCheck check) { + // Save the next instruction, in case the check gets removed. + HInstruction next = check.next; + Range indexRange = ranges[check.index]; + Range lengthRange = ranges[check.length]; + + // Check if the index is strictly below the upper bound of the length + // range. + Value maxIndex = lengthRange.upper - info.intOne; + bool belowLength = maxIndex != const MaxIntValue() + && indexRange.upper.min(maxIndex) == indexRange.upper; + + // Check if the index is strictly below the lower bound of the length + // range. + belowLength = belowLength + || (indexRange.upper != lengthRange.lower + && indexRange.upper.min(lengthRange.lower) == indexRange.upper); + if (indexRange.isPositive && belowLength) { + check.block.rewrite(check, check.index); + check.block.remove(check); + } else if (indexRange.isNegative || lengthRange < indexRange) { + check.staticChecks = HBoundsCheck.ALWAYS_FALSE; + // The check is always false, and whatever instruction it + // dominates is dead code. + return indexRange; + } else if (indexRange.isPositive) { + check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO; + } else if (belowLength) { + check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH; + } + + if (indexRange.isPositive) { + // If the test passes, we know the lower bound of the length is + // greater or equal than the lower bound of the index. + Value low = lengthRange.lower.max(indexRange.lower); + if (low != const UnknownValue()) { + HInstruction instruction = + createRangeConversion(next, check.length); + ranges[instruction] = info.newRange(low, lengthRange.upper); + } + } + + if (!belowLength) { + // Update the range of the index if using the maximum index + // narrows it. + Range newIndexRange = indexRange.intersection( + info.newRange(info.intZero, maxIndex)); + if (indexRange == newIndexRange) return indexRange; + HInstruction instruction = createRangeConversion(next, check.index); + ranges[instruction] = newIndexRange; + return newIndexRange; + } + + return indexRange; + } + + Range visitRelational(HRelational relational) { + HInstruction right = relational.right; + HInstruction left = relational.left; + if (!left.isInteger(types)) return info.newUnboundRange(); + if (!right.isInteger(types)) return info.newUnboundRange(); + BinaryOperation operation = relational.operation(constantSystem); + Range rightRange = ranges[relational.right]; + Range leftRange = ranges[relational.left]; + + if (relational is HIdentity) { + handleEqualityCheck(relational); + } else if (operation.apply(leftRange, rightRange)) { + relational.block.rewrite( + relational, graph.addConstantBool(true, constantSystem)); + relational.block.remove(relational); + } else if (reverseOperation(operation).apply(leftRange, rightRange)) { + relational.block.rewrite( + relational, graph.addConstantBool(false, constantSystem)); + relational.block.remove(relational); + } + return info.newUnboundRange(); + } + + void handleEqualityCheck(HRelational node) { + Range right = ranges[node.right]; + Range left = ranges[node.left]; + if (left.isSingleValue && right.isSingleValue && left == right) { + node.block.rewrite( + node, graph.addConstantBool(true, constantSystem)); + node.block.remove(node); + } + } + + Range handleBinaryOperation(HBinaryArithmetic instruction) { + if (!instruction.isInteger(types)) return info.newUnboundRange(); + return instruction.operation(constantSystem).apply( + ranges[instruction.left], ranges[instruction.right]); + } + + Range visitAdd(HAdd add) { + return handleBinaryOperation(add); + } + + Range visitSubtract(HSubtract sub) { + return handleBinaryOperation(sub); + } + + Range visitBitAnd(HBitAnd node) { + if (!node.isInteger(types)) return info.newUnboundRange(); + HInstruction right = node.right; + HInstruction left = node.left; + if (left.isInteger(types) && right.isInteger(types)) { + return ranges[left] & ranges[right]; + } + + Range tryComputeRange(HInstruction instruction) { + Range range = ranges[instruction]; + if (range.isPositive) { + return info.newRange(info.intZero, range.upper); + } else if (range.isNegative) { + return info.newRange(range.lower, info.intZero); + } + return info.newUnboundRange(); + } + + if (left.isInteger(types)) { + return tryComputeRange(left); + } else if (right.isInteger(types)) { + return tryComputeRange(right); + } + return info.newUnboundRange(); + } + + Range visitCheck(HCheck instruction) { + if (ranges[instruction.checkedInput] == null) { + return info.newUnboundRange(); + } + return ranges[instruction.checkedInput]; + } + + HInstruction createRangeConversion(HInstruction cursor, + HInstruction instruction) { + HRangeConversion newInstruction = new HRangeConversion(instruction); + conversions.add(newInstruction); + cursor.block.addBefore(cursor, newInstruction); + // Update the users of the instruction dominated by [cursor] to + // use the new instruction, that has an narrower range. + Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor); + for (HInstruction user in dominatedUsers) { + user.changeUse(instruction, newInstruction); + } + return newInstruction; + } + + static BinaryOperation reverseOperation(BinaryOperation operation) { + if (operation == const LessOperation()) { + return const GreaterEqualOperation(); + } else if (operation == const LessEqualOperation()) { + return const GreaterOperation(); + } else if (operation == const GreaterOperation()) { + return const LessEqualOperation(); + } else if (operation == const GreaterEqualOperation()) { + return const LessOperation(); + } else { + return null; + } + } + + Range computeConstrainedRange(BinaryOperation operation, + Range leftRange, + Range rightRange) { + Range range; + if (operation == const LessOperation()) { + range = info.newRange( + const MinIntValue(), rightRange.upper - info.intOne); + } else if (operation == const LessEqualOperation()) { + range = info.newRange(const MinIntValue(), rightRange.upper); + } else if (operation == const GreaterOperation()) { + range = info.newRange( + rightRange.lower + info.intOne, const MaxIntValue()); + } else if (operation == const GreaterEqualOperation()) { + range = info.newRange(rightRange.lower, const MaxIntValue()); + } else { + range = info.newUnboundRange(); + } + return range.intersection(leftRange); + } + + Range visitConditionalBranch(HConditionalBranch branch) { + var condition = branch.condition; + // TODO(ngeoffray): Handle complex conditions. + if (condition is !HRelational) return info.newUnboundRange(); + if (condition is HIdentity) return info.newUnboundRange(); + HInstruction right = condition.right; + HInstruction left = condition.left; + if (!left.isInteger(types)) return info.newUnboundRange(); + if (!right.isInteger(types)) return info.newUnboundRange(); + + Range rightRange = ranges[right]; + Range leftRange = ranges[left]; + Operation operation = condition.operation(constantSystem); + Operation reverse = reverseOperation(operation); + // Only update the true branch if this block is the only + // predecessor. + if (branch.trueBranch.predecessors.length == 1) { + assert(branch.trueBranch.predecessors[0] == branch.block); + // Update the true branch to use narrower ranges for [left] and + // [right]. + Range range = computeConstrainedRange(operation, leftRange, rightRange); + if (leftRange != range) { + HInstruction instruction = + createRangeConversion(branch.trueBranch.first, left); + ranges[instruction] = range; + } + + range = computeConstrainedRange(reverse, rightRange, leftRange); + if (rightRange != range) { + HInstruction instruction = + createRangeConversion(branch.trueBranch.first, right); + ranges[instruction] = range; + } + } + + // Only update the false branch if this block is the only + // predecessor. + if (branch.falseBranch.predecessors.length == 1) { + assert(branch.falseBranch.predecessors[0] == branch.block); + // Update the false branch to use narrower ranges for [left] and + // [right]. + Range range = computeConstrainedRange(reverse, leftRange, rightRange); + if (leftRange != range) { + HInstruction instruction = + createRangeConversion(branch.falseBranch.first, left); + ranges[instruction] = range; + } + + range = computeConstrainedRange(operation, rightRange, leftRange); + if (rightRange != range) { + HInstruction instruction = + createRangeConversion(branch.falseBranch.first, right); + ranges[instruction] = range; + } + } + + return info.newUnboundRange(); + } + + Range visitRangeConversion(HRangeConversion conversion) { + return ranges[conversion]; + } +} + +/** + * Recognizes a number of patterns in a loop update instruction and + * tries to infer a range for the loop phi. + */ +class LoopUpdateRecognizer extends HBaseVisitor { + final HPhi loopPhi; + final Map<HInstruction, Range> ranges; + final HTypeMap types; + final ValueRangeInfo info; + LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types, this.info); + + Range visitAdd(HAdd operation) { + Range range = getRangeForRecognizableOperation(operation); + if (range == null) return info.newUnboundRange(); + Range initial = ranges[loopPhi.inputs[0]]; + if (range.isPositive) { + return info.newRange(initial.lower, const MaxIntValue()); + } else if (range.isNegative) { + return info.newRange(const MinIntValue(), initial.upper); + } + return info.newUnboundRange(); + } + + Range visitSubtract(HSubtract operation) { + Range range = getRangeForRecognizableOperation(operation); + if (range == null) return info.newUnboundRange(); + Range initial = ranges[loopPhi.inputs[0]]; + if (range.isPositive) { + return info.newRange(const MinIntValue(), initial.upper); + } else if (range.isNegative) { + return info.newRange(initial.lower, const MaxIntValue()); + } + return info.newUnboundRange(); + } + + Range visitPhi(HPhi phi) { + Range phiRange; + for (HInstruction input in phi.inputs) { + HInstruction instruction = unwrap(input); + // If one of the inputs is the loop phi, then we're only + // interested in the other inputs: a loop phi feeding itself means + // it is not being updated. + if (instruction == loopPhi) continue; + + // If another loop phi is involved, it's too complex to analyze. + if (instruction is HPhi && instruction.block.isLoopHeader()) return null; + + Range inputRange = instruction.accept(this); + if (inputRange == null) return null; + if (phiRange == null) { + phiRange = inputRange; + } else { + phiRange = phiRange.union(inputRange); + } + } + return phiRange; + } + + /** + * If [operation] is recognizable, returns the inferred range. + * Otherwise returns [null]. + */ + Range getRangeForRecognizableOperation(HBinaryArithmetic operation) { + if (!operation.left.isInteger(types)) return null; + if (!operation.right.isInteger(types)) return null; + HInstruction left = unwrap(operation.left); + HInstruction right = unwrap(operation.right); + // We only recognize operations that operate on the loop phi. + bool isLeftLoopPhi = (left == loopPhi); + bool isRightLoopPhi = (right == loopPhi); + if (!isLeftLoopPhi && !isRightLoopPhi) return null; + + var other = isLeftLoopPhi ? right : left; + // If the analysis already computed range for the update, use it. + if (ranges[other] != null) return ranges[other]; + + // We currently only handle constants in updates if the + // update does not have a range. + if (other.isConstant()) { + Value value = info.newIntValue(other.constant.value); + return info.newRange(value, value); + } + return null; + } + + /** + * [HCheck] instructions may check the loop phi. Since we only + * recognize updates on the loop phi, we must [unwrap] the [HCheck] + * instruction to check if it references the loop phi. + */ + HInstruction unwrap(instruction) { + if (instruction is HCheck) return unwrap(instruction.checkedInput); + // [HPhi] might have two different [HCheck] instructions as + // inputs, checking the same instruction. + if (instruction is HPhi && !instruction.block.isLoopHeader()) { + HInstruction result = unwrap(instruction.inputs[0]); + for (int i = 1; i < instruction.inputs.length; i++) { + if (result != unwrap(instruction.inputs[i])) return instruction; + } + return result; + } + return instruction; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart new file mode 100644 index 0000000..d565de7 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart
@@ -0,0 +1,157 @@ +// 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. + +part of ssa; + +class ValueSet { + int size = 0; + List<HInstruction> table; + ValueSetNode collisions; + ValueSet() : table = new List<HInstruction>.fixedLength(8); + + bool get isEmpty => size == 0; + int get length => size; + + void add(HInstruction instruction) { + assert(lookup(instruction) == null); + int hashCode = instruction.gvnHashCode(); + int capacity = table.length; + // Resize when half of the hash table is in use. + if (size >= capacity >> 1) { + capacity = capacity << 1; + resize(capacity); + } + // Try to insert in the hash table first. + int index = hashCode % capacity; + if (table[index] == null) { + table[index] = instruction; + } else { + collisions = new ValueSetNode(instruction, hashCode, collisions); + } + size++; + } + + HInstruction lookup(HInstruction instruction) { + int hashCode = instruction.gvnHashCode(); + int index = hashCode % table.length; + // Look in the hash table. + HInstruction probe = table[index]; + if (probe != null && probe.gvnEquals(instruction)) return probe; + // Look in the collisions list. + for (ValueSetNode node = collisions; node != null; node = node.next) { + if (node.hashCode == hashCode) { + HInstruction cached = node.value; + if (cached.gvnEquals(instruction)) return cached; + } + } + return null; + } + + void kill(int flags) { + if (flags == 0) return; + int depends = HInstruction.computeDependsOnFlags(flags); + // Kill in the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null && (instruction.flags & depends) != 0) { + table[index] = null; + size--; + } + } + // Kill in the collisions list. + ValueSetNode previous = null; + ValueSetNode current = collisions; + while (current != null) { + ValueSetNode next = current.next; + HInstruction cached = current.value; + if ((cached.flags & depends) != 0) { + if (previous == null) { + collisions = next; + } else { + previous.next = next; + } + size--; + } else { + previous = current; + } + current = next; + } + } + + ValueSet copy() { + return copyTo(new ValueSet(), table, collisions); + } + + List<HInstruction> toList() { + return copyTo(<HInstruction>[], table, collisions); + } + + // Copy the instructions in value set defined by [table] and + // [collisions] into [other] and returns [other]. The copy is done + // by iterating through the hash table and the collisions list and + // calling [:other.add:]. + static copyTo(var other, List<HInstruction> table, ValueSetNode collisions) { + // Copy elements from the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null) other.add(instruction); + } + // Copy elements from the collision list. + ValueSetNode current = collisions; + while (current != null) { + // TODO(kasperl): Maybe find a way of reusing the hash code + // rather than recomputing it every time. + other.add(current.value); + current = current.next; + } + return other; + } + + ValueSet intersection(ValueSet other) { + if (size > other.size) return other.intersection(this); + ValueSet result = new ValueSet(); + // Look in the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null && other.lookup(instruction) != null) { + result.add(instruction); + } + } + // Look in the collision list. + ValueSetNode current = collisions; + while (current != null) { + HInstruction value = current.value; + if (other.lookup(value) != null) { + result.add(value); + } + current = current.next; + } + return result; + } + + void resize(int capacity) { + var oldSize = size; + var oldTable = table; + var oldCollisions = collisions; + // Reset the table with a bigger capacity. + assert(capacity > table.length); + size = 0; + table = new List<HInstruction>.fixedLength(capacity); + collisions = null; + // Add the old instructions to the new table. + copyTo(this, oldTable, oldCollisions); + // Make sure we preserved all elements and that no resizing + // happened as part of this resizing. + assert(size == oldSize); + assert(table.length == capacity); + } +} + +class ValueSetNode { + final HInstruction value; + final int hash; + int get hashCode => hash; + ValueSetNode next; + ValueSetNode(this.value, this.hash, this.next); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart b/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart new file mode 100644 index 0000000..34f960d --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart
@@ -0,0 +1,669 @@ +// 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. + +part of ssa; + +/** + * The [LiveRange] class covers a range where an instruction is live. + */ +class LiveRange { + final int start; + // [end] is not final because it can be updated due to loops. + int end; + LiveRange(this.start, this.end) { + assert(start <= end); + } + + String toString() => '[$start $end['; +} + +/** + * The [LiveInterval] class contains the list of ranges where an + * instruction is live. + */ +class LiveInterval { + /** + * The id where the instruction is defined. + */ + int start; + final List<LiveRange> ranges; + LiveInterval() : ranges = <LiveRange>[]; + + /** + * Update all ranges that are contained in [from, to[ to + * die at [to]. + */ + void loopUpdate(int from, int to) { + for (LiveRange range in ranges) { + if (from <= range.start && range.end < to) { + range.end = to; + } + } + } + + /** + * Add a new range to this interval. + */ + void add(LiveRange interval) { + ranges.add(interval); + } + + /** + * Returns true if one of the ranges of this interval dies at [at]. + */ + bool diesAt(int at) { + for (LiveRange range in ranges) { + if (range.end == at) return true; + } + return false; + } + + String toString() { + List<String> res = new List<String>(); + for (final interval in ranges) res.add(interval.toString()); + return '(${Strings.join(res, ', ')})'; + } +} + +/** + * The [LiveEnvironment] class contains the liveIn set of a basic + * block. A liveIn set of a block contains the instructions that are + * live when entering that block. + */ +class LiveEnvironment { + /** + * The instruction id where the basic block starts. See + * [SsaLiveIntervalBuilder.instructionId]. + */ + int startId; + + /** + * The instruction id where the basic block ends. + */ + final int endId; + + /** + * Loop markers that will be updated once the loop header is + * visited. The liveIn set of the loop header will be merged into this + * environment. [loopMarkers] is a mapping from block header to the + * end instruction id of the loop exit block. + */ + final Map<HBasicBlock, int> loopMarkers; + + /** + * The instructions that are live in this basic block. The values of + * the map contain the instruction ids where the instructions die. + * It will be used when adding a range to the live interval of an + * instruction. + */ + final Map<HInstruction, int> liveInstructions; + + /** + * Map containing the live intervals of instructions. + */ + final Map<HInstruction, LiveInterval> liveIntervals; + + LiveEnvironment(this.liveIntervals, this.endId) + : liveInstructions = new Map<HInstruction, int>(), + loopMarkers = new Map<HBasicBlock, int>(); + + /** + * Remove an instruction from the liveIn set. This method also + * updates the live interval of [instruction] to contain the new + * range: [id, / id contained in [liveInstructions] /]. + */ + void remove(HInstruction instruction, int id) { + // Special case the HCheck instruction to have the same live + // interval as the instruction it is checking. + if (instruction is HCheck) { + var input = instruction.checkedInput; + while (input is HCheck) input = input.checkedInput; + liveIntervals.putIfAbsent(input, () => new LiveInterval()); + // Unconditionally force the live interval of the HCheck to + // be the live interval of the instruction it is checking. + liveIntervals[instruction] = liveIntervals[input]; + } else { + LiveInterval range = liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + int lastId = liveInstructions[instruction]; + // If [lastId] is null, then this instruction is not being used. + range.add(new LiveRange(id, lastId == null ? id : lastId)); + // The instruction is defined at [id]. + range.start = id; + } + liveInstructions.remove(instruction); + } + + /** + * Add [instruction] to the liveIn set. If the instruction is not + * already in the set, we save the id where it dies. + */ + void add(HInstruction instruction, int userId) { + // Note that we are visiting the graph in post-dominator order, so + // the first time we see a variable is when it dies. + liveInstructions.putIfAbsent(instruction, () => userId); + if (instruction is HCheck) { + // Special case the HCheck instruction to mark the actual + // checked instruction live. + var input = instruction.checkedInput; + while (input is HCheck) input = input.checkedInput; + liveInstructions.putIfAbsent(input, () => userId); + } + } + + /** + * Merge this environment with [other]. Update the end id of + * instructions in case they are different between this and [other]. + */ + void mergeWith(LiveEnvironment other) { + other.liveInstructions.forEach((HInstruction instruction, int existingId) { + // If both environments have the same instruction id of where + // [instruction] dies, there is no need to update the live + // interval of [instruction]. For example the if block and the + // else block have the same end id for an instruction that is + // being used in the join block and defined before the if/else. + if (existingId == endId) return; + LiveInterval range = liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + range.add(new LiveRange(other.startId, existingId)); + liveInstructions[instruction] = endId; + }); + other.loopMarkers.forEach((k, v) { loopMarkers[k] = v; }); + } + + void addLoopMarker(HBasicBlock header, int id) { + assert(!loopMarkers.containsKey(header)); + loopMarkers[header] = id; + } + + void removeLoopMarker(HBasicBlock header) { + assert(loopMarkers.containsKey(header)); + loopMarkers.remove(header); + } + + bool get isEmpty => liveInstructions.isEmpty && loopMarkers.isEmpty; + bool contains(HInstruction instruction) => + liveInstructions.containsKey(instruction); + String toString() => liveInstructions.toString(); +} + +/** + * Builds the live intervals of each instruction. The algorithm visits + * the graph post-dominator tree to find the last uses of an + * instruction, and computes the liveIns of each basic block. + */ +class SsaLiveIntervalBuilder extends HBaseVisitor { + final Compiler compiler; + final Set<HInstruction> generateAtUseSite; + + /** + * A counter to assign start and end ids to live ranges. The initial + * value is not relevant. Note that instructionId goes downward to ease + * reasoning about live ranges (the first instruction of a graph has + * the lowest id). + */ + int instructionId = 0; + + /** + * The liveIns of basic blocks. + */ + final Map<HBasicBlock, LiveEnvironment> liveInstructions; + + /** + * The live intervals of instructions. + */ + final Map<HInstruction, LiveInterval> liveIntervals; + + SsaLiveIntervalBuilder(this.compiler, this.generateAtUseSite) + : liveInstructions = new Map<HBasicBlock, LiveEnvironment>(), + liveIntervals = new Map<HInstruction, LiveInterval>(); + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + if (!liveInstructions[graph.entry].isEmpty) { + compiler.internalError('LiveIntervalBuilder', + node: compiler.currentElement.parseNode(compiler)); + } + } + + void markInputsAsLiveInEnvironment(HInstruction instruction, + LiveEnvironment environment) { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + markAsLiveInEnvironment(instruction.inputs[i], environment); + } + } + + void markAsLiveInEnvironment(HInstruction instruction, + LiveEnvironment environment) { + if (environment.contains(instruction)) return; + environment.add(instruction, instructionId); + // HPhis are treated specially. + if (generateAtUseSite.contains(instruction) && instruction is !HPhi) { + markInputsAsLiveInEnvironment(instruction, environment); + } + } + + void visitBasicBlock(HBasicBlock block) { + LiveEnvironment environment = + new LiveEnvironment(liveIntervals, instructionId); + + // Add to the environment the liveIn of its successor, as well as + // the inputs of the phis of the successor that flow from this block. + for (int i = 0; i < block.successors.length; i++) { + HBasicBlock successor = block.successors[i]; + LiveEnvironment successorEnv = liveInstructions[successor]; + if (successorEnv != null) { + environment.mergeWith(successorEnv); + } else { + environment.addLoopMarker(successor, instructionId); + } + + int index = successor.predecessors.indexOf(block); + for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) { + markAsLiveInEnvironment(phi.inputs[index], environment); + } + } + + // Iterate over all instructions to remove an instruction from the + // environment and add its inputs. + HInstruction instruction = block.last; + while (instruction != null) { + environment.remove(instruction, instructionId); + markInputsAsLiveInEnvironment(instruction, environment); + instruction = instruction.previous; + instructionId--; + } + + // We just remove the phis from the environment. The inputs of the + // phis will be put in the environment of the predecessors. + for (HPhi phi = block.phis.first; phi != null; phi = phi.next) { + environment.remove(phi, instructionId); + } + + // Save the liveInstructions of that block. + environment.startId = instructionId + 1; + liveInstructions[block] = environment; + + // If the block is a loop header, we can remove the loop marker, + // because it will just recompute the loop phis. + // We also check if this loop header has any back edges. If not, + // we know there is no loop marker for it. + if (block.isLoopHeader() && block.predecessors.length > 1) { + updateLoopMarker(block); + } + } + + void updateLoopMarker(HBasicBlock header) { + LiveEnvironment env = liveInstructions[header]; + int lastId = env.loopMarkers[header]; + // Update all instructions that are liveIns in [header] to have a + // range that covers the loop. + env.liveInstructions.forEach((HInstruction instruction, int id) { + LiveInterval range = env.liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + range.loopUpdate(env.startId, lastId); + env.liveInstructions[instruction] = lastId; + }); + + env.removeLoopMarker(header); + + // Update all liveIns set to contain the liveIns of [header]. + liveInstructions.forEach((HBasicBlock block, LiveEnvironment other) { + if (other.loopMarkers.containsKey(header)) { + env.liveInstructions.forEach((HInstruction instruction, int id) { + other.liveInstructions[instruction] = id; + }); + other.removeLoopMarker(header); + env.loopMarkers.forEach((k, v) { other.loopMarkers[k] = v; }); + } + }); + } +} + +/** + * Represents a copy from one instruction to another. The codegen + * also uses this class to represent a copy from one variable to + * another. + */ +class Copy { + final source; + final destination; + Copy(this.source, this.destination); + String toString() => '$destination <- $source'; +} + +/** + * A copy handler contains the copies that a basic block needs to do + * after executing all its instructions. + */ +class CopyHandler { + /** + * The copies from an instruction to a phi of the successor. + */ + final List<Copy> copies; + + /** + * Assignments from an instruction that does not need a name (e.g. a + * constant) to the phi of a successor. + */ + final List<Copy> assignments; + + CopyHandler() + : copies = new List<Copy>(), + assignments = new List<Copy>(); + + void addCopy(HInstruction source, HInstruction destination) { + copies.add(new Copy(source, destination)); + } + + void addAssignment(HInstruction source, HInstruction destination) { + assignments.add(new Copy(source, destination)); + } + + String toString() => 'Copies: $copies, assignments: $assignments'; + bool get isEmpty => copies.isEmpty && assignments.isEmpty; +} + +/** + * Contains the mapping between instructions and their names for code + * generation, as well as the [CopyHandler] for each basic block. + */ +class VariableNames { + final Map<HInstruction, String> ownName; + final Map<HBasicBlock, CopyHandler> copyHandlers; + + // Used to control heuristic that determines how local variables are declared. + final Set<String> allUsedNames; + /** + * Name that is used as a temporary to break cycles in + * parallel copies. We make sure this name is not being used + * anywhere by reserving it when we allocate names for instructions. + */ + final String swapTemp; + /** + * Name that is used in bailout code. We make sure this name is not being used + * anywhere by reserving it when we allocate names for instructions. + */ + final String stateName; + + String getSwapTemp() { + allUsedNames.add(swapTemp); + return swapTemp; + } + + VariableNames() + : ownName = new Map<HInstruction, String>(), + copyHandlers = new Map<HBasicBlock, CopyHandler>(), + allUsedNames = new Set<String>(), + swapTemp = computeFreshWithPrefix("t"), + stateName = computeFreshWithPrefix("state"); + + int get numberOfVariables => allUsedNames.length; + + /** Returns a fresh variable with the given prefix. */ + static String computeFreshWithPrefix(String prefix) { + String name = '${prefix}0'; + int i = 1; + return name; + } + + String getName(HInstruction instruction) { + return ownName[instruction]; + } + + CopyHandler getCopyHandler(HBasicBlock block) { + return copyHandlers[block]; + } + + void addNameUsed(String name) => allUsedNames.add(name); + + bool hasName(HInstruction instruction) => ownName.containsKey(instruction); + + void addCopy(HBasicBlock block, HInstruction source, HPhi destination) { + CopyHandler handler = + copyHandlers.putIfAbsent(block, () => new CopyHandler()); + handler.addCopy(source, destination); + } + + void addAssignment(HBasicBlock block, HInstruction source, HPhi destination) { + CopyHandler handler = + copyHandlers.putIfAbsent(block, () => new CopyHandler()); + handler.addAssignment(source, destination); + } +} + +/** + * Allocates variable names for instructions, making sure they don't collide. + */ +class VariableNamer { + final VariableNames names; + final Compiler compiler; + final Set<String> usedNames; + final List<String> freeTemporaryNames; + int temporaryIndex = 0; + static final RegExp regexp = new RegExp('t[0-9]+'); + + VariableNamer(LiveEnvironment environment, + this.names, + this.compiler) + : usedNames = new Set<String>(), + freeTemporaryNames = new List<String>() { + // [VariableNames.swapTemp] is used when there is a cycle in a copy handler. + // Therefore we make sure no one uses it. + usedNames.add(names.swapTemp); + // [VariableNames.stateName] is being used throughout a bailout function. + // Whenever a bailout-target is reached we set the state-variable to 0. We + // must therefore not have any local variable that could clash with the + // state variable. + // Therefore we make sure no one uses it at any time. + usedNames.add(names.stateName); + + // All liveIns instructions must have a name at this point, so we + // add them to the list of used names. + environment.liveInstructions.forEach((HInstruction instruction, int index) { + String name = names.getName(instruction); + if (name != null) { + usedNames.add(name); + names.addNameUsed(name); + } + }); + } + + String allocateWithHint(String originalName) { + int i = 0; + JavaScriptBackend backend = compiler.backend; + String name = backend.namer.safeVariableName(originalName); + while (usedNames.contains(name)) { + name = backend.namer.safeVariableName('$originalName${i++}'); + } + return name; + } + + String allocateTemporary() { + while (!freeTemporaryNames.isEmpty) { + String name = freeTemporaryNames.removeLast(); + if (!usedNames.contains(name)) return name; + } + String name = 't${temporaryIndex++}'; + while (usedNames.contains(name)) name = 't${temporaryIndex++}'; + return name; + } + + HPhi firstPhiUserWithElement(HInstruction instruction) { + for (HInstruction user in instruction.usedBy) { + if (user is HPhi && user.sourceElement != null) { + return user; + } + } + return null; + } + + String allocateName(HInstruction instruction) { + String name; + if (instruction is HCheck) { + // Special case this instruction to use the name of its + // input if it has one. + var temp = instruction; + do { + temp = temp.checkedInput; + name = names.ownName[temp]; + } while (name == null && temp is HCheck); + if (name != null) return addAllocatedName(instruction, name); + } + + if (instruction.sourceElement != null) { + name = allocateWithHint(instruction.sourceElement.name.slowToString()); + } else { + // We could not find an element for the instruction. If the + // instruction is used by a phi, try to use the name of the phi. + // Otherwise, just allocate a temporary name. + HPhi phi = firstPhiUserWithElement(instruction); + if (phi != null) { + name = allocateWithHint(phi.sourceElement.name.slowToString()); + } else { + name = allocateTemporary(); + } + } + return addAllocatedName(instruction, name); + } + + String addAllocatedName(HInstruction instruction, String name) { + usedNames.add(name); + names.addNameUsed(name); + names.ownName[instruction] = name; + return name; + } + + /** + * Frees [instruction]'s name so it can be used for other instructions. + */ + void freeName(HInstruction instruction) { + String ownName = names.ownName[instruction]; + if (ownName != null) { + // We check if we have already looked for temporary names + // because if we haven't, chances are the temporary we allocate + // in this block can match a phi with the same name in the + // successor block. + if (temporaryIndex != 0 && regexp.hasMatch(ownName)) { + freeTemporaryNames.addLast(ownName); + } + usedNames.remove(ownName); + } + } +} + +/** + * Visits all blocks in the graph, sets names to instructions, and + * creates the [CopyHandler] for each block. This class needs to have + * the liveIns set as well as all the live intervals of instructions. + * It visits the graph in dominator order, so that at each entry of a + * block, the instructions in its liveIns set have names. + * + * When visiting a block, it goes through all instructions. For each + * instruction, it frees the names of the inputs that die at that + * instruction, and allocates a name to the instruction. For each phi, + * it adds a copy to the CopyHandler of the corresponding predecessor. + */ +class SsaVariableAllocator extends HBaseVisitor { + + final Compiler compiler; + final Map<HBasicBlock, LiveEnvironment> liveInstructions; + final Map<HInstruction, LiveInterval> liveIntervals; + final Set<HInstruction> generateAtUseSite; + + final VariableNames names; + + SsaVariableAllocator(this.compiler, + this.liveInstructions, + this.liveIntervals, + this.generateAtUseSite) + : this.names = new VariableNames(); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + VariableNamer namer = new VariableNamer( + liveInstructions[block], names, compiler); + + block.forEachPhi((HPhi phi) { + handlePhi(phi, namer); + }); + + block.forEachInstruction((HInstruction instruction) { + handleInstruction(instruction, namer); + }); + } + + /** + * Returns whether [instruction] needs a name. Instructions that + * have no users or that are generated at use site does not need a name. + */ + bool needsName(HInstruction instruction) { + if (instruction is HThis) return false; + if (instruction is HParameterValue) return true; + if (instruction.usedBy.isEmpty) return false; + if (generateAtUseSite.contains(instruction)) return false; + // A [HCheck] instruction that has control flow needs a name only if its + // checked input needs a name (e.g. a check [HConstant] does not + // need a name). + if (instruction is HCheck && instruction.isControlFlow()) { + HCheck check = instruction; + return needsName(instruction.checkedInput); + } + return true; + } + + /** + * Returns whether [instruction] dies at the instruction [at]. + */ + bool diesAt(HInstruction instruction, HInstruction at) { + LiveInterval atInterval = liveIntervals[at]; + LiveInterval instructionInterval = liveIntervals[instruction]; + int start = atInterval.start; + return instructionInterval.diesAt(start); + } + + void handleInstruction(HInstruction instruction, VariableNamer namer) { + // TODO(ager): We cannot perform this check to free names for + // HCheck instructions because they are special cased to have the + // same live intervals as the instruction they are checking. This + // includes sharing the start id with the checked + // input. Therefore, for HCheck(checkedInput, otherInput) we would + // end up checking that otherInput dies not here, but at the + // location of checkedInput. We should preserve the start id for + // the check instruction. + if (instruction is! HCheck) { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + HInstruction input = instruction.inputs[i]; + // If [input] has a name, and its use here is the last use, free + // its name. + if (needsName(input) && diesAt(input, instruction)) { + namer.freeName(input); + } + } + } + + if (needsName(instruction)) { + namer.allocateName(instruction); + } + } + + void handlePhi(HPhi phi, VariableNamer namer) { + if (!needsName(phi)) return; + + for (int i = 0; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + HBasicBlock predecessor = phi.block.predecessors[i]; + if (!needsName(input)) { + names.addAssignment(predecessor, input, phi); + } else { + names.addCopy(predecessor, input, phi); + } + } + + namer.allocateName(phi); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart b/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart new file mode 100644 index 0000000..ef294bd --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/string_validator.dart
@@ -0,0 +1,214 @@ +// 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. + +// Check the validity of string literals. + +library stringvalidator; + +import "dart:collection"; + +import "dart2jslib.dart"; +import "tree/tree.dart"; +import "elements/elements.dart"; +import "util/characters.dart"; +import "scanner/scannerlib.dart" show Token; + +class StringValidator { + final DiagnosticListener listener; + + StringValidator(this.listener); + + DartString validateQuotedString(Token token) { + SourceString source = token.value; + StringQuoting quoting = quotingFromString(source); + int leftQuote = quoting.leftQuoteLength; + int rightQuote = quoting.rightQuoteLength; + SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); + return validateString(token, + token.charOffset + leftQuote, + content, + quoting); + } + + DartString validateInterpolationPart(Token token, StringQuoting quoting, + {bool isFirst: false, + bool isLast: false}) { + SourceString source = token.value; + int leftQuote = 0; + int rightQuote = 0; + if (isFirst) leftQuote = quoting.leftQuoteLength; + if (isLast) rightQuote = quoting.rightQuoteLength; + SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); + return validateString(token, + token.charOffset + leftQuote, + content, + quoting); + } + + static StringQuoting quotingFromString(SourceString sourceString) { + Iterator<int> source = sourceString.iterator; + bool raw = false; + int quoteLength = 1; + source.moveNext(); + int quoteChar = source.current; + if (quoteChar == $r) { + raw = true; + source.moveNext(); + quoteChar = source.current; + } + assert(quoteChar == $SQ || quoteChar == $DQ); + // String has at least one quote. Check it if has three. + // If it only have two, the string must be an empty string literal, + // and end after the second quote. + bool multiline = false; + if (source.moveNext() && source.current == quoteChar && source.moveNext()) { + int code = source.current; + assert(code == quoteChar); // If not, there is a bug in the parser. + quoteLength = 3; + // Check if a multiline string starts with a newline (CR, LF or CR+LF). + if (source.moveNext()) { + code = source.current; + if (code == $CR) { + quoteLength += 1; + if (source.moveNext() && source.current == $LF) { + quoteLength += 1; + } + } else if (code == $LF) { + quoteLength += 1; + } + } + } + return StringQuoting.getQuoting(quoteChar, raw, quoteLength); + } + + void stringParseError(String message, Token token, int offset) { + listener.cancel("$message @ $offset", token : token); + } + + /** + * Validates the escape sequences and special characters of a string literal. + * Returns a DartString if valid, and null if not. + */ + DartString validateString(Token token, + int startOffset, + SourceString string, + StringQuoting quoting) { + // We need to check for invalid x and u escapes, for line + // terminators in non-multiline strings, and for invalid Unicode + // scalar values (either directly or as u-escape values). We also check + // for unpaired UTF-16 surrogates. + int length = 0; + int index = startOffset; + bool containsEscape = false; + bool previousWasLeadSurrogate = false; + bool invalidUtf16 = false; + for(HasNextIterator<int> iter = new HasNextIterator(string.iterator); + iter.hasNext; + length++) { + index++; + int code = iter.next(); + if (code == $BACKSLASH) { + if (quoting.raw) continue; + containsEscape = true; + if (!iter.hasNext) { + stringParseError("Incomplete escape sequence",token, index); + return null; + } + index++; + code = iter.next(); + if (code == $x) { + for (int i = 0; i < 2; i++) { + if (!iter.hasNext) { + stringParseError("Incomplete escape sequence", token, index); + return null; + } + index++; + code = iter.next(); + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + } + // A two-byte hex escape can't generate an invalid value. + continue; + } else if (code == $u) { + int escapeStart = index - 1; + index++; + code = iter.hasNext ? iter.next() : 0; + int value = 0; + if (code == $OPEN_CURLY_BRACKET) { + // expect 1-6 hex digits. + int count = 0; + while (iter.hasNext) { + code = iter.next(); + index++; + if (code == $CLOSE_CURLY_BRACKET) { + break; + } + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + count++; + value = value * 16 + hexDigitValue(code); + } + if (code != $CLOSE_CURLY_BRACKET || count == 0 || count > 6) { + int errorPosition = index - count; + if (count > 6) errorPosition += 6; + stringParseError("Invalid character in escape sequence", + token, errorPosition); + return null; + } + } else { + // Expect four hex digits, including the one just read. + for (int i = 0; i < 4; i++) { + if (i > 0) { + if (iter.hasNext) { + index++; + code = iter.next(); + } else { + code = 0; + } + } + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + value = value * 16 + hexDigitValue(code); + } + } + code = value; + } + } + if (code >= 0x10000) length++; + // This handles both unescaped characters and the value of unicode + // escapes. + if (previousWasLeadSurrogate) { + if (!isUtf16TrailSurrogate(code)) { + invalidUtf16 = true; + break; + } + previousWasLeadSurrogate = false; + } else if (isUtf16LeadSurrogate(code)) { + previousWasLeadSurrogate = true; + } else if (!isUnicodeScalarValue(code)) { + invalidUtf16 = true; + break; + } + } + if (previousWasLeadSurrogate || invalidUtf16) { + stringParseError("Invalid Utf16 surrogate", token, index); + return null; + } + // String literal successfully validated. + if (quoting.raw || !containsEscape) { + // A string without escapes could just as well have been raw. + return new DartString.rawString(string, length); + } + return new DartString.escapedString(string, length); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart b/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart new file mode 100644 index 0000000..ea56771 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart
@@ -0,0 +1,321 @@ +// 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. + +library parser; + +import 'dart:io'; +import 'dart:scalarlist'; + +import 'dart:utf'; + +import '../elements/elements.dart'; +import '../scanner/scanner_implementation.dart'; +import '../scanner/scannerlib.dart'; +import '../tree/tree.dart'; +import '../util/characters.dart'; +import '../source_file.dart'; +import '../ssa/ssa.dart'; + +import '../../compiler.dart' as api; + +part '../diagnostic_listener.dart'; +part '../scanner/byte_array_scanner.dart'; +part '../scanner/byte_strings.dart'; + +int charCount = 0; +Stopwatch stopwatch; + +void main() { + toolMain(new Options().arguments); +} + +void toolMain(List<String> arguments) { + filesWithCrashes = []; + stopwatch = new Stopwatch(); + MyOptions options = new MyOptions(); + + void printStats() { + int kb = (charCount / 1024).round().toInt(); + String stats = + '$classCount classes (${kb}Kb) in ${stopwatch.elapsedMilliseconds}ms'; + if (errorCount != 0) { + stats = '$stats with $errorCount errors'; + } + if (options.diet) { + print('Diet parsed $stats.'); + } else { + print('Parsed $stats.'); + } + if (filesWithCrashes.length != 0) { + print('The following ${filesWithCrashes.length} files caused a crash:'); + for (String file in filesWithCrashes) { + print(file); + } + } + } + + for (String argument in arguments) { + if (argument == "--diet") { + options.diet = true; + continue; + } + if (argument == "--throw") { + options.throwOnError = true; + continue; + } + if (argument == "--scan-only") { + options.scanOnly = true; + continue; + } + if (argument == "--read-only") { + options.readOnly = true; + continue; + } + if (argument == "--ast") { + options.buildAst = true; + continue; + } + if (argument == "-") { + parseFilesFrom(stdin, options, printStats); + return; + } + stopwatch.start(); + parseFile(argument, options); + stopwatch.stop(); + } + + printStats(); +} + +void parseFile(String filename, MyOptions options) { + List<int> bytes = read(filename); + charCount += bytes.length; + if (options.readOnly) return; + MySourceFile file = new MySourceFile(filename, bytes); + final Listener listener = options.buildAst + ? new MyNodeListener(file, options) + : new MyListener(file); + final Parser parser = options.diet + ? new PartialParser(listener) + : new Parser(listener); + try { + Token token = scan(file); + if (!options.scanOnly) parser.parseUnit(token); + } on ParserError catch (ex) { + if (options.throwOnError) { + throw; + } else { + print(ex); + } + } catch (ex) { + print('Error in file: $filename'); + throw; + } + if (options.buildAst) { + MyNodeListener l = listener; + if (!l.nodes.isEmpty) { + String message = 'Stack not empty after parsing'; + print(formatError(message, l.nodes.head.getBeginToken(), + l.nodes.head.getEndToken(), file)); + throw message; + } + } +} + +Token scan(MySourceFile source) { + Scanner scanner = new ByteArrayScanner(source.rawText); + return scanner.tokenize(); +} + +var filesWithCrashes; + +void parseFilesFrom(InputStream input, MyOptions options, Function whenDone) { + void readLine(String line) { + stopwatch.start(); + try { + parseFile(line, options); + } catch (ex, trace) { + filesWithCrashes.add(line); + print(ex); + print(trace); + } + stopwatch.stop(); + } + forEachLine(input, readLine, whenDone); +} + +void forEachLine(InputStream input, + void lineHandler(String line), + void closeHandler()) { + StringInputStream stringStream = new StringInputStream(input); + stringStream.onLine = () { + String line; + while ((line = stringStream.readLine()) != null) { + lineHandler(line); + } + }; + stringStream.onClosed = closeHandler; +} + +List<int> read(String filename) { + RandomAccessFile file = new File(filename).openSync(); + bool threw = true; + try { + int size = file.lengthSync(); + List<int> bytes = new Uint8List(size + 1); + file.readListSync(bytes, 0, size); + bytes[size] = $EOF; + threw = false; + return bytes; + } finally { + try { + file.closeSync(); + } catch (ex) { + if (!threw) throw; + } + } +} + +int classCount = 0; +int errorCount = 0; + +class MyListener extends Listener { + final SourceFile file; + + MyListener(this.file); + + void beginClassDeclaration(Token token) { + classCount++; + } + + void beginInterface(Token token) { + classCount++; + } + + void error(String message, Token token) { + throw new ParserError(formatError(message, token, token, file)); + } +} + +String formatError(String message, Token beginToken, Token endToken, + SourceFile file) { + ++errorCount; + if (beginToken == null) return '${file.filename}: $message'; + String tokenString = endToken.toString(); + int begin = beginToken.charOffset; + int end = endToken.charOffset + tokenString.length; + return file.getLocationMessage(message, begin, end, true, (x) => x); +} + +class MyNodeListener extends NodeListener { + MyNodeListener(SourceFile file, MyOptions options) + : super(new MyCanceller(file, options), null); + + void beginClassDeclaration(Token token) { + classCount++; + } + + void beginInterface(Token token) { + classCount++; + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + super.endClassDeclaration(interfacesCount, beginToken, + extendsKeyword, implementsKeyword, + endToken); + ClassNode node = popNode(); // Discard ClassNode and assert the type. + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + super.endInterface(supertypeCount, interfaceKeyword, extendsKeyword, + endToken); + ClassNode node = popNode(); // Discard ClassNode and assert the type. + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + super.endTopLevelFields(count, beginToken, endToken); + VariableDefinitions node = popNode(); // Discard node and assert the type. + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + super.endFunctionTypeAlias(typedefKeyword, endToken); + Typedef node = popNode(); // Discard Typedef and assert type type. + } + + void endLibraryTag(bool hasPrefix, Token beginToken, Token endToken) { + super.endLibraryTag(hasPrefix, beginToken, endToken); + ScriptTag node = popNode(); // Discard ScriptTag and assert type type. + } + + void log(message) { + print(message); + } +} + +class MyCanceller implements DiagnosticListener { + final SourceFile file; + final MyOptions options; + + MyCanceller(this.file, this.options); + + void log(String message) {} + + void cancel(String reason, {node, token, instruction, element}) { + Token beginToken; + Token endToken; + if (token != null) { + beginToken = token; + endToken = token; + } else if (node != null) { + beginToken = node.getBeginToken(); + endToken = node.getEndToken(); + } + String message = formatError(reason, beginToken, endToken, file); + if (options.throwOnError) throw new ParserError(message); + print(message); + } +} + +class MyOptions { + bool diet = false; + bool throwOnError = false; + bool scanOnly = false; + bool readOnly = false; + bool buildAst = false; +} + +class MySourceFile extends SourceFile { + final rawText; + var stringText; + + MySourceFile(filename, this.rawText) : super(filename, null); + + String get text { + if (rawText is String) { + return rawText; + } else { + if (stringText == null) { + stringText = new String.fromCharCodes(rawText); + if (stringText.endsWith('\u0000')) { + // Strip trailing NUL used by ByteArrayScanner to signal EOF. + stringText = stringText.substring(0, stringText.length - 1); + } + } + return stringText; + } + } + + set text(String newText) { + throw "not supported"; + } +} + +class Mock { + const Mock(); + bool get useColors => true; + internalError(message) { throw message.toString(); } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart new file mode 100644 index 0000000..5a3494d --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart
@@ -0,0 +1,235 @@ +// 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. + +part of tree; + +/** + * The [DartString] type represents a Dart string value as a sequence of Unicode + * Scalar Values. + * After parsing, any valid [LiteralString] will contain a [DartString] + * representing its content after removing quotes and resolving escapes in + * its source. + */ +abstract class DartString extends Iterable<int> { + factory DartString.empty() => const LiteralDartString(""); + // This is a convenience constructor. If you need a const literal DartString, + // use [const LiteralDartString(string)] directly. + factory DartString.literal(String string) => new LiteralDartString(string); + factory DartString.rawString(SourceString source, int length) => + new RawSourceDartString(source, length); + factory DartString.escapedString(SourceString source, int length) => + new EscapedSourceDartString(source, length); + factory DartString.concat(DartString first, DartString second) { + if (first.isEmpty) return second; + if (second.isEmpty) return first; + return new ConsDartString(first, second); + } + const DartString(); + int get length; + bool get isEmpty => length == 0; + Iterator<int> get iterator; + String slowToString(); + + bool operator ==(var other) { + if (other is !DartString) return false; + DartString otherString = other; + if (length != otherString.length) return false; + Iterator it1 = iterator; + Iterator it2 = otherString.iterator; + while (it1.moveNext()) { + if (!it2.moveNext()) return false; + if (it1.current != it2.current) return false; + } + return true; + } + String toString() => "DartString#${length}:${slowToString()}"; + SourceString get source; +} + + +/** + * A [DartString] where the content is represented by an actual [String]. + */ +class LiteralDartString extends DartString { + final String string; + const LiteralDartString(this.string); + int get length => string.length; + Iterator<int> get iterator => new StringCodeIterator(string); + String slowToString() => string; + SourceString get source => new StringWrapper(string); +} + +/** + * A [DartString] where the content comes from a slice of the program source. + */ +abstract class SourceBasedDartString extends DartString { + String toStringCache = null; + final SourceString source; + final int length; + SourceBasedDartString(this.source, this.length); + Iterator<int> get iterator; +} + +/** + * Special case of a [SourceBasedDartString] where we know the source doesn't + * contain any escapes. + */ +class RawSourceDartString extends SourceBasedDartString { + RawSourceDartString(source, length) : super(source, length); + Iterator<int> get iterator => source.iterator; + String slowToString() { + if (toStringCache != null) return toStringCache; + toStringCache = source.slowToString(); + return toStringCache; + } +} + +/** + * General case of a [SourceBasedDartString] where the source might contain + * escapes. + */ +class EscapedSourceDartString extends SourceBasedDartString { + EscapedSourceDartString(source, length) : super(source, length); + Iterator<int> get iterator { + if (toStringCache != null) return new StringCodeIterator(toStringCache); + return new StringEscapeIterator(source); + } + String slowToString() { + if (toStringCache != null) return toStringCache; + StringBuffer buffer = new StringBuffer(); + StringEscapeIterator it = new StringEscapeIterator(source); + while (it.moveNext()) { + buffer.addCharCode(it.current); + } + toStringCache = buffer.toString(); + return toStringCache; + } +} + +/** + * The concatenation of two [DartString]s. + */ +class ConsDartString extends DartString { + final DartString left; + final DartString right; + final int length; + String toStringCache; + ConsDartString(DartString left, DartString right) + : this.left = left, + this.right = right, + length = left.length + right.length; + + Iterator<int> get iterator => new ConsDartStringIterator(this); + + String slowToString() { + if (toStringCache != null) return toStringCache; + toStringCache = left.slowToString().concat(right.slowToString()); + return toStringCache; + } + SourceString get source => new StringWrapper(slowToString()); +} + +class ConsDartStringIterator implements Iterator<int> { + HasNextIterator<int> currentIterator; + DartString right; + bool hasNextLookAhead; + int _current = null; + + ConsDartStringIterator(ConsDartString cons) + : currentIterator = new HasNextIterator<int>(cons.left.iterator), + right = cons.right { + hasNextLookAhead = currentIterator.hasNext; + if (!hasNextLookAhead) { + nextPart(); + } + } + + int get current => _current; + + bool moveNext() { + if (!hasNextLookAhead) { + _current = null; + return false; + } + _current = currentIterator.next(); + hasNextLookAhead = currentIterator.hasNext; + if (!hasNextLookAhead) { + nextPart(); + } + return true; + } + void nextPart() { + if (right != null) { + currentIterator = new HasNextIterator<int>(right.iterator); + right = null; + hasNextLookAhead = currentIterator.hasNext; + } + } +} + +/** + *Iterator that returns the actual string contents of a string with escapes. + */ +class StringEscapeIterator implements Iterator<int>{ + final Iterator<int> source; + int _current = null; + + StringEscapeIterator(SourceString source) : this.source = source.iterator; + + int get current => _current; + + bool moveNext() { + if (!source.moveNext()) { + _current = null; + return false; + } + int code = source.current; + if (code != $BACKSLASH) { + _current = code; + return true; + } + source.moveNext(); + code = source.current; + switch (code) { + case $n: _current = $LF; break; + case $r: _current = $CR; break; + case $t: _current = $TAB; break; + case $b: _current = $BS; break; + case $f: _current = $FF; break; + case $v: _current = $VTAB; break; + case $x: + source.moveNext(); + int value = hexDigitValue(source.current); + source.moveNext(); + value = value * 16 + hexDigitValue(source.current); + _current = value; + break; + case $u: + int value = 0; + source.moveNext(); + code = source.current; + if (code == $OPEN_CURLY_BRACKET) { + source.moveNext(); + while (source.current != $CLOSE_CURLY_BRACKET) { + value = value * 16 + hexDigitValue(source.current); + source.moveNext(); + } + _current = value; + break; + } + // Four digit hex value. + value = hexDigitValue(code); + for (int i = 0; i < 3; i++) { + source.moveNext(); + value = value * 16 + hexDigitValue(source.current); + } + _current = value; + break; + default: + _current = code; + } + return true; + } +} +
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart new file mode 100644 index 0000000..1b3b455 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart
@@ -0,0 +1,2086 @@ +// 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. + +part of tree; + +abstract class Visitor<R> { + const Visitor(); + + R visitNode(Node node); + + R visitBlock(Block node) => visitStatement(node); + R visitBreakStatement(BreakStatement node) => visitGotoStatement(node); + R visitCascade(Cascade node) => visitExpression(node); + R visitCascadeReceiver(CascadeReceiver node) => visitExpression(node); + R visitCaseMatch(CaseMatch node) => visitNode(node); + R visitCatchBlock(CatchBlock node) => visitNode(node); + R visitClassNode(ClassNode node) => visitNode(node); + R visitCombinator(Combinator node) => visitNode(node); + R visitConditional(Conditional node) => visitExpression(node); + R visitContinueStatement(ContinueStatement node) => visitGotoStatement(node); + R visitDoWhile(DoWhile node) => visitLoop(node); + R visitEmptyStatement(EmptyStatement node) => visitStatement(node); + R visitExport(Export node) => visitLibraryDependency(node); + R visitExpression(Expression node) => visitNode(node); + R visitExpressionStatement(ExpressionStatement node) => visitStatement(node); + R visitFor(For node) => visitLoop(node); + R visitForIn(ForIn node) => visitLoop(node); + R visitFunctionDeclaration(FunctionDeclaration node) => visitStatement(node); + R visitFunctionExpression(FunctionExpression node) => visitExpression(node); + R visitGotoStatement(GotoStatement node) => visitStatement(node); + R visitIdentifier(Identifier node) => visitExpression(node); + R visitIf(If node) => visitStatement(node); + R visitImport(Import node) => visitLibraryDependency(node); + R visitLabel(Label node) => visitNode(node); + R visitLabeledStatement(LabeledStatement node) => visitStatement(node); + R visitLibraryDependency(LibraryDependency node) => visitLibraryTag(node); + R visitLibraryName(LibraryName node) => visitLibraryTag(node); + R visitLibraryTag(LibraryTag node) => visitNode(node); + R visitLiteral(Literal node) => visitExpression(node); + R visitLiteralBool(LiteralBool node) => visitLiteral(node); + R visitLiteralDouble(LiteralDouble node) => visitLiteral(node); + R visitLiteralInt(LiteralInt node) => visitLiteral(node); + R visitLiteralList(LiteralList node) => visitExpression(node); + R visitLiteralMap(LiteralMap node) => visitExpression(node); + R visitLiteralMapEntry(LiteralMapEntry node) => visitNode(node); + R visitLiteralNull(LiteralNull node) => visitLiteral(node); + R visitLiteralString(LiteralString node) => visitStringNode(node); + R visitStringJuxtaposition(StringJuxtaposition node) => visitStringNode(node); + R visitLoop(Loop node) => visitStatement(node); + R visitMixinApplication(MixinApplication node) => visitNode(node); + R visitModifiers(Modifiers node) => visitNode(node); + R visitNamedArgument(NamedArgument node) => visitExpression(node); + R visitNamedMixinApplication(NamedMixinApplication node) { + return visitMixinApplication(node); + } + R visitNewExpression(NewExpression node) => visitExpression(node); + R visitNodeList(NodeList node) => visitNode(node); + R visitOperator(Operator node) => visitIdentifier(node); + R visitParenthesizedExpression(ParenthesizedExpression node) { + return visitExpression(node); + } + R visitPart(Part node) => visitLibraryTag(node); + R visitPartOf(PartOf node) => visitNode(node); + R visitPostfix(Postfix node) => visitNodeList(node); + R visitPrefix(Prefix node) => visitNodeList(node); + R visitReturn(Return node) => visitStatement(node); + R visitScriptTag(ScriptTag node) => visitNode(node); + R visitSend(Send node) => visitExpression(node); + R visitSendSet(SendSet node) => visitSend(node); + R visitStatement(Statement node) => visitNode(node); + R visitStringNode(StringNode node) => visitExpression(node); + R visitStringInterpolation(StringInterpolation node) => visitStringNode(node); + R visitStringInterpolationPart(StringInterpolationPart node) { + return visitNode(node); + } + R visitSwitchCase(SwitchCase node) => visitNode(node); + R visitSwitchStatement(SwitchStatement node) => visitStatement(node); + R visitThrow(Throw node) => visitStatement(node); + R visitTryStatement(TryStatement node) => visitStatement(node); + R visitTypeAnnotation(TypeAnnotation node) => visitNode(node); + R visitTypedef(Typedef node) => visitNode(node); + R visitTypeVariable(TypeVariable node) => visitNode(node); + R visitVariableDefinitions(VariableDefinitions node) => visitStatement(node); + R visitWhile(While node) => visitLoop(node); +} + +Token firstBeginToken(Node first, Node second) { + Token token = null; + if (first != null) { + token = first.getBeginToken(); + } + if (token == null && second != null) { + // [token] might be null even when [first] is not, e.g. for empty Modifiers. + token = second.getBeginToken(); + } + return token; +} + +/** + * A node in a syntax tree. + * + * The abstract part of "abstract syntax tree" is invalidated when + * supporting tools such as code formatting. These tools need concrete + * syntax such as parentheses and no constant folding. + * + * We support these tools by storing additional references back to the + * token stream. These references are stored in fields ending with + * "Token". + */ +abstract class Node extends TreeElementMixin implements Spannable { + final int hashCode; + static int _HASH_COUNTER = 0; + + Node() : hashCode = ++_HASH_COUNTER; + + accept(Visitor visitor); + + visitChildren(Visitor visitor); + + /** + * Returns this node unparsed to Dart source string. + */ + toString() => unparse(this); + + /** + * Returns Xml-like tree representation of this node. + */ + toDebugString() { + return PrettyPrinter.prettyPrint(this); + } + + String getObjectDescription() => super.toString(); + + Token getBeginToken(); + + Token getEndToken(); + + Block asBlock() => null; + BreakStatement asBreakStatement() => null; + Cascade asCascade() => null; + CascadeReceiver asCascadeReceiver() => null; + CaseMatch asCaseMatch() => null; + CatchBlock asCatchBlock() => null; + ClassNode asClassNode() => null; + Combinator asCombinator() => null; + Conditional asConditional() => null; + ContinueStatement asContinueStatement() => null; + DoWhile asDoWhile() => null; + EmptyStatement asEmptyStatement() => null; + Export asExport() => null; + Expression asExpression() => null; + ExpressionStatement asExpressionStatement() => null; + For asFor() => null; + ForIn asForIn() => null; + FunctionDeclaration asFunctionDeclaration() => null; + FunctionExpression asFunctionExpression() => null; + Identifier asIdentifier() => null; + If asIf() => null; + Import asImport() => null; + Label asLabel() => null; + LabeledStatement asLabeledStatement() => null; + LibraryName asLibraryName() => null; + LiteralBool asLiteralBool() => null; + LiteralDouble asLiteralDouble() => null; + LiteralInt asLiteralInt() => null; + LiteralList asLiteralList() => null; + LiteralMap asLiteralMap() => null; + LiteralMapEntry asLiteralMapEntry() => null; + LiteralNull asLiteralNull() => null; + LiteralString asLiteralString() => null; + MixinApplication asMixinApplication() => null; + Modifiers asModifiers() => null; + NamedArgument asNamedArgument() => null; + NamedMixinApplication asNamedMixinApplication() => null; + NodeList asNodeList() => null; + Operator asOperator() => null; + ParenthesizedExpression asParenthesizedExpression() => null; + Part asPart() => null; + PartOf asPartOf() => null; + Return asReturn() => null; + ScriptTag asScriptTag() => null; + Send asSend() => null; + SendSet asSendSet() => null; + Statement asStatement() => null; + StringInterpolation asStringInterpolation() => null; + StringInterpolationPart asStringInterpolationPart() => null; + StringJuxtaposition asStringJuxtaposition() => null; + StringNode asStringNode() => null; + SwitchCase asSwitchCase() => null; + SwitchStatement asSwitchStatement() => null; + Throw asThrow() => null; + TryStatement asTryStatement() => null; + TypeAnnotation asTypeAnnotation() => null; + TypeVariable asTypeVariable() => null; + Typedef asTypedef() => null; + VariableDefinitions asVariableDefinitions() => null; + While asWhile() => null; + + bool isValidBreakTarget() => false; + bool isValidContinueTarget() => false; +} + +class ClassNode extends Node { + final Modifiers modifiers; + final Identifier name; + final Node superclass; + final NodeList interfaces; + final NodeList typeParameters; + final NodeList body; + + // TODO(ahe, karlklose): the default keyword is not recorded. + final TypeAnnotation defaultClause; + + final Token beginToken; + final Token extendsKeyword; + final Token endToken; + + ClassNode(this.modifiers, this.name, this.typeParameters, this.superclass, + this.interfaces, this.defaultClause, this.beginToken, + this.extendsKeyword, this.body, this.endToken); + + ClassNode asClassNode() => this; + + accept(Visitor visitor) => visitor.visitClassNode(this); + + visitChildren(Visitor visitor) { + if (name != null) name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + if (superclass != null) superclass.accept(visitor); + if (interfaces != null) interfaces.accept(visitor); + if (body != null) body.accept(visitor); + } + + bool get isInterface => identical(beginToken.stringValue, 'interface'); + + bool get isClass => !isInterface; + + Token getBeginToken() => beginToken; + + Token getEndToken() => endToken; +} + +class MixinApplication extends Node { + final TypeAnnotation superclass; + final NodeList mixins; + + MixinApplication(this.superclass, this.mixins); + + MixinApplication asMixinApplication() => this; + + accept(Visitor visitor) => visitor.visitMixinApplication(this); + + visitChildren(Visitor visitor) { + if (superclass != null) superclass.accept(visitor); + if (mixins != null) mixins.accept(visitor); + } + + Token getBeginToken() => superclass.getBeginToken(); + Token getEndToken() => mixins.getEndToken(); +} + +// TODO(kasperl): Let this share some structure with the typedef for function +// type aliases? +class NamedMixinApplication extends Node implements MixinApplication { + final Identifier name; + final NodeList typeParameters; + + final Modifiers modifiers; + final MixinApplication mixinApplication; + final NodeList interfaces; + + final Token typedefKeyword; + final Token endToken; + + NamedMixinApplication(this.name, this.typeParameters, + this.modifiers, this.mixinApplication, this.interfaces, + this.typedefKeyword, this.endToken); + + TypeAnnotation get superclass => mixinApplication.superclass; + NodeList get mixins => mixinApplication.mixins; + + MixinApplication asMixinApplication() => this; + NamedMixinApplication asNamedMixinApplication() => this; + + accept(Visitor visitor) => visitor.visitNamedMixinApplication(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + if (modifiers != null) modifiers.accept(visitor); + if (interfaces != null) interfaces.accept(visitor); + mixinApplication.accept(visitor); + } + + Token getBeginToken() => typedefKeyword; + Token getEndToken() => endToken; +} + +abstract class Expression extends Node { + Expression(); + + Expression asExpression() => this; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); +} + +abstract class Statement extends Node { + Statement(); + + Statement asStatement() => this; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); + + bool isValidBreakTarget() => true; +} + +/** + * A message send aka method invocation. In Dart, most operations can + * (and should) be considered as message sends. Getters and setters + * are just methods with a special syntax. Consequently, we model + * property access, assignment, operators, and method calls with this + * one node. + */ +class Send extends Expression { + final Node receiver; + final Node selector; + final NodeList argumentsNode; + Link<Node> get arguments => argumentsNode.nodes; + + Send([this.receiver, this.selector, this.argumentsNode]); + Send.postfix(this.receiver, this.selector, [Node argument = null]) + : argumentsNode = (argument == null) + ? new Postfix() + : new Postfix.singleton(argument); + Send.prefix(this.receiver, this.selector, [Node argument = null]) + : argumentsNode = (argument == null) + ? new Prefix() + : new Prefix.singleton(argument); + + Send asSend() => this; + + accept(Visitor visitor) => visitor.visitSend(this); + + visitChildren(Visitor visitor) { + if (receiver != null) receiver.accept(visitor); + if (selector != null) selector.accept(visitor); + if (argumentsNode != null) argumentsNode.accept(visitor); + } + + int argumentCount() { + return (argumentsNode == null) ? -1 : argumentsNode.slowLength(); + } + + bool get isSuperCall { + return receiver != null && + receiver.asIdentifier() != null && + receiver.asIdentifier().isSuper(); + } + bool get isOperator => selector is Operator; + bool get isPropertyAccess => argumentsNode == null; + bool get isFunctionObjectInvocation => selector == null; + bool get isPrefix => argumentsNode is Prefix; + bool get isPostfix => argumentsNode is Postfix; + bool get isCall => !isOperator && !isPropertyAccess; + bool get isIndex => + isOperator && identical(selector.asOperator().source.stringValue, '[]'); + bool get isLogicalAnd => + isOperator && identical(selector.asOperator().source.stringValue, '&&'); + bool get isLogicalOr => + isOperator && identical(selector.asOperator().source.stringValue, '||'); + bool get isParameterCheck => + isOperator && identical(selector.asOperator().source.stringValue, '?'); + + Token getBeginToken() { + if (isPrefix && !isIndex) return selector.getBeginToken(); + return firstBeginToken(receiver, selector); + } + + Token getEndToken() { + if (isPrefix) { + if (receiver != null) return receiver.getEndToken(); + if (selector != null) return selector.getEndToken(); + return null; + } + if (!isPostfix && argumentsNode != null) { + return argumentsNode.getEndToken(); + } + if (selector != null) return selector.getEndToken(); + return receiver.getBeginToken(); + } + + Send copyWithReceiver(Node newReceiver) { + assert(receiver == null); + return new Send(newReceiver, selector, argumentsNode); + } +} + +class Postfix extends NodeList { + Postfix() : super(null, const Link<Node>()); + Postfix.singleton(Node argument) : super.singleton(argument); +} + +class Prefix extends NodeList { + Prefix() : super(null, const Link<Node>()); + Prefix.singleton(Node argument) : super.singleton(argument); +} + +class SendSet extends Send { + final Operator assignmentOperator; + SendSet(receiver, selector, this.assignmentOperator, argumentsNode) + : super(receiver, selector, argumentsNode); + SendSet.postfix(receiver, + selector, + this.assignmentOperator, + [Node argument = null]) + : super.postfix(receiver, selector, argument); + SendSet.prefix(receiver, + selector, + this.assignmentOperator, + [Node argument = null]) + : super.prefix(receiver, selector, argument); + + SendSet asSendSet() => this; + + accept(Visitor visitor) => visitor.visitSendSet(this); + + visitChildren(Visitor visitor) { + super.visitChildren(visitor); + if (assignmentOperator != null) assignmentOperator.accept(visitor); + } + + Send copyWithReceiver(Node newReceiver) { + assert(receiver == null); + return new SendSet(newReceiver, selector, assignmentOperator, + argumentsNode); + } + + Token getBeginToken() { + if (isPrefix) return assignmentOperator.getBeginToken(); + return super.getBeginToken(); + } + + Token getEndToken() { + if (isPostfix) return assignmentOperator.getEndToken(); + return super.getEndToken(); + } +} + +class NewExpression extends Expression { + /** The token NEW or CONST */ + final Token newToken; + + // Note: we expect that send.receiver is null. + final Send send; + + NewExpression([this.newToken, this.send]); + + accept(Visitor visitor) => visitor.visitNewExpression(this); + + visitChildren(Visitor visitor) { + if (send != null) send.accept(visitor); + } + + bool isConst() { + return identical(newToken.stringValue, 'const') + || identical(newToken.stringValue, '@'); + } + + Token getBeginToken() => newToken; + + Token getEndToken() => send.getEndToken(); +} + +class NodeList extends Node { + final Link<Node> nodes; + final Token beginToken; + final Token endToken; + final SourceString delimiter; + bool get isEmpty => nodes.isEmpty; + + NodeList([this.beginToken, this.nodes, this.endToken, this.delimiter]); + + Iterator<Node> get iterator => nodes.iterator; + + NodeList.singleton(Node node) : this(null, const Link<Node>().prepend(node)); + NodeList.empty() : this(null, const Link<Node>()); + + NodeList asNodeList() => this; + + int slowLength() { + int result = 0; + for (Link<Node> cursor = nodes; !cursor.isEmpty; cursor = cursor.tail) { + result++; + } + return result; + } + + accept(Visitor visitor) => visitor.visitNodeList(this); + + visitChildren(Visitor visitor) { + if (nodes == null) return; + for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) { + if (link.head != null) link.head.accept(visitor); + } + } + + Token getBeginToken() { + if (beginToken != null) return beginToken; + if (nodes != null) { + for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) { + if (link.head.getBeginToken() != null) { + return link.head.getBeginToken(); + } + if (link.head.getEndToken() != null) { + return link.head.getEndToken(); + } + } + } + return endToken; + } + + Token getEndToken() { + if (endToken != null) return endToken; + if (nodes != null) { + Link<Node> link = nodes; + if (link.isEmpty) return beginToken; + while (!link.tail.isEmpty) link = link.tail; + if (link.head.getEndToken() != null) return link.head.getEndToken(); + if (link.head.getBeginToken() != null) return link.head.getBeginToken(); + } + return beginToken; + } +} + +class Block extends Statement { + final NodeList statements; + + Block(this.statements); + + Block asBlock() => this; + + accept(Visitor visitor) => visitor.visitBlock(this); + + visitChildren(Visitor visitor) { + if (statements != null) statements.accept(visitor); + } + + Token getBeginToken() => statements.getBeginToken(); + + Token getEndToken() => statements.getEndToken(); +} + +class If extends Statement { + final ParenthesizedExpression condition; + final Statement thenPart; + final Statement elsePart; + + final Token ifToken; + final Token elseToken; + + If(this.condition, this.thenPart, this.elsePart, + this.ifToken, this.elseToken); + + If asIf() => this; + + bool get hasElsePart => elsePart != null; + + void validate() { + // TODO(ahe): Check that condition has size one. + } + + accept(Visitor visitor) => visitor.visitIf(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (thenPart != null) thenPart.accept(visitor); + if (elsePart != null) elsePart.accept(visitor); + } + + Token getBeginToken() => ifToken; + + Token getEndToken() { + if (elsePart == null) return thenPart.getEndToken(); + return elsePart.getEndToken(); + } +} + +class Conditional extends Expression { + final Expression condition; + final Expression thenExpression; + final Expression elseExpression; + + final Token questionToken; + final Token colonToken; + + Conditional(this.condition, this.thenExpression, + this.elseExpression, this.questionToken, this.colonToken); + + Conditional asConditional() => this; + + accept(Visitor visitor) => visitor.visitConditional(this); + + visitChildren(Visitor visitor) { + condition.accept(visitor); + thenExpression.accept(visitor); + elseExpression.accept(visitor); + } + + Token getBeginToken() => condition.getBeginToken(); + + Token getEndToken() => elseExpression.getEndToken(); +} + +class For extends Loop { + /** Either a variable declaration or an expression. */ + final Node initializer; + /** Either an expression statement or an empty statement. */ + final Statement conditionStatement; + final NodeList update; + + final Token forToken; + + For(this.initializer, this.conditionStatement, this.update, body, + this.forToken) : super(body); + + For asFor() => this; + + Expression get condition { + if (conditionStatement is ExpressionStatement) { + return conditionStatement.asExpressionStatement().expression; + } else { + return null; + } + } + + accept(Visitor visitor) => visitor.visitFor(this); + + visitChildren(Visitor visitor) { + if (initializer != null) initializer.accept(visitor); + if (conditionStatement != null) conditionStatement.accept(visitor); + if (update != null) update.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => forToken; + + Token getEndToken() { + return body.getEndToken(); + } +} + +class FunctionDeclaration extends Statement { + final FunctionExpression function; + + FunctionDeclaration(this.function); + + FunctionDeclaration asFunctionDeclaration() => this; + + accept(Visitor visitor) => visitor.visitFunctionDeclaration(this); + + visitChildren(Visitor visitor) => function.accept(visitor); + + Token getBeginToken() => function.getBeginToken(); + Token getEndToken() => function.getEndToken(); +} + +class FunctionExpression extends Expression { + final Node name; + + /** + * List of VariableDefinitions or NodeList. + * + * A NodeList can only occur at the end and holds named parameters. + */ + final NodeList parameters; + + final Statement body; + final TypeAnnotation returnType; + final Modifiers modifiers; + final NodeList initializers; + + final Token getOrSet; + + FunctionExpression(this.name, this.parameters, this.body, this.returnType, + this.modifiers, this.initializers, this.getOrSet) { + assert(modifiers != null); + } + + FunctionExpression asFunctionExpression() => this; + + accept(Visitor visitor) => visitor.visitFunctionExpression(this); + + visitChildren(Visitor visitor) { + if (modifiers != null) modifiers.accept(visitor); + if (returnType != null) returnType.accept(visitor); + if (name != null) name.accept(visitor); + if (parameters != null) parameters.accept(visitor); + if (initializers != null) initializers.accept(visitor); + if (body != null) body.accept(visitor); + } + + bool hasBody() => body.asEmptyStatement() == null; + + bool hasEmptyBody() { + Block block = body.asBlock(); + if (block == null) return false; + return block.statements.isEmpty; + } + + Token getBeginToken() { + Token token = firstBeginToken(modifiers, returnType); + if (token != null) return token; + if (getOrSet != null) return getOrSet; + return firstBeginToken(name, parameters); + } + + Token getEndToken() { + Token token = (body == null) ? null : body.getEndToken(); + token = (token == null) ? parameters.getEndToken() : token; + return (token == null) ? name.getEndToken() : token; + } +} + +typedef void DecodeErrorHandler(Token token, var error); + +abstract class Literal<T> extends Expression { + final Token token; + final DecodeErrorHandler handler; + + Literal(Token this.token, DecodeErrorHandler this.handler); + + T get value; + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => token; + + Token getEndToken() => token; +} + +class LiteralInt extends Literal<int> { + LiteralInt(Token token, DecodeErrorHandler handler) : super(token, handler); + + LiteralInt asLiteralInt() => this; + + int get value { + try { + Token valueToken = token; + if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next; + return int.parse(valueToken.value.slowToString()); + } on FormatException catch (ex) { + (this.handler)(token, ex); + } + } + + accept(Visitor visitor) => visitor.visitLiteralInt(this); +} + +class LiteralDouble extends Literal<double> { + LiteralDouble(Token token, DecodeErrorHandler handler) + : super(token, handler); + + LiteralDouble asLiteralDouble() => this; + + double get value { + try { + Token valueToken = token; + if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next; + return double.parse(valueToken.value.slowToString()); + } on FormatException catch (ex) { + (this.handler)(token, ex); + } + } + + accept(Visitor visitor) => visitor.visitLiteralDouble(this); +} + +class LiteralBool extends Literal<bool> { + LiteralBool(Token token, DecodeErrorHandler handler) : super(token, handler); + + LiteralBool asLiteralBool() => this; + + bool get value { + if (identical(token.stringValue, 'true')) return true; + if (identical(token.stringValue, 'false')) return false; + (this.handler)(token, "not a bool ${token.value}"); + } + + accept(Visitor visitor) => visitor.visitLiteralBool(this); +} + + +class StringQuoting { + static const StringQuoting SINGLELINE_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 1); + static const StringQuoting RAW_SINGLELINE_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 1); + static const StringQuoting MULTILINE_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 3); + static const StringQuoting RAW_MULTILINE_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 3); + static const StringQuoting MULTILINE_NL_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 4); + static const StringQuoting RAW_MULTILINE_NL_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 4); + static const StringQuoting MULTILINE_NL2_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 5); + static const StringQuoting RAW_MULTILINE_NL2_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 5); + static const StringQuoting SINGLELINE_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 1); + static const StringQuoting RAW_SINGLELINE_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 1); + static const StringQuoting MULTILINE_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 3); + static const StringQuoting RAW_MULTILINE_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 3); + static const StringQuoting MULTILINE_NL_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 4); + static const StringQuoting RAW_MULTILINE_NL_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 4); + static const StringQuoting MULTILINE_NL2_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 5); + static const StringQuoting RAW_MULTILINE_NL2_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 5); + + + static const List<StringQuoting> mapping = const <StringQuoting>[ + SINGLELINE_DQ, + RAW_SINGLELINE_DQ, + MULTILINE_DQ, + RAW_MULTILINE_DQ, + MULTILINE_NL_DQ, + RAW_MULTILINE_NL_DQ, + MULTILINE_NL2_DQ, + RAW_MULTILINE_NL2_DQ, + SINGLELINE_SQ, + RAW_SINGLELINE_SQ, + MULTILINE_SQ, + RAW_MULTILINE_SQ, + MULTILINE_NL_SQ, + RAW_MULTILINE_NL_SQ, + MULTILINE_NL2_SQ, + RAW_MULTILINE_NL2_SQ + ]; + final bool raw; + final int leftQuoteCharCount; + final int quote; + const StringQuoting(this.quote, {bool raw, int leftQuoteLength}) + : this.raw = raw, this.leftQuoteCharCount = leftQuoteLength; + String get quoteChar => identical(quote, $DQ) ? '"' : "'"; + + int get leftQuoteLength => (raw ? 1 : 0) + leftQuoteCharCount; + int get rightQuoteLength => (leftQuoteCharCount > 2) ? 3 : 1; + static StringQuoting getQuoting(int quote, bool raw, int quoteLength) { + int index = quoteLength - 1; + if (quoteLength > 2) index -= 1; + return mapping[(raw ? 1 : 0) + index * 2 + (identical(quote, $SQ) ? 8 : 0)]; + } +} + +/** + * Superclass for classes representing string literals. + */ +abstract class StringNode extends Expression { + DartString get dartString; + bool get isInterpolation; + + StringNode asStringNode() => this; +} + +class LiteralString extends StringNode { + final Token token; + /** Non-null on validated string literals. */ + final DartString dartString; + + LiteralString(this.token, this.dartString); + + LiteralString asLiteralString() => this; + + void visitChildren(Visitor visitor) {} + + bool get isInterpolation => false; + bool isValidated() => dartString != null; + + Token getBeginToken() => token; + Token getEndToken() => token; + + accept(Visitor visitor) => visitor.visitLiteralString(this); +} + +class LiteralNull extends Literal<SourceString> { + LiteralNull(Token token) : super(token, null); + + LiteralNull asLiteralNull() => this; + + SourceString get value => null; + + accept(Visitor visitor) => visitor.visitLiteralNull(this); +} + +class LiteralList extends Expression { + final NodeList typeArguments; + final NodeList elements; + + final Token constKeyword; + + LiteralList(this.typeArguments, this.elements, this.constKeyword); + + bool isConst() => constKeyword != null; + + LiteralList asLiteralList() => this; + accept(Visitor visitor) => visitor.visitLiteralList(this); + + visitChildren(Visitor visitor) { + if (typeArguments != null) typeArguments.accept(visitor); + elements.accept(visitor); + } + + Token getBeginToken() { + if (constKeyword != null) return constKeyword; + return firstBeginToken(typeArguments, elements); + } + + Token getEndToken() => elements.getEndToken(); +} + +class Identifier extends Expression { + final Token token; + + SourceString get source => token.value; + + Identifier(Token this.token); + + bool isThis() => identical(source.stringValue, 'this'); + + bool isSuper() => identical(source.stringValue, 'super'); + + Identifier asIdentifier() => this; + + accept(Visitor visitor) => visitor.visitIdentifier(this); + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => token; + + Token getEndToken() => token; +} + +class Operator extends Identifier { + Operator(Token token) : super(token); + + Operator asOperator() => this; + + accept(Visitor visitor) => visitor.visitOperator(this); +} + +class Return extends Statement { + final Node expression; + final Token beginToken; + final Token endToken; + + Return(this.beginToken, this.endToken, this.expression); + + Return asReturn() => this; + + bool get hasExpression => expression != null; + + bool get isRedirectingFactoryBody => beginToken.stringValue == '='; + + accept(Visitor visitor) => visitor.visitReturn(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() { + if (endToken == null) return expression.getEndToken(); + return endToken; + } +} + +class ExpressionStatement extends Statement { + final Expression expression; + final Token endToken; + + ExpressionStatement(this.expression, this.endToken); + + ExpressionStatement asExpressionStatement() => this; + + accept(Visitor visitor) => visitor.visitExpressionStatement(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => endToken; +} + +class Throw extends Statement { + final Expression expression; + + final Token throwToken; + final Token endToken; + + Throw(this.expression, this.throwToken, this.endToken); + + Throw asThrow() => this; + + accept(Visitor visitor) => visitor.visitThrow(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => throwToken; + + Token getEndToken() => endToken; +} + +class TypeAnnotation extends Node { + final Expression typeName; + final NodeList typeArguments; + + TypeAnnotation(Expression this.typeName, NodeList this.typeArguments); + + TypeAnnotation asTypeAnnotation() => this; + + accept(Visitor visitor) => visitor.visitTypeAnnotation(this); + + visitChildren(Visitor visitor) { + typeName.accept(visitor); + if (typeArguments != null) typeArguments.accept(visitor); + } + + Token getBeginToken() => typeName.getBeginToken(); + + Token getEndToken() => typeName.getEndToken(); +} + +class TypeVariable extends Node { + final Identifier name; + final TypeAnnotation bound; + TypeVariable(Identifier this.name, TypeAnnotation this.bound); + + accept(Visitor visitor) => visitor.visitTypeVariable(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + if (bound != null) { + bound.accept(visitor); + } + } + + TypeVariable asTypeVariable() => this; + + Token getBeginToken() => name.getBeginToken(); + + Token getEndToken() { + return (bound != null) ? bound.getEndToken() : name.getEndToken(); + } +} + +class VariableDefinitions extends Statement { + final TypeAnnotation type; + final Modifiers modifiers; + final NodeList definitions; + VariableDefinitions(this.type, this.modifiers, this.definitions) { + assert(modifiers != null); + } + + VariableDefinitions asVariableDefinitions() => this; + + accept(Visitor visitor) => visitor.visitVariableDefinitions(this); + + visitChildren(Visitor visitor) { + if (type != null) type.accept(visitor); + if (definitions != null) definitions.accept(visitor); + } + + Token getBeginToken() { + var token = firstBeginToken(modifiers, type); + if (token == null) { + token = definitions.getBeginToken(); + } + return token; + } + + Token getEndToken() => definitions.getEndToken(); +} + +abstract class Loop extends Statement { + Expression get condition; + final Statement body; + + Loop(this.body); + + bool isValidContinueTarget() => true; +} + +class DoWhile extends Loop { + final Token doKeyword; + final Token whileKeyword; + final Token endToken; + + final Expression condition; + + DoWhile(Statement body, Expression this.condition, + Token this.doKeyword, Token this.whileKeyword, Token this.endToken) + : super(body); + + DoWhile asDoWhile() => this; + + accept(Visitor visitor) => visitor.visitDoWhile(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => doKeyword; + + Token getEndToken() => endToken; +} + +class While extends Loop { + final Token whileKeyword; + final Expression condition; + + While(Expression this.condition, Statement body, + Token this.whileKeyword) : super(body); + + While asWhile() => this; + + accept(Visitor visitor) => visitor.visitWhile(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => whileKeyword; + + Token getEndToken() => body.getEndToken(); +} + +class ParenthesizedExpression extends Expression { + final Expression expression; + final BeginGroupToken beginToken; + + ParenthesizedExpression(Expression this.expression, + BeginGroupToken this.beginToken); + + ParenthesizedExpression asParenthesizedExpression() => this; + + accept(Visitor visitor) => visitor.visitParenthesizedExpression(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() => beginToken.endGroup; +} + +/** Representation of modifiers such as static, abstract, final, etc. */ +class Modifiers extends Node { + /** + * Pseudo-constant for empty modifiers. + */ + static final Modifiers EMPTY = new Modifiers(new NodeList.empty()); + + /* TODO(ahe): The following should be validated relating to modifiers: + * 1. The nodes must come in a certain order. + * 2. The keywords "var" and "final" may not be used at the same time. + * 3. The keywords "abstract" and "external" may not be used at the same time. + * 4. The type of an element must be null if isVar() is true. + */ + + final NodeList nodes; + /** Bit pattern to easy check what modifiers are present. */ + final int flags; + + static const int FLAG_STATIC = 1; + static const int FLAG_ABSTRACT = FLAG_STATIC << 1; + static const int FLAG_FINAL = FLAG_ABSTRACT << 1; + static const int FLAG_VAR = FLAG_FINAL << 1; + static const int FLAG_CONST = FLAG_VAR << 1; + static const int FLAG_FACTORY = FLAG_CONST << 1; + static const int FLAG_EXTERNAL = FLAG_FACTORY << 1; + + Modifiers(NodeList nodes) : this.withFlags(nodes, computeFlags(nodes.nodes)); + + Modifiers.withFlags(this.nodes, this.flags); + + static int computeFlags(Link<Node> nodes) { + int flags = 0; + for (; !nodes.isEmpty; nodes = nodes.tail) { + String value = nodes.head.asIdentifier().source.stringValue; + if (identical(value, 'static')) flags |= FLAG_STATIC; + else if (identical(value, 'abstract')) flags |= FLAG_ABSTRACT; + else if (identical(value, 'final')) flags |= FLAG_FINAL; + else if (identical(value, 'var')) flags |= FLAG_VAR; + else if (identical(value, 'const')) flags |= FLAG_CONST; + else if (identical(value, 'factory')) flags |= FLAG_FACTORY; + else if (identical(value, 'external')) flags |= FLAG_EXTERNAL; + else throw 'internal error: ${nodes.head}'; + } + return flags; + } + + Node findModifier(String modifier) { + Link<Node> nodeList = nodes.nodes; + for (; !nodeList.isEmpty; nodeList = nodeList.tail) { + String value = nodeList.head.asIdentifier().source.stringValue; + if(identical(value, modifier)) { + return nodeList.head; + } + } + return null; + } + + Modifiers asModifiers() => this; + Token getBeginToken() => nodes.getBeginToken(); + Token getEndToken() => nodes.getEndToken(); + accept(Visitor visitor) => visitor.visitModifiers(this); + visitChildren(Visitor visitor) => nodes.accept(visitor); + + bool isStatic() => (flags & FLAG_STATIC) != 0; + bool isAbstract() => (flags & FLAG_ABSTRACT) != 0; + bool isFinal() => (flags & FLAG_FINAL) != 0; + bool isVar() => (flags & FLAG_VAR) != 0; + bool isConst() => (flags & FLAG_CONST) != 0; + bool isFactory() => (flags & FLAG_FACTORY) != 0; + bool isExternal() => (flags & FLAG_EXTERNAL) != 0; + + Node getStatic() => findModifier('static'); + + /** + * Use this to check if the declaration is either explicitly or implicitly + * final. + */ + bool isFinalOrConst() => isFinal() || isConst(); + + String toString() { + LinkBuilder<String> builder = new LinkBuilder<String>(); + if (isStatic()) builder.addLast('static'); + if (isAbstract()) builder.addLast('abstract'); + if (isFinal()) builder.addLast('final'); + if (isVar()) builder.addLast('var'); + if (isConst()) builder.addLast('const'); + if (isFactory()) builder.addLast('factory'); + if (isExternal()) builder.addLast('external'); + StringBuffer buffer = new StringBuffer(); + builder.toLink().printOn(buffer, ', '); + return buffer.toString(); + } +} + +class StringInterpolation extends StringNode { + final LiteralString string; + final NodeList parts; + + StringInterpolation(this.string, this.parts); + + StringInterpolation asStringInterpolation() => this; + + DartString get dartString => null; + bool get isInterpolation => true; + + accept(Visitor visitor) => visitor.visitStringInterpolation(this); + + visitChildren(Visitor visitor) { + string.accept(visitor); + parts.accept(visitor); + } + + Token getBeginToken() => string.getBeginToken(); + Token getEndToken() => parts.getEndToken(); +} + +class StringInterpolationPart extends Node { + final Expression expression; + final LiteralString string; + + StringInterpolationPart(this.expression, this.string); + + StringInterpolationPart asStringInterpolationPart() => this; + + accept(Visitor visitor) => visitor.visitStringInterpolationPart(this); + + visitChildren(Visitor visitor) { + expression.accept(visitor); + string.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => string.getEndToken(); +} + +/** + * A class representing juxtaposed string literals. + * The string literals can be both plain literals and string interpolations. + */ +class StringJuxtaposition extends StringNode { + final Expression first; + final Expression second; + + /** + * Caches the check for whether this juxtaposition contains a string + * interpolation + */ + bool isInterpolationCache = null; + + /** + * Caches a Dart string representation of the entire juxtaposition's + * content. Only juxtapositions that don't (transitively) contains + * interpolations have a static representation. + */ + DartString dartStringCache = null; + + StringJuxtaposition(this.first, this.second); + + StringJuxtaposition asStringJuxtaposition() => this; + + bool get isInterpolation { + if (isInterpolationCache == null) { + isInterpolationCache = (first.accept(const IsInterpolationVisitor()) || + second.accept(const IsInterpolationVisitor())); + } + return isInterpolationCache; + } + + /** + * Retrieve a single DartString that represents this entire juxtaposition + * of string literals. + * Should only be called if [isInterpolation] returns false. + */ + DartString get dartString { + if (isInterpolation) { + throw new SpannableAssertionFailure( + this, "Getting dartString on interpolation;"); + } + if (dartStringCache == null) { + DartString firstString = first.accept(const GetDartStringVisitor()); + DartString secondString = second.accept(const GetDartStringVisitor()); + if (firstString == null || secondString == null) { + return null; + } + dartStringCache = new DartString.concat(firstString, secondString); + } + return dartStringCache; + } + + accept(Visitor visitor) => visitor.visitStringJuxtaposition(this); + + void visitChildren(Visitor visitor) { + first.accept(visitor); + second.accept(visitor); + } + + Token getBeginToken() => first.getBeginToken(); + + Token getEndToken() => second.getEndToken(); +} + +class EmptyStatement extends Statement { + final Token semicolonToken; + + EmptyStatement(this.semicolonToken); + + EmptyStatement asEmptyStatement() => this; + + accept(Visitor visitor) => visitor.visitEmptyStatement(this); + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => semicolonToken; + + Token getEndToken() => semicolonToken; +} + +class LiteralMap extends Expression { + final NodeList typeArguments; + final NodeList entries; + + final Token constKeyword; + + LiteralMap(this.typeArguments, this.entries, this.constKeyword); + + bool isConst() => constKeyword != null; + + LiteralMap asLiteralMap() => this; + + accept(Visitor visitor) => visitor.visitLiteralMap(this); + + visitChildren(Visitor visitor) { + if (typeArguments != null) typeArguments.accept(visitor); + entries.accept(visitor); + } + + Token getBeginToken() { + if (constKeyword != null) return constKeyword; + return firstBeginToken(typeArguments, entries); + } + + Token getEndToken() => entries.getEndToken(); +} + +class LiteralMapEntry extends Node { + final Expression key; + final Expression value; + + final Token colonToken; + + LiteralMapEntry(this.key, this.colonToken, this.value); + + LiteralMapEntry asLiteralMapEntry() => this; + + accept(Visitor visitor) => visitor.visitLiteralMapEntry(this); + + visitChildren(Visitor visitor) { + key.accept(visitor); + value.accept(visitor); + } + + Token getBeginToken() => key.getBeginToken(); + + Token getEndToken() => value.getEndToken(); +} + +class NamedArgument extends Expression { + final Identifier name; + final Expression expression; + + final Token colonToken; + + NamedArgument(this.name, this.colonToken, this.expression); + + NamedArgument asNamedArgument() => this; + + accept(Visitor visitor) => visitor.visitNamedArgument(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + expression.accept(visitor); + } + + Token getBeginToken() => name.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class SwitchStatement extends Statement { + final ParenthesizedExpression parenthesizedExpression; + final NodeList cases; + + final Token switchKeyword; + + SwitchStatement(this.parenthesizedExpression, this.cases, + this.switchKeyword); + + SwitchStatement asSwitchStatement() => this; + + Expression get expression => parenthesizedExpression.expression; + + accept(Visitor visitor) => visitor.visitSwitchStatement(this); + + visitChildren(Visitor visitor) { + parenthesizedExpression.accept(visitor); + cases.accept(visitor); + } + + Token getBeginToken() => switchKeyword; + + Token getEndToken() => cases.getEndToken(); +} + +class CaseMatch extends Node { + final Token caseKeyword; + final Expression expression; + final Token colonToken; + CaseMatch(this.caseKeyword, this.expression, this.colonToken); + + CaseMatch asCaseMatch() => this; + Token getBeginToken() => caseKeyword; + Token getEndToken() => colonToken; + accept(Visitor visitor) => visitor.visitCaseMatch(this); + visitChildren(Visitor visitor) => expression.accept(visitor); +} + +class SwitchCase extends Node { + // The labels and case patterns are collected in [labelsAndCases]. + // The default keyword, if present, is collected in [defaultKeyword]. + // Any actual switch case must have at least one 'case' or 'default' + // clause. + // Notice: The labels and cases can occur interleaved in the source. + // They are separated here, since the order is irrelevant to the meaning + // of the switch. + + /** List of [Label] and [CaseMatch] nodes. */ + final NodeList labelsAndCases; + /** A "default" keyword token, if applicable. */ + final Token defaultKeyword; + /** List of statements, the body of the case. */ + final NodeList statements; + + final Token startToken; + + SwitchCase(this.labelsAndCases, this.defaultKeyword, + this.statements, this.startToken); + + SwitchCase asSwitchCase() => this; + + bool get isDefaultCase => defaultKeyword != null; + + bool isValidContinueTarget() => true; + + accept(Visitor visitor) => visitor.visitSwitchCase(this); + + visitChildren(Visitor visitor) { + labelsAndCases.accept(visitor); + statements.accept(visitor); + } + + Token getBeginToken() { + return startToken; + } + + Token getEndToken() { + if (statements.nodes.isEmpty) { + // All cases must have at least one expression or be the default. + if (defaultKeyword != null) { + // The colon after 'default'. + return defaultKeyword.next; + } + // The colon after the last expression. + return labelsAndCases.getEndToken(); + } else { + return statements.getEndToken(); + } + } +} + +abstract class GotoStatement extends Statement { + final Identifier target; + final Token keywordToken; + final Token semicolonToken; + + GotoStatement(this.target, this.keywordToken, this.semicolonToken); + + visitChildren(Visitor visitor) { + if (target != null) target.accept(visitor); + } + + Token getBeginToken() => keywordToken; + + Token getEndToken() => semicolonToken; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); +} + +class BreakStatement extends GotoStatement { + BreakStatement(Identifier target, Token keywordToken, Token semicolonToken) + : super(target, keywordToken, semicolonToken); + + BreakStatement asBreakStatement() => this; + + accept(Visitor visitor) => visitor.visitBreakStatement(this); +} + +class ContinueStatement extends GotoStatement { + ContinueStatement(Identifier target, Token keywordToken, Token semicolonToken) + : super(target, keywordToken, semicolonToken); + + ContinueStatement asContinueStatement() => this; + + accept(Visitor visitor) => visitor.visitContinueStatement(this); +} + +class ForIn extends Loop { + final Node declaredIdentifier; + final Expression expression; + + final Token forToken; + final Token inToken; + + ForIn(this.declaredIdentifier, this.expression, + Statement body, this.forToken, this.inToken) : super(body); + + Expression get condition => null; + + ForIn asForIn() => this; + + accept(Visitor visitor) => visitor.visitForIn(this); + + visitChildren(Visitor visitor) { + declaredIdentifier.accept(visitor); + expression.accept(visitor); + body.accept(visitor); + } + + Token getBeginToken() => forToken; + + Token getEndToken() => body.getEndToken(); +} + +class Label extends Node { + final Identifier identifier; + final Token colonToken; + + Label(this.identifier, this.colonToken); + + String slowToString() => identifier.source.slowToString(); + + Label asLabel() => this; + + accept(Visitor visitor) => visitor.visitLabel(this); + + void visitChildren(Visitor visitor) { + identifier.accept(visitor); + } + + Token getBeginToken() => identifier.token; + Token getEndToken() => colonToken; +} + +class LabeledStatement extends Statement { + final NodeList labels; + final Statement statement; + + LabeledStatement(this.labels, this.statement); + + LabeledStatement asLabeledStatement() => this; + + accept(Visitor visitor) => visitor.visitLabeledStatement(this); + + visitChildren(Visitor visitor) { + labels.accept(visitor); + statement.accept(visitor); + } + + Token getBeginToken() => labels.getBeginToken(); + + Token getEndToken() => statement.getEndToken(); + + bool isValidContinueTarget() => statement.isValidContinueTarget(); + + Node getBody() => statement; +} + +class ScriptTag extends Node { + final Identifier tag; + final StringNode argument; + final Identifier prefixIdentifier; + final StringNode prefix; + + final Token beginToken; + final Token endToken; + + ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix, + this.beginToken, this.endToken); + + bool isImport() => tag.source == const SourceString("import"); + bool isSource() => tag.source == const SourceString("source"); + bool isLibrary() => tag.source == const SourceString("library"); + + ScriptTag asScriptTag() => this; + + accept(Visitor visitor) => visitor.visitScriptTag(this); + + visitChildren(Visitor visitor) { + tag.accept(visitor); + argument.accept(visitor); + if (prefixIdentifier != null) prefixIdentifier.accept(visitor); + if (prefix != null) prefix.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() => endToken; + + LibraryTag toLibraryTag() { + if (isImport()) { + Identifier prefixNode; + if (prefix != null) { + SourceString source = prefix.dartString.source; + Token prefixToken = prefix.getBeginToken(); + Token token = new StringToken.fromSource(IDENTIFIER_INFO, source, + prefixToken.charOffset); + token.next = prefixToken.next; + prefixNode = new Identifier(token); + } + return new Import(tag.token, argument, prefixNode, null, null); + } else if (isLibrary()) { + return new LibraryName(tag.token, argument, null); + } else if (isSource()) { + return new Part(tag.token, argument, null); + } else { + throw 'Unknown script tag ${tag.token.slowToString()}'; + } + } +} + +abstract class LibraryTag extends Node { + final Link<MetadataAnnotation> metadata; + + LibraryTag(this.metadata); + + bool get isLibraryName => false; + bool get isImport => false; + bool get isExport => false; + bool get isPart => false; + bool get isPartOf => false; +} + +class LibraryName extends LibraryTag { + final Expression name; + + final Token libraryKeyword; + + LibraryName(this.libraryKeyword, + this.name, + Link<MetadataAnnotation> metadata) + : super(metadata); + + bool get isLibraryName => true; + + LibraryName asLibraryName() => this; + + accept(Visitor visitor) => visitor.visitLibraryName(this); + + visitChildren(Visitor visitor) => name.accept(visitor); + + Token getBeginToken() => libraryKeyword; + + Token getEndToken() => name.getEndToken().next; +} + +/** + * This tag describes a dependency between one library and the exported + * identifiers of another library. The other library is specified by the [uri]. + * Combinators filter away some identifiers from the other library. + */ +abstract class LibraryDependency extends LibraryTag { + final StringNode uri; + final NodeList combinators; + + LibraryDependency(this.uri, + this.combinators, + Link<MetadataAnnotation> metadata) + : super(metadata); +} + +/** + * An [:import:] library tag. + * + * An import tag is dependency on another library where the exported identifiers + * are put into the import scope of the importing library. The import scope is + * only visible inside the library. + */ +class Import extends LibraryDependency { + final Identifier prefix; + final Token importKeyword; + + Import(this.importKeyword, StringNode uri, + this.prefix, NodeList combinators, + Link<MetadataAnnotation> metadata) + : super(uri, combinators, metadata); + + bool get isImport => true; + + Import asImport() => this; + + Token get asKeyword => prefix == null ? null : uri.getEndToken().next; + + accept(Visitor visitor) => visitor.visitImport(this); + + visitChildren(Visitor visitor) { + uri.accept(visitor); + if (prefix != null) prefix.accept(visitor); + if (combinators != null) combinators.accept(visitor); + } + + Token getBeginToken() => importKeyword; + + Token getEndToken() { + if (combinators != null) return combinators.getEndToken().next; + if (prefix != null) return prefix.getEndToken().next; + return uri.getEndToken().next; + } +} + +/** + * An [:export:] library tag. + * + * An export tag is dependency on another library where the exported identifiers + * are put into the export scope of the exporting library. The export scope is + * not visible inside the library. + */ +class Export extends LibraryDependency { + final Token exportKeyword; + + Export(this.exportKeyword, + StringNode uri, + NodeList combinators, + Link<MetadataAnnotation> metadata) + : super(uri, combinators, metadata); + + bool get isExport => true; + + Export asExport() => this; + + accept(Visitor visitor) => visitor.visitExport(this); + + visitChildren(Visitor visitor) { + uri.accept(visitor); + if (combinators != null) combinators.accept(visitor); + } + + Token getBeginToken() => exportKeyword; + + Token getEndToken() { + if (combinators != null) return combinators.getEndToken().next; + return uri.getEndToken().next; + } +} + +class Part extends LibraryTag { + final StringNode uri; + + final Token partKeyword; + + Part(this.partKeyword, this.uri, Link<MetadataAnnotation> metadata) + : super(metadata); + + bool get isPart => true; + + Part asPart() => this; + + accept(Visitor visitor) => visitor.visitPart(this); + + visitChildren(Visitor visitor) => uri.accept(visitor); + + Token getBeginToken() => partKeyword; + + Token getEndToken() => uri.getEndToken().next; +} + +class PartOf extends Node { + final Expression name; + + final Token partKeyword; + + final Link<MetadataAnnotation> metadata; + + PartOf(this.partKeyword, this.name, this.metadata); + + Token get ofKeyword => partKeyword.next; + + bool get isPartOf => true; + + PartOf asPartOf() => this; + + accept(Visitor visitor) => visitor.visitPartOf(this); + + visitChildren(Visitor visitor) => name.accept(visitor); + + Token getBeginToken() => partKeyword; + + Token getEndToken() => name.getEndToken().next; +} + +class Combinator extends Node { + final NodeList identifiers; + + final Token keywordToken; + + Combinator(this.identifiers, this.keywordToken); + + bool get isShow => identical(keywordToken.stringValue, 'show'); + + bool get isHide => identical(keywordToken.stringValue, 'hide'); + + Combinator asCombinator() => this; + + accept(Visitor visitor) => visitor.visitCombinator(this); + + visitChildren(Visitor visitor) => identifiers.accept(visitor); + + Token getBeginToken() => keywordToken; + + Token getEndToken() => identifiers.getEndToken(); +} + +class Typedef extends Node { + final TypeAnnotation returnType; + final Identifier name; + final NodeList typeParameters; + final NodeList formals; + + final Token typedefKeyword; + final Token endToken; + + Typedef(this.returnType, this.name, this.typeParameters, this.formals, + this.typedefKeyword, this.endToken); + + Typedef asTypedef() => this; + + accept(Visitor visitor) => visitor.visitTypedef(this); + + visitChildren(Visitor visitor) { + if (returnType != null) returnType.accept(visitor); + name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + formals.accept(visitor); + } + + Token getBeginToken() => typedefKeyword; + + Token getEndToken() => endToken; +} + +class TryStatement extends Statement { + final Block tryBlock; + final NodeList catchBlocks; + final Block finallyBlock; + + final Token tryKeyword; + final Token finallyKeyword; + + TryStatement(this.tryBlock, this.catchBlocks, this.finallyBlock, + this.tryKeyword, this.finallyKeyword); + + TryStatement asTryStatement() => this; + + accept(Visitor visitor) => visitor.visitTryStatement(this); + + visitChildren(Visitor visitor) { + tryBlock.accept(visitor); + catchBlocks.accept(visitor); + if (finallyBlock != null) finallyBlock.accept(visitor); + } + + Token getBeginToken() => tryKeyword; + + Token getEndToken() { + if (finallyBlock != null) return finallyBlock.getEndToken(); + if (!catchBlocks.isEmpty) return catchBlocks.getEndToken(); + return tryBlock.getEndToken(); + } +} + +class Cascade extends Expression { + final Expression expression; + Cascade(this.expression); + + Cascade asCascade() => this; + accept(Visitor visitor) => visitor.visitCascade(this); + + void visitChildren(Visitor visitor) { + expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class CascadeReceiver extends Expression { + final Expression expression; + final Token cascadeOperator; + CascadeReceiver(this.expression, this.cascadeOperator); + + CascadeReceiver asCascadeReceiver() => this; + accept(Visitor visitor) => visitor.visitCascadeReceiver(this); + + void visitChildren(Visitor visitor) { + expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class CatchBlock extends Node { + final TypeAnnotation type; + final NodeList formals; + final Block block; + + final Token onKeyword; + final Token catchKeyword; + + CatchBlock(this.type, this.formals, this.block, + this.onKeyword, this.catchKeyword); + + CatchBlock asCatchBlock() => this; + + accept(Visitor visitor) => visitor.visitCatchBlock(this); + + Node get exception { + if (formals == null || formals.nodes.isEmpty) return null; + VariableDefinitions declarations = formals.nodes.head; + return declarations.definitions.nodes.head; + } + + Node get trace { + if (formals == null || formals.nodes.isEmpty) return null; + Link<Node> declarations = formals.nodes.tail; + if (declarations.isEmpty) return null; + VariableDefinitions head = declarations.head; + return head.definitions.nodes.head; + } + + visitChildren(Visitor visitor) { + if (type != null) type.accept(visitor); + if (formals != null) formals.accept(visitor); + block.accept(visitor); + } + + Token getBeginToken() => onKeyword != null ? onKeyword : catchKeyword; + + Token getEndToken() => block.getEndToken(); +} + +class Initializers { + static bool isSuperConstructorCall(Send node) { + return (node.receiver == null && + node.selector.asIdentifier() != null && + node.selector.asIdentifier().isSuper()) || + (node.receiver != null && + node.receiver.asIdentifier() != null && + node.receiver.asIdentifier().isSuper() && + node.selector.asIdentifier() != null); + } + + static bool isConstructorRedirect(Send node) { + return (node.receiver == null && + node.selector.asIdentifier() != null && + node.selector.asIdentifier().isThis()) || + (node.receiver != null && + node.receiver.asIdentifier() != null && + node.receiver.asIdentifier().isThis() && + node.selector.asIdentifier() != null); + } +} + +class GetDartStringVisitor extends Visitor<DartString> { + const GetDartStringVisitor(); + DartString visitNode(Node node) => null; + DartString visitStringJuxtaposition(StringJuxtaposition node) + => node.dartString; + DartString visitLiteralString(LiteralString node) => node.dartString; +} + +class IsInterpolationVisitor extends Visitor<bool> { + const IsInterpolationVisitor(); + bool visitNode(Node node) => false; + bool visitStringInterpolation(StringInterpolation node) => true; + bool visitStringJuxtaposition(StringJuxtaposition node) + => node.isInterpolation; +} + +/** + * If the given node is a send set, it visits its initializer (first + * argument). + * + * TODO(ahe): This method is controversial, the team needs to discuss + * if top-level methods are acceptable and what naming conventions to + * use. + */ +initializerDo(Node node, f(Node node)) { + SendSet send = node.asSendSet(); + if (send != null) return f(send.arguments.head); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart new file mode 100644 index 0000000..129c637 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart
@@ -0,0 +1,480 @@ +// 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. + +part of tree; + +/** + * Pretty-prints Node tree in XML-like format. + * + * TODO(smok): Add main() to run from command-line to print out tree for given + * .dart file. + */ +class PrettyPrinter implements Visitor { + + /** String used to represent one level of indent. */ + static const String INDENT = " "; + + StringBuffer sb; + Link<String> tagStack; + + PrettyPrinter() : + sb = new StringBuffer(), + tagStack = const Link<String>(); + + void pushTag(String tag) { + tagStack = tagStack.prepend(tag); + } + + String popTag() { + assert(!tagStack.isEmpty); + String tag = tagStack.head; + tagStack = tagStack.tail; + return tag; + } + + /** + * Adds given string to result string. + */ + void add(SourceString string) { + string.printOn(sb); + } + + void addBeginAndEndTokensToParams(Node node, Map params) { + params['getBeginToken'] = tokenToStringOrNull(node.getBeginToken()); + params['getEndToken'] = tokenToStringOrNull(node.getEndToken()); + } + + /** + * Adds given node type to result string. + * The method "opens" the node, meaning that all output after calling + * this method and before calling closeNode() will represent contents + * of given node. + */ + void openNode(Node node, String type, [Map params]) { + if (params == null) params = new Map(); + addCurrentIndent(); + sb.add("<"); + addBeginAndEndTokensToParams(node, params); + addTypeWithParams(type, params); + sb.add(">\n"); + pushTag(type); + } + + /** + * Adds given node to result string. + */ + void openAndCloseNode(Node node, String type, [Map params]) { + if (params == null) params = new Map(); + addCurrentIndent(); + sb.add("<"); + addBeginAndEndTokensToParams(node, params); + addTypeWithParams(type, params); + sb.add("/>\n"); + } + + /** + * Closes current node type. + */ + void closeNode() { + String tag = popTag(); + addCurrentIndent(); + sb.add("</"); + addTypeWithParams(tag); + sb.add(">\n"); + } + + void addTypeWithParams(String type, [Map params]) { + if (params == null) params = new Map(); + sb.add("${type}"); + params.forEach((k, v) { + String value; + if (v != null) { + value = v + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', "'"); + } else { + value = "[null]"; + } + sb.add(' $k="$value"'); + }); + } + + void addCurrentIndent() { + tagStack.forEach((_) { sb.add(INDENT); }); + } + + /** + * Pretty-prints given node tree into string. + */ + static String prettyPrint(Node node) { + var p = new PrettyPrinter(); + node.accept(p); + return p.sb.toString(); + } + + visitNodeWithChildren(Node node, String type) { + openNode(node, type); + node.visitChildren(this); + closeNode(); + } + + visitBlock(Block node) { + visitNodeWithChildren(node, "Block"); + } + + visitBreakStatement(BreakStatement node) { + visitNodeWithChildren(node, "BreakStatement"); + } + + visitCascade(Cascade node) { + visitNodeWithChildren(node, "Cascade"); + } + + visitCascadeReceiver(CascadeReceiver node) { + visitNodeWithChildren(node, "CascadeReceiver"); + } + + visitCaseMatch(CaseMatch node) { + visitNodeWithChildren(node, "CaseMatch"); + } + + visitCatchBlock(CatchBlock node) { + visitNodeWithChildren(node, "CatchBlock"); + } + + visitClassNode(ClassNode node) { + openNode(node, "ClassNode", { + "extendsKeyword" : tokenToStringOrNull(node.extendsKeyword) + }); + visitChildNode(node.name, "name"); + visitChildNode(node.superclass, "superclass"); + visitChildNode(node.interfaces, "interfaces"); + visitChildNode(node.typeParameters, "typeParameters"); + visitChildNode(node.defaultClause, "defaultClause"); + closeNode(); + } + + visitConditional(Conditional node) { + visitNodeWithChildren(node, "Conditional"); + } + + visitContinueStatement(ContinueStatement node) { + visitNodeWithChildren(node, "ContinueStatement"); + } + + visitDoWhile(DoWhile node) { + visitNodeWithChildren(node, "DoWhile"); + } + + visitEmptyStatement(EmptyStatement node) { + visitNodeWithChildren(node, "EmptyStatement"); + } + + visitExpressionStatement(ExpressionStatement node) { + visitNodeWithChildren(node, "ExpressionStatement"); + } + + visitFor(For node) { + visitNodeWithChildren(node, "For"); + } + + visitForIn(ForIn node) { + visitNodeWithChildren(node, "ForIn"); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visitNodeWithChildren(node, "FunctionDeclaration"); + } + + visitFunctionExpression(FunctionExpression node) { + openNode(node, "FunctionExpression", { + "getOrSet" : tokenToStringOrNull(node.getOrSet) + }); + visitChildNode(node.modifiers, "modifiers"); + visitChildNode(node.returnType, "returnType"); + visitChildNode(node.name, "name"); + visitChildNode(node.parameters, "parameters"); + visitChildNode(node.initializers, "initializers"); + visitChildNode(node.body, "body"); + closeNode(); + } + + visitIdentifier(Identifier node) { + openAndCloseNode(node, "Identifier", {"token" : node.token.slowToString()}); + } + + visitIf(If node) { + visitNodeWithChildren(node, "If"); + } + + visitLabel(Label node) { + visitNodeWithChildren(node, "Label"); + } + + visitLabeledStatement(LabeledStatement node) { + visitNodeWithChildren(node, "LabeledStatement"); + } + + // Custom. + printLiteral(Literal node, String type) { + openAndCloseNode(node, type, {"value" : node.value.toString()}); + } + + visitLiteralBool(LiteralBool node) { + printLiteral(node, "LiteralBool"); + } + + visitLiteralDouble(LiteralDouble node) { + printLiteral(node, "LiteralDouble"); + } + + visitLiteralInt(LiteralInt node) { + printLiteral(node, "LiteralInt"); + } + + /** Returns token string value or [null] if token is [null]. */ + tokenToStringOrNull(Token token) => token == null ? null : token.stringValue; + + visitLiteralList(LiteralList node) { + openNode(node, "LiteralList", { + "constKeyword" : tokenToStringOrNull(node.constKeyword) + }); + visitChildNode(node.typeArguments, "typeArguments"); + visitChildNode(node.elements, "elements"); + closeNode(); + } + + visitLiteralMap(LiteralMap node) { + visitNodeWithChildren(node, "LiteralMap"); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visitNodeWithChildren(node, "LiteralMapEntry"); + } + + visitLiteralNull(LiteralNull node) { + printLiteral(node, "LiteralNull"); + } + + visitLiteralString(LiteralString node) { + openAndCloseNode(node, "LiteralString", + {"value" : node.token.slowToString()}); + } + + visitMixinApplication(MixinApplication node) { + visitNodeWithChildren(node, "MixinApplication"); + } + + visitModifiers(Modifiers node) { + visitNodeWithChildren(node, "Modifiers"); + } + + visitNamedArgument(NamedArgument node) { + visitNodeWithChildren(node, "NamedArgument"); + } + + visitNamedMixinApplication(NamedMixinApplication node) { + visitNodeWithChildren(node, "NamedMixinApplication"); + } + + visitNewExpression(NewExpression node) { + visitNodeWithChildren(node, "NewExpression"); + } + + visitNodeList(NodeList node) { + var params = { + "delimiter" : + node.delimiter != null ? node.delimiter.stringValue : null + }; + if (node.nodes.toList().length == 0) { + openAndCloseNode(node, "NodeList", params); + } else { + openNode(node, "NodeList", params); + node.visitChildren(this); + closeNode(); + } + } + + visitOperator(Operator node) { + openAndCloseNode(node, "Operator", {"value" : node.token.slowToString()}); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + visitNodeWithChildren(node, "ParenthesizedExpression"); + } + + visitReturn(Return node) { + openNode(node, "Return"); + visitChildNode(node.expression, "expression"); + closeNode(); + } + + visitScriptTag(ScriptTag node) { + visitNodeWithChildren(node, "ScriptTag"); + } + + visitChildNode(Node node, String fieldName) { + if (node == null) return; + addCurrentIndent(); + sb.add("<$fieldName>\n"); + pushTag(fieldName); + node.accept(this); + popTag(); + addCurrentIndent(); + sb.add("</$fieldName>\n"); + } + + openSendNodeWithFields(Send node, String type) { + openNode(node, type, { + "isPrefix" : "${node.isPrefix}", + "isPostfix" : "${node.isPostfix}", + "isIndex" : "${node.isIndex}" + }); + visitChildNode(node.receiver, "receiver"); + visitChildNode(node.selector, "selector"); + visitChildNode(node.argumentsNode, "argumentsNode"); + } + + visitSend(Send node) { + openSendNodeWithFields(node, "Send"); + closeNode(); + } + + visitSendSet(SendSet node) { + openSendNodeWithFields(node, "SendSet"); + visitChildNode(node.assignmentOperator, "assignmentOperator"); + closeNode(); + } + + visitStringInterpolation(StringInterpolation node) { + visitNodeWithChildren(node, "StringInterpolation"); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + visitNodeWithChildren(node, "StringInterpolationPart"); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + visitNodeWithChildren(node, "StringJuxtaposition"); + } + + visitSwitchCase(SwitchCase node) { + visitNodeWithChildren(node, "SwitchCase"); + } + + visitSwitchStatement(SwitchStatement node) { + visitNodeWithChildren(node, "SwitchStatement"); + } + + visitThrow(Throw node) { + visitNodeWithChildren(node, "Throw"); + } + + visitTryStatement(TryStatement node) { + visitNodeWithChildren(node, "TryStatement"); + } + + visitTypeAnnotation(TypeAnnotation node) { + openNode(node, "TypeAnnotation"); + visitChildNode(node.typeName, "typeName"); + visitChildNode(node.typeArguments, "typeArguments"); + closeNode(); + } + + visitTypedef(Typedef node) { + visitNodeWithChildren(node, "Typedef"); + } + + visitTypeVariable(TypeVariable node) { + openNode(node, "TypeVariable"); + visitChildNode(node.name, "name"); + visitChildNode(node.bound, "bound"); + closeNode(); + } + + visitVariableDefinitions(VariableDefinitions node) { + openNode(node, "VariableDefinitions"); + visitChildNode(node.type, "type"); + visitChildNode(node.modifiers, "modifiers"); + visitChildNode(node.definitions, "definitions"); + closeNode(); + } + + visitWhile(While node) { + visitNodeWithChildren(node, "While"); + } + + visitNode(Node node) { + unimplemented('visitNode', node: node); + } + + visitCombinator(Combinator node) { + unimplemented('visitNode', node: node); + } + + visitExport(Export node) { + unimplemented('visitNode', node: node); + } + + visitExpression(Expression node) { + unimplemented('visitNode', node: node); + } + + visitGotoStatement(GotoStatement node) { + unimplemented('visitNode', node: node); + } + + visitImport(Import node) { + unimplemented('visitNode', node: node); + } + + visitLibraryDependency(Node node) { + unimplemented('visitNode', node: node); + } + + visitLibraryName(LibraryName node) { + unimplemented('visitNode', node: node); + } + + visitLibraryTag(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + visitLiteral(Literal node) { + unimplemented('visitNode', node: node); + } + + visitLoop(Loop node) { + unimplemented('visitNode', node: node); + } + + visitPart(Part node) { + unimplemented('visitNode', node: node); + } + + visitPartOf(PartOf node) { + unimplemented('visitNode', node: node); + } + + visitPostfix(Postfix node) { + unimplemented('visitNode', node: node); + } + + visitPrefix(Prefix node) { + unimplemented('visitNode', node: node); + } + + visitStatement(Statement node) { + unimplemented('visitNode', node: node); + } + + visitStringNode(StringNode node) { + unimplemented('visitNode', node: node); + } + + unimplemented(String message, {Node node}) { + throw message; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart new file mode 100644 index 0000000..3faa36c --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart
@@ -0,0 +1,22 @@ +// 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. + +library tree; + +import 'dart:math'; +import 'dart:collection'; + +import '../scanner/scannerlib.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +import '../resolution/secret_tree_element.dart' show TreeElementMixin; + +import '../elements/elements.dart' show MetadataAnnotation; + +part 'dartstring.dart'; +part 'nodes.dart'; +part 'prettyprint.dart'; +part 'unparser.dart'; +part 'visitors.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart new file mode 100644 index 0000000..9c20f2d --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart
@@ -0,0 +1,627 @@ +// 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. + +part of tree; + +String unparse(Node node) { + Unparser unparser = new Unparser(); + unparser.unparse(node); + return unparser.result; +} + +class Unparser implements Visitor { + final StringBuffer sb; + + String get result => sb.toString(); + + Unparser() : sb = new StringBuffer(); + + void add(SourceString string) { + string.printOn(sb); + } + + void addToken(Token token) { + if (token == null) return; + add(token.value); + if (identical(token.kind, KEYWORD_TOKEN) + || identical(token.kind, IDENTIFIER_TOKEN)) { + sb.add(' '); + } + } + + unparse(Node node) { visit(node); } + + visit(Node node) { + if (node != null) node.accept(this); + } + + visitBlock(Block node) { + visit(node.statements); + } + + visitCascade(Cascade node) { + visit(node.expression); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + } + + unparseClassWithBody(ClassNode node, Iterable<Node> members) { + addToken(node.beginToken); + if (node.beginToken.stringValue == 'abstract') { + addToken(node.beginToken.next); + } + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + if (node.extendsKeyword != null) { + sb.add(' '); + addToken(node.extendsKeyword); + visit(node.superclass); + } + if (!node.interfaces.isEmpty) { + sb.add(' '); + visit(node.interfaces); + } + if (node.defaultClause != null) { + sb.add(' default '); + visit(node.defaultClause); + } + sb.add('{'); + for (final member in members) { + visit(member); + } + sb.add('}'); + } + + visitClassNode(ClassNode node) { + unparseClassWithBody(node, node.body.nodes); + } + + visitMixinApplication(MixinApplication node) { + visit(node.superclass); + sb.add(' with '); + visit(node.mixins); + } + + visitNamedMixinApplication(NamedMixinApplication node) { + sb.add('typedef '); + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + sb.add(' = '); + if (!node.modifiers.nodes.isEmpty) { + visit(node.modifiers); + sb.add(' '); + } + visit(node.mixinApplication); + if (node.interfaces != null) { + sb.add(' implements '); + visit(node.interfaces); + } + sb.add(';'); + } + + visitConditional(Conditional node) { + visit(node.condition); + add(node.questionToken.value); + visit(node.thenExpression); + add(node.colonToken.value); + visit(node.elseExpression); + } + + visitExpressionStatement(ExpressionStatement node) { + visit(node.expression); + add(node.endToken.value); + } + + visitFor(For node) { + add(node.forToken.value); + sb.add('('); + visit(node.initializer); + sb.add(';'); + visit(node.conditionStatement); + visit(node.update); + sb.add(')'); + visit(node.body); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visit(node.function); + } + + void unparseFunctionName(Node name) { + // TODO(antonm): that's a workaround as currently FunctionExpression + // names are modelled with Send and it emits operator[] as only + // operator, without [] which are expected to be emitted with + // arguments. + if (name is Send) { + Send send = name; + assert(send is !SendSet); + if (!send.isOperator) { + // Looks like a factory method. + visit(send.receiver); + sb.add('.'); + } else { + visit(send.receiver); + Identifier identifier = send.selector.asIdentifier(); + if (identical(identifier.token.kind, KEYWORD_TOKEN)) { + sb.add(' '); + } else if (identifier.source == const SourceString('negate')) { + // TODO(ahe): Remove special case for negate. + sb.add(' '); + } + } + visit(send.selector); + } else { + visit(name); + } + } + + visitFunctionExpression(FunctionExpression node) { + if (!node.modifiers.nodes.isEmpty) { + visit(node.modifiers); + sb.add(' '); + } + if (node.returnType != null) { + visit(node.returnType); + sb.add(' '); + } + if (node.getOrSet != null) { + add(node.getOrSet.value); + sb.add(' '); + } + unparseFunctionName(node.name); + visit(node.parameters); + visit(node.initializers); + visit(node.body); + } + + visitIdentifier(Identifier node) { + add(node.token.value); + } + + visitIf(If node) { + add(node.ifToken.value); + visit(node.condition); + visit(node.thenPart); + if (node.hasElsePart) { + add(node.elseToken.value); + if (node.elsePart is !Block) sb.add(' '); + visit(node.elsePart); + } + } + + visitLiteralBool(LiteralBool node) { + add(node.token.value); + } + + visitLiteralDouble(LiteralDouble node) { + add(node.token.value); + // -Lit is represented as a send. + if (node.token.kind == PLUS_TOKEN) add(node.token.next.value); + } + + visitLiteralInt(LiteralInt node) { + add(node.token.value); + // -Lit is represented as a send. + if (node.token.kind == PLUS_TOKEN) add(node.token.next.value); + } + + visitLiteralString(LiteralString node) { + add(node.token.value); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + visit(node.first); + sb.add(" "); + visit(node.second); + } + + visitLiteralNull(LiteralNull node) { + add(node.token.value); + } + + visitNewExpression(NewExpression node) { + addToken(node.newToken); + visit(node.send); + } + + visitLiteralList(LiteralList node) { + if (node.constKeyword != null) add(node.constKeyword.value); + visit(node.typeArguments); + visit(node.elements); + // If list is empty, emit space after [] to disambiguate cases like []==[]. + if (node.elements.isEmpty) sb.add(' '); + } + + visitModifiers(Modifiers node) => node.visitChildren(this); + + /** + * Unparses given NodeList starting from specific node. + */ + unparseNodeListFrom(NodeList node, Link<Node> from) { + if (from.isEmpty) return; + String delimiter = (node.delimiter == null) ? "" : "${node.delimiter}"; + visit(from.head); + for (Link link = from.tail; !link.isEmpty; link = link.tail) { + sb.add(delimiter); + visit(link.head); + } + } + + visitNodeList(NodeList node) { + addToken(node.beginToken); + if (node.nodes != null) { + unparseNodeListFrom(node, node.nodes); + } + if (node.endToken != null) add(node.endToken.value); + } + + visitOperator(Operator node) { + visitIdentifier(node); + } + + visitReturn(Return node) { + if (node.isRedirectingFactoryBody) { + sb.add(' '); + } + add(node.beginToken.value); + if (node.hasExpression && node.beginToken.stringValue != '=>') { + sb.add(' '); + } + visit(node.expression); + if (node.endToken != null) add(node.endToken.value); + } + + unparseSendReceiver(Send node, {bool spacesNeeded: false}) { + if (node.receiver == null) return; + visit(node.receiver); + CascadeReceiver asCascadeReceiver = node.receiver.asCascadeReceiver(); + if (asCascadeReceiver != null) { + add(asCascadeReceiver.cascadeOperator.value); + } else if (node.selector.asOperator() == null) { + sb.add('.'); + } else if (spacesNeeded) { + sb.add(' '); + } + } + + visitSend(Send node) { + Operator op = node.selector.asOperator(); + String opString = op != null ? op.source.stringValue : null; + bool spacesNeeded = identical(opString, 'is') || identical(opString, 'as'); + + if (node.isPrefix) visit(node.selector); + unparseSendReceiver(node, spacesNeeded: spacesNeeded); + if (!node.isPrefix && !node.isIndex) visit(node.selector); + if (spacesNeeded) sb.add(' '); + // Also add a space for sequences like x + +1 and y - -y. + // TODO(ahe): remove case for '+' when we drop the support for it. + if (node.argumentsNode != null && (identical(opString, '-') + || identical(opString, '+'))) { + Token beginToken = node.argumentsNode.getBeginToken(); + if (beginToken != null && identical(beginToken.stringValue, opString)) { + sb.add(' '); + } + } + visit(node.argumentsNode); + } + + visitSendSet(SendSet node) { + if (node.isPrefix) { + sb.add(' '); + visit(node.assignmentOperator); + } + unparseSendReceiver(node); + if (node.isIndex) { + sb.add('['); + visit(node.arguments.head); + sb.add(']'); + if (!node.isPrefix) visit(node.assignmentOperator); + unparseNodeListFrom(node.argumentsNode, node.argumentsNode.nodes.tail); + } else { + visit(node.selector); + if (!node.isPrefix) { + visit(node.assignmentOperator); + if (node.assignmentOperator.source.slowToString() != '=') sb.add(' '); + } + visit(node.argumentsNode); + } + } + + visitThrow(Throw node) { + add(node.throwToken.value); + if (node.expression != null) { + sb.add(' '); + visit(node.expression); + } + node.endToken.value.printOn(sb); + } + + visitTypeAnnotation(TypeAnnotation node) { + visit(node.typeName); + visit(node.typeArguments); + } + + visitTypeVariable(TypeVariable node) { + visit(node.name); + if (node.bound != null) { + sb.add(' extends '); + visit(node.bound); + } + } + + visitVariableDefinitions(VariableDefinitions node) { + visit(node.modifiers); + if (!node.modifiers.nodes.isEmpty) { + sb.add(' '); + } + if (node.type != null) { + visit(node.type); + sb.add(' '); + } + visit(node.definitions); + } + + visitDoWhile(DoWhile node) { + add(node.doKeyword.value); + if (node.body is !Block) sb.add(' '); + visit(node.body); + add(node.whileKeyword.value); + visit(node.condition); + sb.add(node.endToken.value); + } + + visitWhile(While node) { + addToken(node.whileKeyword); + visit(node.condition); + visit(node.body); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + add(node.getBeginToken().value); + visit(node.expression); + add(node.getEndToken().value); + } + + visitStringInterpolation(StringInterpolation node) { + visit(node.string); + visit(node.parts); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + sb.add('\${'); // TODO(ahe): Preserve the real tokens. + visit(node.expression); + sb.add('}'); + visit(node.string); + } + + visitEmptyStatement(EmptyStatement node) { + add(node.semicolonToken.value); + } + + visitGotoStatement(GotoStatement node) { + add(node.keywordToken.value); + if (node.target != null) { + sb.add(' '); + visit(node.target); + } + add(node.semicolonToken.value); + } + + visitBreakStatement(BreakStatement node) { + visitGotoStatement(node); + } + + visitContinueStatement(ContinueStatement node) { + visitGotoStatement(node); + } + + visitForIn(ForIn node) { + add(node.forToken.value); + sb.add('('); + visit(node.declaredIdentifier); + sb.add(' '); + addToken(node.inToken); + visit(node.expression); + sb.add(')'); + visit(node.body); + } + + visitLabel(Label node) { + visit(node.identifier); + add(node.colonToken.value); + } + + visitLabeledStatement(LabeledStatement node) { + visit(node.labels); + visit(node.statement); + } + + visitLiteralMap(LiteralMap node) { + if (node.constKeyword != null) add(node.constKeyword.value); + if (node.typeArguments != null) visit(node.typeArguments); + visit(node.entries); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visit(node.key); + add(node.colonToken.value); + visit(node.value); + } + + visitNamedArgument(NamedArgument node) { + visit(node.name); + add(node.colonToken.value); + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + addToken(node.switchKeyword); + visit(node.parenthesizedExpression); + visit(node.cases); + } + + visitSwitchCase(SwitchCase node) { + visit(node.labelsAndCases); + if (node.isDefaultCase) { + sb.add('default:'); + } + visit(node.statements); + } + + unparseImportTag(String uri, [String prefix]) { + final suffix = prefix == null ? '' : ' as $prefix'; + sb.add('import "$uri"$suffix;'); + } + + visitScriptTag(ScriptTag node) { + add(node.beginToken.value); + visit(node.tag); + sb.add('('); + visit(node.argument); + if (node.prefixIdentifier != null) { + visit(node.prefixIdentifier); + sb.add(':'); + visit(node.prefix); + } + sb.add(')'); + add(node.endToken.value); + } + + visitTryStatement(TryStatement node) { + addToken(node.tryKeyword); + visit(node.tryBlock); + visit(node.catchBlocks); + if (node.finallyKeyword != null) { + addToken(node.finallyKeyword); + visit(node.finallyBlock); + } + } + + visitCaseMatch(CaseMatch node) { + add(node.caseKeyword.value); + sb.add(" "); + visit(node.expression); + add(node.colonToken.value); + } + + visitCatchBlock(CatchBlock node) { + addToken(node.onKeyword); + if (node.type != null) { + visit(node.type); + sb.add(' '); + } + addToken(node.catchKeyword); + visit(node.formals); + visit(node.block); + } + + visitTypedef(Typedef node) { + addToken(node.typedefKeyword); + if (node.returnType != null) { + visit(node.returnType); + sb.add(' '); + } + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + visit(node.formals); + add(node.endToken.value); + } + + visitLibraryName(LibraryName node) { + addToken(node.libraryKeyword); + node.visitChildren(this); + add(node.getEndToken().value); + } + + visitImport(Import node) { + addToken(node.importKeyword); + visit(node.uri); + if (node.prefix != null) { + sb.add(' '); + addToken(node.asKeyword); + visit(node.prefix); + } + if (node.combinators != null) { + sb.add(' '); + visit(node.combinators); + } + add(node.getEndToken().value); + } + + visitExport(Export node) { + addToken(node.exportKeyword); + visit(node.uri); + if (node.combinators != null) { + sb.add(' '); + visit(node.combinators); + } + add(node.getEndToken().value); + } + + visitPart(Part node) { + addToken(node.partKeyword); + visit(node.uri); + add(node.getEndToken().value); + } + + visitPartOf(PartOf node) { + addToken(node.partKeyword); + addToken(node.ofKeyword); + visit(node.name); + add(node.getEndToken().value); + } + + visitCombinator(Combinator node) { + addToken(node.keywordToken); + visit(node.identifiers); + } + + visitNode(Node node) { + throw 'internal error'; // Should not be called. + } + + visitExpression(Expression node) { + throw 'internal error'; // Should not be called. + } + + visitLibraryTag(LibraryTag node) { + throw 'internal error'; // Should not be called. + } + + visitLibraryDependency(Node node) { + throw 'internal error'; // Should not be called. + } + + visitLiteral(Literal node) { + throw 'internal error'; // Should not be called. + } + + visitLoop(Loop node) { + throw 'internal error'; // Should not be called. + } + + visitPostfix(Postfix node) { + throw 'internal error'; // Should not be called. + } + + visitPrefix(Prefix node) { + throw 'internal error'; // Should not be called. + } + + visitStatement(Statement node) { + throw 'internal error'; // Should not be called. + } + + visitStringNode(StringNode node) { + throw 'internal error'; // Should not be called. + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart b/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart new file mode 100644 index 0000000..9096cff --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart
@@ -0,0 +1,21 @@ +// Copyright (c) 2011, 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. + +part of tree; + +/** + * This visitor takes another visitor and applies it to every + * node in the tree. There is currently no way to control the + * traversal. + */ +class TraversingVisitor extends Visitor { + final Visitor visitor; + + TraversingVisitor(Visitor this.visitor); + + visitNode(Node node) { + node.accept(visitor); + node.visitChildren(this); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart b/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart new file mode 100644 index 0000000..6a3b93f --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart
@@ -0,0 +1,78 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class TreeValidatorTask extends CompilerTask { + TreeValidatorTask(Compiler compiler) : super(compiler); + + void validate(Node tree) { + assert(check(tree)); + } + + bool check(Node tree) { + List<InvalidNodeError> errors = []; + void report(node, message) { + final error = new InvalidNodeError(node, message); + errors.add(error); + compiler.reportWarning(node, message); + }; + final validator = new ValidatorVisitor(report); + tree.accept(new TraversingVisitor(validator)); + + return errors.isEmpty; + } +} + +class ValidatorVisitor extends Visitor { + final Function reportInvalidNode; + + ValidatorVisitor(Function this.reportInvalidNode); + + expect(Node node, bool test, [message]) { + if (!test) reportInvalidNode(node, message); + } + + visitNode(Node node) {} + + visitSendSet(SendSet node) { + final selector = node.selector; + final name = node.assignmentOperator.source.stringValue; + final arguments = node.arguments; + + expect(node, arguments != null); + expect(node, selector is Identifier, 'selector is not assignable'); + if (identical(name, '++') || identical(name, '--')) { + expect(node, node.assignmentOperator is Operator); + if (node.isIndex) { + expect(node.arguments.tail.head, node.arguments.tail.isEmpty); + } else { + expect(node.arguments.head, node.arguments.isEmpty); + } + } else { + expect(node, !node.arguments.isEmpty); + } + } + + visitReturn(Return node) { + if (!node.isRedirectingFactoryBody && node.hasExpression) { + // We allow non-expression expressions in Return nodes, but only when + // using them to hold redirecting factory constructors. + expect(node, node.expression.asExpression() != null); + } + } +} + +class InvalidNodeError { + final Node node; + final String message; + InvalidNodeError(this.node, [this.message]); + + toString() { + String nodeString = node.toDebugString(); + String result = 'invalid node: $nodeString'; + if (message != null) result = '$result ($message)'; + return result; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart b/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart new file mode 100644 index 0000000..01fa607 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/typechecker.dart
@@ -0,0 +1,751 @@ +// 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. + +part of dart2js; + +class TypeCheckerTask extends CompilerTask { + TypeCheckerTask(Compiler compiler) : super(compiler); + String get name => "Type checker"; + + static const bool LOG_FAILURES = false; + + void check(Node tree, TreeElements elements) { + measure(() { + Visitor visitor = + new TypeCheckerVisitor(compiler, elements, compiler.types); + try { + tree.accept(visitor); + } on CancelTypeCheckException catch (e) { + if (LOG_FAILURES) { + // Do not warn about unimplemented features; log message instead. + compiler.log("'${e.node}': ${e.reason}"); + } + } + }); + } +} + +class CancelTypeCheckException { + final Node node; + final String reason; + + CancelTypeCheckException(this.node, this.reason); +} + +class TypeCheckerVisitor implements Visitor<DartType> { + final Compiler compiler; + final TreeElements elements; + final Types types; + + Node lastSeenNode; + DartType expectedReturnType; + ClassElement currentClass; + + Link<DartType> cascadeTypes = const Link<DartType>(); + + DartType intType; + DartType doubleType; + DartType boolType; + DartType stringType; + DartType objectType; + DartType listType; + + TypeCheckerVisitor(this.compiler, this.elements, this.types) { + intType = compiler.intClass.computeType(compiler); + doubleType = compiler.doubleClass.computeType(compiler); + boolType = compiler.boolClass.computeType(compiler); + stringType = compiler.stringClass.computeType(compiler); + objectType = compiler.objectClass.computeType(compiler); + listType = compiler.listClass.computeType(compiler); + } + + DartType fail(node, [reason]) { + String message = 'cannot type-check'; + if (reason != null) { + message = '$message: $reason'; + } + throw new CancelTypeCheckException(node, message); + } + + reportTypeWarning(Node node, MessageKind kind, [Map arguments = const {}]) { + compiler.reportWarning(node, new TypeWarning(kind, arguments)); + } + + // TODO(karlklose): remove these functions. + DartType unhandledStatement() => StatementType.NOT_RETURNING; + DartType unhandledExpression() => types.dynamicType; + + DartType analyzeNonVoid(Node node) { + DartType type = analyze(node); + if (type == types.voidType) { + reportTypeWarning(node, MessageKind.VOID_EXPRESSION); + } + return type; + } + + DartType analyzeWithDefault(Node node, DartType defaultValue) { + return node != null ? analyze(node) : defaultValue; + } + + DartType analyze(Node node) { + if (node == null) { + final String error = 'internal error: unexpected node: null'; + if (lastSeenNode != null) { + fail(null, error); + } else { + compiler.cancel(error); + } + } else { + lastSeenNode = node; + } + DartType result = node.accept(this); + // TODO(karlklose): record type? + if (result == null) { + fail(node, 'internal error: type is null'); + } + return result; + } + + /** + * Check if a value of type t can be assigned to a variable, + * parameter or return value of type s. + */ + checkAssignable(Node node, DartType s, DartType t) { + if (!types.isAssignable(s, t)) { + reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE, + {'fromType': s, 'toType': t}); + } + } + + checkCondition(Expression condition) { + checkAssignable(condition, boolType, analyze(condition)); + } + + void pushCascadeType(DartType type) { + cascadeTypes = cascadeTypes.prepend(type); + } + + DartType popCascadeType() { + DartType type = cascadeTypes.head; + cascadeTypes = cascadeTypes.tail; + return type; + } + + DartType visitBlock(Block node) { + return analyze(node.statements); + } + + DartType visitCascade(Cascade node) { + analyze(node.expression); + return popCascadeType(); + } + + DartType visitCascadeReceiver(CascadeReceiver node) { + DartType type = analyze(node.expression); + pushCascadeType(type); + return type; + } + + DartType visitClassNode(ClassNode node) { + fail(node); + } + + DartType visitMixinApplication(MixinApplication node) { + fail(node); + } + + DartType visitNamedMixinApplication(NamedMixinApplication node) { + fail(node); + } + + DartType visitDoWhile(DoWhile node) { + StatementType bodyType = analyze(node.body); + checkCondition(node.condition); + return bodyType.join(StatementType.NOT_RETURNING); + } + + DartType visitExpressionStatement(ExpressionStatement node) { + analyze(node.expression); + return StatementType.NOT_RETURNING; + } + + /** Dart Programming Language Specification: 11.5.1 For Loop */ + DartType visitFor(For node) { + analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING); + checkCondition(node.condition); + analyzeWithDefault(node.update, StatementType.NOT_RETURNING); + StatementType bodyType = analyze(node.body); + return bodyType.join(StatementType.NOT_RETURNING); + } + + DartType visitFunctionDeclaration(FunctionDeclaration node) { + analyze(node.function); + return StatementType.NOT_RETURNING; + } + + DartType visitFunctionExpression(FunctionExpression node) { + DartType type; + DartType returnType; + DartType previousType; + final FunctionElement element = elements[node]; + if (Elements.isUnresolved(element)) return types.dynamicType; + if (identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR) || + identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY)) { + type = types.dynamicType; + returnType = types.voidType; + } else { + FunctionType functionType = computeType(element); + returnType = functionType.returnType; + type = functionType; + } + DartType previous = expectedReturnType; + expectedReturnType = returnType; + if (element.isMember()) currentClass = element.getEnclosingClass(); + StatementType bodyType = analyze(node.body); + if (returnType != types.voidType && returnType != types.dynamicType + && bodyType != StatementType.RETURNING) { + MessageKind kind; + if (bodyType == StatementType.MAYBE_RETURNING) { + kind = MessageKind.MAYBE_MISSING_RETURN; + } else { + kind = MessageKind.MISSING_RETURN; + } + reportTypeWarning(node.name, kind); + } + expectedReturnType = previous; + return type; + } + + DartType visitIdentifier(Identifier node) { + if (node.isThis()) { + return currentClass.computeType(compiler); + } else { + // This is an identifier of a formal parameter. + return types.dynamicType; + } + } + + DartType visitIf(If node) { + checkCondition(node.condition); + StatementType thenType = analyze(node.thenPart); + StatementType elseType = node.hasElsePart ? analyze(node.elsePart) + : StatementType.NOT_RETURNING; + return thenType.join(elseType); + } + + DartType visitLoop(Loop node) { + return unhandledStatement(); + } + + DartType lookupMethodType(Node node, ClassElement classElement, + SourceString name) { + Element member = classElement.lookupLocalMember(name); + if (member == null) { + classElement.ensureResolved(compiler); + for (Link<DartType> supertypes = classElement.allSupertypes; + !supertypes.isEmpty && member == null; + supertypes = supertypes.tail) { + ClassElement lookupTarget = supertypes.head.element; + member = lookupTarget.lookupLocalMember(name); + } + } + if (member != null && member.kind == ElementKind.FUNCTION) { + return computeType(member); + } + reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND, + {'className': classElement.name, 'methodName': name}); + return types.dynamicType; + } + + // TODO(johnniwinther): Provide the element from which the type came in order + // to give better error messages. + void analyzeArguments(Send send, DartType type) { + Link<Node> arguments = send.arguments; + if (type == null || identical(type, types.dynamicType)) { + while(!arguments.isEmpty) { + analyze(arguments.head); + arguments = arguments.tail; + } + } else { + FunctionType funType = type; + Link<DartType> parameterTypes = funType.parameterTypes; + Link<DartType> optionalParameterTypes = funType.optionalParameterTypes; + while (!arguments.isEmpty) { + Node argument = arguments.head; + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + argument = namedArgument.expression; + SourceString argumentName = namedArgument.name.source; + DartType namedParameterType = + funType.getNamedParameterType(argumentName); + if (namedParameterType == null) { + // TODO(johnniwinther): Provide better information on the called + // function. + reportTypeWarning(argument, MessageKind.NAMED_ARGUMENT_NOT_FOUND, + {'argumentName': argumentName}); + + analyze(argument); + } else { + checkAssignable(argument, namedParameterType, analyze(argument)); + } + } else { + if (parameterTypes.isEmpty) { + if (optionalParameterTypes.isEmpty) { + // TODO(johnniwinther): Provide better information on the + // called function. + reportTypeWarning(argument, MessageKind.ADDITIONAL_ARGUMENT); + + analyze(argument); + } else { + checkAssignable(argument, optionalParameterTypes.head, + analyze(argument)); + optionalParameterTypes = optionalParameterTypes.tail; + } + } else { + checkAssignable(argument, parameterTypes.head, analyze(argument)); + parameterTypes = parameterTypes.tail; + } + } + arguments = arguments.tail; + } + if (!parameterTypes.isEmpty) { + // TODO(johnniwinther): Provide better information on the called + // function. + reportTypeWarning(send, MessageKind.MISSING_ARGUMENT, + {'argumentType': parameterTypes.head}); + } + } + } + + DartType visitSend(Send node) { + Element element = elements[node]; + + if (Elements.isClosureSend(node, element)) { + // TODO(karlklose): Finish implementation. + return types.dynamicType; + } + + Identifier selector = node.selector.asIdentifier(); + String name = selector.source.stringValue; + + if (node.isOperator && identical(name, 'is')) { + analyze(node.receiver); + return boolType; + } else if (node.isOperator) { + final Node firstArgument = node.receiver; + final DartType firstArgumentType = analyze(node.receiver); + final arguments = node.arguments; + final Node secondArgument = arguments.isEmpty ? null : arguments.head; + final DartType secondArgumentType = + analyzeWithDefault(secondArgument, null); + + if (identical(name, '+') || identical(name, '=') || identical(name, '-') + || identical(name, '*') || identical(name, '/') || identical(name, '%') + || identical(name, '~/') || identical(name, '|') || identical(name, '&') + || identical(name, '^') || identical(name, '~')|| identical(name, '<<') + || identical(name, '>>') || identical(name, '[]')) { + return types.dynamicType; + } else if (identical(name, '<') || identical(name, '>') || identical(name, '<=') + || identical(name, '>=') || identical(name, '==') || identical(name, '!=') + || identical(name, '===') || identical(name, '!==')) { + return boolType; + } else if (identical(name, '||') || identical(name, '&&') || identical(name, '!')) { + checkAssignable(firstArgument, boolType, firstArgumentType); + if (!arguments.isEmpty) { + // TODO(karlklose): check number of arguments in validator. + checkAssignable(secondArgument, boolType, secondArgumentType); + } + return boolType; + } + fail(selector, 'unexpected operator ${name}'); + + } else if (node.isPropertyAccess) { + if (node.receiver != null) { + // TODO(karlklose): we cannot handle fields. + return unhandledExpression(); + } + if (element == null) return types.dynamicType; + return computeType(element); + + } else if (node.isFunctionObjectInvocation) { + fail(node.receiver, 'function object invocation unimplemented'); + + } else { + FunctionType computeFunType() { + if (node.receiver != null) { + DartType receiverType = analyze(node.receiver); + if (receiverType.element == compiler.dynamicClass) return null; + if (receiverType == null) { + fail(node.receiver, 'receivertype is null'); + } + if (identical(receiverType.element.kind, ElementKind.GETTER)) { + FunctionType getterType = receiverType; + receiverType = getterType.returnType; + } + ElementKind receiverKind = receiverType.element.kind; + if (identical(receiverKind, ElementKind.TYPEDEF)) { + // TODO(karlklose): handle typedefs. + return null; + } + if (identical(receiverKind, ElementKind.TYPE_VARIABLE)) { + // TODO(karlklose): handle type variables. + return null; + } + if (!identical(receiverKind, ElementKind.CLASS)) { + fail(node.receiver, 'unexpected receiver kind: ${receiverKind}'); + } + ClassElement classElement = receiverType.element; + // TODO(karlklose): substitute type arguments. + DartType memberType = + lookupMethodType(selector, classElement, selector.source); + if (identical(memberType.element, compiler.dynamicClass)) return null; + return memberType; + } else { + if (Elements.isUnresolved(element)) { + fail(node, 'unresolved ${node.selector}'); + } else if (identical(element.kind, ElementKind.FUNCTION)) { + return computeType(element); + } else if (element.isForeign(compiler)) { + return null; + } else if (identical(element.kind, ElementKind.VARIABLE) + || identical(element.kind, ElementKind.FIELD)) { + // TODO(karlklose): handle object invocations. + return null; + } else { + fail(node, 'unexpected element kind ${element.kind}'); + } + } + } + FunctionType funType = computeFunType(); + analyzeArguments(node, funType); + return (funType != null) ? funType.returnType : types.dynamicType; + } + } + + visitSendSet(SendSet node) { + Identifier selector = node.selector; + final name = node.assignmentOperator.source.stringValue; + if (identical(name, '++') || identical(name, '--')) { + final Element element = elements[node.selector]; + final DartType receiverType = computeType(element); + // TODO(karlklose): this should be the return type instead of int. + return node.isPrefix ? intType : receiverType; + } else { + DartType targetType = computeType(elements[node]); + Node value = node.arguments.head; + checkAssignable(value, targetType, analyze(value)); + return targetType; + } + } + + DartType visitLiteralInt(LiteralInt node) { + return intType; + } + + DartType visitLiteralDouble(LiteralDouble node) { + return doubleType; + } + + DartType visitLiteralBool(LiteralBool node) { + return boolType; + } + + DartType visitLiteralString(LiteralString node) { + return stringType; + } + + DartType visitStringJuxtaposition(StringJuxtaposition node) { + analyze(node.first); + analyze(node.second); + return stringType; + } + + DartType visitLiteralNull(LiteralNull node) { + return types.dynamicType; + } + + DartType visitNewExpression(NewExpression node) { + Element element = elements[node.send]; + analyzeArguments(node.send, computeType(element)); + return analyze(node.send.selector); + } + + DartType visitLiteralList(LiteralList node) { + return listType; + } + + DartType visitNodeList(NodeList node) { + DartType type = StatementType.NOT_RETURNING; + bool reportedDeadCode = false; + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + DartType nextType = analyze(link.head); + if (type == StatementType.RETURNING) { + if (!reportedDeadCode) { + reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE); + reportedDeadCode = true; + } + } else if (type == StatementType.MAYBE_RETURNING){ + if (nextType == StatementType.RETURNING) { + type = nextType; + } + } else { + type = nextType; + } + } + return type; + } + + DartType visitOperator(Operator node) { + fail(node, 'internal error'); + } + + /** Dart Programming Language Specification: 11.10 Return */ + DartType visitReturn(Return node) { + if (identical(node.getBeginToken().stringValue, 'native')) { + return StatementType.RETURNING; + } + if (node.isRedirectingFactoryBody) { + // TODO(lrn): Typecheck the body. It must refer to the constructor + // of a subtype. + return StatementType.RETURNING; + } + + final expression = node.expression; + final isVoidFunction = (identical(expectedReturnType, types.voidType)); + + // Executing a return statement return e; [...] It is a static type warning + // if the type of e may not be assigned to the declared return type of the + // immediately enclosing function. + if (expression != null) { + final expressionType = analyze(expression); + if (isVoidFunction + && !types.isAssignable(expressionType, types.voidType)) { + reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID); + } else { + checkAssignable(expression, expectedReturnType, expressionType); + } + + // Let f be the function immediately enclosing a return statement of the + // form 'return;' It is a static warning if both of the following conditions + // hold: + // - f is not a generative constructor. + // - The return type of f may not be assigned to void. + } else if (!types.isAssignable(expectedReturnType, types.voidType)) { + reportTypeWarning(node, MessageKind.RETURN_NOTHING, + {'returnType': expectedReturnType}); + } + return StatementType.RETURNING; + } + + DartType visitThrow(Throw node) { + if (node.expression != null) analyze(node.expression); + return StatementType.RETURNING; + } + + DartType computeType(Element element) { + if (Elements.isUnresolved(element)) return types.dynamicType; + DartType result = element.computeType(compiler); + return (result != null) ? result : types.dynamicType; + } + + DartType visitTypeAnnotation(TypeAnnotation node) { + return elements.getType(node); + } + + visitTypeVariable(TypeVariable node) { + return types.dynamicType; + } + + DartType visitVariableDefinitions(VariableDefinitions node) { + DartType type = analyzeWithDefault(node.type, types.dynamicType); + if (type == types.voidType) { + reportTypeWarning(node.type, MessageKind.VOID_VARIABLE); + type = types.dynamicType; + } + for (Link<Node> link = node.definitions.nodes; !link.isEmpty; + link = link.tail) { + Node initialization = link.head; + compiler.ensure(initialization is Identifier + || initialization is Send); + if (initialization is Send) { + DartType initializer = analyzeNonVoid(link.head); + checkAssignable(node, type, initializer); + } + } + return StatementType.NOT_RETURNING; + } + + DartType visitWhile(While node) { + checkCondition(node.condition); + StatementType bodyType = analyze(node.body); + Expression cond = node.condition.asParenthesizedExpression().expression; + if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) { + // If the condition is a constant boolean expression denoting true, + // control-flow always enters the loop body. + // TODO(karlklose): this should be StatementType.RETURNING unless there + // is a break in the loop body that has the loop or a label outside the + // loop as a target. + return bodyType; + } else { + return bodyType.join(StatementType.NOT_RETURNING); + } + } + + DartType visitParenthesizedExpression(ParenthesizedExpression node) { + return analyze(node.expression); + } + + DartType visitConditional(Conditional node) { + checkCondition(node.condition); + DartType thenType = analyzeNonVoid(node.thenExpression); + DartType elseType = analyzeNonVoid(node.elseExpression); + if (types.isSubtype(thenType, elseType)) { + return thenType; + } else if (types.isSubtype(elseType, thenType)) { + return elseType; + } else { + return objectType; + } + } + + DartType visitModifiers(Modifiers node) {} + + visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + return stringType; + } + + visitStringInterpolationPart(StringInterpolationPart node) { + node.visitChildren(this); + return stringType; + } + + visitEmptyStatement(EmptyStatement node) { + return StatementType.NOT_RETURNING; + } + + visitBreakStatement(BreakStatement node) { + return StatementType.NOT_RETURNING; + } + + visitContinueStatement(ContinueStatement node) { + return StatementType.NOT_RETURNING; + } + + visitForIn(ForIn node) { + analyze(node.expression); + StatementType bodyType = analyze(node.body); + return bodyType.join(StatementType.NOT_RETURNING); + } + + visitLabel(Label node) { } + + visitLabeledStatement(LabeledStatement node) { + return node.statement.accept(this); + } + + visitLiteralMap(LiteralMap node) { + return unhandledExpression(); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + return unhandledExpression(); + } + + visitNamedArgument(NamedArgument node) { + return unhandledExpression(); + } + + visitSwitchStatement(SwitchStatement node) { + return unhandledStatement(); + } + + visitSwitchCase(SwitchCase node) { + return unhandledStatement(); + } + + visitCaseMatch(CaseMatch node) { + return unhandledStatement(); + } + + visitTryStatement(TryStatement node) { + return unhandledStatement(); + } + + visitScriptTag(ScriptTag node) { + return unhandledExpression(); + } + + visitCatchBlock(CatchBlock node) { + return unhandledStatement(); + } + + visitTypedef(Typedef node) { + return unhandledStatement(); + } + + DartType visitNode(Node node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitCombinator(Combinator node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitExport(Export node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitExpression(Expression node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitGotoStatement(GotoStatement node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitImport(Import node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryName(LibraryName node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryTag(LibraryTag node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLiteral(Literal node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPart(Part node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPartOf(PartOf node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPostfix(Postfix node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPrefix(Prefix node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitStatement(Statement node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitStringNode(StringNode node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryDependency(LibraryDependency node) { + compiler.unimplemented('visitNode', node: node); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart b/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart new file mode 100644 index 0000000..6c05203 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
@@ -0,0 +1,1678 @@ +// 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. + +part of types; + +class CancelTypeInferenceException { + final Node node; + final String reason; + + CancelTypeInferenceException(this.node, this.reason); +} + +/** + * A singleton concrete type. More precisely, a [BaseType] is one of the + * following: + * + * - a non-asbtract class like [: int :] or [: Uri :] but not [: List :] + * - the null base type + * - the unknown base type + */ +abstract class BaseType { + bool isClass(); + bool isUnknown(); + bool isNull(); +} + +/** + * A non-asbtract class like [: int :] or [: Uri :] but not [: List :]. + */ +class ClassBaseType implements BaseType { + final ClassElement element; + + ClassBaseType(this.element); + + bool operator ==(BaseType other) { + if (identical(this, other)) return true; + if (other is! ClassBaseType) return false; + return element == other.element; + } + int get hashCode => element.hashCode; + String toString() => element.name.slowToString(); + bool isClass() => true; + bool isUnknown() => false; + bool isNull() => false; +} + +/** + * The unknown base type. + */ +class UnknownBaseType implements BaseType { + const UnknownBaseType(); + bool operator ==(BaseType other) => other is UnknownBaseType; + int get hashCode => 0; + bool isClass() => false; + bool isUnknown() => true; + bool isNull() => false; + toString() => "unknown"; +} + +/** + * The null base type. + */ +class NullBaseType implements BaseType { + const NullBaseType(); + bool operator ==(BaseType other) => other is NullBaseType; + int get hashCode => 1; + bool isClass() => false; + bool isUnknown() => false; + bool isNull() => true; + toString() => "null"; +} + +/** + * An immutable set of base types, like [: {int, bool} :] or the unknown + * concrete type. + */ +abstract class ConcreteType { + factory ConcreteType.empty() { + return new UnionType(new Set<BaseType>()); + } + + /** + * The singleton constituted of the unknown base type is the unknown concrete + * type. + */ + factory ConcreteType.singleton(int maxConcreteTypeSize, BaseType baseType) { + if (baseType.isUnknown() || maxConcreteTypeSize < 1) { + return new UnknownConcreteType(); + } + Set<BaseType> singletonSet = new Set<BaseType>(); + singletonSet.add(baseType); + return new UnionType(singletonSet); + } + + factory ConcreteType.unknown() { + return const UnknownConcreteType(); + } + + ConcreteType union(int maxConcreteTypeSize, ConcreteType other); + bool isUnkown(); + bool isEmpty(); + Set<BaseType> get baseTypes; + + /** + * Returns the unique element of [: this :] if [: this :] is a singleton, + * null otherwise. + */ + ClassElement getUniqueType(); +} + +/** + * The unkown concrete type: it is absorbing for the union. + */ +class UnknownConcreteType implements ConcreteType { + const UnknownConcreteType(); + bool isUnkown() => true; + bool isEmpty() => false; + bool operator ==(ConcreteType other) => identical(this, other); + Set<BaseType> get baseTypes => + new Set<BaseType>.from([const UnknownBaseType()]); + int get hashCode => 0; + ConcreteType union(int maxConcreteTypeSize, ConcreteType other) => this; + ClassElement getUniqueType() => null; + toString() => "unknown"; +} + +/** + * An immutable set of base types, like [: {int, bool} :]. + */ +class UnionType implements ConcreteType { + final Set<BaseType> baseTypes; + + /** + * The argument should NOT be mutated later. Do not call directly, use + * ConcreteType.singleton instead. + */ + UnionType(this.baseTypes); + + bool isUnkown() => false; + bool isEmpty() => baseTypes.isEmpty; + + bool operator ==(ConcreteType other) { + if (other is! UnionType) return false; + if (baseTypes.length != other.baseTypes.length) return false; + return baseTypes.containsAll(other.baseTypes); + } + + int get hashCode { + int result = 1; + for (final baseType in baseTypes) { + result = 31 * result + baseType.hashCode; + } + return result; + } + + // TODO(polux): Collapse {num, int, ...}, {num, double, ...} and + // {int, double,...} into {num, ...} as an optimization. It will require + // UnionType to know about these class elements, which is cumbersome because + // there are no nested classes. We need factory methods instead. + ConcreteType union(int maxConcreteTypeSize, ConcreteType other) { + if (other.isUnkown()) { + return const UnknownConcreteType(); + } + UnionType otherUnion = other; // cast + Set<BaseType> newBaseTypes = new Set<BaseType>.from(baseTypes); + newBaseTypes.addAll(otherUnion.baseTypes); + return newBaseTypes.length > maxConcreteTypeSize + ? const UnknownConcreteType() + : new UnionType(newBaseTypes); + } + + ClassElement getUniqueType() { + if (baseTypes.length == 1) { + var iterator = baseTypes.iterator; + iterator.moveNext(); + BaseType uniqueBaseType = iterator.current; + if (uniqueBaseType.isClass()) { + ClassBaseType uniqueClassType = uniqueBaseType; + return uniqueClassType.element; + } + } + return null; + } + + String toString() => baseTypes.toString(); +} + +/** + * The cartesian product of concrete types: an iterable of [BaseTypeTuple]s. For + * instance, the cartesian product of the concrete types [: {A, B} :] and + * [: {C, D} :] is an itearble whose iterators will yield [: (A, C) :], + * [: (A, D) :], [: (B, C) :] and finally [: (B, D) :]. + */ +class ConcreteTypeCartesianProduct + extends Iterable<ConcreteTypesEnvironment> { + final ConcreteTypesInferrer inferrer; + final ClassElement typeOfThis; + final Map<Element, ConcreteType> concreteTypes; + ConcreteTypeCartesianProduct(this.inferrer, this.typeOfThis, + this.concreteTypes); + Iterator get iterator => concreteTypes.isEmpty + ? [new ConcreteTypesEnvironment(inferrer, new ClassBaseType(typeOfThis))] + .iterator + : new ConcreteTypeCartesianProductIterator(inferrer, + new ClassBaseType(typeOfThis), concreteTypes); + String toString() { + List<ConcreteTypesEnvironment> cartesianProduct = + new List<ConcreteTypesEnvironment>.from(this); + return cartesianProduct.toString(); + } +} + +/** + * An helper class for [ConcreteTypeCartesianProduct]. + */ +class ConcreteTypeCartesianProductIterator + implements Iterator<ConcreteTypesEnvironment> { + final ConcreteTypesInferrer inferrer; + final BaseType baseTypeOfThis; + final Map<Element, ConcreteType> concreteTypes; + final Map<Element, BaseType> nextValues; + final Map<Element, Iterator> state; + int size = 1; + int counter = 0; + ConcreteTypesEnvironment _current; + + ConcreteTypeCartesianProductIterator(this.inferrer, this.baseTypeOfThis, + Map<Element, ConcreteType> concreteTypes) + : this.concreteTypes = concreteTypes, + nextValues = new Map<Element, BaseType>(), + state = new Map<Element, Iterator>() { + if (concreteTypes.isEmpty) { + size = 0; + return; + } + for (final e in concreteTypes.keys) { + final baseTypes = concreteTypes[e].baseTypes; + size *= baseTypes.length; + } + } + + ConcreteTypesEnvironment get current => _current; + + ConcreteTypesEnvironment takeSnapshot() { + Map<Element, ConcreteType> result = new Map<Element, ConcreteType>(); + nextValues.forEach((k, v) { + result[k] = inferrer.singletonConcreteType(v); + }); + return new ConcreteTypesEnvironment.of(inferrer, result, baseTypeOfThis); + } + + bool moveNext() { + if (counter >= size) { + _current = null; + return false; + } + Element keyToIncrement = null; + for (final key in concreteTypes.keys) { + final iterator = state[key]; + if (iterator != null && iterator.moveNext()) { + nextValues[key] = state[key].current; + break; + } + Iterator newIterator = concreteTypes[key].baseTypes.iterator; + state[key] = newIterator; + newIterator.moveNext(); + nextValues[key] = newIterator.current; + } + counter++; + _current = takeSnapshot(); + return true; + } +} + +/** + * [BaseType] Constants. + */ +class BaseTypes { + final ClassBaseType intBaseType; + final ClassBaseType doubleBaseType; + final ClassBaseType numBaseType; + final ClassBaseType boolBaseType; + final ClassBaseType stringBaseType; + final ClassBaseType listBaseType; + final ClassBaseType mapBaseType; + final ClassBaseType objectBaseType; + final ClassBaseType typeBaseType; + + static _getNativeListClass(Compiler compiler) { + // TODO(polux): switch to other implementations on other backends + JavaScriptBackend backend = compiler.backend; + return backend.jsArrayClass; + } + + BaseTypes(Compiler compiler) : + intBaseType = new ClassBaseType(compiler.intClass), + doubleBaseType = new ClassBaseType(compiler.doubleClass), + numBaseType = new ClassBaseType(compiler.numClass), + boolBaseType = new ClassBaseType(compiler.boolClass), + stringBaseType = new ClassBaseType(compiler.stringClass), + // in the Javascript backend, lists are implemented by JsArray + listBaseType = new ClassBaseType(_getNativeListClass(compiler)), + mapBaseType = new ClassBaseType(compiler.mapClass), + objectBaseType = new ClassBaseType(compiler.objectClass), + typeBaseType = new ClassBaseType(compiler.typeClass); +} + +/** + * A method-local immutable mapping from variables to their inferred + * [ConcreteTypes]. Each visitor owns one. + */ +class ConcreteTypesEnvironment { + final ConcreteTypesInferrer inferrer; + final Map<Element, ConcreteType> environment; + final BaseType typeOfThis; + + ConcreteTypesEnvironment(this.inferrer, [this.typeOfThis]) : + this.environment = new Map<Element, ConcreteType>(); + ConcreteTypesEnvironment.of(this.inferrer, this.environment, this.typeOfThis); + + ConcreteType lookupType(Element element) => environment[element]; + ConcreteType lookupTypeOfThis() { + return (typeOfThis == null) + ? null + : inferrer.singletonConcreteType(typeOfThis); + } + + ConcreteTypesEnvironment put(Element element, ConcreteType type) { + Map<Element, ConcreteType> newMap = + new Map<Element, ConcreteType>.from(environment); + newMap[element] = type; + return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis); + } + + ConcreteTypesEnvironment join(ConcreteTypesEnvironment other) { + if (typeOfThis != other.typeOfThis) { + throw "trying to join incompatible environments"; + } + Map<Element, ConcreteType> newMap = + new Map<Element, ConcreteType>.from(environment); + other.environment.forEach((element, type) { + ConcreteType currentType = newMap[element]; + if (element == null) { + newMap[element] = type; + } else { + newMap[element] = inferrer.union(currentType, type); + } + }); + return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis); + } + + bool operator ==(ConcreteTypesEnvironment other) { + if (other is! ConcreteTypesEnvironment) return false; + if (typeOfThis != other.typeOfThis) return false; + if (environment.length != other.environment.length) return false; + for (Element key in environment.keys) { + if (!other.environment.containsKey(key) + || (environment[key] != other.environment[key])) { + return false; + } + } + return true; + } + + int get hashCode { + int result = (typeOfThis != null) ? typeOfThis.hashCode : 1; + environment.forEach((element, concreteType) { + result = 31 * (31 * result + element.hashCode) + + concreteType.hashCode; + }); + return result; + } + + String toString() => "{ this: $typeOfThis, env: ${environment.toString()} }"; +} + +/** + * A work item for the type inference queue. + */ +class InferenceWorkItem { + FunctionElement method; + ConcreteTypesEnvironment environment; + InferenceWorkItem(this.method, this.environment); + + toString() => "{ method = ${method.name.slowToString()}, " + "environment = $environment }"; +} + +/** + * A task which conservatively infers a [ConcreteType] for each sub expression + * of the program. The entry point is [analyzeMain]. + */ +class ConcreteTypesInferrer { + static final bool LOG_FAILURES = true; + + final String name = "Type inferrer"; + + final Compiler compiler; + + /** + * When true, the string literal [:"__dynamic_for_test":] is inferred to + * have the unknown type. + */ + // TODO(polux): get rid of this hack once we have a natural way of inferring + // the unknown type. + bool testMode = false; + + /** + * Constants representing builtin base types. Initialized in [initialize] + * and not in the constructor because the compiler elements are not yet + * populated. + */ + BaseTypes baseTypes; + + /** + * Constant representing [:ConcreteList#[]:] where [:ConcreteList:] is the + * concrete implmentation of lists for the selected backend. + */ + FunctionElement listIndex; + + /** + * Constant representing [:ConcreteList#[]=:] where [:ConcreteList:] is the + * concrete implmentation of lists for the selected backend. + */ + FunctionElement listIndexSet; + + /** + * Constant representing [:List():]. + */ + FunctionElement listConstructor; + + /** + * A cache from (function x argument base types) to concrete types, + * used to memoize [analyzeMonoSend]. Another way of seeing [cache] is as a + * map from [FunctionElement]s to "templates" in the sense of "The Cartesian + * Product Algorithm - Simple and Precise Type Inference of Parametric + * Polymorphism" by Ole Agesen. + */ + final Map<FunctionElement, Map<ConcreteTypesEnvironment, ConcreteType>> cache; + + /** A map from expressions to their inferred concrete types. */ + final Map<Node, ConcreteType> inferredTypes; + + /** A map from fields to their inferred concrete types. */ + final Map<Element, ConcreteType> inferredFieldTypes; + + /** The work queue consumed by [analyzeMain]. */ + final Queue<InferenceWorkItem> workQueue; + + /** [: callers[f] :] is the list of [: f :]'s possible callers. */ + final Map<FunctionElement, Set<FunctionElement>> callers; + + /** [: readers[field] :] is the list of [: field :]'s possible readers. */ + final Map<Element, Set<FunctionElement>> readers; + + /** The inferred type of elements stored in Lists. */ + ConcreteType listElementType; + + /** + * A map from parameters to their inferred concrete types. It plays no role + * in the analysis, it is write only. + */ + final Map<VariableElement, ConcreteType> inferredParameterTypes; + + ConcreteTypesInferrer(Compiler compiler) + : this.compiler = compiler, + cache = new Map<FunctionElement, + Map<ConcreteTypesEnvironment, ConcreteType>>(), + inferredTypes = new Map<Node, ConcreteType>(), + inferredFieldTypes = new Map<Element, ConcreteType>(), + inferredParameterTypes = new Map<VariableElement, ConcreteType>(), + workQueue = new Queue<InferenceWorkItem>(), + callers = new Map<FunctionElement, Set<FunctionElement>>(), + readers = new Map<Element, Set<FunctionElement>>(), + listElementType = new ConcreteType.empty() { + unknownConcreteType = new ConcreteType.unknown(); + emptyConcreteType = new ConcreteType.empty(); + } + + /** + * Populates [cache] with ad hoc rules like: + * + * {int} + {int} -> {int} + * {int} + {double} -> {num} + * {int} + {num} -> {double} + * ... + */ + populateCacheWithBuiltinRules() { + // Builds the environment that would be looked up if we were to analyze + // o.method(arg) where o has concrete type {receiverType} and arg has + // concrete type {argumentType}. + ConcreteTypesEnvironment makeEnvironment(BaseType receiverType, + FunctionElement method, + BaseType argumentType) { + ArgumentsTypes argumentsTypes = new ArgumentsTypes( + [singletonConcreteType(argumentType)], + new Map()); + Map<Element, ConcreteType> argumentMap = + associateArguments(method, argumentsTypes); + return new ConcreteTypesEnvironment.of(this, argumentMap, receiverType); + } + + // Adds the rule {receiverType}.method({argumentType}) -> {returnType} + // to cache. + void rule(ClassBaseType receiverType, String method, + BaseType argumentType, BaseType returnType) { + // The following line shouldn't be needed but the mock compiler doesn't + // resolve num for some reason. + receiverType.element.ensureResolved(compiler); + FunctionElement methodElement = + receiverType.element.lookupMember(new SourceString(method)); + ConcreteTypesEnvironment environment = + makeEnvironment(receiverType, methodElement, argumentType); + Map<ConcreteTypesEnvironment, ConcreteType> map = + cache.containsKey(methodElement) + ? cache[methodElement] + : new Map<ConcreteTypesEnvironment, ConcreteType>(); + map[environment] = singletonConcreteType(returnType); + cache[methodElement] = map; + } + + // The hardcoded typing rules. + final ClassBaseType int = baseTypes.intBaseType; + final ClassBaseType double = baseTypes.doubleBaseType; + final ClassBaseType num = baseTypes.numBaseType; + for (String method in ['+', '*', '-']) { + rule(int, method, int, int); + rule(int, method, double, num); + rule(int, method, num, num); + + rule(double, method, double, double); + rule(double, method, int, num); + rule(double, method, num, num); + + rule(num, method, int, num); + rule(num, method, double, num); + rule(num, method, num, num); + } + } + + // --- utility methods --- + + /** The unknown concrete type */ + ConcreteType unknownConcreteType; + + /** The empty concrete type */ + ConcreteType emptyConcreteType; + + /** Creates a singleton concrete type containing [baseType]. */ + ConcreteType singletonConcreteType(BaseType baseType) { + return new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseType); + } + + /** Returns the union of its two arguments */ + ConcreteType union(ConcreteType concreteType1, ConcreteType concreteType2) { + return concreteType1.union(compiler.maxConcreteTypeSize, concreteType2); + } + + /** + * Returns all the members with name [methodName]. + */ + List<Element> getMembersByName(SourceString methodName) { + // TODO(polux): memoize? + var result = new List<Element>(); + for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) { + Element elem = cls.lookupLocalMember(methodName); + if (elem != null) { + result.add(elem); + } + } + return result; + } + + /** + * Sets the concrete type associated to [node] to the union of the inferred + * concrete type so far and [type]. + */ + void augmentInferredType(Node node, ConcreteType type) { + ConcreteType currentType = inferredTypes[node]; + inferredTypes[node] = (currentType == null) + ? type + : union(currentType, type); + } + + /** + * Returns the current inferred concrete type of [field]. + */ + ConcreteType getFieldType(Element field) { + ConcreteType result = inferredFieldTypes[field]; + return (result == null) ? emptyConcreteType : result; + } + + /** + * Sets the concrete type associated to [field] to the union of the inferred + * concrete type so far and [type]. + */ + void augmentFieldType(Element field, ConcreteType type) { + ConcreteType oldType = inferredFieldTypes[field]; + ConcreteType newType = (oldType != null) + ? union(oldType, type) + : type; + if (oldType != newType) { + inferredFieldTypes[field] = newType; + final fieldReaders = readers[field]; + if (fieldReaders != null) { + for (final reader in fieldReaders) { + final readerInstances = cache[reader]; + if (readerInstances != null) { + readerInstances.forEach((environment, _) { + workQueue.addLast(new InferenceWorkItem(reader, environment)); + }); + } + } + } + } + } + + /// Augment the inferred type of elements stored in Lists. + void augmentListElementType(ConcreteType type) { + ConcreteType newType = union(listElementType, type); + if (newType != listElementType) { + invalidateCallers(listIndex); + listElementType = newType; + } + } + + /** + * Sets the concrete type associated to [parameter] to the union of the + * inferred concrete type so far and [type]. + */ + void augmentParameterType(VariableElement parameter, ConcreteType type) { + ConcreteType oldType = inferredParameterTypes[parameter]; + inferredParameterTypes[parameter] = + (oldType == null) ? type : union(oldType, type); + } + + /** + * Add [caller] to the set of [callee]'s callers. + */ + void addCaller(FunctionElement callee, FunctionElement caller) { + Set<FunctionElement> current = callers[callee]; + if (current != null) { + current.add(caller); + } else { + Set<FunctionElement> newSet = new Set<FunctionElement>(); + newSet.add(caller); + callers[callee] = newSet; + } + } + + /** + * Add [reader] to the set of [field]'s readers. + */ + void addReader(Element field, FunctionElement reader) { + Set<FunctionElement> current = readers[field]; + if (current != null) { + current.add(reader); + } else { + Set<FunctionElement> newSet = new Set<FunctionElement>(); + newSet.add(reader); + readers[field] = newSet; + } + } + + /** + * Add callers of [function] to the workqueue. + */ + void invalidateCallers(FunctionElement function) { + Set<FunctionElement> methodCallers = callers[function]; + if (methodCallers == null) return; + for (FunctionElement caller in methodCallers) { + Map<ConcreteTypesEnvironment, ConcreteType> callerInstances = + cache[caller]; + if (callerInstances != null) { + callerInstances.forEach((environment, _) { + workQueue.addLast( + new InferenceWorkItem(caller, environment)); + }); + } + } + } + + // -- query -- + + /** + * Get the inferred concrete type of [node]. + */ + ConcreteType getConcreteTypeOfNode(Node node) => inferredTypes[node]; + + /** + * Get the inferred concrete type of [parameter]. + */ + ConcreteType getConcreteTypeOfParameter(VariableElement parameter) { + return inferredParameterTypes[parameter]; + } + + // --- analysis --- + + /** + * Returns the concrete type returned by [function] given arguments of + * concrete types [argumentsTypes]. If [function] is static then + * [receiverType] must be null, else [function] must be a member of the class + * of [receiverType]. + */ + ConcreteType getSendReturnType(FunctionElement function, + ClassElement receiverType, + ArgumentsTypes argumentsTypes) { + ConcreteType result = emptyConcreteType; + Map<Element, ConcreteType> argumentMap = + associateArguments(function, argumentsTypes); + // if the association failed, this send will never occur or will fail + if (argumentMap == null) { + return emptyConcreteType; + } + + argumentMap.forEach(augmentParameterType); + ConcreteTypeCartesianProduct product = + new ConcreteTypeCartesianProduct(this, receiverType, argumentMap); + for (ConcreteTypesEnvironment environment in product) { + result = union(result, + getMonomorphicSendReturnType(function, environment)); + } + return result; + } + + /** + * Given a method signature and a list of concrete types, builds a map from + * formals to their corresponding concrete types. Returns null if the + * association is impossible (for instance: too many arguments). + */ + Map<Element, ConcreteType> associateArguments(FunctionElement function, + ArgumentsTypes argumentsTypes) { + final Map<Element, ConcreteType> result = new Map<Element, ConcreteType>(); + final FunctionSignature signature = function.computeSignature(compiler); + + // guard 1: too many arguments + if (argumentsTypes.length > signature.parameterCount) { + return null; + } + // guard 2: not enough arguments + if (argumentsTypes.positional.length < signature.requiredParameterCount) { + return null; + } + // guard 3: too many positional arguments + if (signature.optionalParametersAreNamed && + argumentsTypes.positional.length > signature.requiredParameterCount) { + return null; + } + + handleLeftoverOptionalParameter(Element parameter) { + // TODO(polux): use default value whenever available + // TODO(polux): add a marker to indicate whether an argument was provided + // in order to handle "?parameter" tests + result[parameter] = singletonConcreteType(const NullBaseType()); + } + + final Iterator<ConcreteType> remainingPositionalArguments = + argumentsTypes.positional.iterator; + // we attach each positional parameter to its corresponding positional + // argument + for (Link<Element> requiredParameters = signature.requiredParameters; + !requiredParameters.isEmpty; + requiredParameters = requiredParameters.tail) { + final Element requiredParameter = requiredParameters.head; + // we know moveNext() succeeds because of guard 2 + remainingPositionalArguments.moveNext(); + result[requiredParameter] = remainingPositionalArguments.current; + } + if (signature.optionalParametersAreNamed) { + // we build a map out of the remaining named parameters + Link<Element> remainingOptionalParameters = signature.optionalParameters; + final Map<SourceString, Element> leftOverNamedParameters = + new Map<SourceString, Element>(); + for (; + !remainingOptionalParameters.isEmpty; + remainingOptionalParameters = remainingOptionalParameters.tail) { + final Element namedParameter = remainingOptionalParameters.head; + leftOverNamedParameters[namedParameter.name] = namedParameter; + } + // we attach the named arguments to their corresponding optional + // parameters + for (Identifier identifier in argumentsTypes.named.keys) { + final ConcreteType concreteType = argumentsTypes.named[identifier]; + SourceString source = identifier.source; + final Element namedParameter = leftOverNamedParameters[source]; + // unexisting or already used named parameter + if (namedParameter == null) return null; + result[namedParameter] = concreteType; + leftOverNamedParameters.remove(source); + } + leftOverNamedParameters.forEach((_, Element parameter) { + handleLeftoverOptionalParameter(parameter); + }); + } else { // optional parameters are positional + // we attach the remaining positional arguments to their corresponding + // optional parameters + Link<Element> remainingOptionalParameters = signature.optionalParameters; + while (remainingPositionalArguments.moveNext()) { + final Element optionalParameter = remainingOptionalParameters.head; + result[optionalParameter] = remainingPositionalArguments.current; + // we know tail is defined because of guard 1 + remainingOptionalParameters = remainingOptionalParameters.tail; + } + for (; + !remainingOptionalParameters.isEmpty; + remainingOptionalParameters = remainingOptionalParameters.tail) { + handleLeftoverOptionalParameter(remainingOptionalParameters.head); + } + } + return result; + } + + ConcreteType getMonomorphicSendReturnType( + FunctionElement function, + ConcreteTypesEnvironment environment) { + ConcreteType specialType = getSpecialCaseReturnType(function, environment); + if (specialType != null) return specialType; + + Map<ConcreteTypesEnvironment, ConcreteType> template = cache[function]; + if (template == null) { + template = new Map<ConcreteTypesEnvironment, ConcreteType>(); + cache[function] = template; + } + ConcreteType type = template[environment]; + if (type != null) { + return type; + } else { + workQueue.addLast( + new InferenceWorkItem(function, environment)); + // in case of a constructor, optimize by returning the class + return emptyConcreteType; + } + } + + /** + * Handles external methods that cannot be cached because they depend on some + * other state of [ConcreteTypesInferrer] like [:List#[]:] and + * [:List#[]=:]. Returns null if [function] and [environment] don't form a + * special case + */ + ConcreteType getSpecialCaseReturnType(FunctionElement function, + ConcreteTypesEnvironment environment) { + if (function == listIndex) { + ConcreteType indexType = environment.lookupType( + listIndex.functionSignature.requiredParameters.head); + if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { + return new ConcreteType.empty(); + } + return listElementType; + } else if (function == listIndexSet) { + Link<Element> parameters = + listIndexSet.functionSignature.requiredParameters; + ConcreteType indexType = environment.lookupType(parameters.head); + if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { + return new ConcreteType.empty(); + } + ConcreteType elementType = environment.lookupType(parameters.tail.head); + augmentListElementType(elementType); + return new ConcreteType.empty(); + } + return null; + } + + ConcreteType analyze(FunctionElement element, + ConcreteTypesEnvironment environment) { + return element.isGenerativeConstructor() + ? analyzeConstructor(element, environment) + : analyzeMethod(element, environment); + } + + ConcreteType analyzeMethod(FunctionElement element, + ConcreteTypesEnvironment environment) { + TreeElements elements = + compiler.enqueuer.resolution.resolvedElements[element]; + ConcreteType specialResult = handleSpecialMethod(element, environment); + if (specialResult != null) return specialResult; + FunctionExpression tree = element.parseNode(compiler); + if (tree.hasBody()) { + Visitor visitor = + new TypeInferrerVisitor(elements, element, this, environment); + return tree.accept(visitor); + } else { + // TODO(polux): implement visitForeingCall and always use the + // implementation element instead of this hack + return new ConcreteType.unknown(); + } + } + + ConcreteType analyzeConstructor(FunctionElement element, + ConcreteTypesEnvironment environment) { + ClassElement enclosingClass = element.enclosingElement; + FunctionExpression tree = compiler.parser.parse(element); + TreeElements elements = + compiler.enqueuer.resolution.resolvedElements[element]; + Visitor visitor = + new TypeInferrerVisitor(elements, element, this, environment); + + // handle initializing formals + element.functionSignature.forEachParameter((param) { + if (param.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParam = param; + augmentFieldType(fieldParam.fieldElement, + environment.lookupType(param)); + } + }); + + // analyze initializers, including a possible call to super or a redirect + bool foundSuperOrRedirect = false; + if (tree.initializers != null) { + // we look for a possible call to super in the initializer list + for (final init in tree.initializers) { + init.accept(visitor); + if (init.asSendSet() == null) { + foundSuperOrRedirect = true; + } + } + } + + // if no call to super or redirect has been found, call the default + // constructor (if the current class is not Object). + if (!foundSuperOrRedirect) { + ClassElement superClass = enclosingClass.superclass; + if (enclosingClass != compiler.objectClass) { + FunctionElement target = superClass.lookupConstructor( + new Selector.callDefaultConstructor(enclosingClass.getLibrary())); + final superClassConcreteType = singletonConcreteType( + new ClassBaseType(enclosingClass)); + getSendReturnType(target, enclosingClass, + new ArgumentsTypes(new List(), new Map())); + } + } + + tree.accept(visitor); + return singletonConcreteType(new ClassBaseType(enclosingClass)); + } + + /** + * Hook that performs side effects on some special method calls (like + * [:List(length):]) and possibly returns a concrete type + * (like [:{JsArray}:]). + */ + ConcreteType handleSpecialMethod(FunctionElement element, + ConcreteTypesEnvironment environment) { + // When List([length]) is called with some length, we must augment + // listElementType with {null}. + if (element == listConstructor) { + Link<Element> parameters = + listConstructor.functionSignature.optionalParameters; + ConcreteType lengthType = environment.lookupType(parameters.head); + if (lengthType.baseTypes.contains(baseTypes.intBaseType)) { + augmentListElementType(singletonConcreteType(new NullBaseType())); + } + return singletonConcreteType(baseTypes.listBaseType); + } + } + + /* Initialization code that cannot be run in the constructor because it + * requires the compiler's elements to be populated. + */ + void initialize() { + baseTypes = new BaseTypes(compiler); + ClassElement jsArrayClass = baseTypes.listBaseType.element; + listIndex = jsArrayClass.lookupMember(const SourceString('[]')); + listIndexSet = + jsArrayClass.lookupMember(const SourceString('[]=')); + listConstructor = + compiler.listClass.lookupConstructor( + new Selector.callConstructor(const SourceString(''), + compiler.listClass.getLibrary())); + } + + /** + * Performs concrete type inference of the code reachable from [element]. + * Returns [:true:] if and only if analysis succeeded. + */ + bool analyzeMain(Element element) { + initialize(); + cache[element] = new Map<ConcreteTypesEnvironment, ConcreteType>(); + populateCacheWithBuiltinRules(); + try { + workQueue.addLast( + new InferenceWorkItem(element, new ConcreteTypesEnvironment(this))); + while (!workQueue.isEmpty) { + InferenceWorkItem item = workQueue.removeFirst(); + ConcreteType concreteType = analyze(item.method, item.environment); + var template = cache[item.method]; + if (template[item.environment] == concreteType) continue; + template[item.environment] = concreteType; + invalidateCallers(item.method); + } + return true; + } on CancelTypeInferenceException catch(e) { + if (LOG_FAILURES) { + compiler.log(e.reason); + } + return false; + } + } + + /** + * Dumps debugging information on the standard output. + */ + void debug() { + print("callers :"); + callers.forEach((k,v) { + print(" $k: $v"); + }); + print("readers :"); + readers.forEach((k,v) { + print(" $k: $v"); + }); + print("inferredFieldTypes:"); + inferredFieldTypes.forEach((k,v) { + print(" $k: $v"); + }); + print("inferredParameterTypes:"); + inferredParameterTypes.forEach((k,v) { + print(" $k: $v"); + }); + print("cache:"); + cache.forEach((k,v) { + print(" $k: $v"); + }); + print("inferred expression types: "); + inferredTypes.forEach((k,v) { + print(" $k: $v"); + }); + } + + /** + * Fail with a message and abort. + */ + void fail(node, [reason]) { + String message = 'cannot infer types'; + if (reason != null) { + message = '$message: $reason'; + } + throw new CancelTypeInferenceException(node, message); + } +} + +/** + * Represents the concrete types of the arguments of a send, indexed by + * position or name. + */ +class ArgumentsTypes { + final List<ConcreteType> positional; + final Map<Identifier, ConcreteType> named; + ArgumentsTypes(this.positional, this.named); + int get length => positional.length + named.length; + toString() => "{ positional = $positional, named = $named }"; +} + +/** + * The core logic of the type inference algorithm. + */ +class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> { + final ConcreteTypesInferrer inferrer; + + final FunctionElement currentMethod; + ConcreteTypesEnvironment environment; + Node lastSeenNode; + + TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer, + this.environment) + : super(elements); + + ArgumentsTypes analyzeArguments(Link<Node> arguments) { + final positional = new List<ConcreteType>(); + final named = new Map<Identifier, ConcreteType>(); + for(Link<Node> iterator = arguments; + !iterator.isEmpty; + iterator = iterator.tail) { + Node node = iterator.head; + NamedArgument namedArgument = node.asNamedArgument(); + if (namedArgument != null) { + named[namedArgument.name] = analyze(namedArgument.expression); + } else { + positional.add(analyze(node)); + } + } + return new ArgumentsTypes(positional, named); + } + + /** + * A proxy to accept which does book keeping and error reporting. Returns null + * if [node] is a non-returning statement, its inferred concrete type + * otherwise. + */ + ConcreteType analyze(Node node) { + if (node == null) { + final String error = 'internal error: unexpected node: null'; + inferrer.fail(lastSeenNode, error); + } else { + lastSeenNode = node; + } + ConcreteType result = node.accept(this); + if (result == null) { + inferrer.fail(node, 'internal error: inferred type is null'); + } + inferrer.augmentInferredType(node, result); + return result; + } + + ConcreteType visitBlock(Block node) { + return analyze(node.statements); + } + + ConcreteType visitCascade(Cascade node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCascadeReceiver(CascadeReceiver node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitClassNode(ClassNode node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitDoWhile(DoWhile node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitExpressionStatement(ExpressionStatement node) { + analyze(node.expression); + return inferrer.emptyConcreteType; + } + + ConcreteType visitFor(For node) { + if (node.initializer != null) { + analyze(node.initializer); + } + analyze(node.conditionStatement); + ConcreteType result = inferrer.emptyConcreteType; + ConcreteTypesEnvironment oldEnvironment; + do { + oldEnvironment = environment; + analyze(node.conditionStatement); + analyze(node.body); + analyze(node.update); + environment = oldEnvironment.join(environment); + // TODO(polux): Maybe have a destructive join-method that returns a boolean + // value indicating whether something changed to avoid performing this + // comparison twice. + } while (oldEnvironment != environment); + return result; + } + + ConcreteType visitFunctionDeclaration(FunctionDeclaration node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitFunctionExpression(FunctionExpression node) { + return analyze(node.body); + } + + ConcreteType visitIdentifier(Identifier node) { + if (node.isThis()) { + ConcreteType result = environment.lookupTypeOfThis(); + if (result == null) { + inferrer.fail(node, '"this" has no type'); + } + return result; + } + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitIf(If node) { + analyze(node.condition); + ConcreteType thenType = analyze(node.thenPart); + ConcreteTypesEnvironment snapshot = environment; + ConcreteType elseType = node.hasElsePart ? analyze(node.elsePart) + : inferrer.emptyConcreteType; + environment = environment.join(snapshot); + return inferrer.union(thenType, elseType); + } + + ConcreteType visitLoop(Loop node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType analyzeSetElement(Element receiver, ConcreteType argumentType) { + environment = environment.put(receiver, argumentType); + if (receiver.isField()) { + inferrer.augmentFieldType(receiver, argumentType); + } else if (receiver.isSetter()){ + FunctionElement setter = receiver; + // TODO(polux): A setter always returns void so there's no need to + // invalidate its callers even if it is called with new arguments. + // However, if we start to record more than returned types, like + // exceptions for instance, we need to do it by uncommenting the following + // line. + // inferrer.addCaller(setter, currentMethod); + inferrer.getSendReturnType(setter, receiver.enclosingElement, + new ArgumentsTypes([argumentType], new Map())); + } + return argumentType; + } + + ConcreteType analyzeSetNode(Node receiver, ConcreteType argumentType, + SourceString name) { + ConcreteType receiverType = analyze(receiver); + + void augmentField(ClassElement receiverType, Element member) { + if (member.isField()) { + inferrer.augmentFieldType(member, argumentType); + } else if (member.isAbstractField()){ + AbstractFieldElement abstractField = member; + FunctionElement setter = abstractField.setter; + // TODO(polux): A setter always returns void so there's no need to + // invalidate its callers even if it is called with new arguments. + // However, if we start to record more than returned types, like + // exceptions for instance, we need to do it by uncommenting the + // following line. + // inferrer.addCaller(setter, currentMethod); + inferrer.getSendReturnType(setter, receiverType, + new ArgumentsTypes([argumentType], new Map())); + } + // since this is a sendSet we ignore non-fields + } + + if (receiverType.isUnkown()) { + for (Element member in inferrer.getMembersByName(name)) { + if (!(member.isField() || member.isAbstractField())) continue; + Element cls = member.getEnclosingClass(); + augmentField(cls, member); + } + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isClass()) continue; + ClassBaseType baseReceiverClassType = baseReceiverType; + Element member = baseReceiverClassType.element.lookupMember(name); + if (member != null) { + augmentField(baseReceiverClassType.element, member); + } + } + } + return argumentType; + } + + SourceString canonicalizeCompoundOperator(SourceString op) { + // TODO(ahe): This class should work on elements or selectors, not + // names. Otherwise, it is repeating work the resolver has + // already done (or should have done). In this case, the problem + // is that the resolver is not recording the selectors it is + // registering in registerBinaryOperator in + // ResolverVisitor.visitSendSet. + String stringValue = op.stringValue; + if (stringValue == '++') return const SourceString(r'+'); + else if (stringValue == '--') return const SourceString(r'-'); + else return Elements.mapToUserOperatorOrNull(op); + } + + ConcreteType visitSendSet(SendSet node) { + // Operator []= has a different behaviour than other send sets: it is + // actually a send whose return type is that of its second argument. + if (node.selector.asIdentifier().source.stringValue == '[]') { + ConcreteType receiverType = analyze(node.receiver); + ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments); + analyzeDynamicSend(receiverType, const SourceString('[]='), + argumentsTypes); + return argumentsTypes.positional[1]; + } + + // All other operators have a single argument (++ and -- have an implicit + // argument: 1). We will store its type in argumentType. + ConcreteType argumentType; + SourceString operatorName = node.assignmentOperator.source; + SourceString compoundOperatorName = + canonicalizeCompoundOperator(node.assignmentOperator.source); + // ++, --, +=, -=, ... + if (compoundOperatorName != null) { + ConcreteType receiverType = visitGetterSend(node); + // argumentsTypes is either computed from the actual arguments or [{int}] + // in case of ++ or --. + ArgumentsTypes argumentsTypes; + if (operatorName.stringValue == '++' + || operatorName.stringValue == '--') { + List<ConcreteType> positionalArguments = <ConcreteType>[ + inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType)]; + argumentsTypes = new ArgumentsTypes(positionalArguments, new Map()); + } else { + argumentsTypes = analyzeArguments(node.arguments); + } + argumentType = analyzeDynamicSend(receiverType, compoundOperatorName, + argumentsTypes); + // The simple assignment case: receiver = argument. + } else { + argumentType = analyze(node.argumentsNode); + } + + Element element = elements[node]; + if (element != null) { + return analyzeSetElement(element, argumentType); + } else { + return analyzeSetNode(node.receiver, argumentType, + node.selector.asIdentifier().source); + } + } + + ConcreteType visitLiteralInt(LiteralInt node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType); + } + + ConcreteType visitLiteralDouble(LiteralDouble node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.doubleBaseType); + } + + ConcreteType visitLiteralBool(LiteralBool node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } + + ConcreteType visitLiteralString(LiteralString node) { + // TODO(polux): get rid of this hack once we have a natural way of inferring + // the unknown type. + if (inferrer.testMode + && node.dartString.slowToString() == "__dynamic_for_test") { + return inferrer.unknownConcreteType; + } + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitStringJuxtaposition(StringJuxtaposition node) { + analyze(node.first); + analyze(node.second); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitLiteralNull(LiteralNull node) { + return inferrer.singletonConcreteType(const NullBaseType()); + } + + ConcreteType visitNewExpression(NewExpression node) { + Element constructor = elements[node.send]; + inferrer.addCaller(constructor, currentMethod); + ClassElement cls = constructor.enclosingElement; + return inferrer.getSendReturnType(constructor, cls, + analyzeArguments(node.send.arguments)); + } + + ConcreteType visitLiteralList(LiteralList node) { + ConcreteType elementsType = new ConcreteType.empty(); + // We compute the union of the types of the list literal's elements. + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + elementsType = inferrer.union(elementsType, analyze(link.head)); + } + inferrer.augmentListElementType(elementsType); + return inferrer.singletonConcreteType(inferrer.baseTypes.listBaseType); + } + + ConcreteType visitNodeList(NodeList node) { + ConcreteType type = inferrer.emptyConcreteType; + // The concrete type of a sequence of statements is the union of the + // statement's types. + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + type = inferrer.union(type, analyze(link.head)); + } + return type; + } + + ConcreteType visitOperator(Operator node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitReturn(Return node) { + final expression = node.expression; + return (expression == null) + ? inferrer.singletonConcreteType(const NullBaseType()) + : analyze(expression); + } + + ConcreteType visitThrow(Throw node) { + if (node.expression != null) analyze(node.expression); + return inferrer.emptyConcreteType; + } + + ConcreteType visitTypeAnnotation(TypeAnnotation node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTypeVariable(TypeVariable node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; !link.isEmpty; + link = link.tail) { + analyze(link.head); + } + return inferrer.emptyConcreteType; + } + + ConcreteType visitWhile(While node) { + analyze(node.condition); + ConcreteType result = inferrer.emptyConcreteType; + ConcreteTypesEnvironment oldEnvironment; + do { + oldEnvironment = environment; + analyze(node.condition); + analyze(node.body); + environment = oldEnvironment.join(environment); + } while (oldEnvironment != environment); + return result; + } + + ConcreteType visitParenthesizedExpression(ParenthesizedExpression node) { + return analyze(node.expression); + } + + ConcreteType visitConditional(Conditional node) { + analyze(node.condition); + ConcreteType thenType = analyze(node.thenExpression); + ConcreteType elseType = analyze(node.elseExpression); + return inferrer.union(thenType, elseType); + } + + ConcreteType visitModifiers(Modifiers node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitStringInterpolationPart(StringInterpolationPart node) { + node.visitChildren(this); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitEmptyStatement(EmptyStatement node) { + return inferrer.emptyConcreteType; + } + + ConcreteType visitBreakStatement(BreakStatement node) { + return inferrer.emptyConcreteType; + } + + ConcreteType visitContinueStatement(ContinueStatement node) { + // TODO(polux): we can be more precise + return inferrer.emptyConcreteType; + } + + ConcreteType visitForIn(ForIn node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitLabel(Label node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitLabeledStatement(LabeledStatement node) { + return analyze(node.statement); + } + + ConcreteType visitLiteralMap(LiteralMap node) { + visitNodeList(node.entries); + return inferrer.singletonConcreteType(inferrer.baseTypes.mapBaseType); + } + + ConcreteType visitLiteralMapEntry(LiteralMapEntry node) { + // We don't need to visit the key, it's always a string. + return analyze(node.value); + } + + ConcreteType visitNamedArgument(NamedArgument node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitSwitchStatement(SwitchStatement node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitSwitchCase(SwitchCase node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCaseMatch(CaseMatch node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTryStatement(TryStatement node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitScriptTag(ScriptTag node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCatchBlock(CatchBlock node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTypedef(Typedef node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitSuperSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitOperatorSend(Send node) { + SourceString name = + canonicalizeMethodName(node.selector.asIdentifier().source); + if (name == const SourceString('is')) { + return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } + return visitDynamicSend(node); + } + + ConcreteType analyzeFieldRead(Element field) { + inferrer.addReader(field, currentMethod); + return inferrer.getFieldType(field); + } + + ConcreteType analyzeGetterSend(ClassElement receiverType, + FunctionElement getter) { + inferrer.addCaller(getter, currentMethod); + return inferrer.getSendReturnType(getter, + receiverType, + new ArgumentsTypes([], new Map())); + } + + ConcreteType visitGetterSend(Send node) { + Element element = elements[node]; + if (element != null) { + // node is a local variable or a field of this + ConcreteType result = environment.lookupType(element); + if (result != null) { + // node is a local variable + return result; + } else { + // node is a field or a getter of this + if (element.isField()) { + return analyzeFieldRead(element); + } else { + assert(element.isGetter()); + ClassElement receiverType = element.enclosingElement; + return analyzeGetterSend(receiverType, element); + } + } + } else { + // node is a field of not(this) + assert(node.receiver != null); + + ConcreteType result = inferrer.emptyConcreteType; + void augmentResult(ClassElement baseReceiverType, Element member) { + if (member.isField()) { + result = inferrer.union(result, analyzeFieldRead(member)); + } else if (member.isAbstractField()){ + // call to a getter + AbstractFieldElement abstractField = member; + result = inferrer.union( + result, + analyzeGetterSend(baseReceiverType, abstractField.getter)); + } + // since this is a get we ignore non-fields + } + + ConcreteType receiverType = analyze(node.receiver); + if (receiverType.isUnkown()) { + List<Element> members = + inferrer.getMembersByName(node.selector.asIdentifier().source); + for (Element member in members) { + if (!(member.isField() || member.isAbstractField())) continue; + Element cls = member.getEnclosingClass(); + augmentResult(cls, member); + } + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isNull()) { + ClassBaseType classBaseType = baseReceiverType; + ClassElement cls = classBaseType.element; + Element getterOrField = + cls.lookupMember(node.selector.asIdentifier().source); + if (getterOrField != null) { + augmentResult(cls, getterOrField); + } + } + } + } + return result; + } + } + + ConcreteType visitClosureSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType analyzeDynamicSend(ConcreteType receiverType, + SourceString canonicalizedMethodName, + ArgumentsTypes argumentsTypes) { + ConcreteType result = inferrer.emptyConcreteType; + + if (receiverType.isUnkown()) { + List<Element> methods = + inferrer.getMembersByName(canonicalizedMethodName); + for (Element element in methods) { + // TODO(polux): when we handle closures, we must handle sends to fields + // that are closures. + if (!element.isFunction()) continue; + FunctionElement method = element; + inferrer.addCaller(method, currentMethod); + Element cls = method.enclosingElement; + result = inferrer.union( + result, + inferrer.getSendReturnType(method, cls, argumentsTypes)); + } + + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isNull()) { + ClassBaseType classBaseReceiverType = baseReceiverType; + ClassElement cls = classBaseReceiverType.element; + FunctionElement method = cls.lookupMember(canonicalizedMethodName); + if (method != null) { + inferrer.addCaller(method, currentMethod); + result = inferrer.union( + result, + inferrer.getSendReturnType(method, cls, argumentsTypes)); + } + } + } + } + return result; + } + + SourceString canonicalizeMethodName(SourceString name) { + // TODO(polux): handle unary- + SourceString operatorName = + Elements.constructOperatorNameOrNull(name, false); + if (operatorName != null) return operatorName; + return name; + } + + ConcreteType visitDynamicSend(Send node) { + ConcreteType receiverType = (node.receiver != null) + ? analyze(node.receiver) + : inferrer.singletonConcreteType( + new ClassBaseType(currentMethod.getEnclosingClass())); + SourceString name = + canonicalizeMethodName(node.selector.asIdentifier().source); + ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments); + if (name.stringValue == '!=') { + ConcreteType returnType = analyzeDynamicSend(receiverType, + const SourceString('=='), + argumentsTypes); + return returnType.isEmpty() + ? returnType + : inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } else { + return analyzeDynamicSend(receiverType, name, argumentsTypes); + } + } + + ConcreteType visitForeignSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitStaticSend(Send node) { + Element element = elements[node]; + inferrer.addCaller(element, currentMethod); + return inferrer.getSendReturnType(element, null, + analyzeArguments(node.arguments)); + } + + void internalError(String reason, {Node node}) { + inferrer.fail(node, reason); + } + + ConcreteType visitTypeReferenceSend(Send) { + return inferrer.singletonConcreteType(inferrer.baseTypes.typeBaseType); + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/types/types.dart b/pkgs/markdown/lib/src/compiler/implementation/types/types.dart new file mode 100644 index 0000000..fdce427 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/types/types.dart
@@ -0,0 +1,260 @@ +// 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. + +library types; + +import 'dart:collection' show Queue; + +import '../dart2jslib.dart' hide Selector; +import '../js_backend/js_backend.dart' show JavaScriptBackend; +import '../tree/tree.dart'; +import '../elements/elements.dart'; +import '../util/util.dart'; +import '../universe/universe.dart'; + +part 'concrete_types_inferrer.dart'; + +/** + * The types task infers guaranteed types globally. + */ +class TypesTask extends CompilerTask { + final String name = 'Type inference'; + final Set<Element> untypedElements; + final Map<Element, Link<Element>> typedSends; + ConcreteTypesInferrer concreteTypesInferrer; + + TypesTask(Compiler compiler) + : untypedElements = new Set<Element>(), + typedSends = new Map<Element, Link<Element>>(), + concreteTypesInferrer = compiler.enableConcreteTypeInference + ? new ConcreteTypesInferrer(compiler) : null, + super(compiler); + + /** + * Called once for each method during the resolution phase of the + * compiler. + */ + void analyze(Node node, TreeElements elements) { + measure(() { + node.accept(new ConcreteTypeInferencer(this, elements)); + }); + } + + /** + * Called when resolution is complete. + */ + void onResolutionComplete(Element mainElement) { + measure(() { + if (concreteTypesInferrer != null) { + bool success = concreteTypesInferrer.analyzeMain(mainElement); + if (!success) { + // If the concrete type inference bailed out, we pretend it didn't + // happen. In the future we might want to record that it failed but + // use the partial results as hints. + concreteTypesInferrer = null; + } + } + }); + } + + /** + * Return the (inferred) guaranteed concrete type of [element] or null. + */ + ConcreteType getGuaranteedTypeOfElement(Element element) { + return measure(() { + if (!element.isParameter()) return null; + if (concreteTypesInferrer != null) { + ConcreteType guaranteedType = concreteTypesInferrer + .getConcreteTypeOfParameter(element); + if (guaranteedType != null) return guaranteedType; + } + Element holder = element.enclosingElement; + Link<Element> types = typedSends[holder]; + if (types == null) return null; + if (!holder.isFunction()) return null; + if (untypedElements.contains(holder)) return null; + FunctionElement function = holder; + FunctionSignature signature = function.computeSignature(compiler); + for (Element parameter in signature.requiredParameters) { + if (types.isEmpty) return null; + if (element == parameter) { + return new ConcreteType.singleton(compiler.maxConcreteTypeSize, + new ClassBaseType(types.head)); + } + types = types.tail; + } + return null; + }); + } + + /** + * Return the (inferred) guaranteed concrete type of [node] or null. + * [node] must be an AST node of [owner]. + */ + ConcreteType getGuaranteedTypeOfNode(Node node, Element owner) { + return measure(() { + if (concreteTypesInferrer != null) { + return concreteTypesInferrer.getConcreteTypeOfNode(node); + } + return null; + }); + } +} + +/** + * Infers concrete types for a single method or expression. + */ +class ConcreteTypeInferencer extends Visitor { + final TypesTask task; + final TreeElements elements; + final ClassElement boolClass; + final ClassElement doubleClass; + final ClassElement intClass; + final ClassElement listClass; + final ClassElement nullClass; + final ClassElement stringClass; + + final Map<Node, ClassElement> concreteTypes; + + ConcreteTypeInferencer(TypesTask task, this.elements) + : this.task = task, + this.boolClass = task.compiler.boolClass, + this.doubleClass = task.compiler.doubleClass, + this.intClass = task.compiler.intClass, + this.listClass = task.compiler.listClass, + this.nullClass = task.compiler.nullClass, + this.stringClass = task.compiler.stringClass, + this.concreteTypes = new Map<Node, ClassElement>(); + + visitNode(Node node) => node.visitChildren(this); + + visitLiteralString(LiteralString node) { + recordConcreteType(node, stringClass); + } + + visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + recordConcreteType(node, stringClass); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + node.visitChildren(this); + recordConcreteType(node, stringClass); + } + + recordConcreteType(Node node, ClassElement cls) { + concreteTypes[node] = cls; + } + + visitLiteralBool(LiteralBool node) { + recordConcreteType(node, boolClass); + } + + visitLiteralDouble(LiteralDouble node) { + recordConcreteType(node, doubleClass); + } + + visitLiteralInt(LiteralInt node) { + recordConcreteType(node, intClass); + } + + visitLiteralList(LiteralList node) { + node.visitChildren(this); + recordConcreteType(node, listClass); + } + + visitLiteralMap(LiteralMap node) { + node.visitChildren(this); + // TODO(ahe): map class? + } + + visitLiteralNull(LiteralNull node) { + recordConcreteType(node, nullClass); + } + + Link<Element> computeConcreteSendArguments(Send node) { + if (node.argumentsNode == null) return null; + if (node.arguments.isEmpty) return const Link<Element>(); + if (node.receiver != null && concreteTypes[node.receiver] == null) { + return null; + } + LinkBuilder<Element> types = new LinkBuilder<Element>(); + for (Node argument in node.arguments) { + Element type = concreteTypes[argument]; + if (type == null) return null; + types.addLast(type); + } + return types.toLink(); + } + + visitSend(Send node) { + node.visitChildren(this); + Element element = elements[node.selector]; + if (element == null) return; + if (!Elements.isStaticOrTopLevelFunction(element)) return; + if (node.argumentsNode == null) { + // interest(node, 'closurized method'); + task.untypedElements.add(element); + return; + } + Link<Element> types = computeConcreteSendArguments(node); + if (types != null) { + Link<Element> existing = task.typedSends[element]; + if (existing == null) { + task.typedSends[element] = types; + } else { + // interest(node, 'multiple invocations'); + Link<Element> lub = computeLubs(existing, types); + if (lub == null) { + task.untypedElements.add(element); + } else { + task.typedSends[element] = lub; + } + } + } else { + // interest(node, 'dynamically typed invocation'); + task.untypedElements.add(element); + } + } + + visitSendSet(SendSet node) { + // TODO(ahe): Implement this. For now, overridden to avoid calling + // visitSend through super. + node.visitChildren(this); + } + + void interest(Node node, String note) { + var message = MessageKind.GENERIC.message({'text': note}); + task.compiler.reportWarning(node, message); + } + + /** + * Computes the pairwise Least Upper Bound (LUB) of the elements of + * [a] and [b]. Returns [:null:] if it gives up, or if the lists + * aren't the same length. + */ + Link<Element> computeLubs(Link<Element> a, Link<Element> b) { + LinkBuilder<Element> lubs = new LinkBuilder<Element>(); + while (!a.isEmpty && !b.isEmpty) { + Element lub = computeLub(a.head, b.head); + if (lub == null) return null; + lubs.addLast(lub); + a = a.tail; + b = b.tail; + } + return (a.isEmpty && b.isEmpty) ? lubs.toLink() : null; + } + + /** + * Computes the Least Upper Bound (LUB) of [a] and [b]. Returns + * [:null:] if it gives up. + */ + Element computeLub(Element a, Element b) { + // Fast common case, but also simple initial implementation. + if (identical(a, b)) return a; + + // TODO(ahe): Improve the following "computation"... + return null; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart new file mode 100644 index 0000000..23bc782 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart
@@ -0,0 +1,140 @@ +// 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. + +part of universe; + +// TODO(kasperl): This actually holds getters and setters just fine +// too and stricly they aren't functions. Maybe this needs a better +// name -- something like ElementSet seems a bit too generic. +class FunctionSet extends PartialTypeTree { + + FunctionSet(Compiler compiler) : super(compiler); + + FunctionSetNode newSpecializedNode(ClassElement type) + => new FunctionSetNode(type); + + // TODO(kasperl): Allow static members too? + void add(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), true); + node.membersByName[element.name] = element; + } + + // TODO(kasperl): Allow static members too? + void remove(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), false); + if (node != null) node.membersByName.remove(element.name); + } + + // TODO(kasperl): Allow static members too? + bool contains(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), false); + return (node != null) + ? node.membersByName.containsKey(element.name) + : false; + } + + /** + * Returns all elements that may be invoked with the given [selector]. + */ + Set<Element> filterBySelector(Selector selector) { + // TODO(kasperl): For now, we use a different implementation for + // filtering if the tree contains interface subtypes. + return containsInterfaceSubtypes + ? filterAllBySelector(selector) + : filterHierarchyBySelector(selector); + } + + /** + * Returns whether the set has any element matching the given + * [selector]. + */ + bool hasAnyElementMatchingSelector(Selector selector) { + // TODO(kasperl): For now, we use a different implementation for + // filtering if the tree contains interface subtypes. + return containsInterfaceSubtypes + ? hasAnyInAll(selector) + : hasAnyInHierarchy(selector); + } + + Set<Element> filterAllBySelector(Selector selector) { + Set<Element> result = new Set<Element>(); + if (root == null) return result; + root.visitRecursively((FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (member != null && selector.appliesUnnamed(member, compiler)) { + result.add(member); + } + return true; + }); + return result; + } + + Set<Element> filterHierarchyBySelector(Selector selector) { + Set<Element> result = new Set<Element>(); + if (root == null) return result; + visitHierarchy(selectorType(selector), (FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + if (member != null && selector.appliesUntyped(member, compiler)) { + result.add(member); + } + return true; + }); + return result; + } + + bool hasAnyInAll(Selector selector) { + bool result = false; + if (root == null) return result; + root.visitRecursively((FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (member != null && selector.appliesUnnamed(member, compiler)) { + result = true; + // End the traversal. + return false; + } + return true; + }); + return result; + } + + bool hasAnyInHierarchy(Selector selector) { + bool result = false; + if (root == null) return result; + visitHierarchy(selectorType(selector), (FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + if (member != null && selector.appliesUntyped(member, compiler)) { + result = true; + // End the traversal. + return false; + } + return true; + }); + return result; + } + + void forEach(Function f) { + if (root == null) return; + root.visitRecursively((FunctionSetNode node) { + node.membersByName.forEach( + (SourceString _, Element element) => f(element)); + return true; + }); + } +} + +class FunctionSetNode extends PartialTypeTreeNode { + + final Map<SourceString, Element> membersByName; + + FunctionSetNode(ClassElement type) : super(type), + membersByName = new Map<SourceString, Element>(); + +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart new file mode 100644 index 0000000..f310174 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart
@@ -0,0 +1,190 @@ +// 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. + +part of universe; + +abstract class PartialTypeTree { + + final Compiler compiler; + PartialTypeTreeNode root; + + // TODO(kasperl): This should be final but the VM will not allow + // that without making the map a compile-time constant. + Map<ClassElement, PartialTypeTreeNode> nodes = + new Map<ClassElement, PartialTypeTreeNode>(); + + // TODO(kasperl): For now, we keep track of whether or not the tree + // contains two classes with a subtype relationship that isn't a + // subclass relationship. + bool containsInterfaceSubtypes = false; + + // TODO(kasperl): This should be final but the VM will not allow + // that without making the set a compile-time constant. + Set<ClassElement> unseenInterfaceSubtypes = + new Set<ClassElement>(); + + PartialTypeTree(this.compiler); + + PartialTypeTreeNode newSpecializedNode(ClassElement type); + + PartialTypeTreeNode newNode(ClassElement type) { + PartialTypeTreeNode node = newSpecializedNode(type); + nodes[type] = node; + if (containsInterfaceSubtypes) return node; + + // Check if the implied interface of the new class is implemented + // by another class that is already in the tree. + if (unseenInterfaceSubtypes.contains(type)) { + containsInterfaceSubtypes = true; + unseenInterfaceSubtypes.clear(); + return node; + } + + // Run through all the implied interfaces the class that we're + // adding implements and see if any of them are already in the + // tree. If so, we have a tree with interface subtypes. If not, + // keep track of them so we can deal with it if the interface is + // added to the tree later. + for (Link link = type.interfaces; !link.isEmpty; link = link.tail) { + InterfaceType superType = link.head; + ClassElement superTypeElement = superType.element; + if (nodes.containsKey(superTypeElement)) { + containsInterfaceSubtypes = true; + unseenInterfaceSubtypes.clear(); + break; + } else { + unseenInterfaceSubtypes.add(superTypeElement); + } + } + return node; + } + + // TODO(kasperl): Move this to the Selector class? + /** + * Returns a [ClassElement] that is an upper bound of the receiver type on + * [selector]. + */ + ClassElement selectorType(Selector selector) { + // TODO(ngeoffray): Should the tree be specialized with DartType? + DartType type = selector.receiverType; + if (type == null) return compiler.objectClass; + // TODO(kasperl): Should [dynamic] return Object? + if (identical(type.kind, TypeKind.MALFORMED_TYPE)) + return compiler.objectClass; + // TODO(johnniwinther): Change to use [DartType.unalias]. + if (type.element.isTypedef()) return compiler.functionClass; + return type.element; + } + + /** + * Finds the tree node corresponding to the given [type]. If [insert] + * is true, we always return a node that matches the type by + * inserting a new node if necessary. If [insert] is false, we + * return null if we cannot find a node that matches the [type]. + */ + PartialTypeTreeNode findNode(ClassElement type, bool insert) { + if (root == null) { + if (!insert) return null; + root = newNode(compiler.objectClass); + } + + PartialTypeTreeNode current = root; + L: while (!identical(current.type, type)) { + assert(type.isSubclassOf(current.type)); + + // Run through the children. If we find a subtype of the type + // we are looking for we go that way. If not, we keep track of + // the subtypes so we can move them from being children of the + // current node to being children of a new node if we need + // to insert that. + Link<PartialTypeTreeNode> subtypes = const Link(); + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + ClassElement childType = child.type; + if (type.isSubclassOf(childType)) { + assert(subtypes.isEmpty); + current = child; + continue L; + } else if (childType.isSubclassOf(type)) { + if (insert) subtypes = subtypes.prepend(child); + } + } + + // If we are not inserting any nodes, we are done. + if (!insert) return null; + + // Create a new node and move the children of the current node + // that are subtypes of the type of the new node below the new + // node in the hierarchy. + PartialTypeTreeNode node = newNode(type); + if (!subtypes.isEmpty) { + node.children = subtypes; + Link<PartialTypeTreeNode> remaining = const Link(); + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + if (!child.type.isSubclassOf(type)) { + remaining = remaining.prepend(child); + } + } + current.children = remaining; + } + + // Add the new node as a child node of the current node and return it. + current.children = current.children.prepend(node); + return node; + } + + // We found an exact match. No need to insert new nodes. + assert(identical(current.type, type)); + return current; + } + + /** + * Visits all superclass and subclass nodes for the given [type]. If + * the [visit] function ever returns false, we abort the traversal. + */ + void visitHierarchy(ClassElement type, bool visit(PartialTypeTreeNode node)) { + assert(!containsInterfaceSubtypes); + PartialTypeTreeNode current = root; + L: while (!identical(current.type, type)) { + assert(type.isSubclassOf(current.type)); + if (!visit(current)) return; + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + ClassElement childType = child.type; + if (type.isSubclassOf(childType)) { + current = child; + continue L; + } else if (childType.isSubclassOf(type)) { + if (!child.visitRecursively(visit)) return; + } + } + return; + } + current.visitRecursively(visit); + } + +} + +class PartialTypeTreeNode { + + final ClassElement type; + Link<PartialTypeTreeNode> children; + + PartialTypeTreeNode(this.type) : children = const Link(); + + /** + * Visits this node and its children recursively. If the visit + * callback ever returns false, the visiting stops early. + */ + bool visitRecursively(bool visit(PartialTypeTreeNode node)) { + if (!visit(this)) return false; + for (Link link = children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + if (!child.visitRecursively(visit)) return false; + } + return true; + } + +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart new file mode 100644 index 0000000..cf98a10 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart
@@ -0,0 +1,132 @@ +// 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. + +part of universe; + +class SelectorMap<T> extends PartialTypeTree { + + SelectorMap(Compiler compiler) : super(compiler); + + SelectorMapNode<T> newSpecializedNode(ClassElement type) + => new SelectorMapNode<T>(type); + + T operator [](Selector selector) { + SelectorMapNode<T> node = findNode(selectorType(selector), false); + if (node == null) return null; + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return null; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + if (existing.selector.equalsUntyped(selector)) return existing.value; + } + return null; + } + + void operator []=(Selector selector, T value) { + SelectorMapNode<T> node = findNode(selectorType(selector), true); + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) { + // No existing selectors with the given name. Create a new + // linked list. + SelectorValue<T> head = new SelectorValue<T>(selector, value); + node.selectorsByName[selector.name] = + new Link<SelectorValue<T>>().prepend(head); + } else { + // Run through the linked list of selectors with the same name. If + // we find one that matches, we update the value in the mapping. + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + // It is safe to ignore the type here, because all selector + // mappings that are stored in a single node have the same type. + if (existing.selector.equalsUntyped(selector)) { + existing.value = value; + return; + } + } + // We could not find an existing mapping for the selector, so + // we add a new one to the existing linked list. + SelectorValue<T> head = new SelectorValue<T>(selector, value); + node.selectorsByName[selector.name] = selectors.prepend(head); + } + } + + // TODO(kasperl): Share code with the [] operator? + bool containsKey(Selector selector) { + SelectorMapNode<T> node = findNode(selectorType(selector), false); + if (node == null) return false; + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return false; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + if (existing.selector.equalsUntyped(selector)) return true; + } + return false; + } + + /** + * Visits all mappings for selectors that may be used to invoke the + * given [member] element. If the [visit] function ever returns false, + * we abort the traversal early. + */ + void visitMatching(Element member, bool visit(Selector selector, T value)) { + assert(member.isMember()); + if (root == null) return; + // TODO(kasperl): For now, we use a different implementation for + // visiting if the tree contains interface subtypes. + if (containsInterfaceSubtypes) { + visitAllMatching(member, visit); + } else { + visitHierarchyMatching(member, visit); + } + } + + void visitAllMatching(Element member, bool visit(selector, value)) { + root.visitRecursively((SelectorMapNode<T> node) { + Link<SelectorValue<T>> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + Selector selector = existing.selector; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (selector.appliesUnnamed(member, compiler)) { + if (!visit(selector, existing.value)) return false; + } + } + return true; + }); + } + + void visitHierarchyMatching(Element member, bool visit(selector, value)) { + visitHierarchy(member.getEnclosingClass(), (SelectorMapNode<T> node) { + Link<SelectorValue<T>> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + Selector selector = existing.selector; + if (selector.appliesUntyped(member, compiler)) { + if (!visit(selector, existing.value)) return false; + } + } + return true; + }); + } + +} + +class SelectorMapNode<T> extends PartialTypeTreeNode { + + final Map<SourceString, Link<SelectorValue<T>>> selectorsByName; + + SelectorMapNode(ClassElement type) : super(type), + selectorsByName = new Map<SourceString, Link<SelectorValue<T>>>(); + +} + +class SelectorValue<T> { + final Selector selector; + T value; + SelectorValue(this.selector, this.value); + toString() => "$selector -> $value"; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart b/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart new file mode 100644 index 0000000..6b64382 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart
@@ -0,0 +1,495 @@ +// 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. + +library universe; + +import '../closure.dart'; +import '../elements/elements.dart'; +import '../dart2jslib.dart'; +import '../dart_types.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../js/js.dart' as js; + +part 'function_set.dart'; +part 'partial_type_tree.dart'; +part 'selector_map.dart'; + +class Universe { + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements are declaration elements. + */ + // TODO(karlklose): these sets should be merged. + final Set<ClassElement> instantiatedClasses; + final Set<DartType> instantiatedTypes; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements are declaration elements. + */ + final Set<FunctionElement> staticFunctionsNeedingGetter; + final Map<SourceString, Set<Selector>> invokedNames; + final Map<SourceString, Set<Selector>> invokedGetters; + final Map<SourceString, Set<Selector>> invokedSetters; + + /** + * Fields accessed. Currently only the codegen knows this + * information. The resolver is too conservative when seeing a + * getter and only registers an invoked getter. + */ + final Map<SourceString, Set<Selector>> fieldGetters; + + /** + * Fields set. See comment in [fieldGetters]. + */ + final Map<SourceString, Set<Selector>> fieldSetters; + final Set<DartType> isChecks; + + Universe() : instantiatedClasses = new Set<ClassElement>(), + instantiatedTypes = new Set<DartType>(), + staticFunctionsNeedingGetter = new Set<FunctionElement>(), + invokedNames = new Map<SourceString, Set<Selector>>(), + invokedGetters = new Map<SourceString, Set<Selector>>(), + fieldGetters = new Map<SourceString, Set<Selector>>(), + fieldSetters = new Map<SourceString, Set<Selector>>(), + invokedSetters = new Map<SourceString, Set<Selector>>(), + isChecks = new Set<DartType>(); + + bool hasMatchingSelector(Set<Selector> selectors, + Element member, + Compiler compiler) { + if (selectors == null) return false; + for (Selector selector in selectors) { + if (selector.appliesUnnamed(member, compiler)) return true; + } + return false; + } + + bool hasInvocation(Element member, Compiler compiler) { + return hasMatchingSelector(invokedNames[member.name], member, compiler); + } + + bool hasInvokedGetter(Element member, Compiler compiler) { + return hasMatchingSelector(invokedGetters[member.name], member, compiler); + } + + bool hasInvokedSetter(Element member, Compiler compiler) { + return hasMatchingSelector(invokedSetters[member.name], member, compiler); + } + + bool hasFieldGetter(Element member, Compiler compiler) { + return hasMatchingSelector(fieldGetters[member.name], member, compiler); + } + + bool hasFieldSetter(Element member, Compiler compiler) { + return hasMatchingSelector(fieldSetters[member.name], member, compiler); + } +} + +class SelectorKind { + final String name; + const SelectorKind(this.name); + + static const SelectorKind GETTER = const SelectorKind('getter'); + static const SelectorKind SETTER = const SelectorKind('setter'); + static const SelectorKind CALL = const SelectorKind('call'); + static const SelectorKind OPERATOR = const SelectorKind('operator'); + static const SelectorKind INDEX = const SelectorKind('index'); + + toString() => name; +} + +class Selector { + final SelectorKind kind; + final SourceString name; + final LibraryElement library; // Library is null for non-private selectors. + + // The numbers of arguments of the selector. Includes named arguments. + final int argumentCount; + final List<SourceString> namedArguments; + final List<SourceString> orderedNamedArguments; + + Selector( + this.kind, + SourceString name, + LibraryElement library, + this.argumentCount, + [List<SourceString> namedArguments = const <SourceString>[]]) + : this.name = name, + this.library = name.isPrivate() ? library : null, + this.namedArguments = namedArguments, + this.orderedNamedArguments = namedArguments.isEmpty + ? namedArguments + : <SourceString>[] { + assert(!name.isPrivate() || library != null); + } + + Selector.getter(SourceString name, LibraryElement library) + : this(SelectorKind.GETTER, name, library, 0); + + Selector.getterFrom(Selector selector) + : this(SelectorKind.GETTER, selector.name, selector.library, 0); + + Selector.setter(SourceString name, LibraryElement library) + : this(SelectorKind.SETTER, name, library, 1); + + Selector.unaryOperator(SourceString name) + : this(SelectorKind.OPERATOR, + Elements.constructOperatorName(name, true), + null, 0); + + Selector.binaryOperator(SourceString name) + : this(SelectorKind.OPERATOR, + Elements.constructOperatorName(name, false), + null, 1); + + Selector.index() + : this(SelectorKind.INDEX, + Elements.constructOperatorName(const SourceString("[]"), false), + null, 1); + + Selector.indexSet() + : this(SelectorKind.INDEX, + Elements.constructOperatorName(const SourceString("[]="), false), + null, 2); + + Selector.call(SourceString name, + LibraryElement library, + int arity, + [List<SourceString> named = const []]) + : this(SelectorKind.CALL, name, library, arity, named); + + Selector.callClosure(int arity, [List<SourceString> named = const []]) + : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null, + arity, named); + + Selector.callClosureFrom(Selector selector) + : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null, + selector.argumentCount, selector.namedArguments); + + Selector.callConstructor(SourceString constructorName, + LibraryElement library) + : this(SelectorKind.CALL, + constructorName, + library, + 0, + const []); + + Selector.callDefaultConstructor(LibraryElement library) + : this(SelectorKind.CALL, const SourceString(""), library, 0, const []); + + // TODO(kasperl): This belongs somewhere else. + Selector.noSuchMethod() + : this(SelectorKind.CALL, Compiler.NO_SUCH_METHOD, null, + Compiler.NO_SUCH_METHOD_ARG_COUNT); + + bool isGetter() => identical(kind, SelectorKind.GETTER); + bool isSetter() => identical(kind, SelectorKind.SETTER); + bool isCall() => identical(kind, SelectorKind.CALL); + bool isClosureCall() { + SourceString callName = Compiler.CALL_OPERATOR_NAME; + return isCall() && name == callName; + } + + bool isIndex() => identical(kind, SelectorKind.INDEX) && argumentCount == 1; + bool isIndexSet() => identical(kind, SelectorKind.INDEX) && argumentCount == 2; + + bool isOperator() => identical(kind, SelectorKind.OPERATOR); + bool isUnaryOperator() => isOperator() && argumentCount == 0; + bool isBinaryOperator() => isOperator() && argumentCount == 1; + + /** Check whether this is a call to 'assert'. */ + bool isAssert() => isCall() && identical(name.stringValue, "assert"); + + int get hashCode => argumentCount + 1000 * namedArguments.length; + int get namedArgumentCount => namedArguments.length; + int get positionalArgumentCount => argumentCount - namedArgumentCount; + DartType get receiverType => null; + + Selector get asUntyped => this; + + /** + * The member name for invocation mirrors created from this selector. + */ + String get invocationMirrorMemberName => + isSetter() ? '${name.slowToString()}=' : name.slowToString(); + + int get invocationMirrorKind { + const int METHOD = 0; + const int GETTER = 1; + const int SETTER = 2; + int kind = METHOD; + if (isGetter()) { + kind = GETTER; + } else if (isSetter()) { + kind = SETTER; + } + return kind; + } + + bool appliesUnnamed(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + return appliesUntyped(element, compiler); + } + + bool appliesUntyped(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + if (Elements.isUnresolved(element)) return false; + if (name.isPrivate() && library != element.getLibrary()) return false; + if (element.isForeign(compiler)) return true; + if (element.isSetter()) return isSetter(); + if (element.isGetter()) return isGetter() || isCall(); + if (element.isField()) return isGetter() || isSetter() || isCall(); + if (isGetter()) return true; + if (isSetter()) return false; + + FunctionElement function = element; + FunctionSignature parameters = function.computeSignature(compiler); + if (argumentCount > parameters.parameterCount) return false; + int requiredParameterCount = parameters.requiredParameterCount; + int optionalParameterCount = parameters.optionalParameterCount; + if (positionalArgumentCount < requiredParameterCount) return false; + + if (!parameters.optionalParametersAreNamed) { + // We have already checked that the number of arguments are + // not greater than the number of parameters. Therefore the + // number of positional arguments are not greater than the + // number of parameters. + assert(positionalArgumentCount <= parameters.parameterCount); + return namedArguments.isEmpty; + } else { + if (positionalArgumentCount > requiredParameterCount) return false; + assert(positionalArgumentCount == requiredParameterCount); + if (namedArgumentCount > optionalParameterCount) return false; + Set<SourceString> nameSet = new Set<SourceString>(); + parameters.optionalParameters.forEach((Element element) { + nameSet.add(element.name); + }); + for (SourceString name in namedArguments) { + if (!nameSet.contains(name)) return false; + // TODO(5213): By removing from the set we are checking + // that we are not passing the name twice. We should have this + // check in the resolver also. + nameSet.remove(name); + } + return true; + } + } + + bool sameNameHack(Element element, Compiler compiler) { + // TODO(ngeoffray): Remove workaround checks. + return element == compiler.assertMethod + || element.isConstructor() + || name == element.name; + } + + bool applies(Element element, Compiler compiler) { + if (!sameNameHack(element, compiler)) return false; + return appliesUnnamed(element, compiler); + } + + /** + * Fills [list] with the arguments in a defined order. + * + * [compileArgument] is a function that returns a compiled version + * of an argument located in [arguments]. + * + * [compileConstant] is a function that returns a compiled constant + * of an optional argument that is not in [arguments. + * + * Returns [:true:] if the selector and the [element] match; [:false:] + * otherwise. + * + * Invariant: [element] must be the implementation element. + */ + bool addArgumentsToList(Link<Node> arguments, + List list, + FunctionElement element, + compileArgument(Node argument), + compileConstant(Element element), + Compiler compiler) { + assert(invariant(element, element.isImplementation)); + if (!this.applies(element, compiler)) return false; + + FunctionSignature parameters = element.computeSignature(compiler); + parameters.forEachRequiredParameter((element) { + list.add(compileArgument(arguments.head)); + arguments = arguments.tail; + }); + + if (!parameters.optionalParametersAreNamed) { + parameters.forEachOptionalParameter((element) { + if (!arguments.isEmpty) { + list.add(compileArgument(arguments.head)); + arguments = arguments.tail; + } else { + list.add(compileConstant(element)); + } + }); + } else { + // Visit named arguments and add them into a temporary list. + List compiledNamedArguments = []; + for (; !arguments.isEmpty; arguments = arguments.tail) { + NamedArgument namedArgument = arguments.head; + compiledNamedArguments.add(compileArgument(namedArgument.expression)); + } + // Iterate over the optional parameters of the signature, and try to + // find them in [compiledNamedArguments]. If found, we use the + // value in the temporary list, otherwise the default value. + parameters.orderedOptionalParameters.forEach((element) { + int foundIndex = namedArguments.indexOf(element.name); + if (foundIndex != -1) { + list.add(compiledNamedArguments[foundIndex]); + } else { + list.add(compileConstant(element)); + } + }); + } + return true; + } + + static bool sameNames(List<SourceString> first, List<SourceString> second) { + for (int i = 0; i < first.length; i++) { + if (first[i] != second[i]) return false; + } + return true; + } + + bool operator ==(other) { + if (other is !Selector) return false; + return identical(receiverType, other.receiverType) + && equalsUntyped(other); + } + + bool equalsUntyped(Selector other) { + return name == other.name + && kind == other.kind + && identical(library, other.library) + && argumentCount == other.argumentCount + && namedArguments.length == other.namedArguments.length + && sameNames(namedArguments, other.namedArguments); + } + + List<SourceString> getOrderedNamedArguments() { + if (namedArguments.isEmpty) return namedArguments; + if (!orderedNamedArguments.isEmpty) return orderedNamedArguments; + + orderedNamedArguments.addAll(namedArguments); + orderedNamedArguments.sort((SourceString first, SourceString second) { + return first.slowToString().compareTo(second.slowToString()); + }); + return orderedNamedArguments; + } + + String namedArgumentsToString() { + if (namedArgumentCount > 0) { + StringBuffer result = new StringBuffer(); + for (int i = 0; i < namedArgumentCount; i++) { + if (i != 0) result.add(', '); + result.add(namedArguments[i].slowToString()); + } + return "[$result]"; + } + return ''; + } + + String toString() { + String named = ''; + String type = ''; + if (namedArgumentCount > 0) named = ', named=${namedArgumentsToString()}'; + if (receiverType != null) type = ', type=$receiverType'; + return 'Selector($kind, ${name.slowToString()}, ' + 'arity=$argumentCount$named$type)'; + } +} + +class TypedSelector extends Selector { + /** + * The type of the receiver. Any subtype of that type can be the + * target of the invocation. + */ + final DartType receiverType; + + final Selector asUntyped; + + TypedSelector(DartType this.receiverType, Selector selector) + : asUntyped = selector.asUntyped, + super(selector.kind, + selector.name, + selector.library, + selector.argumentCount, + selector.namedArguments) { + // Invariant: Typed selector can not be based on a malformed type. + assert(!identical(receiverType.kind, TypeKind.MALFORMED_TYPE)); + assert(asUntyped.receiverType == null); + } + + /** + * Check if [element] will be the one used at runtime when being + * invoked on an instance of [cls]. + */ + bool hasElementIn(ClassElement cls, Element element) { + // Use the selector for the lookup instead of [:element.name:] + // because the selector has the right privacy information. + Element resolved = cls.lookupSelector(this); + if (resolved == element) return true; + if (resolved == null) return false; + if (resolved.isAbstractField()) { + AbstractFieldElement field = resolved; + if (element == field.getter || element == field.setter) { + return true; + } else { + ClassElement otherCls = field.getEnclosingClass(); + // We have not found a match, but another class higher in the + // hierarchy may define the getter or the setter. + return hasElementIn(otherCls.superclass, element); + } + } + return false; + } + + bool appliesUnnamed(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + // [TypedSelector] are only used when compiling. + assert(compiler.phase == Compiler.PHASE_COMPILING); + if (!element.isMember()) return false; + + // A closure can be called through any typed selector: + // class A { + // get foo => () => 42; + // bar() => foo(); // The call to 'foo' is a typed selector. + // } + ClassElement other = element.getEnclosingClass(); + if (identical(other.superclass, compiler.closureClass)) { + return appliesUntyped(element, compiler); + } + + Element self = receiverType.element; + if (self.isTypedef()) { + // A typedef is a function type that doesn't have any + // user-defined members. + return false; + } + + if (other.implementsInterface(self) + || other.isSubclassOf(self) + || compiler.world.hasAnySubclassThatImplements(other, receiverType)) { + return appliesUntyped(element, compiler); + } + + // If [self] is a subclass of [other], it inherits the + // implementation of [element]. + ClassElement cls = self; + if (cls.isSubclassOf(other)) { + // Resolve an invocation of [element.name] on [self]. If it + // is found, this selector is a candidate. + return hasElementIn(self, element) && appliesUntyped(element, compiler); + } + + return false; + } +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart b/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart new file mode 100644 index 0000000..5961d07 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/characters.dart
@@ -0,0 +1,143 @@ +// Copyright (c) 2011, 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. + +library characters; + +const int $EOF = 0; +const int $STX = 2; +const int $BS = 8; +const int $TAB = 9; +const int $LF = 10; +const int $VTAB = 11; +const int $FF = 12; +const int $CR = 13; +const int $SPACE = 32; +const int $BANG = 33; +const int $DQ = 34; +const int $HASH = 35; +const int $$ = 36; +const int $PERCENT = 37; +const int $AMPERSAND = 38; +const int $SQ = 39; +const int $OPEN_PAREN = 40; +const int $CLOSE_PAREN = 41; +const int $STAR = 42; +const int $PLUS = 43; +const int $COMMA = 44; +const int $MINUS = 45; +const int $PERIOD = 46; +const int $SLASH = 47; +const int $0 = 48; +const int $1 = 49; +const int $2 = 50; +const int $3 = 51; +const int $4 = 52; +const int $5 = 53; +const int $6 = 54; +const int $7 = 55; +const int $8 = 56; +const int $9 = 57; +const int $COLON = 58; +const int $SEMICOLON = 59; +const int $LT = 60; +const int $EQ = 61; +const int $GT = 62; +const int $QUESTION = 63; +const int $AT = 64; +const int $A = 65; +const int $B = 66; +const int $C = 67; +const int $D = 68; +const int $E = 69; +const int $F = 70; +const int $G = 71; +const int $H = 72; +const int $I = 73; +const int $J = 74; +const int $K = 75; +const int $L = 76; +const int $M = 77; +const int $N = 78; +const int $O = 79; +const int $P = 80; +const int $Q = 81; +const int $R = 82; +const int $S = 83; +const int $T = 84; +const int $U = 85; +const int $V = 86; +const int $W = 87; +const int $X = 88; +const int $Y = 89; +const int $Z = 90; +const int $OPEN_SQUARE_BRACKET = 91; +const int $BACKSLASH = 92; +const int $CLOSE_SQUARE_BRACKET = 93; +const int $CARET = 94; +const int $_ = 95; +const int $BACKPING = 96; +const int $a = 97; +const int $b = 98; +const int $c = 99; +const int $d = 100; +const int $e = 101; +const int $f = 102; +const int $g = 103; +const int $h = 104; +const int $i = 105; +const int $j = 106; +const int $k = 107; +const int $l = 108; +const int $m = 109; +const int $n = 110; +const int $o = 111; +const int $p = 112; +const int $q = 113; +const int $r = 114; +const int $s = 115; +const int $t = 116; +const int $u = 117; +const int $v = 118; +const int $w = 119; +const int $x = 120; +const int $y = 121; +const int $z = 122; +const int $OPEN_CURLY_BRACKET = 123; +const int $BAR = 124; +const int $CLOSE_CURLY_BRACKET = 125; +const int $TILDE = 126; +const int $DEL = 127; +const int $NBSP = 160; +const int $LS = 0x2028; +const int $PS = 0x2029; + +const int $FIRST_SURROGATE = 0xd800; +const int $LAST_SURROGATE = 0xdfff; +const int $LAST_CODE_POINT = 0x10ffff; + +bool isHexDigit(int characterCode) { + if (characterCode <= $9) return $0 <= characterCode; + characterCode |= $a ^ $A; + return ($a <= characterCode && characterCode <= $f); +} + +int hexDigitValue(int hexDigit) { + assert(isHexDigit(hexDigit)); + // hexDigit is one of '0'..'9', 'A'..'F' and 'a'..'f'. + if (hexDigit <= $9) return hexDigit - $0; + return (hexDigit | ($a ^ $A)) - ($a - 10); +} + +bool isUnicodeScalarValue(int value) { + return value < $FIRST_SURROGATE || + (value > $LAST_SURROGATE && value <= $LAST_CODE_POINT); +} + +bool isUtf16LeadSurrogate(int value) { + return value >= 0xd800 && value <= 0xdbff; +} + +bool isUtf16TrailSurrogate(int value) { + return value >= 0xdc00 && value <= 0xdfff; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/link.dart b/pkgs/markdown/lib/src/compiler/implementation/util/link.dart new file mode 100644 index 0000000..9cc81cc --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/link.dart
@@ -0,0 +1,81 @@ +// Copyright (c) 2011, 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. + +part of org_dartlang_compiler_util; + +class Link<T> extends Iterable<T> { + T get head => null; + Link<T> get tail => null; + + factory Link.fromList(List<T> list) { + switch (list.length) { + case 0: + return new Link<T>(); + case 1: + return new LinkEntry<T>(list[0]); + case 2: + return new LinkEntry<T>(list[0], new LinkEntry<T>(list[1])); + case 3: + return new LinkEntry<T>( + list[0], new LinkEntry<T>(list[1], new LinkEntry<T>(list[2]))); + } + Link link = new Link<T>(); + for (int i = list.length ; i > 0; i--) { + link = link.prepend(list[i - 1]); + } + return link; + } + + const Link(); + + Link<T> prepend(T element) { + return new LinkEntry<T>(element, this); + } + + Iterator<T> get iterator => new LinkIterator<T>(this); + + void printOn(StringBuffer buffer, [separatedBy]) { + } + + List toList() => new List<T>.fixedLength(0); + + bool get isEmpty => true; + + Link<T> reverse() => this; + + Link<T> reversePrependAll(Link<T> from) { + if (from.isEmpty) return this; + return this.prepend(from.head).reversePrependAll(from.tail); + } + + Link<T> skip(int n) { + if (n == 0) return this; + throw new RangeError('Index $n out of range'); + } + + void forEach(void f(T element)) {} + + bool operator ==(other) { + if (other is !Link<T>) return false; + return other.isEmpty; + } + + String toString() => "[]"; + + get length { + throw new UnsupportedError('get:length'); + } + + int slowLength() => 0; +} + +abstract class LinkBuilder<T> { + factory LinkBuilder() = LinkBuilderImplementation; + + Link<T> toLink(); + void addLast(T t); + + final int length; + final bool isEmpty; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart new file mode 100644 index 0000000..d00ec86 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart
@@ -0,0 +1,142 @@ +// Copyright (c) 2011, 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. + +part of util_implementation; + +class LinkIterator<T> implements Iterator<T> { + T _current; + Link<T> _link; + + LinkIterator(Link<T> this._link); + + T get current => _current; + + bool moveNext() { + if (_link.isEmpty) { + _current = null; + return false; + } + _current = _link.head; + _link = _link.tail; + return true; + } +} + +class LinkEntry<T> extends Link<T> { + final T head; + Link<T> tail; + + LinkEntry(T this.head, [Link<T> tail]) + : this.tail = ((tail == null) ? new Link<T>() : tail); + + Link<T> prepend(T element) { + // TODO(ahe): Use new Link<T>, but this cost 8% performance on VM. + return new LinkEntry<T>(element, this); + } + + void printOn(StringBuffer buffer, [separatedBy]) { + buffer.add(head); + if (separatedBy == null) separatedBy = ''; + for (Link link = tail; !link.isEmpty; link = link.tail) { + buffer.add(separatedBy); + buffer.add(link.head); + } + } + + String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.add('[ '); + printOn(buffer, ', '); + buffer.add(' ]'); + return buffer.toString(); + } + + Link<T> reverse() { + Link<T> result = const Link(); + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + result = result.prepend(link.head); + } + return result; + } + + Link<T> reversePrependAll(Link<T> from) { + Link<T> result; + for (result = this; !from.isEmpty; from = from.tail) { + result = result.prepend(from.head); + } + return result; + } + + Link<T> skip(int n) { + Link<T> link = this; + for (int i = 0 ; i < n ; i++) { + if (link.isEmpty) { + throw new RangeError('Index $n out of range'); + } + link = link.tail; + } + return link; + } + + bool get isEmpty => false; + + List<T> toList() { + List<T> list = new List<T>(); + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + list.addLast(link.head); + } + return list; + } + + void forEach(void f(T element)) { + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + f(link.head); + } + } + + bool operator ==(other) { + if (other is !Link<T>) return false; + Link<T> myElements = this; + while (!myElements.isEmpty && !other.isEmpty) { + if (myElements.head != other.head) { + return false; + } + myElements = myElements.tail; + other = other.tail; + } + return myElements.isEmpty && other.isEmpty; + } + + int slowLength() => 1 + tail.slowLength(); +} + +class LinkBuilderImplementation<T> implements LinkBuilder<T> { + LinkEntry<T> head = null; + LinkEntry<T> lastLink = null; + int length = 0; + + LinkBuilderImplementation(); + + Link<T> toLink() { + if (head == null) return const Link(); + lastLink.tail = const Link(); + Link<T> link = head; + lastLink = null; + head = null; + return link; + } + + void addLast(T t) { + length++; + LinkEntry<T> entry = new LinkEntry<T>(t, null); + if (head == null) { + head = entry; + } else { + lastLink.tail = entry; + } + lastLink = entry; + } + + bool get isEmpty => length == 0; +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart b/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart new file mode 100644 index 0000000..fed5f15 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart
@@ -0,0 +1,65 @@ +// 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. + +library uri_extras; + +import 'dart:math'; +import 'dart:uri'; + +String relativize(Uri base, Uri uri, bool isWindows) { + if (!base.path.startsWith('/')) { + // Also throw an exception if [base] or base.path is null. + throw new ArgumentError('Expected absolute path: ${base.path}'); + } + if (!uri.path.startsWith('/')) { + // Also throw an exception if [uri] or uri.path is null. + throw new ArgumentError('Expected absolute path: ${uri.path}'); + } + bool equalsNCS(String a, String b) { + return a.toLowerCase() == b.toLowerCase(); + } + + String normalize(String path) { + if (isWindows) { + return path.toLowerCase(); + } else { + return path; + } + } + + if (equalsNCS(base.scheme, 'file') && + equalsNCS(base.scheme, uri.scheme) && + base.userInfo == uri.userInfo && + equalsNCS(base.domain, uri.domain) && + base.port == uri.port && + uri.query == "" && uri.fragment == "") { + if (normalize(uri.path).startsWith(normalize(base.path))) { + return uri.path.substring(base.path.length); + } + List<String> uriParts = uri.path.split('/'); + List<String> baseParts = base.path.split('/'); + int common = 0; + int length = min(uriParts.length, baseParts.length); + while (common < length && + normalize(uriParts[common]) == normalize(baseParts[common])) { + common++; + } + if (common == 1 || (isWindows && common == 2)) { + // The first part will always be an empty string because the + // paths are absolute. On Windows, we must also consider drive + // letters or hostnames. + return uri.path; + } + StringBuffer sb = new StringBuffer(); + for (int i = common + 1; i < baseParts.length; i++) { + sb.add('../'); + } + for (int i = common; i < uriParts.length - 1; i++) { + sb.add('${uriParts[i]}/'); + } + sb.add('${uriParts.last}'); + return sb.toString(); + } + return uri.toString(); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/util.dart b/pkgs/markdown/lib/src/compiler/implementation/util/util.dart new file mode 100644 index 0000000..8a07687 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/util.dart
@@ -0,0 +1,109 @@ +// Copyright (c) 2011, 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. + +library org_dartlang_compiler_util; + +import 'util_implementation.dart'; +import 'characters.dart'; + +part 'link.dart'; + +/** + * Tagging interface for classes from which source spans can be generated. + */ +// TODO(johnniwinther): Find a better name. +// TODO(ahe): How about "Bolt"? +abstract class Spannable {} + +class _SpannableSentinel implements Spannable { + final String name; + + const _SpannableSentinel(this.name); + + String toString() => name; +} + +const Spannable CURRENT_ELEMENT_SPANNABLE = + const _SpannableSentinel("Current element"); + +class SpannableAssertionFailure { + final Spannable node; + final String message; + SpannableAssertionFailure(this.node, this.message); + + String toString() => 'Compiler crashed: $message.'; +} + +/// Writes the characters of [string] on [buffer]. The characters +/// are escaped as suitable for JavaScript and JSON. [buffer] is +/// anything which supports [:add:] and [:addCharCode:], for example, +/// [StringBuffer]. Note that JS supports \xnn and \unnnn whereas JSON only +/// supports the \unnnn notation. Therefore we use the \unnnn notation. +void writeJsonEscapedCharsOn(String string, buffer) { + void addCodeUnitEscaped(var buffer, int code) { + assert(code < 0x10000); + buffer.add(r'\u'); + if (code < 0x1000) { + buffer.add('0'); + if (code < 0x100) { + buffer.add('0'); + if (code < 0x10) { + buffer.add('0'); + } + } + } + buffer.add(code.toRadixString(16)); + } + + void writeEscapedOn(String string, var buffer) { + for (int i = 0; i < string.length; i++) { + int code = string.charCodeAt(i); + if (code == $DQ) { + buffer.add(r'\"'); + } else if (code == $TAB) { + buffer.add(r'\t'); + } else if (code == $LF) { + buffer.add(r'\n'); + } else if (code == $CR) { + buffer.add(r'\r'); + } else if (code == $DEL) { + addCodeUnitEscaped(buffer, $DEL); + } else if (code == $LS) { + // This Unicode line terminator and $PS are invalid in JS string + // literals. + addCodeUnitEscaped(buffer, $LS); // 0x2028. + } else if (code == $PS) { + addCodeUnitEscaped(buffer, $PS); // 0x2029. + } else if (code == $BACKSLASH) { + buffer.add(r'\\'); + } else { + if (code < 0x20) { + addCodeUnitEscaped(buffer, code); + // We emit DEL (ASCII 0x7f) as an escape because it would be confusing + // to have it unescaped in a string literal. We also escape + // everything above 0x7f because that means we don't have to worry + // about whether the web server serves it up as Latin1 or UTF-8. + } else if (code < 0x7f) { + buffer.addCharCode(code); + } else { + // This will output surrogate pairs in the form \udxxx\udyyy, rather + // than the more logical \u{zzzzzz}. This should work in JavaScript + // (especially old UCS-2 based implementations) and is the only + // format that is allowed in JSON. + addCodeUnitEscaped(buffer, code); + } + } + } + } + + for (int i = 0; i < string.length; i++) { + int code = string.charCodeAt(i); + if (code < 0x20 || code == $DEL || code == $DQ || code == $LS || + code == $PS || code == $BACKSLASH || code >= 0x80) { + writeEscapedOn(string, buffer); + return; + } + } + buffer.add(string); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart b/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart new file mode 100644 index 0000000..bbb852a --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart
@@ -0,0 +1,9 @@ +// Copyright (c) 2011, 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. + +library util_implementation; + +import 'util.dart'; + +part 'link_implementation.dart';
diff --git a/pkgs/markdown/lib/src/compiler/implementation/warnings.dart b/pkgs/markdown/lib/src/compiler/implementation/warnings.dart new file mode 100644 index 0000000..56f3698 --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/warnings.dart
@@ -0,0 +1,568 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class MessageKind { + final String template; + const MessageKind(this.template); + + static const GENERIC = const MessageKind('#{text}'); + + static const NOT_ASSIGNABLE = const MessageKind( + '#{fromType} is not assignable to #{toType}'); + static const VOID_EXPRESSION = const MessageKind( + 'expression does not yield a value'); + static const VOID_VARIABLE = const MessageKind( + 'variable cannot be of type void'); + static const RETURN_VALUE_IN_VOID = const MessageKind( + 'cannot return value from void function'); + static const RETURN_NOTHING = const MessageKind( + 'value of type #{returnType} expected'); + static const MISSING_ARGUMENT = const MessageKind( + 'missing argument of type #{argumentType}'); + static const ADDITIONAL_ARGUMENT = const MessageKind( + 'additional argument'); + static const NAMED_ARGUMENT_NOT_FOUND = const MessageKind( + "no named argument '#{argumentName}' found on method"); + static const METHOD_NOT_FOUND = const MessageKind( + 'no method named #{methodName} in class #{className}'); + static const MEMBER_NOT_STATIC = const MessageKind( + '#{className}.#{memberName} is not static'); + static const NO_INSTANCE_AVAILABLE = const MessageKind( + '#{name} is only available in instance methods'); + + static const UNREACHABLE_CODE = const MessageKind( + 'unreachable code'); + static const MISSING_RETURN = const MessageKind( + 'missing return'); + static const MAYBE_MISSING_RETURN = const MessageKind( + 'not all paths lead to a return or throw statement'); + + static const CANNOT_RESOLVE = const MessageKind( + 'cannot resolve #{name}'); + static const CANNOT_RESOLVE_CONSTRUCTOR = const MessageKind( + 'cannot resolve constructor #{constructorName}'); + static const CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind( + 'cannot resolve constructor #{constructorName} for implicit super call'); + static const CANNOT_RESOLVE_TYPE = const MessageKind( + 'cannot resolve type #{typeName}'); + static const DUPLICATE_DEFINITION = const MessageKind( + 'duplicate definition of #{name}'); + static const DUPLICATE_IMPORT = const MessageKind( + 'duplicate import of #{name}'); + static const DUPLICATE_EXPORT = const MessageKind( + 'duplicate export of #{name}'); + static const NOT_A_TYPE = const MessageKind( + '#{node} is not a type'); + static const NOT_A_PREFIX = const MessageKind( + '#{node} is not a prefix'); + static const NO_SUPER_IN_OBJECT = const MessageKind( + "'Object' does not have a superclass"); + static const CANNOT_FIND_CONSTRUCTOR = const MessageKind( + 'cannot find constructor #{constructorName}'); + static const CANNOT_FIND_CONSTRUCTOR2 = const MessageKind( + 'cannot find constructor #{constructorName} in #{className}'); + static const CYCLIC_CLASS_HIERARCHY = const MessageKind( + '#{className} creates a cycle in the class hierarchy'); + static const INVALID_RECEIVER_IN_INITIALIZER = const MessageKind( + 'field initializer expected'); + static const NO_SUPER_IN_STATIC = const MessageKind( + "'super' is only available in instance methods"); + static const DUPLICATE_INITIALIZER = const MessageKind( + 'field #{fieldName} is initialized more than once'); + static const ALREADY_INITIALIZED = const MessageKind( + '#{fieldName} was already initialized here'); + static const INIT_STATIC_FIELD = const MessageKind( + 'cannot initialize static field #{fieldName}'); + static const NOT_A_FIELD = const MessageKind( + '#{fieldName} is not a field'); + static const CONSTRUCTOR_CALL_EXPECTED = const MessageKind( + "only call to 'this' or 'super' constructor allowed"); + static const INVALID_FOR_IN = const MessageKind( + 'invalid for-in variable declaration.'); + static const INVALID_INITIALIZER = const MessageKind( + 'invalid initializer'); + static const FUNCTION_WITH_INITIALIZER = const MessageKind( + 'only constructors can have initializers'); + static const REDIRECTING_CONSTRUCTOR_CYCLE = const MessageKind( + 'cyclic constructor redirection'); + static const REDIRECTING_CONSTRUCTOR_HAS_BODY = const MessageKind( + 'redirecting constructor cannot have a body'); + static const REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER = const MessageKind( + 'redirecting constructor cannot have other initializers'); + static const SUPER_INITIALIZER_IN_OBJECT = const MessageKind( + "'Object' cannot have a super initializer"); + static const DUPLICATE_SUPER_INITIALIZER = const MessageKind( + 'cannot have more than one super initializer'); + static const INVALID_ARGUMENTS = const MessageKind( + "arguments do not match the expected parameters of #{methodName}"); + static const NO_MATCHING_CONSTRUCTOR = const MessageKind( + "super call arguments and constructor parameters don't match"); + static const NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind( + "implicit super call arguments and constructor parameters don't match"); + static const FIELD_PARAMETER_NOT_ALLOWED = const MessageKind( + 'a field parameter is only allowed in generative constructors'); + static const INVALID_PARAMETER = const MessageKind( + "cannot resolve parameter"); + static const NOT_INSTANCE_FIELD = const MessageKind( + '#{fieldName} is not an instance field'); + static const NO_CATCH_NOR_FINALLY = const MessageKind( + "expected 'catch' or 'finally'"); + static const EMPTY_CATCH_DECLARATION = const MessageKind( + 'expected an identifier in catch declaration'); + static const EXTRA_CATCH_DECLARATION = const MessageKind( + 'extra parameter in catch declaration'); + static const PARAMETER_WITH_TYPE_IN_CATCH = const MessageKind( + 'cannot use type annotations in catch'); + static const PARAMETER_WITH_MODIFIER_IN_CATCH = const MessageKind( + 'cannot use modifiers in catch'); + static const OPTIONAL_PARAMETER_IN_CATCH = const MessageKind( + 'cannot use optional parameters in catch'); + static const THROW_WITHOUT_EXPRESSION = const MessageKind( + 'cannot use re-throw outside of catch block (expression expected after ' + '"throw")'); + static const UNBOUND_LABEL = const MessageKind( + 'cannot resolve label #{labelName}'); + static const NO_BREAK_TARGET = const MessageKind( + 'break statement not inside switch or loop'); + static const NO_CONTINUE_TARGET = const MessageKind( + 'continue statement not inside loop'); + static const EXISTING_LABEL = const MessageKind( + 'original declaration of duplicate label #{labelName}'); + static const DUPLICATE_LABEL = const MessageKind( + 'duplicate declaration of label #{labelName}'); + static const UNUSED_LABEL = const MessageKind( + 'unused label #{labelName}'); + static const INVALID_CONTINUE = const MessageKind( + 'target of continue is not a loop or switch case'); + static const INVALID_BREAK = const MessageKind( + 'target of break is not a statement'); + + static const TYPE_VARIABLE_AS_CONSTRUCTOR = const MessageKind( + 'cannot use type variable as constructor'); + static const DUPLICATE_TYPE_VARIABLE_NAME = const MessageKind( + 'type variable #{typeVariableName} already declared'); + static const TYPE_VARIABLE_WITHIN_STATIC_MEMBER = const MessageKind( + 'cannot refer to type variable #{typeVariableName} ' + 'within a static member'); + + static const INVALID_USE_OF_SUPER = const MessageKind( + 'super not allowed here'); + static const INVALID_CASE_DEFAULT = const MessageKind( + 'default only allowed on last case of a switch'); + + static const SWITCH_CASE_TYPES_NOT_EQUAL = const MessageKind( + "case expressions don't all have the same type."); + static const SWITCH_CASE_VALUE_OVERRIDES_EQUALS = const MessageKind( + "case expression value overrides 'operator=='."); + static const SWITCH_INVALID = const MessageKind( + "switch cases contain invalid expressions."); + + static const INVALID_ARGUMENT_AFTER_NAMED = const MessageKind( + 'non-named argument after named argument'); + + static const NOT_A_COMPILE_TIME_CONSTANT = const MessageKind( + 'not a compile-time constant'); + static const CYCLIC_COMPILE_TIME_CONSTANTS = const MessageKind( + 'cycle in the compile-time constant computation'); + static const CONSTRUCTOR_IS_NOT_CONST = const MessageKind( + 'constructor is not a const constructor'); + + static const KEY_NOT_A_STRING_LITERAL = const MessageKind( + 'map-literal key not a string literal'); + + static const NO_SUCH_LIBRARY_MEMBER = const MessageKind( + '#{libraryName} has no member named #{memberName}'); + + static const CANNOT_INSTANTIATE_INTERFACE = const MessageKind( + "cannot instantiate interface '#{interfaceName}'"); + + static const CANNOT_INSTANTIATE_TYPEDEF = const MessageKind( + "cannot instantiate typedef '#{typedefName}'"); + + static const CANNOT_INSTANTIATE_TYPE_VARIABLE = const MessageKind( + "cannot instantiate type variable '#{typeVariableName}'"); + + static const NO_DEFAULT_CLASS = const MessageKind( + "no default class on enclosing interface '#{interfaceName}'"); + + static const CYCLIC_TYPE_VARIABLE = const MessageKind( + "cyclic reference to type variable #{typeVariableName}"); + + static const CLASS_NAME_EXPECTED = const MessageKind( + "class name expected"); + + static const INTERFACE_TYPE_EXPECTED = const MessageKind( + "interface type expected"); + + static const CANNOT_EXTEND = const MessageKind( + "#{type} cannot be extended"); + + static const CANNOT_IMPLEMENT = const MessageKind( + "#{type} cannot be implemented"); + + static const DUPLICATE_EXTENDS_IMPLEMENTS = const MessageKind( + "Error: #{type} can not be both extended and implemented."); + + static const DUPLICATE_IMPLEMENTS = const MessageKind( + "Error: #{type} must not occur more than once " + "in the implements clause."); + + static const ILLEGAL_SUPER_SEND = const MessageKind( + "#{name} cannot be called on super"); + + static const ADDITIONAL_TYPE_ARGUMENT = const MessageKind( + "additional type argument"); + + static const MISSING_TYPE_ARGUMENT = const MessageKind( + "missing type argument"); + + // TODO(johnniwinther): Use ADDITIONAL_TYPE_ARGUMENT or MISSING_TYPE_ARGUMENT + // instead. + static const TYPE_ARGUMENT_COUNT_MISMATCH = const MessageKind( + "incorrect number of type arguments on #{type}"); + + static const MISSING_ARGUMENTS_TO_ASSERT = const MessageKind( + "missing arguments to assert"); + + static const GETTER_MISMATCH = const MessageKind( + "Error: setter disagrees on: #{modifiers}."); + + static const SETTER_MISMATCH = const MessageKind( + "Error: getter disagrees on: #{modifiers}."); + + static const ILLEGAL_SETTER_FORMALS = const MessageKind( + "Error: a setter must have exactly one argument."); + + static const NO_STATIC_OVERRIDE = const MessageKind( + "Error: static member cannot override instance member '#{memberName}' of " + "'#{className}'."); + + static const NO_STATIC_OVERRIDE_CONT = const MessageKind( + "Info: this is the instance member that cannot be overridden " + "by a static member."); + + static const CANNOT_OVERRIDE_FIELD_WITH_METHOD = const MessageKind( + "Error: method cannot override field '#{memberName}' of '#{className}'."); + + static const CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT = const MessageKind( + "Info: this is the field that cannot be overridden by a method."); + + static const CANNOT_OVERRIDE_METHOD_WITH_FIELD = const MessageKind( + "Error: field cannot override method '#{memberName}' of '#{className}'."); + + static const CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT = const MessageKind( + "Info: this is the method that cannot be overridden by a field."); + + static const BAD_ARITY_OVERRIDE = const MessageKind( + "Error: cannot override method '#{memberName}' in '#{className}'; " + "the parameters do not match."); + + static const BAD_ARITY_OVERRIDE_CONT = const MessageKind( + "Info: this is the method whose parameters do not match."); + + static const MISSING_FORMALS = const MessageKind( + "Error: Formal parameters are missing."); + + static const EXTRA_FORMALS = const MessageKind( + "Error: Formal parameters are not allowed here."); + + static const UNARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have no parameters."); + + static const MINUS_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator - must have 0 or 1 parameters."); + + static const BINARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have exactly 1 parameter."); + + static const TERNARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have exactly 2 parameters."); + + static const OPERATOR_OPTIONAL_PARAMETERS = const MessageKind( + "Error: Operator #{operatorName} cannot have optional parameters."); + + static const OPERATOR_NAMED_PARAMETERS = const MessageKind( + "Error: Operator #{operatorName} cannot have named parameters."); + + // TODO(ahe): This message is hard to localize. This is acceptable, + // as it will be removed when we ship Dart version 1.0. + static const DEPRECATED_FEATURE_WARNING = const MessageKind( + "Warning: deprecated language feature, #{featureName}, " + "will be removed in a future Dart milestone."); + + // TODO(ahe): This message is hard to localize. This is acceptable, + // as it will be removed when we ship Dart version 1.0. + static const DEPRECATED_FEATURE_ERROR = const MessageKind( + "Error: #{featureName} are not legal " + "due to option --reject-deprecated-language-features."); + + static const CONSTRUCTOR_WITH_RETURN_TYPE = const MessageKind( + "Error: cannot have return type for constructor."); + + static const ILLEGAL_FINAL_METHOD_MODIFIER = const MessageKind( + "Error: cannot have final modifier on method."); + + static const ILLEGAL_CONSTRUCTOR_MODIFIERS = const MessageKind( + "Error: illegal constructor modifiers: #{modifiers}."); + + static const ILLEGAL_MIXIN_APPLICATION_MODIFIERS = const MessageKind( + "Error: illegal mixin application modifiers: #{modifiers}."); + + static const ILLEGAL_MIXIN_SUPERCLASS = const MessageKind( + "Error: class used as mixin must have Object as superclass."); + + static const ILLEGAL_MIXIN_CONSTRUCTOR = const MessageKind( + "Error: class used as mixin cannot have non-factory constructor."); + + static const ILLEGAL_MIXIN_CYCLE = const MessageKind( + "Error: class used as mixin introduces mixin cycle: " + "#{mixinName1} <-> #{mixinName2}."); + + static const ILLEGAL_MIXIN_WITH_SUPER = const MessageKind( + "Error: cannot use class #{className} as a mixin because it uses super."); + + static const ILLEGAL_MIXIN_SUPER_USE = const MessageKind( + "Use of super in class used as mixin."); + + static const PARAMETER_NAME_EXPECTED = const MessageKind( + "Error: parameter name expected."); + + static const CANNOT_RESOLVE_GETTER = const MessageKind( + 'cannot resolve getter.'); + + static const CANNOT_RESOLVE_SETTER = const MessageKind( + 'cannot resolve setter.'); + + static const VOID_NOT_ALLOWED = const MessageKind( + 'type void is only allowed in a return type.'); + + static const BEFORE_TOP_LEVEL = const MessageKind( + 'Error: part header must come before top-level definitions.'); + + static const LIBRARY_NAME_MISMATCH = const MessageKind( + 'Warning: expected part of library name "#{libraryName}".'); + + static const MISSING_PART_OF_TAG = const MessageKind( + 'Note: This file has no part-of tag, but it is being used as a part.'); + + static const DUPLICATED_PART_OF = const MessageKind( + 'Error: duplicated part-of directive.'); + + static const ILLEGAL_DIRECTIVE = const MessageKind( + 'Error: directive not allowed here.'); + + static const DUPLICATED_LIBRARY_NAME = const MessageKind( + 'Warning: duplicated library name "#{libraryName}".'); + + static const INVALID_SOURCE_FILE_LOCATION = const MessageKind(''' +Invalid offset (#{offset}) in source map. +File: #{fileName} +Length: #{length}'''); + + static const TOP_LEVEL_VARIABLE_DECLARED_STATIC = const MessageKind( + "Top-level variable cannot be declared static."); + + static const WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT = const MessageKind( + "Wrong number of arguments to assert. Should be 1, but given " + "#{argumentCount}."); + + static const ASSERT_IS_GIVEN_NAMED_ARGUMENTS = const MessageKind( + "assert takes no named arguments, but given #{argumentCount}."); + + static const FACTORY_REDIRECTION_IN_NON_FACTORY = const MessageKind( + "Error: Factory redirection only allowed in factories."); + + static const MISSING_FACTORY_KEYWORD = const MessageKind( + "Did you forget a factory keyword here?"); + + static const COMPILER_CRASHED = const MessageKind( + "Error: The compiler crashed when compiling this element."); + + static const PLEASE_REPORT_THE_CRASH = const MessageKind(''' +The compiler is broken. + +When compiling the above element, the compiler crashed. It is not +possible to tell if this is caused by a problem in your program or +not. Regardless, the compiler should not crash. + +The Dart team would greatly appreciate if you would take a moment to +report this problem at http://dartbug.com/new. + +Please include the following information: + +* the name and version of your operating system, + +* the Dart SDK build number (#{buildId}), and + +* the entire message you see here (including the full stack trace + below as well as the source location above). +'''); + + + ////////////////////////////////////////////////////////////////////////////// + // Patch errors start. + ////////////////////////////////////////////////////////////////////////////// + + static const PATCH_RETURN_TYPE_MISMATCH = const MessageKind( + "Patch return type '#{patchReturnType}' doesn't match " + "'#{originReturnType}' on origin method '#{methodName}'."); + + static const PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH = const MessageKind( + "Required parameter count of patch method (#{patchParameterCount}) " + "doesn't match parameter count on origin method '#{methodName}' " + "(#{originParameterCount})."); + + static const PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH = const MessageKind( + "Optional parameter count of patch method (#{patchParameterCount}) " + "doesn't match parameter count on origin method '#{methodName}' " + "(#{originParameterCount})."); + + static const PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH = const MessageKind( + "Optional parameters of origin and patch method '#{methodName}' must " + "both be either named or positional."); + + static const PATCH_PARAMETER_MISMATCH = const MessageKind( + "Patch method parameter '#{patchParameter}' doesn't match " + "'#{originParameter}' on origin method #{methodName}."); + + static const PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION = const MessageKind( + "External method without an implementation."); + + static const PATCH_POINT_TO_FUNCTION = const MessageKind( + "Info: This is the function patch '#{functionName}'."); + + static const PATCH_POINT_TO_CLASS = const MessageKind( + "Info: This is the class patch '#{className}'."); + + static const PATCH_POINT_TO_GETTER = const MessageKind( + "Info: This is the getter patch '#{getterName}'."); + + static const PATCH_POINT_TO_SETTER = const MessageKind( + "Info: This is the setter patch '#{setterName}'."); + + static const PATCH_POINT_TO_CONSTRUCTOR = const MessageKind( + "Info: This is the constructor patch '#{constructorName}'."); + + static const PATCH_NON_EXISTING = const MessageKind( + "Error: Origin does not exist for patch '#{name}'."); + + static const PATCH_NONPATCHABLE = const MessageKind( + "Error: Only classes and functions can be patched."); + + static const PATCH_NON_EXTERNAL = const MessageKind( + "Error: Only external functions can be patched."); + + static const PATCH_NON_CLASS = const MessageKind( + "Error: Patching non-class with class patch '#{className}'."); + + static const PATCH_NON_GETTER = const MessageKind( + "Error: Cannot patch non-getter '#{name}' with getter patch."); + + static const PATCH_NO_GETTER = const MessageKind( + "Error: No getter found for getter patch '#{getterName}'."); + + static const PATCH_NON_SETTER = const MessageKind( + "Error: Cannot patch non-setter '#{name}' with setter patch."); + + static const PATCH_NO_SETTER = const MessageKind( + "Error: No setter found for setter patch '#{setterName}'."); + + static const PATCH_NON_CONSTRUCTOR = const MessageKind( + "Error: Cannot patch non-constructor with constructor patch " + "'#{constructorName}'."); + + static const PATCH_NON_FUNCTION = const MessageKind( + "Error: Cannot patch non-function with function patch " + "'#{functionName}'."); + + ////////////////////////////////////////////////////////////////////////////// + // Patch errors end. + ////////////////////////////////////////////////////////////////////////////// + + toString() => template; + + Message message([Map arguments = const {}]) { + return new Message(this, arguments); + } + + CompilationError error([Map arguments = const {}]) { + return new CompilationError(this, arguments); + } +} + +class Message { + final kind; + final Map arguments; + String message; + + Message(this.kind, this.arguments) { + assert(() { computeMessage(); return true; }); + } + + String computeMessage() { + if (message == null) { + message = kind.template; + arguments.forEach((key, value) { + String string = slowToString(value); + message = message.replaceAll('#{${key}}', string); + }); + assert(invariant( + CURRENT_ELEMENT_SPANNABLE, + !message.contains(new RegExp(r"#\{.+\}")), + message: 'Missing arguments in error message: "$message"')); + } + return message; + } + + String toString() { + return computeMessage(); + } + + bool operator==(other) { + if (other is !Message) return false; + return (kind == other.kind) && (toString() == other.toString()); + } + + String slowToString(object) { + if (object is SourceString) { + return object.slowToString(); + } else { + return object.toString(); + } + } +} + +class Diagnostic { + final Message message; + Diagnostic(MessageKind kind, [Map arguments = const {}]) + : message = new Message(kind, arguments); + String toString() => message.toString(); +} + +class TypeWarning extends Diagnostic { + TypeWarning(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class ResolutionError extends Diagnostic { + ResolutionError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class ResolutionWarning extends Diagnostic { + ResolutionWarning(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class CompileTimeConstantError extends Diagnostic { + CompileTimeConstantError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class CompilationError extends Diagnostic { + CompilationError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +}
diff --git a/pkgs/markdown/lib/src/compiler/implementation/world.dart b/pkgs/markdown/lib/src/compiler/implementation/world.dart new file mode 100644 index 0000000..a40050e --- /dev/null +++ b/pkgs/markdown/lib/src/compiler/implementation/world.dart
@@ -0,0 +1,232 @@ +// 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. + +part of dart2js; + +class World { + final Compiler compiler; + final Map<ClassElement, Set<ClassElement>> subtypes; + final Map<ClassElement, Set<MixinApplicationElement>> mixinUses; + final Map<ClassElement, Set<ClassElement>> typesImplementedBySubclasses; + final Set<ClassElement> classesNeedingRti; + final Map<ClassElement, Set<ClassElement>> rtiDependencies; + final FunctionSet userDefinedGetters; + final FunctionSet userDefinedSetters; + + World(Compiler compiler) + : subtypes = new Map<ClassElement, Set<ClassElement>>(), + mixinUses = new Map<ClassElement, Set<MixinApplicationElement>>(), + typesImplementedBySubclasses = + new Map<ClassElement, Set<ClassElement>>(), + userDefinedGetters = new FunctionSet(compiler), + userDefinedSetters = new FunctionSet(compiler), + classesNeedingRti = new Set<ClassElement>(), + rtiDependencies = new Map<ClassElement, Set<ClassElement>>(), + this.compiler = compiler; + + void populate() { + void addSubtypes(ClassElement cls) { + if (cls.resolutionState != STATE_DONE) { + compiler.internalErrorOnElement( + cls, 'Class "${cls.name.slowToString()}" is not resolved.'); + } + + for (DartType type in cls.allSupertypes) { + Set<Element> subtypesOfCls = + subtypes.putIfAbsent(type.element, () => new Set<ClassElement>()); + subtypesOfCls.add(cls); + } + + // Walk through the superclasses, and record the types + // implemented by that type on the superclasses. + DartType type = cls.supertype; + while (type != null) { + Set<Element> typesImplementedBySubclassesOfCls = + typesImplementedBySubclasses.putIfAbsent( + type.element, () => new Set<ClassElement>()); + for (DartType current in cls.allSupertypes) { + typesImplementedBySubclassesOfCls.add(current.element); + } + ClassElement classElement = type.element; + type = classElement.supertype; + } + } + + compiler.resolverWorld.instantiatedClasses.forEach(addSubtypes); + + // Find the classes that need runtime type information. Such + // classes are: + // (1) used in a is check with type variables, + // (2) dependencies of classes in (1), + // (3) subclasses of (2) and (3). + + void potentiallyAddForRti(ClassElement cls) { + if (cls.typeVariables.isEmpty) return; + if (classesNeedingRti.contains(cls)) return; + classesNeedingRti.add(cls); + + Set<ClassElement> classes = subtypes[cls]; + if (classes != null) { + classes.forEach((ClassElement sub) { + potentiallyAddForRti(sub); + }); + } + + Set<ClassElement> dependencies = rtiDependencies[cls]; + if (dependencies != null) { + dependencies.forEach((ClassElement other) { + potentiallyAddForRti(other); + }); + } + } + + compiler.resolverWorld.isChecks.forEach((DartType type) { + if (type is InterfaceType) { + InterfaceType itf = type; + if (!itf.isRaw) { + potentiallyAddForRti(itf.element); + } + } + }); + } + + bool needsRti(ClassElement cls) { + return classesNeedingRti.contains(cls) || compiler.enabledRuntimeType; + } + + void registerMixinUse(MixinApplicationElement mixinApplication, + ClassElement mixin) { + Set<MixinApplicationElement> users = + mixinUses.putIfAbsent(mixin, () => + new Set<MixinApplicationElement>()); + users.add(mixinApplication); + } + + void registerRtiDependency(Element element, Element dependency) { + // We're not dealing with typedef for now. + if (!element.isClass() || !dependency.isClass()) return; + Set<ClassElement> classes = + rtiDependencies.putIfAbsent(element, () => new Set<ClassElement>()); + classes.add(dependency); + } + + void recordUserDefinedGetter(Element element) { + assert(element.isGetter()); + userDefinedGetters.add(element); + } + + void recordUserDefinedSetter(Element element) { + assert(element.isSetter()); + userDefinedSetters.add(element); + } + + bool hasAnyUserDefinedGetter(Selector selector) { + return userDefinedGetters.hasAnyElementMatchingSelector(selector); + } + + bool hasAnyUserDefinedSetter(Selector selector) { + return userDefinedSetters.hasAnyElementMatchingSelector(selector); + } + + // Returns whether a subclass of [superclass] implements [type]. + bool hasAnySubclassThatImplements(ClassElement superclass, DartType type) { + Set<ClassElement> subclasses= typesImplementedBySubclasses[superclass]; + if (subclasses == null) return false; + return subclasses.contains(type.element); + } + + bool hasNoOverridingMember(Element element) { + ClassElement cls = element.getEnclosingClass(); + Set<ClassElement> subclasses = compiler.world.subtypes[cls]; + // TODO(ngeoffray): Implement the full thing. + return subclasses == null || subclasses.isEmpty; + } + + void registerUsedElement(Element element) { + if (element.isMember()) { + if (element.isGetter()) { + // We're collecting user-defined getters to let the codegen know which + // field accesses might have side effects. + recordUserDefinedGetter(element); + } else if (element.isSetter()) { + recordUserDefinedSetter(element); + } + } + } + + /** + * Returns a [MemberSet] that contains the possible targets of the given + * [selector] on a receiver with the given [type]. This includes all sub + * types. + */ + MemberSet _memberSetFor(DartType type, Selector selector) { + assert(compiler != null); + ClassElement cls = type.element; + SourceString name = selector.name; + LibraryElement library = selector.library; + MemberSet result = new MemberSet(name); + Element element = cls.implementation.lookupSelector(selector); + if (element != null) result.add(element); + + bool isPrivate = name.isPrivate(); + Set<ClassElement> subtypesOfCls = subtypes[cls]; + if (subtypesOfCls != null) { + for (ClassElement sub in subtypesOfCls) { + // Private members from a different library are not visible. + if (isPrivate && sub.getLibrary() != library) continue; + element = sub.implementation.lookupLocalMember(name); + if (element != null) result.add(element); + } + } + return result; + } + + /** + * Returns the field in [type] described by the given [selector]. + * If no such field exists, or a subclass overrides the field + * returns [:null:]. + */ + VariableElement locateSingleField(DartType type, Selector selector) { + MemberSet memberSet = _memberSetFor(type, selector); + ClassElement cls = type.element; + Element result = cls.implementation.lookupSelector(selector); + if (result == null) return null; + if (!result.isField()) return null; + + // Verify that no subclass overrides the field. + if (memberSet.elements.length != 1) return null; + assert(memberSet.elements.contains(result)); + return result; + } + + Set<ClassElement> findNoSuchMethodHolders(DartType type) { + Set<ClassElement> result = new Set<ClassElement>(); + Selector noSuchMethodSelector = new Selector.noSuchMethod(); + MemberSet memberSet = _memberSetFor(type, noSuchMethodSelector); + for (Element element in memberSet.elements) { + ClassElement holder = element.getEnclosingClass(); + if (!identical(holder, compiler.objectClass) && + noSuchMethodSelector.applies(element, compiler)) { + result.add(holder); + } + } + return result; + } +} + +/** + * A [MemberSet] contains all the possible targets for a selector. + */ +class MemberSet { + final Set<Element> elements; + final SourceString name; + + MemberSet(SourceString this.name) : elements = new Set<Element>(); + + void add(Element element) { + elements.add(element); + } + + bool get isEmpty => elements.isEmpty; +}
diff --git a/pkgs/markdown/lib/src/libraries.dart b/pkgs/markdown/lib/src/libraries.dart new file mode 100644 index 0000000..5c35483 --- /dev/null +++ b/pkgs/markdown/lib/src/libraries.dart
@@ -0,0 +1,200 @@ +// 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. + +library libraries; + +/** + * A bit flag used by [LibraryInfo] indicating that a library is used by dart2js + */ +const int DART2JS_PLATFORM = 1; + +/** + * A bit flag used by [LibraryInfo] indicating that a library is used by the VM + */ +const int VM_PLATFORM = 2; + +/** + * Mapping of "dart:" library name (e.g. "core") to information about that library. + * This information is structured such that Dart Editor can parse this file + * and extract the necessary information without executing it + * while other tools can access via execution. + */ +const Map<String, LibraryInfo> LIBRARIES = const { + + "async": const LibraryInfo( + "async/async.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/async_patch.dart"), + + "chrome": const LibraryInfo( + "chrome/dartium/chrome_dartium.dart", + category: "Client", + dart2jsPath: "chrome/dart2js/chrome_dart2js.dart", + documented: false, + implementation: true), // Not really, just hiding it for now. + + "collection": const LibraryInfo("collection/collection.dart"), + + "core": const LibraryInfo( + "core/core.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/core_patch.dart"), + + "crypto": const LibraryInfo( + "crypto/crypto.dart"), + + "html": const LibraryInfo( + "html/dartium/html_dartium.dart", + category: "Client", + dart2jsPath: "html/dart2js/html_dart2js.dart"), + + "html_common": const LibraryInfo( + "html/html_common/html_common.dart", + category: "Client", + dart2jsPath: "html/html_common/html_common_dart2js.dart", + documented: false, + implementation: true), + + "indexed_db": const LibraryInfo( + "indexed_db/dartium/indexed_db_dartium.dart", + category: "Client", + dart2jsPath: "indexed_db/dart2js/indexed_db_dart2js.dart"), + + "io": const LibraryInfo( + "io/io.dart", + category: "Server", + dart2jsPatchPath: "_internal/compiler/implementation/lib/io_patch.dart"), + + "isolate": const LibraryInfo( + "isolate/isolate.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/isolate_patch.dart"), + + "json": const LibraryInfo( + "json/json.dart"), + + "math": const LibraryInfo( + "math/math.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/math_patch.dart"), + + "mirrors": const LibraryInfo( + "mirrors/mirrors.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/mirrors_patch.dart"), + + "nativewrappers": const LibraryInfo( + "html/dartium/nativewrappers.dart", + category: "Client", + implementation: true, + documented: false, + platforms: VM_PLATFORM), + + "scalarlist": const LibraryInfo( + "scalarlist/scalarlist.dart", + category: "Server", + dart2jsPatchPath: "_internal/compiler/implementation/lib/scalarlist_patch.dart"), + + "svg": const LibraryInfo( + "svg/dartium/svg_dartium.dart", + category: "Client", + dart2jsPath: "svg/dart2js/svg_dart2js.dart"), + + "uri": const LibraryInfo( + "uri/uri.dart"), + + "utf": const LibraryInfo( + "utf/utf.dart"), + + "web_audio": const LibraryInfo( + "web_audio/dartium/web_audio_dartium.dart", + category: "Client", + dart2jsPath: "web_audio/dart2js/web_audio_dart2js.dart"), + + "_collection-dev": const LibraryInfo( + "_collection_dev/collection_dev.dart", + category: "Internal", + documented: false), + + "_js_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/js_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_interceptors": const LibraryInfo( + "_internal/compiler/implementation/lib/interceptors.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_foreign_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/foreign_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_isolate_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/isolate_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), +}; + +/** + * Information about a "dart:" library. + */ +class LibraryInfo { + + /** + * Path to the library's *.dart file relative to this file. + */ + final String path; + + /** + * The category in which the library should appear in the editor + * (e.g. "Common", "Client", "Server", ...). + */ + final String category; + + /** + * Path to the dart2js library's *.dart file relative to this file + * or null if dart2js uses the common library path defined above. + * Access using the [#getDart2JsPath()] method. + */ + final String dart2jsPath; + + /** + * Path to the dart2js library's patch file relative to this file + * or null if no dart2js patch file associated with this library. + * Access using the [#getDart2JsPatchPath()] method. + */ + final String dart2jsPatchPath; + + /** + * True if this library is documented and should be shown to the user. + */ + final bool documented; + + /** + * Bit flags indicating which platforms consume this library. + * See [DART2JS_LIBRARY] and [VM_LIBRARY]. + */ + final int platforms; + + /** + * True if the library contains implementation details for another library. + * The implication is that these libraries are less commonly used + * and that tools like Dart Editor should not show these libraries + * in a list of all libraries unless the user specifically asks the tool to + * do so. + */ + final bool implementation; + + const LibraryInfo(this.path, { + this.category: "Shared", + this.dart2jsPath, + this.dart2jsPatchPath, + this.implementation: false, + this.documented: true, + this.platforms: DART2JS_PLATFORM | VM_PLATFORM}); + + bool get isDart2jsLibrary => (platforms & DART2JS_PLATFORM) != 0; + bool get isVmLibrary => (platforms & VM_PLATFORM) != 0; +}
diff --git a/pkgs/markdown/lib/src/markdown/ast.dart b/pkgs/markdown/lib/src/markdown/ast.dart new file mode 100644 index 0000000..c966cea --- /dev/null +++ b/pkgs/markdown/lib/src/markdown/ast.dart
@@ -0,0 +1,65 @@ +// 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. + +part of markdown; + +/// Base class for any AST item. Roughly corresponds to Node in the DOM. Will +/// be either an Element or Text. +abstract class Node { + void accept(NodeVisitor visitor); +} + +/// A named tag that can contain other nodes. +class Element implements Node { + final String tag; + final List<Node> children; + final Map<String, String> attributes; + + Element(this.tag, this.children) + : attributes = <String, String>{}; + + Element.empty(this.tag) + : children = null, + attributes = <String, String>{}; + + Element.withTag(this.tag) + : children = [], + attributes = <String, String>{}; + + Element.text(this.tag, String text) + : children = [new Text(text)], + attributes = <String, String>{}; + + bool get isEmpty => children == null; + + void accept(NodeVisitor visitor) { + if (visitor.visitElementBefore(this)) { + for (final child in children) child.accept(visitor); + visitor.visitElementAfter(this); + } + } +} + +/// A plain text element. +class Text implements Node { + final String text; + Text(this.text); + + void accept(NodeVisitor visitor) => visitor.visitText(this); +} + +/// Visitor pattern for the AST. Renderers or other AST transformers should +/// implement this. +abstract class NodeVisitor { + /// Called when a Text node has been reached. + void visitText(Text text); + + /// Called when an Element has been reached, before its children have been + /// visited. Return `false` to skip its children. + bool visitElementBefore(Element element); + + /// Called when an Element has been reached, after its children have been + /// visited. Will not be called if [visitElementBefore] returns `false`. + void visitElementAfter(Element element); +}
diff --git a/pkgs/markdown/lib/src/markdown/block_parser.dart b/pkgs/markdown/lib/src/markdown/block_parser.dart new file mode 100644 index 0000000..67109a4 --- /dev/null +++ b/pkgs/markdown/lib/src/markdown/block_parser.dart
@@ -0,0 +1,464 @@ +// 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. + +part of markdown; + +/// The line contains only whitespace or is empty. +final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); + +/// A series of `=` or `-` (on the next line) define setext-style headers. +final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); + +/// Leading (and trailing) `#` define atx-style headers. +final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); + +/// The line starts with `>` with one optional space after. +final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); + +/// A line indented four spaces. Used for code blocks and lists. +final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); + +/// Three or more hyphens, asterisks or underscores by themselves. Note that +/// a line like `----` is valid as both HR and SETEXT. In case of a tie, +/// SETEXT should win. +final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' + r'(_+[ ]{0,2}){3,}|' + r'(\*+[ ]{0,2}){3,})$'); + +/// Really hacky way to detect block-level embedded HTML. Just looks for +/// "<somename". +final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]'); + +/// A line starting with one of these markers: `-`, `*`, `+`. May have up to +/// three leading spaces before the marker and any number of spaces or tabs +/// after. +final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); + +/// A line starting with a number like `123.`. May have up to three leading +/// spaces before the marker and any number of spaces or tabs after. +final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); + +/// Maintains the internal state needed to parse a series of lines into blocks +/// of markdown suitable for further inline parsing. +class BlockParser { + final List<String> lines; + + /// The markdown document this parser is parsing. + final Document document; + + /// Index of the current line. + int pos; + + BlockParser(this.lines, this.document) + : pos = 0; + + /// Gets the current line. + String get current => lines[pos]; + + /// Gets the line after the current one or `null` if there is none. + String get next { + // Don't read past the end. + if (pos >= lines.length - 1) return null; + return lines[pos + 1]; + } + + void advance() { + pos++; + } + + bool get isDone => pos >= lines.length; + + /// Gets whether or not the current line matches the given pattern. + bool matches(RegExp regex) { + if (isDone) return false; + return regex.firstMatch(current) != null; + } + + /// Gets whether or not the current line matches the given pattern. + bool matchesNext(RegExp regex) { + if (next == null) return false; + return regex.firstMatch(next) != null; + } +} + +abstract class BlockSyntax { + /// Gets the collection of built-in block parsers. To turn a series of lines + /// into blocks, each of these will be tried in turn. Order matters here. + static List<BlockSyntax> get syntaxes { + // Lazy initialize. + if (_syntaxes == null) { + _syntaxes = [ + new EmptyBlockSyntax(), + new BlockHtmlSyntax(), + new SetextHeaderSyntax(), + new HeaderSyntax(), + new CodeBlockSyntax(), + new BlockquoteSyntax(), + new HorizontalRuleSyntax(), + new UnorderedListSyntax(), + new OrderedListSyntax(), + new ParagraphSyntax() + ]; + } + + return _syntaxes; + } + + static List<BlockSyntax> _syntaxes; + + /// Gets the regex used to identify the beginning of this block, if any. + RegExp get pattern => null; + + bool get canEndBlock => true; + + bool canParse(BlockParser parser) { + return pattern.firstMatch(parser.current) != null; + } + + Node parse(BlockParser parser); + + List<String> parseChildLines(BlockParser parser) { + // Grab all of the lines that form the blockquote, stripping off the ">". + final childLines = <String>[]; + + while (!parser.isDone) { + final match = pattern.firstMatch(parser.current); + if (match == null) break; + childLines.add(match[1]); + parser.advance(); + } + + return childLines; + } + + /// Gets whether or not [parser]'s current line should end the previous block. + static bool isAtBlockEnd(BlockParser parser) { + if (parser.isDone) return true; + return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); + } +} + +class EmptyBlockSyntax extends BlockSyntax { + RegExp get pattern => _RE_EMPTY; + + Node parse(BlockParser parser) { + parser.advance(); + + // Don't actually emit anything. + return null; + } +} + +/// Parses setext-style headers. +class SetextHeaderSyntax extends BlockSyntax { + bool canParse(BlockParser parser) { + // Note: matches *next* line, not the current one. We're looking for the + // underlining after this line. + return parser.matchesNext(_RE_SETEXT); + } + + Node parse(BlockParser parser) { + final match = _RE_SETEXT.firstMatch(parser.next); + + final tag = (match[1][0] == '=') ? 'h1' : 'h2'; + final contents = parser.document.parseInline(parser.current); + parser.advance(); + parser.advance(); + + return new Element(tag, contents); + } +} + +/// Parses atx-style headers: `## Header ##`. +class HeaderSyntax extends BlockSyntax { + RegExp get pattern => _RE_HEADER; + + Node parse(BlockParser parser) { + final match = pattern.firstMatch(parser.current); + parser.advance(); + final level = match[1].length; + final contents = parser.document.parseInline(match[2].trim()); + return new Element('h$level', contents); + } +} + +/// Parses email-style blockquotes: `> quote`. +class BlockquoteSyntax extends BlockSyntax { + RegExp get pattern => _RE_BLOCKQUOTE; + + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser); + + // Recursively parse the contents of the blockquote. + final children = parser.document.parseLines(childLines); + + return new Element('blockquote', children); + } +} + +/// Parses preformatted code blocks that are indented four spaces. +class CodeBlockSyntax extends BlockSyntax { + RegExp get pattern => _RE_INDENT; + + List<String> parseChildLines(BlockParser parser) { + final childLines = <String>[]; + + while (!parser.isDone) { + var match = pattern.firstMatch(parser.current); + if (match != null) { + childLines.add(match[1]); + parser.advance(); + } else { + // If there's a codeblock, then a newline, then a codeblock, keep the + // code blocks together. + var nextMatch = parser.next != null ? + pattern.firstMatch(parser.next) : null; + if (parser.current.trim() == '' && nextMatch != null) { + childLines.add(''); + childLines.add(nextMatch[1]); + parser.advance(); + parser.advance(); + } else { + break; + } + } + } + return childLines; + } + + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser); + + // The Markdown tests expect a trailing newline. + childLines.add(''); + + // Escape the code. + final escaped = classifySource(Strings.join(childLines, '\n')); + + return new Element.text('pre', escaped); + } +} + +/// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. +class HorizontalRuleSyntax extends BlockSyntax { + RegExp get pattern => _RE_HR; + + Node parse(BlockParser parser) { + final match = pattern.firstMatch(parser.current); + parser.advance(); + return new Element.empty('hr'); + } +} + +/// Parses inline HTML at the block level. This differs from other markdown +/// implementations in several ways: +/// +/// 1. This one is way way WAY simpler. +/// 2. All HTML tags at the block level will be treated as blocks. If you +/// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. +/// As soon as it sees something like HTML, it stops mucking with it until +/// it hits the next block. +/// 3. Absolutely no HTML parsing or validation is done. We're a markdown +/// parser not an HTML parser! +class BlockHtmlSyntax extends BlockSyntax { + RegExp get pattern => _RE_HTML; + + bool get canEndBlock => false; + + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit a blank line. + while (!parser.isDone && !parser.matches(_RE_EMPTY)) { + childLines.add(parser.current); + parser.advance(); + } + + return new Text(Strings.join(childLines, '\n')); + } +} + +class ListItem { + bool forceBlock = false; + final List<String> lines; + + ListItem(this.lines); +} + +/// Base class for both ordered and unordered lists. +abstract class ListSyntax extends BlockSyntax { + bool get canEndBlock => false; + + String get listTag; + + Node parse(BlockParser parser) { + final items = <ListItem>[]; + var childLines = <String>[]; + + endItem() { + if (childLines.length > 0) { + items.add(new ListItem(childLines)); + childLines = <String>[]; + } + } + + var match; + tryMatch(RegExp pattern) { + match = pattern.firstMatch(parser.current); + return match != null; + } + + bool afterEmpty = false; + while (!parser.isDone) { + if (tryMatch(_RE_EMPTY)) { + // Add a blank line to the current list item. + childLines.add(''); + } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) { + // End the current list item and start a new one. + endItem(); + childLines.add(match[1]); + } else if (tryMatch(_RE_INDENT)) { + // Strip off indent and add to current item. + childLines.add(match[1]); + } else if (BlockSyntax.isAtBlockEnd(parser)) { + // Done with the list. + break; + } else { + // Anything else is paragraph text or other stuff that can be in a list + // item. However, if the previous item is a blank line, this means we're + // done with the list and are starting a new top-level paragraph. + if ((childLines.length > 0) && (childLines.last == '')) break; + childLines.add(parser.current); + } + parser.advance(); + } + + endItem(); + + // Markdown, because it hates us, specifies two kinds of list items. If you + // have a list like: + // + // * one + // * two + // + // Then it will insert the conents of the lines directly in the <li>, like: + // <ul> + // <li>one</li> + // <li>two</li> + // <ul> + // + // If, however, there are blank lines between the items, each is wrapped in + // paragraphs: + // + // * one + // + // * two + // + // <ul> + // <li><p>one</p></li> + // <li><p>two</p></li> + // <ul> + // + // In other words, sometimes we parse the contents of a list item like a + // block, and sometimes line an inline. The rules our parser implements are: + // + // - If it has more than one line, it's a block. + // - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT, + // UL, OL) it's a block. (This is for cases like "* > quote".) + // - If there was a blank line between this item and the previous one, it's + // a block. + // - If there was a blank line between this item and the next one, it's a + // block. + // - Otherwise, parse it as an inline. + + // Remove any trailing empty lines and note which items are separated by + // empty lines. Do this before seeing which items are single-line so that + // trailing empty lines on the last item don't force it into being a block. + for (int i = 0; i < items.length; i++) { + for (int j = items[i].lines.length - 1; j > 0; j--) { + if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) { + // Found an empty line. Item and one after it are blocks. + if (i < items.length - 1) { + items[i].forceBlock = true; + items[i + 1].forceBlock = true; + } + items[i].lines.removeLast(); + } else { + break; + } + } + } + + // Convert the list items to Nodes. + final itemNodes = <Node>[]; + for (final item in items) { + bool blockItem = item.forceBlock || (item.lines.length > 1); + + // See if it matches some block parser. + final blocksInList = [ + _RE_BLOCKQUOTE, + _RE_HEADER, + _RE_HR, + _RE_INDENT, + _RE_UL, + _RE_OL + ]; + + if (!blockItem) { + for (final pattern in blocksInList) { + if (pattern.firstMatch(item.lines[0]) != null) { + blockItem = true; + break; + } + } + } + + // Parse the item as a block or inline. + if (blockItem) { + // Block list item. + final children = parser.document.parseLines(item.lines); + itemNodes.add(new Element('li', children)); + } else { + // Raw list item. + final contents = parser.document.parseInline(item.lines[0]); + itemNodes.add(new Element('li', contents)); + } + } + + return new Element(listTag, itemNodes); + } +} + +/// Parses unordered lists. +class UnorderedListSyntax extends ListSyntax { + RegExp get pattern => _RE_UL; + String get listTag => 'ul'; +} + +/// Parses ordered lists. +class OrderedListSyntax extends ListSyntax { + RegExp get pattern => _RE_OL; + String get listTag => 'ol'; +} + +/// Parses paragraphs of regular text. +class ParagraphSyntax extends BlockSyntax { + bool get canEndBlock => false; + + bool canParse(BlockParser parser) => true; + + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit something that ends a paragraph. + while (!BlockSyntax.isAtBlockEnd(parser)) { + childLines.add(parser.current); + parser.advance(); + } + + final contents = parser.document.parseInline( + Strings.join(childLines, '\n')); + return new Element('p', contents); + } +}
diff --git a/pkgs/markdown/lib/src/markdown/html_renderer.dart b/pkgs/markdown/lib/src/markdown/html_renderer.dart new file mode 100644 index 0000000..1695e96 --- /dev/null +++ b/pkgs/markdown/lib/src/markdown/html_renderer.dart
@@ -0,0 +1,61 @@ +// 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. + +part of markdown; + +String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes); + +/// Translates a parsed AST to HTML. +class HtmlRenderer implements NodeVisitor { + static final _BLOCK_TAGS = new RegExp( + 'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre'); + + StringBuffer buffer; + + HtmlRenderer(); + + String render(List<Node> nodes) { + buffer = new StringBuffer(); + + for (final node in nodes) node.accept(this); + + return buffer.toString(); + } + + void visitText(Text text) { + buffer.add(text.text); + } + + bool visitElementBefore(Element element) { + // Hackish. Separate block-level elements with newlines. + if (!buffer.isEmpty && + _BLOCK_TAGS.firstMatch(element.tag) != null) { + buffer.add('\n'); + } + + buffer.add('<${element.tag}'); + + // Sort the keys so that we generate stable output. + // TODO(rnystrom): This assumes keys returns a fresh mutable + // collection. + final attributeNames = element.attributes.keys.toList(); + attributeNames.sort((a, b) => a.compareTo(b)); + for (final name in attributeNames) { + buffer.add(' $name="${element.attributes[name]}"'); + } + + if (element.isEmpty) { + // Empty element like <hr/>. + buffer.add(' />'); + return false; + } else { + buffer.add('>'); + return true; + } + } + + void visitElementAfter(Element element) { + buffer.add('</${element.tag}>'); + } +}
diff --git a/pkgs/markdown/lib/src/markdown/inline_parser.dart b/pkgs/markdown/lib/src/markdown/inline_parser.dart new file mode 100644 index 0000000..af42e3e --- /dev/null +++ b/pkgs/markdown/lib/src/markdown/inline_parser.dart
@@ -0,0 +1,410 @@ +// 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. + +part of markdown; + +/// Maintains the internal state needed to parse inline span elements in +/// markdown. +class InlineParser { + static List<InlineSyntax> get syntaxes { + // Lazy initialize. + if (_syntaxes == null) { + _syntaxes = <InlineSyntax>[ + // This first regexp matches plain text to accelerate parsing. It must + // be written so that it does not match any prefix of any following + // syntax. Most markdown is plain text, so it is faster to match one + // regexp per 'word' rather than fail to match all the following regexps + // at each non-syntax character position. It is much more important + // that the regexp is fast than complete (for example, adding grouping + // is likely to slow the regexp down enough to negate its benefit). + // Since it is purely for optimization, it can be removed for debugging. + new TextSyntax(r'\s*[A-Za-z0-9]+'), + + // The real syntaxes. + + new AutolinkSyntax(), + new LinkSyntax(), + // "*" surrounded by spaces is left alone. + new TextSyntax(r' \* '), + // "_" surrounded by spaces is left alone. + new TextSyntax(r' _ '), + // Leave already-encoded HTML entities alone. Ensures we don't turn + // "&" into "&amp;" + new TextSyntax(r'&[#a-zA-Z0-9]*;'), + // Encode "&". + new TextSyntax(r'&', sub: '&'), + // Encode "<". (Why not encode ">" too? Gruber is toying with us.) + new TextSyntax(r'<', sub: '<'), + // Parse "**strong**" tags. + new TagSyntax(r'\*\*', tag: 'strong'), + // Parse "__strong__" tags. + new TagSyntax(r'__', tag: 'strong'), + // Parse "*emphasis*" tags. + new TagSyntax(r'\*', tag: 'em'), + // Parse "_emphasis_" tags. + // TODO(rnystrom): Underscores in the middle of a word should not be + // parsed as emphasis like_in_this. + new TagSyntax(r'_', tag: 'em'), + // Parse inline code within double backticks: "``code``". + new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'), + // Parse inline code within backticks: "`code`". + new CodeSyntax(r'`([^`]*)`') + ]; + } + + return _syntaxes; + } + + static List<InlineSyntax> _syntaxes; + + /// The string of markdown being parsed. + final String source; + + /// The markdown document this parser is parsing. + final Document document; + + /// The current read position. + int pos = 0; + + /// Starting position of the last unconsumed text. + int start = 0; + + final List<TagState> _stack; + + InlineParser(this.source, this.document) + : _stack = <TagState>[]; + + List<Node> parse() { + // Make a fake top tag to hold the results. + _stack.add(new TagState(0, 0, null)); + + while (!isDone) { + bool matched = false; + + // See if any of the current tags on the stack match. We don't allow tags + // of the same kind to nest, so this takes priority over other possible // matches. + for (int i = _stack.length - 1; i > 0; i--) { + if (_stack[i].tryMatch(this)) { + matched = true; + break; + } + } + if (matched) continue; + + // See if the current text matches any defined markdown syntax. + for (final syntax in syntaxes) { + if (syntax.tryMatch(this)) { + matched = true; + break; + } + } + if (matched) continue; + + // If we got here, it's just text. + advanceBy(1); + } + + // Unwind any unmatched tags and get the results. + return _stack[0].close(this, null); + } + + writeText() { + writeTextRange(start, pos); + start = pos; + } + + writeTextRange(int start, int end) { + if (end > start) { + final text = source.substring(start, end); + final nodes = _stack.last.children; + + // If the previous node is text too, just append. + if ((nodes.length > 0) && (nodes.last is Text)) { + final newNode = new Text('${nodes.last.text}$text'); + nodes[nodes.length - 1] = newNode; + } else { + nodes.add(new Text(text)); + } + } + } + + addNode(Node node) { + _stack.last.children.add(node); + } + + // TODO(rnystrom): Only need this because RegExp doesn't let you start + // searching from a given offset. + String get currentSource => source.substring(pos, source.length); + + bool get isDone => pos == source.length; + + void advanceBy(int length) { + pos += length; + } + + void consume(int length) { + pos += length; + start = pos; + } +} + +/// Represents one kind of markdown tag that can be parsed. +abstract class InlineSyntax { + final RegExp pattern; + + InlineSyntax(String pattern) + : pattern = new RegExp(pattern, multiLine: true); + + bool tryMatch(InlineParser parser) { + final startMatch = pattern.firstMatch(parser.currentSource); + if ((startMatch != null) && (startMatch.start == 0)) { + // Write any existing plain text up to this point. + parser.writeText(); + + if (onMatch(parser, startMatch)) { + parser.consume(startMatch[0].length); + } + return true; + } + return false; + } + + bool onMatch(InlineParser parser, Match match); +} + +/// Matches stuff that should just be passed through as straight text. +class TextSyntax extends InlineSyntax { + String substitute; + TextSyntax(String pattern, {String sub}) + : super(pattern), + substitute = sub; + + bool onMatch(InlineParser parser, Match match) { + if (substitute == null) { + // Just use the original matched text. + parser.advanceBy(match[0].length); + return false; + } + + // Insert the substitution. + parser.addNode(new Text(substitute)); + return true; + } +} + +/// Matches autolinks like `<http://foo.com>`. +class AutolinkSyntax extends InlineSyntax { + AutolinkSyntax() + : super(r'<((http|https|ftp)://[^>]*)>'); + // TODO(rnystrom): Make case insensitive. + + bool onMatch(InlineParser parser, Match match) { + final url = match[1]; + + final anchor = new Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = url; + parser.addNode(anchor); + + return true; + } +} + +/// Matches syntax that has a pair of tags and becomes an element, like `*` for +/// `<em>`. Allows nested tags. +class TagSyntax extends InlineSyntax { + final RegExp endPattern; + final String tag; + + TagSyntax(String pattern, {String tag, String end}) + : super(pattern), + endPattern = new RegExp((end != null) ? end : pattern, multiLine: true), + tag = tag; + // TODO(rnystrom): Doing this.field doesn't seem to work with named args. + + bool onMatch(InlineParser parser, Match match) { + parser._stack.add(new TagState(parser.pos, + parser.pos + match[0].length, this)); + return true; + } + + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + parser.addNode(new Element(tag, state.children)); + return true; + } +} + +/// Matches inline links like `[blah] [id]` and `[blah] (url)`. +class LinkSyntax extends TagSyntax { + /// The regex for the end of a link needs to handle both reference style and + /// inline styles as well as optional titles for inline links. To make that + /// a bit more palatable, this breaks it into pieces. + static get linkPattern { + final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. + final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. + final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. + return '\](?:($refLink|$inlineLink)|)'; + + // The groups matched by this are: + // 1: Will be non-empty if it's either a ref or inline link. Will be empty + // if it's just a bare pair of square brackets with nothing after them. + // 2: Contains the id inside [] for a reference-style link. + // 3: Contains the URL for an inline link. + // 4: Contains the title, if present, for an inline link. + } + + LinkSyntax() + : super(r'\[', end: linkPattern); + + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + var url; + var title; + + // If we didn't match refLink or inlineLink, then it means there was + // nothing after the first square bracket, so it isn't a normal markdown + // link at all. Instead, we allow users of the library to specify a special + // resolver function ([setImplicitLinkResolver]) that may choose to handle + // this. Otherwise, it's just treated as plain text. + if ((match[1] == null) || (match[1] == '')) { + if (_implicitLinkResolver == null) return false; + + // Only allow implicit links if the content is just text. + // TODO(rnystrom): Do we want to relax this? + if (state.children.length != 1) return false; + if (state.children[0] is! Text) return false; + + Text link = state.children[0]; + + // See if we have a resolver that will generate a link for us. + final node = _implicitLinkResolver(link.text); + if (node == null) return false; + + parser.addNode(node); + return true; + } + + if ((match[3] != null) && (match[3] != '')) { + // Inline link like [foo](url). + url = match[3]; + title = match[4]; + + // For whatever reason, markdown allows angle-bracketed URLs here. + if (url.startsWith('<') && url.endsWith('>')) { + url = url.substring(1, url.length - 1); + } + } else { + // Reference link like [foo] [bar]. + var id = match[2]; + if (id == '') { + // The id is empty ("[]") so infer it from the contents. + id = parser.source.substring(state.startPos + 1, parser.pos); + } + + // References are case-insensitive. + id = id.toLowerCase(); + + // Look up the link. + final link = parser.document.refLinks[id]; + // If it's an unknown link just emit plaintext. + if (link == null) return false; + + url = link.url; + title = link.title; + } + + final anchor = new Element('a', state.children); + anchor.attributes['href'] = escapeHtml(url); + if ((title != null) && (title != '')) { + anchor.attributes['title'] = escapeHtml(title); + } + + parser.addNode(anchor); + return true; + } +} + +/// Matches backtick-enclosed inline code blocks. +class CodeSyntax extends InlineSyntax { + CodeSyntax(String pattern) + : super(pattern); + + bool onMatch(InlineParser parser, Match match) { + parser.addNode(new Element.text('code', escapeHtml(match[1]))); + return true; + } +} + +/// Keeps track of a currently open tag while it is being parsed. The parser +/// maintains a stack of these so it can handle nested tags. +class TagState { + /// The point in the original source where this tag started. + int startPos; + + /// The point in the original source where open tag ended. + int endPos; + + /// The syntax that created this node. + final TagSyntax syntax; + + /// The children of this node. Will be `null` for text nodes. + final List<Node> children; + + TagState(this.startPos, this.endPos, this.syntax) + : children = <Node>[]; + + /// Attempts to close this tag by matching the current text against its end + /// pattern. + bool tryMatch(InlineParser parser) { + Match endMatch = syntax.endPattern.firstMatch(parser.currentSource); + if ((endMatch != null) && (endMatch.start == 0)) { + // Close the tag. + close(parser, endMatch); + return true; + } + + return false; + } + + /// Pops this tag off the stack, completes it, and adds it to the output. + /// Will discard any unmatched tags that happen to be above it on the stack. + /// If this is the last node in the stack, returns its children. + List<Node> close(InlineParser parser, Match endMatch) { + // If there are unclosed tags on top of this one when it's closed, that + // means they are mismatched. Mismatched tags are treated as plain text in + // markdown. So for each tag above this one, we write its start tag as text + // and then adds its children to this one's children. + int index = parser._stack.indexOf(this); + + // Remove the unmatched children. + final unmatchedTags = parser._stack.getRange(index + 1, + parser._stack.length - index - 1); + parser._stack.removeRange(index + 1, parser._stack.length - index - 1); + + // Flatten them out onto this tag. + for (final unmatched in unmatchedTags) { + // Write the start tag as text. + parser.writeTextRange(unmatched.startPos, unmatched.endPos); + + // Bequeath its children unto this tag. + children.addAll(unmatched.children); + } + + // Pop this off the stack. + parser.writeText(); + parser._stack.removeLast(); + + // If the stack is empty now, this is the special "results" node. + if (parser._stack.length == 0) return children; + + // We are still parsing, so add this to its parent's children. + if (syntax.onMatchEnd(parser, endMatch, this)) { + parser.consume(endMatch[0].length); + } else { + // Didn't close correctly so revert to text. + parser.start = startPos; + parser.advanceBy(endMatch[0].length); + } + + return null; + } +}
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml new file mode 100644 index 0000000..f45db3a --- /dev/null +++ b/pkgs/markdown/pubspec.yaml
@@ -0,0 +1,7 @@ +name: markdown +author: "Dart Team <misc@dartlang.org>" +# homepage: https://github.com/dart-lang/csslib +description: A library for converting markdown to HTML. +version: 0.3.4 +dependencies: + unittest: any \ No newline at end of file
diff --git a/pkgs/markdown/test/LICENSE b/pkgs/markdown/test/LICENSE new file mode 100644 index 0000000..81764fd --- /dev/null +++ b/pkgs/markdown/test/LICENSE
@@ -0,0 +1,24 @@ +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
diff --git a/pkgs/markdown/test/lib/classify.dart b/pkgs/markdown/test/lib/classify.dart new file mode 100644 index 0000000..2838fbd --- /dev/null +++ b/pkgs/markdown/test/lib/classify.dart
@@ -0,0 +1,209 @@ +// 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. + +library classify; + +import 'src/compiler/implementation/scanner/scannerlib.dart'; +// TODO(rnystrom): Use "package:" URL (#4968). +import 'markdown.dart' as md; + +/** + * Kinds of tokens that we care to highlight differently. The values of the + * fields here will be used as CSS class names for the generated spans. + */ +class Classification { + static const NONE = null; + static const ERROR = "e"; + static const COMMENT = "c"; + static const IDENTIFIER = "i"; + static const KEYWORD = "k"; + static const OPERATOR = "o"; + static const STRING = "s"; + static const NUMBER = "n"; + static const PUNCTUATION = "p"; + + // A few things that are nice to make different: + static const TYPE_IDENTIFIER = "t"; + + // Between a keyword and an identifier + static const SPECIAL_IDENTIFIER = "r"; + + static const ARROW_OPERATOR = "a"; + + static const STRING_INTERPOLATION = 'si'; +} + +/// Returns a marked up HTML string. If the code does not appear to be valid +/// Dart code, returns the original [text]. +String classifySource(String text) { + try { + var html = new StringBuffer(); + var tokenizer = new StringScanner(text, includeComments: true); + + var whitespaceOffset = 0; + var token = tokenizer.tokenize(); + var inString = false; + while (token.kind != EOF_TOKEN) { + html.add(text.substring(whitespaceOffset, token.charOffset)); + whitespaceOffset = token.charOffset + token.slowCharCount; + + // Track whether or not we're in a string. + switch (token.kind) { + case STRING_TOKEN: + case STRING_INTERPOLATION_TOKEN: + inString = true; + break; + } + + final kind = classify(token); + final escapedText = md.escapeHtml(token.slowToString()); + if (kind != null) { + // Add a secondary class to tokens appearing within a string so that + // we can highlight tokens in an interpolation specially. + var stringClass = inString ? Classification.STRING_INTERPOLATION : ''; + html.add('<span class="$kind $stringClass">$escapedText</span>'); + } else { + html.add(escapedText); + } + + // Track whether or not we're in a string. + if (token.kind == STRING_TOKEN) { + inString = false; + } + token = token.next; + } + return html.toString(); + } catch (e) { + return text; + } +} + +bool _looksLikeType(String name) { + // If the name looks like an UppercaseName, assume it's a type. + return _looksLikePublicType(name) || _looksLikePrivateType(name); +} + +bool _looksLikePublicType(String name) { + // If the name looks like an UppercaseName, assume it's a type. + return name.length >= 2 && isUpper(name[0]) && isLower(name[1]); +} + +bool _looksLikePrivateType(String name) { + // If the name looks like an _UppercaseName, assume it's a type. + return (name.length >= 3 && name[0] == '_' && isUpper(name[1]) + && isLower(name[2])); +} + +// These ensure that they don't return "true" if the string only has symbols. +bool isUpper(String s) => s.toLowerCase() != s; +bool isLower(String s) => s.toUpperCase() != s; + +String classify(Token token) { + switch (token.kind) { + case UNKNOWN_TOKEN: + return Classification.ERROR; + + case IDENTIFIER_TOKEN: + // Special case for names that look like types. + final text = token.slowToString(); + if (_looksLikeType(text) + || text == 'num' + || text == 'bool' + || text == 'int' + || text == 'double') { + return Classification.TYPE_IDENTIFIER; + } + return Classification.IDENTIFIER; + + case STRING_TOKEN: + case STRING_INTERPOLATION_TOKEN: + return Classification.STRING; + + case INT_TOKEN: + case HEXADECIMAL_TOKEN: + case DOUBLE_TOKEN: + return Classification.NUMBER; + + case COMMENT_TOKEN: + return Classification.COMMENT; + + // => is so awesome it is in a class of its own. + case FUNCTION_TOKEN: + return Classification.ARROW_OPERATOR; + + case OPEN_PAREN_TOKEN: + case CLOSE_PAREN_TOKEN: + case OPEN_SQUARE_BRACKET_TOKEN: + case CLOSE_SQUARE_BRACKET_TOKEN: + case OPEN_CURLY_BRACKET_TOKEN: + case CLOSE_CURLY_BRACKET_TOKEN: + case COLON_TOKEN: + case SEMICOLON_TOKEN: + case COMMA_TOKEN: + case PERIOD_TOKEN: + case PERIOD_PERIOD_TOKEN: + return Classification.PUNCTUATION; + + case PLUS_PLUS_TOKEN: + case MINUS_MINUS_TOKEN: + case TILDE_TOKEN: + case BANG_TOKEN: + case EQ_TOKEN: + case BAR_EQ_TOKEN: + case CARET_EQ_TOKEN: + case AMPERSAND_EQ_TOKEN: + case LT_LT_EQ_TOKEN: + case GT_GT_EQ_TOKEN: + case PLUS_EQ_TOKEN: + case MINUS_EQ_TOKEN: + case STAR_EQ_TOKEN: + case SLASH_EQ_TOKEN: + case TILDE_SLASH_EQ_TOKEN: + case PERCENT_EQ_TOKEN: + case QUESTION_TOKEN: + case BAR_BAR_TOKEN: + case AMPERSAND_AMPERSAND_TOKEN: + case BAR_TOKEN: + case CARET_TOKEN: + case AMPERSAND_TOKEN: + case LT_LT_TOKEN: + case GT_GT_TOKEN: + case PLUS_TOKEN: + case MINUS_TOKEN: + case STAR_TOKEN: + case SLASH_TOKEN: + case TILDE_SLASH_TOKEN: + case PERCENT_TOKEN: + case EQ_EQ_TOKEN: + case BANG_EQ_TOKEN: + case EQ_EQ_EQ_TOKEN: + case BANG_EQ_EQ_TOKEN: + case LT_TOKEN: + case GT_TOKEN: + case LT_EQ_TOKEN: + case GT_EQ_TOKEN: + case INDEX_TOKEN: + case INDEX_EQ_TOKEN: + return Classification.OPERATOR; + + // Color keyword token. Most are colored as keywords. + case HASH_TOKEN: + case KEYWORD_TOKEN: + if (token.stringValue == 'void') { + // Color "void" as a type. + return Classification.TYPE_IDENTIFIER; + } + if (token.stringValue == 'this' || token.stringValue == 'super') { + // Color "this" and "super" as identifiers. + return Classification.SPECIAL_IDENTIFIER; + } + return Classification.KEYWORD; + + case EOF_TOKEN: + return Classification.NONE; + + default: + return Classification.NONE; + } +}
diff --git a/pkgs/markdown/test/lib/markdown.dart b/pkgs/markdown/test/lib/markdown.dart new file mode 100644 index 0000000..ef111fb --- /dev/null +++ b/pkgs/markdown/test/lib/markdown.dart
@@ -0,0 +1,118 @@ +// 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. + +/// Parses text in a markdown-like format and renders to HTML. +library markdown; + +import 'classify.dart'; + +// TODO(rnystrom): Use "package:" URL (#4968). +part 'src/markdown/ast.dart'; +part 'src/markdown/block_parser.dart'; +part 'src/markdown/html_renderer.dart'; +part 'src/markdown/inline_parser.dart'; + +/// Converts the given string of markdown to HTML. +String markdownToHtml(String markdown) { + final document = new Document(); + + // Replace windows line endings with unix line endings, and split. + final lines = markdown.replaceAll('\r\n','\n').split('\n'); + document.parseRefLinks(lines); + final blocks = document.parseLines(lines); + return renderToHtml(blocks); +} + +/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents. +String escapeHtml(String html) { + return html.replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); +} + +var _implicitLinkResolver; + +Node setImplicitLinkResolver(Node resolver(String text)) { + _implicitLinkResolver = resolver; +} + +/// Maintains the context needed to parse a markdown document. +class Document { + final Map<String, Link> refLinks; + + Document() + : refLinks = <String, Link>{}; + + parseRefLinks(List<String> lines) { + // This is a hideous regex. It matches: + // [id]: http:foo.com "some title" + // Where there may whitespace in there, and where the title may be in + // single quotes, double quotes, or parentheses. + final indent = r'^[ ]{0,3}'; // Leading indentation. + final id = r'\[([^\]]+)\]'; // Reference id in [brackets]. + final quote = r'"[^"]+"'; // Title in "double quotes". + final apos = r"'[^']+'"; // Title in 'single quotes'. + final paren = r"\([^)]+\)"; // Title in (parentheses). + final pattern = new RegExp( + '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$'); + + for (int i = 0; i < lines.length; i++) { + final match = pattern.firstMatch(lines[i]); + if (match != null) { + // Parse the link. + var id = match[1]; + var url = match[2]; + var title = match[3]; + + if (title == '') { + // No title. + title = null; + } else { + // Remove "", '', or (). + title = title.substring(1, title.length - 1); + } + + // References are case-insensitive. + id = id.toLowerCase(); + + refLinks[id] = new Link(id, url, title); + + // Remove it from the output. We replace it with a blank line which will + // get consumed by later processing. + lines[i] = ''; + } + } + } + + /// Parse the given [lines] of markdown to a series of AST nodes. + List<Node> parseLines(List<String> lines) { + final parser = new BlockParser(lines, this); + + final blocks = []; + while (!parser.isDone) { + for (final syntax in BlockSyntax.syntaxes) { + if (syntax.canParse(parser)) { + final block = syntax.parse(parser); + if (block != null) blocks.add(block); + break; + } + } + } + + return blocks; + } + + /// Takes a string of raw text and processes all inline markdown tags, + /// returning a list of AST nodes. For example, given ``"*this **is** a* + /// `markdown`"``, returns: + /// `<em>this <strong>is</strong> a</em> <code>markdown</code>`. + List<Node> parseInline(String text) => new InlineParser(text, this).parse(); +} + +class Link { + final String id; + final String url; + final String title; + Link(this.id, this.url, this.title); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/compiler.dart b/pkgs/markdown/test/lib/src/compiler/compiler.dart new file mode 100644 index 0000000..bbee705 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/compiler.dart
@@ -0,0 +1,177 @@ +// 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. + +library compiler; + +import 'dart:async'; +import 'dart:uri'; +import 'implementation/apiimpl.dart'; + +// Unless explicitly allowed, passing [:null:] for any argument to the +// methods of library will result in an Error being thrown. + +/** + * Returns a future that completes to the source corresponding to + * [uri]. If an exception occurs, the future completes with this + * exception. + */ +typedef Future<String> CompilerInputProvider(Uri uri); + +/// Deprecated, please use [CompilerInputProvider] instead. +typedef Future<String> ReadStringFromUri(Uri uri); + +/** + * Returns a [StreamSink] that will serve as compiler output for the given + * component. + * + * Components are identified by [name] and [extension]. By convention, + * the empty string [:"":] will represent the main script + * (corresponding to the script parameter of [compile]) even if the + * main script is a library. For libraries that are compiled + * separately, the library name is used. + * + * At least the following extensions can be expected: + * + * * "js" for JavaScript output. + * * "js.map" for source maps. + * * "dart" for Dart output. + * * "dart.map" for source maps. + * + * As more features are added to the compiler, new names and + * extensions may be introduced. + */ +typedef StreamSink<String> CompilerOutputProvider(String name, + String extension); + +/** + * Invoked by the compiler to report diagnostics. If [uri] is + * [:null:], so are [begin] and [end]. No other arguments may be + * [:null:]. If [uri] is not [:null:], neither are [begin] and + * [end]. [uri] indicates the compilation unit from where the + * diagnostic originates. [begin] and [end] are zero-based character + * offsets from the beginning of the compilaton unit. [message] is the + * diagnostic message, and [kind] indicates indicates what kind of + * diagnostic it is. + */ +typedef void DiagnosticHandler(Uri uri, int begin, int end, + String message, Diagnostic kind); + +/** + * Returns a future that completes to a non-null String when [script] + * has been successfully compiled. + * + * The compiler output is obtained by providing an [outputProvider]. + * + * If the compilation fails, the future's value will be [:null:] and + * [handler] will have been invoked at least once with [:kind == + * Diagnostic.ERROR:] or [:kind == Diagnostic.CRASH:]. + * + * Deprecated: if no [outputProvider] is given, the future completes + * to the compiled script. This behavior will be removed in the future + * as the compiler may create multiple files to support lazy loading + * of libraries. + */ +Future<String> compile(Uri script, + Uri libraryRoot, + Uri packageRoot, + CompilerInputProvider inputProvider, + DiagnosticHandler handler, + [List<String> options = const [], + CompilerOutputProvider outputProvider]) { + if (!libraryRoot.path.endsWith("/")) { + throw new ArgumentError("libraryRoot must end with a /"); + } + if (packageRoot != null && !packageRoot.path.endsWith("/")) { + throw new ArgumentError("packageRoot must end with a /"); + } + // TODO(ahe): Consider completing the future with an exception if + // code is null. + Compiler compiler = new Compiler(inputProvider, + outputProvider, + handler, + libraryRoot, + packageRoot, + options); + compiler.run(script); + String code = compiler.assembledCode; + if (code != null && outputProvider != null) { + String outputType = 'js'; + if (options.contains('--output-type=dart')) { + outputType = 'dart'; + } + outputProvider('', outputType) + ..add(code) + ..close(); + code = ''; // Non-null signals success. + } + return new Future.immediate(code); +} + +/** + * Kind of diagnostics that the compiler can report. + */ +class Diagnostic { + /** + * An error as identified by the "Dart Programming Language + * Specification" [http://www.dartlang.org/docs/spec/]. + * + * Note: the compiler may still produce an executable result after + * reporting a compilation error. The specification says: + * + * "A compile-time error must be reported by a Dart compiler before + * the erroneous code is executed." and "If a compile-time error + * occurs within the code of a running isolate A, A is immediately + * suspended." + * + * This means that the compiler can generate code that when executed + * terminates execution. + */ + static const Diagnostic ERROR = const Diagnostic(1, 'error'); + + /** + * A warning as identified by the "Dart Programming Language + * Specification" [http://www.dartlang.org/docs/spec/]. + */ + static const Diagnostic WARNING = const Diagnostic(2, 'warning'); + + /** + * Any other warning that is not covered by [WARNING]. + */ + static const Diagnostic LINT = const Diagnostic(4, 'lint'); + + /** + * Informational messages. + */ + static const Diagnostic INFO = const Diagnostic(8, 'info'); + + /** + * Informational messages that shouldn't be printed unless + * explicitly requested by the user of a compiler. + */ + static const Diagnostic VERBOSE_INFO = const Diagnostic(16, 'verbose info'); + + /** + * An internal error in the compiler. + */ + static const Diagnostic CRASH = const Diagnostic(32, 'crash'); + + /** + * An [int] representation of this kind. The ordinals are designed + * to be used as bitsets. + */ + final int ordinal; + + /** + * The name of this kind. + */ + final String name; + + /** + * This constructor is not private to support user-defined + * diagnostic kinds. + */ + const Diagnostic(this.ordinal, this.name); + + String toString() => name; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/README.txt b/pkgs/markdown/test/lib/src/compiler/implementation/README.txt new file mode 100644 index 0000000..8fe0a29 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/README.txt
@@ -0,0 +1,12 @@ +Dart2JS is the Dart-to-JavaScript compiler for Dart. This compiler +will provide high-quality translation from Dart to JavaScript. + +Some areas that will explored in this project are: + + * high-performance extensible scanner and parser + * concrete type inferencing + * fancy language tool support + * programming environment integration + * SSA-based intermediate representation + * adaptive compilation on the client +
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart b/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart new file mode 100644 index 0000000..663e003 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart
@@ -0,0 +1,266 @@ +// 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. + +library leg_apiimpl; + +import 'dart:uri'; +import 'dart:async'; + +import '../compiler.dart' as api; +import 'dart2jslib.dart' as leg; +import 'tree/tree.dart' as tree; +import 'elements/elements.dart' as elements; +import 'ssa/tracer.dart' as ssa; +import '../../libraries.dart'; +import 'source_file.dart'; + +class Compiler extends leg.Compiler { + api.ReadStringFromUri provider; + api.DiagnosticHandler handler; + final Uri libraryRoot; + final Uri packageRoot; + List<String> options; + bool mockableLibraryUsed = false; + final Set<String> allowedLibraryCategories; + + Compiler(this.provider, + api.CompilerOutputProvider outputProvider, + this.handler, + this.libraryRoot, + this.packageRoot, + List<String> options) + : this.options = options, + this.allowedLibraryCategories = getAllowedLibraryCategories(options), + super( + tracer: new ssa.HTracer(), + outputProvider: outputProvider, + enableTypeAssertions: hasOption(options, '--enable-checked-mode'), + enableUserAssertions: hasOption(options, '--enable-checked-mode'), + enableMinification: hasOption(options, '--minify'), + enableNativeLiveTypeAnalysis: + !hasOption(options, '--disable-native-live-type-analysis'), + emitJavaScript: !hasOption(options, '--output-type=dart'), + disallowUnsafeEval: hasOption(options, '--disallow-unsafe-eval'), + analyzeAll: hasOption(options, '--analyze-all'), + analyzeOnly: hasOption(options, '--analyze-only'), + rejectDeprecatedFeatures: + hasOption(options, '--reject-deprecated-language-features'), + checkDeprecationInSdk: + hasOption(options, + '--report-sdk-use-of-deprecated-language-features'), + strips: getStrips(options), + enableConcreteTypeInference: + hasOption(options, '--enable-concrete-type-inference'), + preserveComments: hasOption(options, '--preserve-comments')) { + if (!libraryRoot.path.endsWith("/")) { + throw new ArgumentError("libraryRoot must end with a /"); + } + if (packageRoot != null && !packageRoot.path.endsWith("/")) { + throw new ArgumentError("packageRoot must end with a /"); + } + } + + static List<String> getStrips(List<String> options) { + for (String option in options) { + if (option.startsWith('--force-strip=')) { + return option.substring('--force-strip='.length).split(','); + } + } + return const <String>[]; + } + + static Set<String> getAllowedLibraryCategories(List<String> options) { + for (String option in options) { + if (option.startsWith('--categories=')) { + var result = option.substring('--categories='.length).split(','); + result.add('Shared'); + result.add('Internal'); + return new Set<String>.from(result); + } + } + return new Set<String>.from(['Client', 'Shared', 'Internal']); + } + + static bool hasOption(List<String> options, String option) { + return options.indexOf(option) >= 0; + } + + // TODO(johnniwinther): Merge better with [translateDartUri] when + // [scanBuiltinLibrary] is removed. + String lookupLibraryPath(String dartLibraryName) { + LibraryInfo info = LIBRARIES[dartLibraryName]; + if (info == null) return null; + if (!info.isDart2jsLibrary) return null; + if (!allowedLibraryCategories.contains(info.category)) return null; + String path = info.dart2jsPath; + if (path == null) { + path = info.path; + } + return "lib/$path"; + } + + String lookupPatchPath(String dartLibraryName) { + LibraryInfo info = LIBRARIES[dartLibraryName]; + if (info == null) return null; + if (!info.isDart2jsLibrary) return null; + String path = info.dart2jsPatchPath; + if (path == null) return null; + return "lib/$path"; + } + + elements.LibraryElement scanBuiltinLibrary(String path) { + Uri uri = libraryRoot.resolve(lookupLibraryPath(path)); + Uri canonicalUri = new Uri.fromComponents(scheme: "dart", path: path); + elements.LibraryElement library = + libraryLoader.loadLibrary(uri, null, canonicalUri); + return library; + } + + void log(message) { + handler(null, null, null, message, api.Diagnostic.VERBOSE_INFO); + } + + /// See [leg.Compiler.translateResolvedUri]. + Uri translateResolvedUri(elements.LibraryElement importingLibrary, + Uri resolvedUri, tree.Node node) { + if (resolvedUri.scheme == 'dart') { + return translateDartUri(importingLibrary, resolvedUri, node); + } + return resolvedUri; + } + + /** + * Reads the script designated by [readableUri]. + */ + leg.Script readScript(Uri readableUri, [tree.Node node]) { + if (!readableUri.isAbsolute()) { + internalError('Relative uri $readableUri provided to readScript(Uri)', + node: node); + } + return fileReadingTask.measure(() { + Uri resourceUri = translateUri(readableUri, node); + String text = ""; + try { + // TODO(ahe): We expect the future to be complete and call value + // directly. In effect, we don't support truly asynchronous API. + text = deprecatedFutureValue(provider(resourceUri)); + } catch (exception) { + if (node != null) { + cancel("$exception", node: node); + } else { + reportDiagnostic(null, "$exception", api.Diagnostic.ERROR); + throw new leg.CompilerCancelledException("$exception"); + } + } + SourceFile sourceFile = new SourceFile(resourceUri.toString(), text); + // We use [readableUri] as the URI for the script since need to preserve + // the scheme in the script because [Script.uri] is used for resolving + // relative URIs mentioned in the script. See the comment on + // [LibraryLoader] for more details. + return new leg.Script(readableUri, sourceFile); + }); + } + + /** + * Translates a readable URI into a resource URI. + * + * See [LibraryLoader] for terminology on URIs. + */ + Uri translateUri(Uri readableUri, tree.Node node) { + switch (readableUri.scheme) { + case 'package': return translatePackageUri(readableUri, node); + default: return readableUri; + } + } + + Uri translateDartUri(elements.LibraryElement importingLibrary, + Uri resolvedUri, tree.Node node) { + LibraryInfo libraryInfo = LIBRARIES[resolvedUri.path]; + String path = lookupLibraryPath(resolvedUri.path); + if (libraryInfo != null && + libraryInfo.category == "Internal") { + bool allowInternalLibraryAccess = false; + if (importingLibrary != null) { + if (importingLibrary.isPlatformLibrary || importingLibrary.isPatch) { + allowInternalLibraryAccess = true; + } else if (importingLibrary.canonicalUri.path.contains( + 'dart/tests/compiler/dart2js_native')) { + allowInternalLibraryAccess = true; + } + } + if (!allowInternalLibraryAccess) { + if (node != null && importingLibrary != null) { + reportDiagnostic(spanFromNode(node), + 'Error: Internal library $resolvedUri is not accessible from ' + '${importingLibrary.canonicalUri}.', + api.Diagnostic.ERROR); + } else { + reportDiagnostic(null, + 'Error: Internal library $resolvedUri is not accessible.', + api.Diagnostic.ERROR); + } + //path = null; + } + } + if (path == null) { + if (node != null) { + reportError(node, 'library not found ${resolvedUri}'); + } else { + reportDiagnostic(null, 'library not found ${resolvedUri}', + api.Diagnostic.ERROR); + } + return null; + } + if (resolvedUri.path == 'html' || + resolvedUri.path == 'io') { + // TODO(ahe): Get rid of mockableLibraryUsed when test.dart + // supports this use case better. + mockableLibraryUsed = true; + } + return libraryRoot.resolve(path); + } + + Uri resolvePatchUri(String dartLibraryPath) { + String patchPath = lookupPatchPath(dartLibraryPath); + if (patchPath == null) return null; + return libraryRoot.resolve(patchPath); + } + + translatePackageUri(Uri uri, tree.Node node) => packageRoot.resolve(uri.path); + + bool run(Uri uri) { + log('Allowed library categories: $allowedLibraryCategories'); + bool success = super.run(uri); + int cumulated = 0; + for (final task in tasks) { + cumulated += task.timing; + log('${task.name} took ${task.timing}msec'); + } + int total = totalCompileTime.elapsedMilliseconds; + log('Total compile-time ${total}msec;' + ' unaccounted ${total - cumulated}msec'); + return success; + } + + void reportDiagnostic(leg.SourceSpan span, String message, + api.Diagnostic kind) { + if (identical(kind, api.Diagnostic.ERROR) + || identical(kind, api.Diagnostic.CRASH)) { + compilationFailed = true; + } + // [:span.uri:] might be [:null:] in case of a [Script] with no [uri]. For + // instance in the [Types] constructor in typechecker.dart. + if (span == null || span.uri == null) { + handler(null, null, null, message, kind); + } else { + handler(translateUri(span.uri, null), span.begin, span.end, + message, kind); + } + } + + bool get isMockCompilation { + return mockableLibraryUsed + && (options.indexOf('--allow-mock-compilation') != -1); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart b/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart new file mode 100644 index 0000000..4ed1564 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/closure.dart
@@ -0,0 +1,663 @@ +// 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. + +library closureToClassMapper; + +import "elements/elements.dart"; +import "dart2jslib.dart"; +import "dart_types.dart"; +import "scanner/scannerlib.dart" show Token; +import "tree/tree.dart"; +import "util/util.dart"; +import "elements/modelx.dart" show ElementX, FunctionElementX, ClassElementX; + +abstract class ClosureNamer { + SourceString getClosureVariableName(SourceString name, int id); +} + + +class ClosureTask extends CompilerTask { + Map<Node, ClosureClassMap> closureMappingCache; + ClosureNamer namer; + ClosureTask(Compiler compiler, this.namer) + : closureMappingCache = new Map<Node, ClosureClassMap>(), + super(compiler); + + String get name => "Closure Simplifier"; + + ClosureClassMap computeClosureToClassMapping(Element element, + Expression node, + TreeElements elements) { + return measure(() { + ClosureClassMap cached = closureMappingCache[node]; + if (cached != null) return cached; + + ClosureTranslator translator = + new ClosureTranslator(compiler, elements, closureMappingCache, namer); + + // The translator will store the computed closure-mappings inside the + // cache. One for given node and one for each nested closure. + if (node is FunctionExpression) { + translator.translateFunction(element, node); + } else { + // Must be the lazy initializer of a static. + assert(node is SendSet); + translator.translateLazyInitializer(element, node); + } + assert(closureMappingCache[node] != null); + return closureMappingCache[node]; + }); + } + + ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { + return measure(() { + ClosureClassMap nestedClosureData = closureMappingCache[node]; + if (nestedClosureData == null) { + compiler.internalError("No closure cache", node: node); + } + return nestedClosureData; + }); + } +} + +class ClosureFieldElement extends ElementX { + ClosureFieldElement(SourceString name, ClassElement enclosing) + : super(name, ElementKind.FIELD, enclosing); + + bool isInstanceMember() => true; + bool isAssignable() => false; + // The names of closure variables don't need renaming, since their use is very + // simple and they have 1-character names in the minified mode. + bool hasFixedBackendName() => true; + String fixedBackendName() => name.slowToString(); + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; + + String toString() => "ClosureFieldElement($name)"; +} + +class ClosureClassElement extends ClassElementX { + ClosureClassElement(SourceString name, + Compiler compiler, + this.methodElement, + Element enclosingElement) + : super(name, + enclosingElement, + // By assigning a fresh class-id we make sure that the hashcode + // is unique, but also emit closure classes after all other + // classes (since the emitter sorts classes by their id). + compiler.getNextFreeClassId(), + STATE_DONE) { + compiler.closureClass.ensureResolved(compiler); + supertype = compiler.closureClass.computeType(compiler); + interfaces = const Link<DartType>(); + allSupertypes = const Link<DartType>().prepend(supertype); + } + + bool isClosure() => true; + + /** + * The most outer method this closure is declared into. + */ + Element methodElement; +} + +class BoxElement extends ElementX { + BoxElement(SourceString name, Element enclosingElement) + : super(name, ElementKind.VARIABLE, enclosingElement); +} + +class ThisElement extends ElementX { + ThisElement(Element enclosing) + : super(const SourceString('this'), ElementKind.PARAMETER, enclosing); + + bool isAssignable() => false; + + // Since there is no declaration corresponding to 'this', use the position of + // the enclosing method. + Token position() => enclosingElement.position(); +} + +class CheckVariableElement extends ElementX { + Element parameter; + CheckVariableElement(SourceString name, this.parameter, Element enclosing) + : super(name, ElementKind.VARIABLE, enclosing); + + // Since there is no declaration for the synthetic 'check' variable, use + // parameter. + Token position() => parameter.position(); +} + +// The box-element for a scope, and the captured variables that need to be +// stored in the box. +class ClosureScope { + Element boxElement; + Map<Element, Element> capturedVariableMapping; + // If the scope is attached to a [For] contains the variables that are + // declared in the initializer of the [For] and that need to be boxed. + // Otherwise contains the empty List. + List<Element> boxedLoopVariables; + + ClosureScope(this.boxElement, this.capturedVariableMapping) + : boxedLoopVariables = const <Element>[]; + + bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty; +} + +class ClosureClassMap { + // The closure's element before any translation. Will be null for methods. + final Element closureElement; + // The closureClassElement will be null for methods that are not local + // closures. + final ClassElement closureClassElement; + // The callElement will be null for methods that are not local closures. + final FunctionElement callElement; + // The [thisElement] makes handling 'this' easier by treating it like any + // other argument. It is only set for instance-members. + final ThisElement thisElement; + + // Maps free locals, arguments and function elements to their captured + // copies. + final Map<Element, Element> freeVariableMapping; + // Maps closure-fields to their captured elements. This is somehow the inverse + // mapping of [freeVariableMapping], but whereas [freeVariableMapping] does + // not deal with boxes, here we map instance-fields (which might represent + // boxes) to their boxElement. + final Map<Element, Element> capturedFieldMapping; + + // Maps scopes ([Loop] and [FunctionExpression] nodes) to their + // [ClosureScope] which contains their box and the + // captured variables that are stored in the box. + // This map will be empty if the method/closure of this [ClosureData] does not + // contain any nested closure. + final Map<Node, ClosureScope> capturingScopes; + + final Set<Element> usedVariablesInTry; + + // A map from the parameter element to the variable element that + // holds the sentinel check. + final Map<Element, Element> parametersWithSentinel; + + ClosureClassMap(this.closureElement, + this.closureClassElement, + this.callElement, + this.thisElement) + : this.freeVariableMapping = new Map<Element, Element>(), + this.capturedFieldMapping = new Map<Element, Element>(), + this.capturingScopes = new Map<Node, ClosureScope>(), + this.usedVariablesInTry = new Set<Element>(), + this.parametersWithSentinel = new Map<Element, Element>(); + + bool isClosure() => closureElement != null; +} + +class ClosureTranslator extends Visitor { + final Compiler compiler; + final TreeElements elements; + int closureFieldCounter = 0; + int boxedFieldCounter = 0; + bool inTryStatement = false; + final Map<Node, ClosureClassMap> closureMappingCache; + + // Map of captured variables. Initially they will map to themselves. If + // a variable needs to be boxed then the scope declaring the variable + // will update this mapping. + Map<Element, Element> capturedVariableMapping; + // List of encountered closures. + List<Expression> closures; + + // The variables that have been declared in the current scope. + List<Element> scopeVariables; + + // Keep track of the mutated variables so that we don't need to box + // non-mutated variables. + Set<Element> mutatedVariables; + + Element outermostElement; + Element currentElement; + + // The closureData of the currentFunctionElement. + ClosureClassMap closureData; + + ClosureNamer namer; + + bool insideClosure = false; + + ClosureTranslator(this.compiler, this.elements, this.closureMappingCache, + this.namer) + : capturedVariableMapping = new Map<Element, Element>(), + closures = <Expression>[], + mutatedVariables = new Set<Element>(); + + void translateFunction(Element element, FunctionExpression node) { + // For constructors the [element] and the [:elements[node]:] may differ. + // The [:elements[node]:] always points to the generative-constructor + // element, whereas the [element] might be the constructor-body element. + visit(node); // [visitFunctionExpression] will call [visitInvokable]. + // When variables need to be boxed their [capturedVariableMapping] is + // updated, but we delay updating the similar freeVariableMapping in the + // closure datas that capture these variables. + // The closures don't have their fields (in the closure class) set, either. + updateClosures(); + } + + void translateLazyInitializer(Element element, SendSet node) { + assert(node.assignmentOperator.source == const SourceString("=")); + Expression initialValue = node.argumentsNode.nodes.head; + visitInvokable(element, node, () { visit(initialValue); }); + updateClosures(); + } + + // This function runs through all of the existing closures and updates their + // free variables to the boxed value. It also adds the field-elements to the + // class representing the closure. At the same time it fills the + // [capturedFieldMapping]. + void updateClosures() { + for (Expression closure in closures) { + // The captured variables that need to be stored in a field of the closure + // class. + Set<Element> fieldCaptures = new Set<Element>(); + Set<Element> boxes = new Set<Element>(); + ClosureClassMap data = closureMappingCache[closure]; + Map<Element, Element> freeVariableMapping = data.freeVariableMapping; + // We get a copy of the keys and iterate over it, to avoid modifications + // to the map while iterating over it. + freeVariableMapping.keys.toList().forEach((Element fromElement) { + assert(fromElement == freeVariableMapping[fromElement]); + Element updatedElement = capturedVariableMapping[fromElement]; + assert(updatedElement != null); + if (fromElement == updatedElement) { + assert(freeVariableMapping[fromElement] == updatedElement); + assert(Elements.isLocal(updatedElement) + || updatedElement.isTypeVariable()); + // The variable has not been boxed. + fieldCaptures.add(updatedElement); + } else { + // A boxed element. + freeVariableMapping[fromElement] = updatedElement; + Element boxElement = updatedElement.enclosingElement; + assert(boxElement.kind == ElementKind.VARIABLE); + boxes.add(boxElement); + } + }); + ClassElement closureElement = data.closureClassElement; + assert(closureElement != null || + (fieldCaptures.isEmpty && boxes.isEmpty)); + void addElement(Element element, SourceString name) { + Element fieldElement = new ClosureFieldElement(name, closureElement); + closureElement.addBackendMember(fieldElement); + data.capturedFieldMapping[fieldElement] = element; + freeVariableMapping[element] = fieldElement; + } + // Add the box elements first so we get the same ordering. + // TODO(sra): What is the canonical order of multiple boxes? + for (Element capturedElement in boxes) { + addElement(capturedElement, capturedElement.name); + } + for (Element capturedElement in + Elements.sortedByPosition(fieldCaptures)) { + int id = closureFieldCounter++; + SourceString name = + namer.getClosureVariableName(capturedElement.name, id); + addElement(capturedElement, name); + } + closureElement.reverseBackendMembers(); + } + } + + void useLocal(Element element) { + // If the element is not declared in the current function and the element + // is not the closure itself we need to mark the element as free variable. + // Note that the check on [insideClosure] is not just an + // optimization: factories have type parameters as function + // parameters, and type parameters are declared in the class, not + // the factory. + if (insideClosure && + element.enclosingElement != currentElement && + element != currentElement) { + assert(closureData.freeVariableMapping[element] == null || + closureData.freeVariableMapping[element] == element); + closureData.freeVariableMapping[element] = element; + } else if (inTryStatement) { + // Don't mark the this-element. This would complicate things in the + // builder. + if (element != closureData.thisElement) { + // TODO(ngeoffray): only do this if the variable is mutated. + closureData.usedVariablesInTry.add(element); + } + } + } + + void declareLocal(Element element) { + scopeVariables.add(element); + } + + visit(Node node) => node.accept(this); + + visitNode(Node node) => node.visitChildren(this); + + visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + Element element = elements[definition]; + assert(element != null); + declareLocal(element); + // We still need to visit the right-hand sides of the init-assignments. + // For SendSets don't visit the left again. Otherwise it would be marked + // as mutated. + if (definition is Send) { + Send assignment = definition; + Node arguments = assignment.argumentsNode; + if (arguments != null) { + visit(arguments); + } + } else { + visit(definition); + } + } + } + + visitIdentifier(Identifier node) { + if (node.isThis()) { + useLocal(closureData.thisElement); + } + node.visitChildren(this); + } + + visitSend(Send node) { + Element element = elements[node]; + if (Elements.isLocal(element)) { + useLocal(element); + } else if (node.receiver == null && + Elements.isInstanceSend(node, elements)) { + useLocal(closureData.thisElement); + } else if (node.isSuperCall) { + useLocal(closureData.thisElement); + } else if (node.isParameterCheck) { + Element parameter = elements[node.receiver]; + FunctionElement enclosing = parameter.enclosingElement; + FunctionExpression function = enclosing.parseNode(compiler); + ClosureClassMap cached = closureMappingCache[function]; + if (!cached.parametersWithSentinel.containsKey(parameter)) { + SourceString parameterName = parameter.name; + String name = '${parameterName.slowToString()}_check'; + Element newElement = new CheckVariableElement(new SourceString(name), + parameter, + enclosing); + useLocal(newElement); + cached.parametersWithSentinel[parameter] = newElement; + } + } + node.visitChildren(this); + } + + visitSendSet(SendSet node) { + Element element = elements[node]; + if (Elements.isLocal(element)) { + mutatedVariables.add(element); + } + super.visitSendSet(node); + } + + visitNewExpression(NewExpression node) { + DartType type = elements.getType(node); + + bool hasTypeVariable(DartType type) { + if (type is TypeVariableType) { + return true; + } else if (type is InterfaceType) { + InterfaceType ifcType = type; + for (DartType argument in ifcType.typeArguments) { + if (hasTypeVariable(argument)) { + return true; + } + } + } + return false; + } + + void analyzeTypeVariables(DartType type) { + if (type is TypeVariableType) { + useLocal(type.element); + } else if (type is InterfaceType) { + InterfaceType ifcType = type; + for (DartType argument in ifcType.typeArguments) { + analyzeTypeVariables(argument); + } + } + } + if (outermostElement.isMember() && + compiler.world.needsRti(outermostElement.getEnclosingClass())) { + if (outermostElement.isInstanceMember() + || outermostElement.isGenerativeConstructor()) { + if (hasTypeVariable(type)) useLocal(closureData.thisElement); + } else if (outermostElement.isFactoryConstructor()) { + analyzeTypeVariables(type); + } + } + + node.visitChildren(this); + } + + // If variables that are declared in the [node] scope are captured and need + // to be boxed create a box-element and update the [capturingScopes] in the + // current [closureData]. + // The boxed variables are updated in the [capturedVariableMapping]. + void attachCapturedScopeVariables(Node node) { + Element box = null; + Map<Element, Element> scopeMapping = new Map<Element, Element>(); + for (Element element in scopeVariables) { + // No need to box non-assignable elements. + if (!element.isAssignable()) continue; + if (!mutatedVariables.contains(element)) continue; + if (capturedVariableMapping.containsKey(element)) { + if (box == null) { + // TODO(floitsch): construct better box names. + SourceString boxName = + namer.getClosureVariableName(const SourceString('box'), + closureFieldCounter++); + box = new BoxElement(boxName, currentElement); + } + String elementName = element.name.slowToString(); + SourceString boxedName = + namer.getClosureVariableName(new SourceString(elementName), + boxedFieldCounter++); + // TODO(kasperl): Should this be a FieldElement instead? + Element boxed = new ElementX(boxedName, ElementKind.FIELD, box); + // No need to rename the fields of a box, so we give them a native name + // right now. + boxed.setFixedBackendName(boxedName.slowToString()); + scopeMapping[element] = boxed; + capturedVariableMapping[element] = boxed; + } + } + if (!scopeMapping.isEmpty) { + ClosureScope scope = new ClosureScope(box, scopeMapping); + closureData.capturingScopes[node] = scope; + } + } + + void inNewScope(Node node, Function action) { + List<Element> oldScopeVariables = scopeVariables; + scopeVariables = new List<Element>(); + action(); + attachCapturedScopeVariables(node); + for (Element element in scopeVariables) { + mutatedVariables.remove(element); + } + scopeVariables = oldScopeVariables; + } + + visitLoop(Loop node) { + inNewScope(node, () { + node.visitChildren(this); + }); + } + + visitFor(For node) { + visitLoop(node); + // See if we have declared loop variables that need to be boxed. + if (node.initializer == null) return; + VariableDefinitions definitions = node.initializer.asVariableDefinitions(); + if (definitions == null) return; + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + List<Element> result = <Element>[]; + for (Link<Node> link = definitions.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + Element element = elements[definition]; + if (capturedVariableMapping.containsKey(element)) { + result.add(element); + }; + } + scopeData.boxedLoopVariables = result; + } + + /** Returns a non-unique name for the given closure element. */ + String computeClosureName(Element element) { + Link<String> parts = const Link<String>(); + SourceString ownName = element.name; + if (ownName == null || ownName.stringValue == "") { + parts = parts.prepend("anon"); + } else { + parts = parts.prepend(ownName.slowToString()); + } + for (Element enclosingElement = element.enclosingElement; + enclosingElement != null && + (identical(enclosingElement.kind, + ElementKind.GENERATIVE_CONSTRUCTOR_BODY) + || identical(enclosingElement.kind, ElementKind.CLASS) + || identical(enclosingElement.kind, ElementKind.FUNCTION) + || identical(enclosingElement.kind, ElementKind.GETTER) + || identical(enclosingElement.kind, ElementKind.SETTER)); + enclosingElement = enclosingElement.enclosingElement) { + SourceString surroundingName = + Elements.operatorNameToIdentifier(enclosingElement.name); + parts = parts.prepend(surroundingName.slowToString()); + } + StringBuffer sb = new StringBuffer(); + parts.printOn(sb, '_'); + return sb.toString(); + } + + ClosureClassMap globalizeClosure(FunctionExpression node, Element element) { + SourceString closureName = new SourceString(computeClosureName(element)); + ClassElement globalizedElement = new ClosureClassElement( + closureName, compiler, element, element.getCompilationUnit()); + FunctionElement callElement = + new FunctionElementX.from(Compiler.CALL_OPERATOR_NAME, + element, + globalizedElement); + globalizedElement.addBackendMember(callElement); + // The nested function's 'this' is the same as the one for the outer + // function. It could be [null] if we are inside a static method. + Element thisElement = closureData.thisElement; + return new ClosureClassMap(element, globalizedElement, + callElement, thisElement); + } + + void visitInvokable(Element element, Expression node, void visitChildren()) { + bool oldInsideClosure = insideClosure; + Element oldFunctionElement = currentElement; + ClosureClassMap oldClosureData = closureData; + + insideClosure = outermostElement != null; + currentElement = element; + if (insideClosure) { + closures.add(node); + closureData = globalizeClosure(node, element); + } else { + outermostElement = element; + Element thisElement = null; + if (element.isInstanceMember() || element.isGenerativeConstructor()) { + thisElement = new ThisElement(element); + } + closureData = new ClosureClassMap(null, null, null, thisElement); + } + closureMappingCache[node] = closureData; + + inNewScope(node, () { + // We have to declare the implicit 'this' parameter. + if (!insideClosure && closureData.thisElement != null) { + declareLocal(closureData.thisElement); + } + // If we are inside a named closure we have to declare ourselve. For + // simplicity we declare the local even if the closure does not have a + // name. + // It will simply not be used. + if (insideClosure) { + declareLocal(element); + } + + if (currentElement.isFactoryConstructor() + && compiler.world.needsRti(currentElement.enclosingElement)) { + // Declare the type parameters in the scope. Generative + // constructors just use 'this'. + ClassElement cls = currentElement.enclosingElement; + cls.typeVariables.forEach((TypeVariableType typeVariable) { + declareLocal(typeVariable.element); + }); + } + + visitChildren(); + }); + + + ClosureClassMap savedClosureData = closureData; + bool savedInsideClosure = insideClosure; + + // Restore old values. + insideClosure = oldInsideClosure; + closureData = oldClosureData; + currentElement = oldFunctionElement; + + // Mark all free variables as captured and use them in the outer function. + Iterable<Element> freeVariables = savedClosureData.freeVariableMapping.keys; + assert(freeVariables.isEmpty || savedInsideClosure); + for (Element freeElement in freeVariables) { + if (capturedVariableMapping[freeElement] != null && + capturedVariableMapping[freeElement] != freeElement) { + compiler.internalError('In closure analyzer', node: node); + } + capturedVariableMapping[freeElement] = freeElement; + useLocal(freeElement); + } + } + + visitFunctionExpression(FunctionExpression node) { + Element element = elements[node]; + + if (element.isParameter()) { + // TODO(ahe): This is a hack. This method should *not* call + // visitChildren. + return node.name.accept(this); + } + + visitInvokable(element, node, () { + // TODO(ahe): This is problematic. The backend should not repeat + // the work of the resolver. It is the resolver's job to create + // parameters, etc. Other phases should only visit statements. + if (node.parameters != null) node.parameters.accept(this); + if (node.initializers != null) node.initializers.accept(this); + if (node.body != null) node.body.accept(this); + }); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + node.visitChildren(this); + declareLocal(elements[node]); + } + + visitTryStatement(TryStatement node) { + // TODO(ngeoffray): implement finer grain state. + bool oldInTryStatement = inTryStatement; + inTryStatement = true; + node.visitChildren(this); + inTryStatement = oldInTryStatement; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart new file mode 100644 index 0000000..c3465e8 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart
@@ -0,0 +1,106 @@ +// 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. + +part of dart2js; + +class CodeBuffer implements StringBuffer { + StringBuffer buffer; + List<CodeBufferMarker> markers; + int lastBufferOffset = 0; + int mappedRangeCounter = 0; + + CodeBuffer() + : buffer = new StringBuffer(), + markers = new List<CodeBufferMarker>(); + + int get length => buffer.length; + + bool get isEmpty { + return buffer.isEmpty; + } + + /** + * Converts [object] to a string and adds it to the buffer. If [object] is a + * [CodeBuffer], adds its markers to [markers]. + */ + CodeBuffer add(var object) { + if (object is CodeBuffer) { + return addBuffer(object); + } + if (mappedRangeCounter == 0) setSourceLocation(null); + buffer.add(object.toString()); + return this; + } + + CodeBuffer addBuffer(CodeBuffer other) { + if (other.markers.length > 0) { + CodeBufferMarker firstMarker = other.markers[0]; + int offsetDelta = + buffer.length + firstMarker.offsetDelta - lastBufferOffset; + markers.add(new CodeBufferMarker(offsetDelta, + firstMarker.sourcePosition)); + for (int i = 1; i < other.markers.length; ++i) { + markers.add(other.markers[i]); + } + lastBufferOffset = buffer.length + other.lastBufferOffset; + } + buffer.add(other.getText()); + } + + CodeBuffer addAll(Iterable<Object> iterable) { + for (Object obj in iterable) { + add(obj); + } + return this; + } + + CodeBuffer addCharCode(int charCode) { + return add(new String.fromCharCodes([charCode])); + } + + CodeBuffer clear() { + buffer.clear(); + markers.clear(); + lastBufferOffset = 0; + return this; + } + + String toString() { + throw "Don't use CodeBuffer.toString() since it drops sourcemap data."; + } + + String getText() { + return buffer.toString(); + } + + void beginMappedRange() { + ++mappedRangeCounter; + } + + void endMappedRange() { + assert(mappedRangeCounter > 0); + --mappedRangeCounter; + } + + void setSourceLocation(var sourcePosition) { + int offsetDelta = buffer.length - lastBufferOffset; + markers.add(new CodeBufferMarker(offsetDelta, sourcePosition)); + lastBufferOffset = buffer.length; + } + + void forEachSourceLocation(void f(int targetOffset, var sourcePosition)) { + int targetOffset = 0; + markers.forEach((marker) { + targetOffset += marker.offsetDelta; + f(targetOffset, marker.sourcePosition); + }); + } +} + +class CodeBufferMarker { + final int offsetDelta; + final sourcePosition; + + CodeBufferMarker(this.offsetDelta, this.sourcePosition); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart new file mode 100644 index 0000000..42b3e7f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/colors.dart
@@ -0,0 +1,15 @@ +// 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. + +library colors; + +const String GREEN_COLOR = '\u001b[32m'; +const String RED_COLOR = '\u001b[31m'; +const String MAGENTA_COLOR = '\u001b[35m'; +const String NO_COLOR = '\u001b[0m'; + +String wrap(String string, String color) => "${color}$string${NO_COLOR}"; +String green(String string) => wrap(string, GREEN_COLOR); +String red(String string) => wrap(string, RED_COLOR); +String magenta(String string) => wrap(string, MAGENTA_COLOR);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart b/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart new file mode 100644 index 0000000..691cbac --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart
@@ -0,0 +1,888 @@ +// 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. + +part of dart2js; + +/** + * The [ConstantHandler] keeps track of compile-time constants, + * initializations of global and static fields, and default values of + * optional parameters. + */ +class ConstantHandler extends CompilerTask { + final ConstantSystem constantSystem; + final bool isMetadata; + + /** + * Contains the initial value of fields. Must contain all static and global + * initializations of const fields. May contain eagerly compiled values for + * statics and instance fields. + */ + final Map<VariableElement, Constant> initialVariableValues; + + /** Set of all registered compiled constants. */ + final Set<Constant> compiledConstants; + + /** The set of variable elements that are in the process of being computed. */ + final Set<VariableElement> pendingVariables; + + /** Caches the statics where the initial value cannot be eagerly compiled. */ + final Set<VariableElement> lazyStatics; + + /** Caches the createRuntimeType function if registered. */ + Element createRuntimeTypeFunction = null; + + ConstantHandler(Compiler compiler, this.constantSystem, + { bool this.isMetadata: false }) + : initialVariableValues = new Map<VariableElement, dynamic>(), + compiledConstants = new Set<Constant>(), + pendingVariables = new Set<VariableElement>(), + lazyStatics = new Set<VariableElement>(), + super(compiler); + + String get name => 'ConstantHandler'; + + void registerCompileTimeConstant(Constant constant) { + registerInstantiatedClass(constant.computeType(compiler).element); + if (constant.isFunction()) { + FunctionConstant function = constant; + registerGetOfStaticFunction(function.element); + } + compiledConstants.add(constant); + } + + void registerInstantiatedClass(ClassElement element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerInstantiatedClass(element); + } + + void registerStaticUse(Element element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerStaticUse(element); + } + + void registerGetOfStaticFunction(FunctionElement element) { + if (isMetadata) return; + compiler.enqueuer.codegen.registerGetOfStaticFunction(element); + } + + void registerStringInstance() { + registerInstantiatedClass(compiler.stringClass); + } + + void registerCreateRuntimeTypeFunction() { + if (createRuntimeTypeFunction != null) return; + SourceString helperName = const SourceString('createRuntimeType'); + createRuntimeTypeFunction = compiler.findHelper(helperName); + registerStaticUse(createRuntimeTypeFunction); + } + + /** + * Compiles the initial value of the given field and stores it in an internal + * map. Returns the initial value (a constant) if it can be computed + * statically. Returns [:null:] if the variable must be initialized lazily. + * + * [work] must contain a [VariableElement] refering to a global or + * static field. + */ + Constant compileWorkItem(CodegenWorkItem work) { + return measure(() { + assert(work.element.kind == ElementKind.FIELD + || work.element.kind == ElementKind.PARAMETER + || work.element.kind == ElementKind.FIELD_PARAMETER); + VariableElement element = work.element; + // Shortcut if it has already been compiled. + Constant result = initialVariableValues[element]; + if (result != null) return result; + if (lazyStatics.contains(element)) return null; + result = compileVariableWithDefinitions(element, work.resolutionTree); + assert(pendingVariables.isEmpty); + return result; + }); + } + + /** + * Returns a compile-time constant, or reports an error if the element is not + * a compile-time constant. + */ + Constant compileConstant(VariableElement element) { + return compileVariable(element, isConst: true); + } + + /** + * Returns the a compile-time constant if the variable could be compiled + * eagerly. Otherwise returns `null`. + */ + Constant compileVariable(VariableElement element, {bool isConst: false}) { + return measure(() { + if (initialVariableValues.containsKey(element)) { + Constant result = initialVariableValues[element]; + return result; + } + return compiler.withCurrentElement(element, () { + TreeElements definitions = compiler.analyzeElement(element); + Constant constant = compileVariableWithDefinitions( + element, definitions, isConst: isConst); + return constant; + }); + }); + } + + /** + * Returns the a compile-time constant if the variable could be compiled + * eagerly. If the variable needs to be initialized lazily returns `null`. + * If the variable is `const` but cannot be compiled eagerly reports an + * error. + */ + Constant compileVariableWithDefinitions(VariableElement element, + TreeElements definitions, + {bool isConst: false}) { + return measure(() { + // Initializers for parameters must be const. + isConst = isConst || element.modifiers.isConst() + || !Elements.isStaticOrTopLevel(element); + if (!isConst && lazyStatics.contains(element)) return null; + + Node node = element.parseNode(compiler); + if (pendingVariables.contains(element)) { + if (isConst) { + MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS; + compiler.reportError(node, + new CompileTimeConstantError(kind)); + } else { + lazyStatics.add(element); + return null; + } + } + pendingVariables.add(element); + + SendSet assignment = node.asSendSet(); + Constant value; + if (assignment == null) { + // No initial value. + value = new NullConstant(); + } else { + Node right = assignment.arguments.head; + value = + compileNodeWithDefinitions(right, definitions, isConst: isConst); + if (compiler.enableTypeAssertions + && value != null + && element.isField()) { + DartType elementType = element.computeType(compiler); + DartType constantType = value.computeType(compiler); + if (elementType.isMalformed || constantType.isMalformed || + !constantSystem.isSubtype(compiler, constantType, elementType)) { + if (isConst) { + compiler.reportError(node, new CompileTimeConstantError( + MessageKind.NOT_ASSIGNABLE, + {'fromType': elementType, 'toType': constantType})); + } else { + // If the field can be lazily initialized, we will throw + // the exception at runtime. + value = null; + } + } + } + } + if (value != null) { + initialVariableValues[element] = value; + } else { + assert(!isConst); + lazyStatics.add(element); + } + pendingVariables.remove(element); + return value; + }); + } + + Constant compileNodeWithDefinitions(Node node, + TreeElements definitions, + {bool isConst: false}) { + return measure(() { + assert(node != null); + CompileTimeConstantEvaluator evaluator = new CompileTimeConstantEvaluator( + this, definitions, compiler, isConst: isConst); + return evaluator.evaluate(node); + }); + } + + /** Attempts to compile a constant expression. Returns null if not possible */ + Constant tryCompileNodeWithDefinitions(Node node, TreeElements definitions) { + return measure(() { + assert(node != null); + try { + TryCompileTimeConstantEvaluator evaluator = + new TryCompileTimeConstantEvaluator(this, definitions, compiler); + return evaluator.evaluate(node); + } on CompileTimeConstantError catch (exn) { + return null; + } + }); + } + + /** + * Returns an [Iterable] of static non final fields that need to be + * initialized. The fields list must be evaluated in order since they might + * depend on each other. + */ + Iterable<VariableElement> getStaticNonFinalFieldsForEmission() { + return initialVariableValues.keys.where((element) { + return element.kind == ElementKind.FIELD + && !element.isInstanceMember() + && !element.modifiers.isFinal() + // The const fields are all either emitted elsewhere or inlined. + && !element.modifiers.isConst(); + }); + } + + /** + * Returns an [Iterable] of static const fields that need to be initialized. + * The fields must be evaluated in order since they might depend on each + * other. + */ + Iterable<VariableElement> getStaticFinalFieldsForEmission() { + return initialVariableValues.keys.where((element) { + return element.kind == ElementKind.FIELD + && !element.isInstanceMember() + && element.modifiers.isFinal(); + }); + } + + List<VariableElement> getLazilyInitializedFieldsForEmission() { + return new List<VariableElement>.from(lazyStatics); + } + + List<Constant> getConstantsForEmission() { + // We must emit dependencies before their uses. + Set<Constant> seenConstants = new Set<Constant>(); + List<Constant> result = new List<Constant>(); + + void addConstant(Constant constant) { + if (!seenConstants.contains(constant)) { + constant.getDependencies().forEach(addConstant); + assert(!seenConstants.contains(constant)); + result.add(constant); + seenConstants.add(constant); + } + } + + compiledConstants.forEach(addConstant); + return result; + } + + Constant getInitialValueFor(VariableElement element) { + Constant initialValue = initialVariableValues[element]; + if (initialValue == null) { + compiler.internalError("No initial value for given element", + element: element); + } + return initialValue; + } +} + +class CompileTimeConstantEvaluator extends Visitor { + bool isEvaluatingConstant; + final ConstantHandler handler; + final TreeElements elements; + final Compiler compiler; + + CompileTimeConstantEvaluator(this.handler, + this.elements, + this.compiler, + {bool isConst: false}) + : this.isEvaluatingConstant = isConst; + + ConstantSystem get constantSystem => handler.constantSystem; + + Constant evaluate(Node node) { + return node.accept(this); + } + + Constant evaluateConstant(Node node) { + bool oldIsEvaluatingConstant = isEvaluatingConstant; + isEvaluatingConstant = true; + Constant result = node.accept(this); + isEvaluatingConstant = oldIsEvaluatingConstant; + assert(result != null); + return result; + } + + Constant visitNode(Node node) { + return signalNotCompileTimeConstant(node); + } + + Constant visitLiteralBool(LiteralBool node) { + handler.registerInstantiatedClass(compiler.boolClass); + return constantSystem.createBool(node.value); + } + + Constant visitLiteralDouble(LiteralDouble node) { + handler.registerInstantiatedClass(compiler.doubleClass); + return constantSystem.createDouble(node.value); + } + + Constant visitLiteralInt(LiteralInt node) { + handler.registerInstantiatedClass(compiler.intClass); + return constantSystem.createInt(node.value); + } + + Constant visitLiteralList(LiteralList node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + List<Constant> arguments = <Constant>[]; + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + arguments.add(evaluateConstant(link.head)); + } + // TODO(floitsch): get type parameters. + DartType type = new InterfaceType(compiler.listClass); + Constant constant = new ListConstant(type, arguments); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitLiteralMap(LiteralMap node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + List<StringConstant> keys = <StringConstant>[]; + Map<StringConstant, Constant> map = new Map<StringConstant, Constant>(); + for (Link<Node> link = node.entries.nodes; + !link.isEmpty; + link = link.tail) { + LiteralMapEntry entry = link.head; + Constant key = evaluateConstant(entry.key); + if (!key.isString() || entry.key.asStringNode() == null) { + MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL; + compiler.reportError(entry.key, new ResolutionError(kind)); + } + StringConstant keyConstant = key; + if (!map.containsKey(key)) keys.add(key); + map[key] = evaluateConstant(entry.value); + } + List<Constant> values = <Constant>[]; + Constant protoValue = null; + for (StringConstant key in keys) { + if (key.value == MapConstant.PROTO_PROPERTY) { + protoValue = map[key]; + } else { + values.add(map[key]); + } + } + bool hasProtoKey = (protoValue != null); + // TODO(floitsch): this should be a List<String> type. + DartType keysType = new InterfaceType(compiler.listClass); + ListConstant keysList = new ListConstant(keysType, keys); + handler.registerCompileTimeConstant(keysList); + SourceString className = hasProtoKey + ? MapConstant.DART_PROTO_CLASS + : MapConstant.DART_CLASS; + ClassElement classElement = compiler.jsHelperLibrary.find(className); + classElement.ensureResolved(compiler); + // TODO(floitsch): copy over the generic type. + DartType type = new InterfaceType(classElement); + handler.registerInstantiatedClass(classElement); + Constant constant = new MapConstant(type, keysList, values, protoValue); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitLiteralNull(LiteralNull node) { + return constantSystem.createNull(); + } + + Constant visitLiteralString(LiteralString node) { + handler.registerStringInstance(); + return constantSystem.createString(node.dartString, node); + } + + Constant visitStringJuxtaposition(StringJuxtaposition node) { + StringConstant left = evaluate(node.first); + StringConstant right = evaluate(node.second); + if (left == null || right == null) return null; + handler.registerStringInstance(); + return constantSystem.createString( + new DartString.concat(left.value, right.value), node); + } + + Constant visitStringInterpolation(StringInterpolation node) { + StringConstant initialString = evaluate(node.string); + if (initialString == null) return null; + DartString accumulator = initialString.value; + for (StringInterpolationPart part in node.parts) { + Constant expression = evaluate(part.expression); + DartString expressionString; + if (expression == null) { + return signalNotCompileTimeConstant(part.expression); + } else if (expression.isNum() || expression.isBool()) { + PrimitiveConstant primitive = expression; + expressionString = new DartString.literal(primitive.value.toString()); + } else if (expression.isString()) { + PrimitiveConstant primitive = expression; + expressionString = primitive.value; + } else { + return signalNotCompileTimeConstant(part.expression); + } + accumulator = new DartString.concat(accumulator, expressionString); + StringConstant partString = evaluate(part.string); + if (partString == null) return null; + accumulator = new DartString.concat(accumulator, partString.value); + }; + handler.registerStringInstance(); + return constantSystem.createString(accumulator, node); + } + + Constant makeTypeConstant(Element element) { + DartType elementType = element.computeType(compiler).asRaw(); + DartType constantType = compiler.typeClass.computeType(compiler); + Constant constant = new TypeConstant(elementType, constantType); + // If we use a type literal in a constant, the compile time + // constant emitter will generate a call to the createRuntimeType + // helper so we register a use of that. + handler.registerCreateRuntimeTypeFunction(); + handler.registerCompileTimeConstant(constant); + return constant; + } + + // TODO(floitsch): provide better error-messages. + Constant visitSend(Send send) { + Element element = elements[send]; + if (send.isPropertyAccess) { + if (Elements.isStaticOrTopLevelFunction(element)) { + Constant constant = new FunctionConstant(element); + handler.registerCompileTimeConstant(constant); + return constant; + } else if (Elements.isStaticOrTopLevelField(element)) { + Constant result; + if (element.modifiers.isConst()) { + result = handler.compileConstant(element); + } else if (element.modifiers.isFinal() && !isEvaluatingConstant) { + result = handler.compileVariable(element); + } + if (result != null) return result; + } else if (Elements.isClass(element) || Elements.isTypedef(element)) { + return makeTypeConstant(element); + } else if (send.receiver != null) { + // Fall through to error handling. + } else if (!Elements.isUnresolved(element) + && element.isVariable() + && element.modifiers.isConst()) { + Constant result = handler.compileConstant(element); + if (result != null) return result; + } + return signalNotCompileTimeConstant(send); + } else if (send.isCall) { + if (identical(element, compiler.identicalFunction) + && send.argumentCount() == 2) { + Constant left = evaluate(send.argumentsNode.nodes.head); + Constant right = evaluate(send.argumentsNode.nodes.tail.head); + Constant result = constantSystem.identity.fold(left, right); + if (result != null) return result; + } else if (Elements.isClass(element) || Elements.isTypedef(element)) { + return makeTypeConstant(element); + } + return signalNotCompileTimeConstant(send); + } else if (send.isPrefix) { + assert(send.isOperator); + Constant receiverConstant = evaluate(send.receiver); + if (receiverConstant == null) return null; + Operator op = send.selector; + Constant folded; + switch (op.source.stringValue) { + case "!": + folded = constantSystem.not.fold(receiverConstant); + break; + case "-": + folded = constantSystem.negate.fold(receiverConstant); + break; + case "~": + folded = constantSystem.bitNot.fold(receiverConstant); + break; + default: + compiler.internalError("Unexpected operator.", node: op); + break; + } + if (folded == null) return signalNotCompileTimeConstant(send); + return folded; + } else if (send.isOperator && !send.isPostfix) { + assert(send.argumentCount() == 1); + Constant left = evaluate(send.receiver); + Constant right = evaluate(send.argumentsNode.nodes.head); + if (left == null || right == null) return null; + Operator op = send.selector.asOperator(); + Constant folded = null; + switch (op.source.stringValue) { + case "+": + folded = constantSystem.add.fold(left, right); + break; + case "-": + folded = constantSystem.subtract.fold(left, right); + break; + case "*": + folded = constantSystem.multiply.fold(left, right); + break; + case "/": + folded = constantSystem.divide.fold(left, right); + break; + case "%": + folded = constantSystem.modulo.fold(left, right); + break; + case "~/": + folded = constantSystem.truncatingDivide.fold(left, right); + break; + case "|": + folded = constantSystem.bitOr.fold(left, right); + break; + case "&": + folded = constantSystem.bitAnd.fold(left, right); + break; + case "^": + folded = constantSystem.bitXor.fold(left, right); + break; + case "||": + folded = constantSystem.booleanOr.fold(left, right); + break; + case "&&": + folded = constantSystem.booleanAnd.fold(left, right); + break; + case "<<": + folded = constantSystem.shiftLeft.fold(left, right); + break; + case ">>": + folded = constantSystem.shiftRight.fold(left, right); + break; + case "<": + folded = constantSystem.less.fold(left, right); + break; + case "<=": + folded = constantSystem.lessEqual.fold(left, right); + break; + case ">": + folded = constantSystem.greater.fold(left, right); + break; + case ">=": + folded = constantSystem.greaterEqual.fold(left, right); + break; + case "==": + if (left.isPrimitive() && right.isPrimitive()) { + folded = constantSystem.equal.fold(left, right); + } + break; + case "===": + folded = constantSystem.identity.fold(left, right); + break; + case "!=": + if (left.isPrimitive() && right.isPrimitive()) { + BoolConstant areEquals = constantSystem.equal.fold(left, right); + if (areEquals == null) { + folded = null; + } else { + folded = areEquals.negate(); + } + } + break; + case "!==": + BoolConstant areIdentical = + constantSystem.identity.fold(left, right); + if (areIdentical == null) { + folded = null; + } else { + folded = areIdentical.negate(); + } + break; + } + if (folded == null) return signalNotCompileTimeConstant(send); + return folded; + } + return signalNotCompileTimeConstant(send); + } + + Constant visitSendSet(SendSet node) { + return signalNotCompileTimeConstant(node); + } + + /** + * Returns the list of constants that are passed to the static function. + * + * Invariant: [target] must be an implementation element. + */ + List<Constant> evaluateArgumentsToConstructor(Node node, + Selector selector, + Link<Node> arguments, + FunctionElement target) { + assert(invariant(node, target.isImplementation)); + List<Constant> compiledArguments = <Constant>[]; + + Function compileArgument = evaluateConstant; + Function compileConstant = handler.compileConstant; + bool succeeded = selector.addArgumentsToList(arguments, + compiledArguments, + target, + compileArgument, + compileConstant, + compiler); + if (!succeeded) { + MessageKind kind = MessageKind.INVALID_ARGUMENTS; + compiler.reportError(node, + new CompileTimeConstantError(kind, {'methodName': target.name})); + } + return compiledArguments; + } + + Constant visitNewExpression(NewExpression node) { + if (!node.isConst()) { + return signalNotCompileTimeConstant(node); + } + + Send send = node.send; + FunctionElement constructor = elements[send]; + constructor = constructor.redirectionTarget; + ClassElement classElement = constructor.getEnclosingClass(); + if (classElement.isInterface()) { + compiler.resolver.resolveMethodElement(constructor); + constructor = constructor.defaultImplementation; + classElement = constructor.getEnclosingClass(); + } + // The constructor must be an implementation to ensure that field + // initializers are handled correctly. + constructor = constructor.implementation; + assert(invariant(node, constructor.isImplementation)); + + Selector selector = elements.getSelector(send); + List<Constant> arguments = evaluateArgumentsToConstructor( + node, selector, send.arguments, constructor); + ConstructorEvaluator evaluator = + new ConstructorEvaluator(node, constructor, handler, compiler); + evaluator.evaluateConstructorFieldValues(arguments); + List<Constant> jsNewArguments = evaluator.buildJsNewArguments(classElement); + + handler.registerInstantiatedClass(classElement); + // TODO(floitsch): take generic types into account. + DartType type = classElement.computeType(compiler); + Constant constant = new ConstructedConstant(type, jsNewArguments); + handler.registerCompileTimeConstant(constant); + return constant; + } + + Constant visitParenthesizedExpression(ParenthesizedExpression node) { + return node.expression.accept(this); + } + + error(Node node) { + // TODO(floitsch): get the list of constants that are currently compiled + // and present some kind of stack-trace. + MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT; + compiler.reportError(node, new CompileTimeConstantError(kind)); + } + + Constant signalNotCompileTimeConstant(Node node) { + if (isEvaluatingConstant) { + error(node); + } + // Else we don't need to do anything. The final handler is only + // optimistically trying to compile constants. So it is normal that we + // sometimes see non-compile time constants. + // Simply return [:null:] which is used to propagate a failing + // compile-time compilation. + return null; + } +} + +class TryCompileTimeConstantEvaluator extends CompileTimeConstantEvaluator { + TryCompileTimeConstantEvaluator(ConstantHandler handler, + TreeElements elements, + Compiler compiler) + : super(handler, elements, compiler, isConst: true); + + error(Node node) { + // Just fail without reporting it anywhere. + throw new CompileTimeConstantError( + MessageKind.NOT_A_COMPILE_TIME_CONSTANT); + } +} + +class ConstructorEvaluator extends CompileTimeConstantEvaluator { + final FunctionElement constructor; + final Map<Element, Constant> definitions; + final Map<Element, Constant> fieldValues; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [constructor] must be an implementation element. + */ + ConstructorEvaluator(Node node, + FunctionElement constructor, + ConstantHandler handler, + Compiler compiler) + : this.constructor = constructor, + this.definitions = new Map<Element, Constant>(), + this.fieldValues = new Map<Element, Constant>(), + super(handler, + compiler.resolver.resolveMethodElement(constructor.declaration), + compiler, + isConst: true) { + assert(invariant(node, constructor.isImplementation)); + } + + Constant visitSend(Send send) { + Element element = elements[send]; + if (Elements.isLocal(element)) { + Constant constant = definitions[element]; + if (constant == null) { + compiler.internalError("Local variable without value", node: send); + } + return constant; + } + return super.visitSend(send); + } + + void potentiallyCheckType(Node node, Element element, Constant constant) { + if (compiler.enableTypeAssertions) { + DartType elementType = element.computeType(compiler); + DartType constantType = constant.computeType(compiler); + // TODO(ngeoffray): Handle type parameters. + if (elementType.element.isTypeVariable()) return; + if (elementType.isMalformed || constantType.isMalformed || + !constantSystem.isSubtype(compiler, constantType, elementType)) { + compiler.reportError(node, new CompileTimeConstantError( + MessageKind.NOT_ASSIGNABLE, + {'fromType': elementType, 'toType': constantType})); + } + } + } + + void updateFieldValue(Node node, Element element, Constant constant) { + potentiallyCheckType(node, element, constant); + fieldValues[element] = constant; + } + + /** + * Given the arguments (a list of constants) assigns them to the parameters, + * updating the definitions map. If the constructor has field-initializer + * parameters (like [:this.x:]), also updates the [fieldValues] map. + */ + void assignArgumentsToParameters(List<Constant> arguments) { + // Assign arguments to parameters. + FunctionSignature parameters = constructor.computeSignature(compiler); + int index = 0; + parameters.orderedForEachParameter((Element parameter) { + Constant argument = arguments[index++]; + Node node = parameter.parseNode(compiler); + potentiallyCheckType(node, parameter, argument); + definitions[parameter] = argument; + if (parameter.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParameterElement = parameter; + updateFieldValue(node, fieldParameterElement.fieldElement, argument); + } + }); + } + + void evaluateSuperOrRedirectSend(Node currentNode, + Selector selector, + Link<Node> arguments, + FunctionElement targetConstructor) { + List<Constant> compiledArguments = evaluateArgumentsToConstructor( + currentNode, selector, arguments, targetConstructor); + + ConstructorEvaluator evaluator = new ConstructorEvaluator( + currentNode, targetConstructor, handler, compiler); + evaluator.evaluateConstructorFieldValues(compiledArguments); + // Copy over the fieldValues from the super/redirect-constructor. + // No need to go through [updateFieldValue] because the + // assignments have already been checked in checked mode. + evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value); + } + + /** + * Runs through the initializers of the given [constructor] and updates + * the [fieldValues] map. + */ + void evaluateConstructorInitializers() { + FunctionExpression functionNode = constructor.parseNode(compiler); + NodeList initializerList = functionNode.initializers; + + bool foundSuperOrRedirect = false; + + if (initializerList != null) { + for (Link<Node> link = initializerList.nodes; + !link.isEmpty; + link = link.tail) { + assert(link.head is Send); + if (link.head is !SendSet) { + // A super initializer or constructor redirection. + Send call = link.head; + FunctionElement targetConstructor = elements[call]; + Selector selector = elements.getSelector(call); + Link<Node> arguments = call.arguments; + evaluateSuperOrRedirectSend( + call, selector, arguments, targetConstructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link<Node> initArguments = init.arguments; + assert(!initArguments.isEmpty && initArguments.tail.isEmpty); + Constant fieldValue = evaluate(initArguments.head); + updateFieldValue(init, elements[init], fieldValue); + } + } + } + + if (!foundSuperOrRedirect) { + // No super initializer found. Try to find the default constructor if + // the class is not Object. + ClassElement enclosingClass = constructor.getEnclosingClass(); + ClassElement superClass = enclosingClass.superclass; + if (enclosingClass != compiler.objectClass) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + + Selector selector = + new Selector.callDefaultConstructor(enclosingClass.getLibrary()); + + FunctionElement targetConstructor = + superClass.lookupConstructor(selector); + if (targetConstructor == null) { + compiler.internalError("no default constructor available", + node: functionNode); + } + + evaluateSuperOrRedirectSend(functionNode, + selector, + const Link<Node>(), + targetConstructor); + } + } + } + + /** + * Simulates the execution of the [constructor] with the given + * [arguments] to obtain the field values that need to be passed to the + * native JavaScript constructor. + */ + void evaluateConstructorFieldValues(List<Constant> arguments) { + compiler.withCurrentElement(constructor, () { + assignArgumentsToParameters(arguments); + evaluateConstructorInitializers(); + }); + } + + List<Constant> buildJsNewArguments(ClassElement classElement) { + List<Constant> jsNewArguments = <Constant>[]; + classElement.implementation.forEachInstanceField( + (ClassElement enclosing, Element field) { + Constant fieldValue = fieldValues[field]; + if (fieldValue == null) { + // Use the default value. + fieldValue = handler.compileConstant(field); + } + jsNewArguments.add(fieldValue); + }, + includeBackendMembers: true, + includeSuperMembers: true); + return jsNewArguments; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart b/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart new file mode 100644 index 0000000..4fef805 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart
@@ -0,0 +1,1130 @@ +// 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. + +part of dart2js; + +/** + * If true, print a warning for each method that was resolved, but not + * compiled. + */ +const bool REPORT_EXCESS_RESOLUTION = false; + +/** + * If true, dump the inferred types after compilation. + */ +const bool DUMP_INFERRED_TYPES = false; + +/** + * A string to identify the revision or build. + * + * This ID is displayed if the compiler crashes and in verbose mode, and is + * an aid in reproducing bug reports. + * + * The actual string is rewritten during the SDK build process. + */ +const String BUILD_ID = '0.3.5.1_r18300'; + +/** + * Contains backend-specific data that is used throughout the compilation of + * one work item. + */ +class ItemCompilationContext { +} + +abstract class WorkItem { + final ItemCompilationContext compilationContext; + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + final Element element; + TreeElements get resolutionTree; + + WorkItem(this.element, this.compilationContext) { + assert(invariant(element, element.isDeclaration)); + } + + bool isAnalyzed() => resolutionTree != null; + + void run(Compiler compiler, Enqueuer world); +} + +/// [WorkItem] used exclusively by the [ResolutionEnqueuer]. +class ResolutionWorkItem extends WorkItem { + TreeElements resolutionTree; + + ResolutionWorkItem(Element element, + ItemCompilationContext compilationContext) + : super(element, compilationContext); + + void run(Compiler compiler, ResolutionEnqueuer world) { + resolutionTree = compiler.analyze(this, world); + } +} + +/// [WorkItem] used exclusively by the [CodegenEnqueuer]. +class CodegenWorkItem extends WorkItem { + final TreeElements resolutionTree; + + bool allowSpeculativeOptimization = true; + List<HTypeGuard> guards = const <HTypeGuard>[]; + + CodegenWorkItem(Element element, + TreeElements this.resolutionTree, + ItemCompilationContext compilationContext) + : super(element, compilationContext) { + assert(invariant(element, resolutionTree != null, + message: 'Resolution tree is null for $element in codegen work item')); + } + + void run(Compiler compiler, CodegenEnqueuer world) { + if (world.isProcessed(element)) return; + compiler.codegen(this, world); + } +} + +class ReadingFilesTask extends CompilerTask { + ReadingFilesTask(Compiler compiler) : super(compiler); + String get name => 'Reading input files'; +} + +abstract class Backend { + final Compiler compiler; + final ConstantSystem constantSystem; + + Backend(this.compiler, + [ConstantSystem constantSystem = DART_CONSTANT_SYSTEM]) + : this.constantSystem = constantSystem; + + void enqueueAllTopLevelFunctions(LibraryElement lib, Enqueuer world) { + lib.forEachExport((Element e) { + if (e.isFunction()) world.addToWorkList(e); + }); + } + + void enqueueHelpers(ResolutionEnqueuer world); + void codegen(CodegenWorkItem work); + + // The backend determines the native resolution enqueuer, with a no-op + // default, so tools like dart2dart can ignore the native classes. + native.NativeEnqueuer nativeResolutionEnqueuer(world) { + return new native.NativeEnqueuer(); + } + native.NativeEnqueuer nativeCodegenEnqueuer(world) { + return new native.NativeEnqueuer(); + } + + void assembleProgram(); + List<CompilerTask> get tasks; + + // TODO(ahe,karlklose): rename this? + void dumpInferredTypes() {} + + ItemCompilationContext createItemCompilationContext() { + return new ItemCompilationContext(); + } + + SourceString getCheckedModeHelper(DartType type) => null; + void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) {} +} + +/** + * Key class used in [TokenMap] in which the hash code for a token is based + * on the [charOffset]. + */ +class TokenKey { + final Token token; + TokenKey(this.token); + int get hashCode => token.charOffset; + operator==(other) => other is TokenKey && token == other.token; +} + +/// Map of tokens and the first associated comment. +/* + * This implementation was chosen among several candidates for its space/time + * efficiency by empirical tests of running dartdoc on dartdoc itself. Time + * measurements for the use of [Compiler.commentMap]: + * + * 1) Using [TokenKey] as key (this class): ~80 msec + * 2) Using [TokenKey] as key + storing a separate map in each script: ~120 msec + * 3) Using [Token] as key in a [Map]: ~38000 msec + * 4) Storing comments is new field in [Token]: ~20 msec + * (Abandoned due to the increased memory usage) + * 5) Storing comments in an [Expando]: ~14000 msec + * 6) Storing token/comments pairs in a linked list: ~5400 msec + */ +class TokenMap { + Map<TokenKey,Token> comments = new Map<TokenKey,Token>(); + + Token operator[] (Token key) { + if (key == null) return null; + return comments[new TokenKey(key)]; + } + + void operator[]= (Token key, Token value) { + if (key == null) return; + comments[new TokenKey(key)] = value; + } +} + +abstract class Compiler implements DiagnosticListener { + final Map<String, LibraryElement> libraries; + final Stopwatch totalCompileTime = new Stopwatch(); + int nextFreeClassId = 0; + World world; + String assembledCode; + Types types; + + /** + * Map from token to the first preceeding comment token. + */ + final TokenMap commentMap = new TokenMap(); + + final bool enableMinification; + final bool enableTypeAssertions; + final bool enableUserAssertions; + final bool enableConcreteTypeInference; + /** + * The maximum size of a concrete type before it widens to dynamic during + * concrete type inference. + */ + final int maxConcreteTypeSize; + final bool analyzeAll; + final bool analyzeOnly; + final bool enableNativeLiveTypeAnalysis; + final bool rejectDeprecatedFeatures; + final bool checkDeprecationInSdk; + + /** + * If [:true:], comment tokens are collected in [commentMap] during scanning. + */ + final bool preserveComments; + + final api.CompilerOutputProvider outputProvider; + + bool disableInlining = false; + + List<Uri> librariesToAnalyzeWhenRun; + + final Tracer tracer; + + CompilerTask measuredTask; + Element _currentElement; + LibraryElement coreLibrary; + LibraryElement isolateLibrary; + LibraryElement isolateHelperLibrary; + LibraryElement jsHelperLibrary; + LibraryElement interceptorsLibrary; + LibraryElement foreignLibrary; + LibraryElement mainApp; + + ClassElement objectClass; + ClassElement closureClass; + ClassElement dynamicClass; + ClassElement boolClass; + ClassElement numClass; + ClassElement intClass; + ClassElement doubleClass; + ClassElement stringClass; + ClassElement functionClass; + ClassElement nullClass; + ClassElement listClass; + ClassElement typeClass; + ClassElement mapClass; + ClassElement jsInvocationMirrorClass; + /// Document class from dart:mirrors. + ClassElement documentClass; + Element assertMethod; + Element identicalFunction; + Element functionApplyMethod; + Element invokeOnMethod; + Element createInvocationMirrorElement; + + Element get currentElement => _currentElement; + withCurrentElement(Element element, f()) { + Element old = currentElement; + _currentElement = element; + try { + return f(); + } on SpannableAssertionFailure catch (ex) { + if (!hasCrashed) { + SourceSpan span = spanFromSpannable(ex.node); + reportDiagnostic(span, ex.message, api.Diagnostic.ERROR); + pleaseReportCrash(); + } + hasCrashed = true; + throw; + } on CompilerCancelledException catch (ex) { + throw; + } on StackOverflowError catch (ex) { + // We cannot report anything useful in this case, because we + // do not have enough stack space. + throw; + } catch (ex) { + try { + unhandledExceptionOnElement(element); + } catch (doubleFault) { + // Ignoring exceptions in exception handling. + } + throw; + } finally { + _currentElement = old; + } + } + + List<CompilerTask> tasks; + ScannerTask scanner; + DietParserTask dietParser; + ParserTask parser; + PatchParserTask patchParser; + LibraryLoader libraryLoader; + TreeValidatorTask validator; + ResolverTask resolver; + closureMapping.ClosureTask closureToClassMapper; + TypeCheckerTask checker; + ti.TypesTask typesTask; + Backend backend; + ConstantHandler constantHandler; + ConstantHandler metadataHandler; + EnqueueTask enqueuer; + CompilerTask fileReadingTask; + + static const SourceString MAIN = const SourceString('main'); + static const SourceString CALL_OPERATOR_NAME = const SourceString('call'); + static const SourceString NO_SUCH_METHOD = const SourceString('noSuchMethod'); + static const int NO_SUCH_METHOD_ARG_COUNT = 1; + static const SourceString CREATE_INVOCATION_MIRROR = + const SourceString('createInvocationMirror'); + static const SourceString INVOKE_ON = const SourceString('invokeOn'); + static const SourceString RUNTIME_TYPE = const SourceString('runtimeType'); + static const SourceString START_ROOT_ISOLATE = + const SourceString('startRootIsolate'); + bool enabledNoSuchMethod = false; + bool enabledRuntimeType = false; + bool enabledFunctionApply = false; + bool enabledInvokeOn = false; + + Stopwatch progress; + + static const int PHASE_SCANNING = 0; + static const int PHASE_RESOLVING = 1; + static const int PHASE_COMPILING = 2; + int phase; + + bool compilationFailed = false; + + bool hasCrashed = false; + + Compiler({this.tracer: const Tracer(), + this.enableTypeAssertions: false, + this.enableUserAssertions: false, + this.enableConcreteTypeInference: false, + this.maxConcreteTypeSize: 5, + this.enableMinification: false, + this.enableNativeLiveTypeAnalysis: false, + bool emitJavaScript: true, + bool generateSourceMap: true, + bool disallowUnsafeEval: false, + this.analyzeAll: false, + this.analyzeOnly: false, + this.rejectDeprecatedFeatures: false, + this.checkDeprecationInSdk: false, + this.preserveComments: false, + outputProvider, + List<String> strips: const []}) + : libraries = new Map<String, LibraryElement>(), + progress = new Stopwatch(), + this.outputProvider = + (outputProvider == null) ? NullSink.outputProvider : outputProvider + { + progress.start(); + world = new World(this); + + closureMapping.ClosureNamer closureNamer; + if (emitJavaScript) { + js_backend.JavaScriptBackend jsBackend = + new js_backend.JavaScriptBackend(this, generateSourceMap, + disallowUnsafeEval); + closureNamer = jsBackend.namer; + backend = jsBackend; + } else { + backend = new dart_backend.DartBackend(this, strips); + } + + // No-op in production mode. + validator = new TreeValidatorTask(this); + + tasks = [ + fileReadingTask = new ReadingFilesTask(this), + libraryLoader = new LibraryLoaderTask(this), + scanner = new ScannerTask(this), + dietParser = new DietParserTask(this), + parser = new ParserTask(this), + patchParser = new PatchParserTask(this), + resolver = new ResolverTask(this), + closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer), + checker = new TypeCheckerTask(this), + typesTask = new ti.TypesTask(this), + constantHandler = new ConstantHandler(this, backend.constantSystem), + enqueuer = new EnqueueTask(this)]; + + tasks.addAll(backend.tasks); + metadataHandler = new ConstantHandler( + this, backend.constantSystem, isMetadata: true); + } + + Universe get resolverWorld => enqueuer.resolution.universe; + Universe get codegenWorld => enqueuer.codegen.universe; + + int getNextFreeClassId() => nextFreeClassId++; + + void ensure(bool condition) { + if (!condition) cancel('failed assertion in leg'); + } + + void unimplemented(String methodName, + {Node node, Token token, HInstruction instruction, + Element element}) { + internalError("$methodName not implemented", + node: node, token: token, + instruction: instruction, element: element); + } + + 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); + } + + void unhandledExceptionOnElement(Element element) { + if (hasCrashed) return; + hasCrashed = true; + reportDiagnostic(spanFromElement(element), + MessageKind.COMPILER_CRASHED.error().toString(), + api.Diagnostic.CRASH); + pleaseReportCrash(); + } + + void pleaseReportCrash() { + print(MessageKind.PLEASE_REPORT_THE_CRASH.message({'buildId': BUILD_ID})); + } + + void cancel(String reason, {Node node, Token token, + HInstruction instruction, Element element}) { + assembledCode = null; // Compilation failed. Make sure that we + // don't return a bogus result. + 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'; + } + reportDiagnostic(span, reason, api.Diagnostic.ERROR); + throw new CompilerCancelledException(reason); + } + + SourceSpan spanFromSpannable(Spannable node, [Uri uri]) { + if (node == CURRENT_ELEMENT_SPANNABLE) { + node = currentElement; + } + if (node is Node) { + return spanFromNode(node, uri); + } else if (node is Token) { + return spanFromTokens(node, node, uri); + } else if (node is HInstruction) { + return spanFromHInstruction(node); + } else if (node is Element) { + return spanFromElement(node); + } else if (node is MetadataAnnotation) { + return spanFromTokens(node.beginToken, node.endToken); + } else { + throw 'No error location.'; + } + } + + void reportFatalError(String reason, Element element, + {Node node, Token token, HInstruction instruction}) { + withCurrentElement(element, () { + cancel(reason, node: node, token: token, instruction: instruction, + element: element); + }); + } + + void log(message) { + reportDiagnostic(null, message, api.Diagnostic.VERBOSE_INFO); + } + + bool run(Uri uri) { + totalCompileTime.start(); + try { + runCompiler(uri); + } on CompilerCancelledException catch (exception) { + log('Error: $exception'); + return false; + } finally { + tracer.close(); + totalCompileTime.stop(); + } + return true; + } + + bool hasIsolateSupport() => isolateLibrary != null; + + /** + * This method is called before [library] import and export scopes have been + * set up. + */ + void onLibraryScanned(LibraryElement library, Uri uri) { + if (dynamicClass != null) { + // When loading the built-in libraries, dynamicClass is null. We + // take advantage of this as core imports js_helper and sees [dynamic] + // this way. + withCurrentElement(dynamicClass, () { + library.addToScope(dynamicClass, this); + }); + } + } + + LibraryElement scanBuiltinLibrary(String filename); + + void initializeSpecialClasses() { + final List missingCoreClasses = []; + ClassElement lookupCoreClass(String name) { + ClassElement result = coreLibrary.find(new SourceString(name)); + if (result == null) { + missingCoreClasses.add(name); + } + return result; + } + objectClass = lookupCoreClass('Object'); + boolClass = lookupCoreClass('bool'); + numClass = lookupCoreClass('num'); + intClass = lookupCoreClass('int'); + doubleClass = lookupCoreClass('double'); + stringClass = lookupCoreClass('String'); + functionClass = lookupCoreClass('Function'); + listClass = lookupCoreClass('List'); + typeClass = lookupCoreClass('Type'); + mapClass = lookupCoreClass('Map'); + if (!missingCoreClasses.isEmpty) { + internalErrorOnElement(coreLibrary, + 'dart:core library does not contain required classes: ' + '$missingCoreClasses'); + } + + final List missingHelperClasses = []; + ClassElement lookupHelperClass(String name) { + ClassElement result = jsHelperLibrary.find(new SourceString(name)); + if (result == null) { + missingHelperClasses.add(name); + } + return result; + } + jsInvocationMirrorClass = lookupHelperClass('JSInvocationMirror'); + closureClass = lookupHelperClass('Closure'); + dynamicClass = lookupHelperClass('Dynamic_'); + nullClass = lookupHelperClass('Null'); + if (!missingHelperClasses.isEmpty) { + internalErrorOnElement(jsHelperLibrary, + 'dart:_js_helper library does not contain required classes: ' + '$missingHelperClasses'); + } + + types = new Types(this, dynamicClass); + } + + void scanBuiltinLibraries() { + jsHelperLibrary = scanBuiltinLibrary('_js_helper'); + interceptorsLibrary = scanBuiltinLibrary('_interceptors'); + foreignLibrary = scanBuiltinLibrary('_foreign_helper'); + isolateHelperLibrary = scanBuiltinLibrary('_isolate_helper'); + // The helper library does not use the native language extension, + // so we manually set the native classes this library defines. + // TODO(ngeoffray): Enable annotations on these classes. + ClassElement cls = + isolateHelperLibrary.find(const SourceString('_WorkerStub')); + cls.setNative('"*Worker"'); + + assertMethod = jsHelperLibrary.find(const SourceString('assertHelper')); + identicalFunction = coreLibrary.find(const SourceString('identical')); + + initializeSpecialClasses(); + + functionClass.ensureResolved(this); + functionApplyMethod = + functionClass.lookupLocalMember(const SourceString('apply')); + jsInvocationMirrorClass.ensureResolved(this); + invokeOnMethod = jsInvocationMirrorClass.lookupLocalMember( + const SourceString('invokeOn')); + + if (preserveComments) { + var uri = new Uri.fromComponents(scheme: 'dart', path: 'mirrors'); + LibraryElement libraryElement = + libraryLoader.loadLibrary(uri, null, uri); + documentClass = libraryElement.find(const SourceString('Comment')); + } + } + + void importHelperLibrary(LibraryElement library) { + if (jsHelperLibrary != null) { + libraryLoader.importLibrary(library, jsHelperLibrary, null); + } + } + + /** + * Get an [Uri] pointing to a patch for the dart: library with + * the given path. Returns null if there is no patch. + */ + Uri resolvePatchUri(String dartLibraryPath); + + void runCompiler(Uri uri) { + assert(uri != null || analyzeOnly); + scanBuiltinLibraries(); + if (librariesToAnalyzeWhenRun != null) { + for (Uri libraryUri in librariesToAnalyzeWhenRun) { + log('analyzing $libraryUri ($BUILD_ID)'); + libraryLoader.loadLibrary(libraryUri, null, libraryUri); + } + } + if (uri != null) { + if (analyzeOnly) { + log('analyzing $uri ($BUILD_ID)'); + } else { + log('compiling $uri ($BUILD_ID)'); + } + mainApp = libraryLoader.loadLibrary(uri, null, uri); + } + Element main = null; + if (mainApp != null) { + main = mainApp.find(MAIN); + if (main == null) { + if (!analyzeOnly) { + // Allow analyze only of libraries with no main. + reportFatalError('Could not find $MAIN', mainApp); + } else if (!analyzeAll) { + reportFatalError( + "Could not find $MAIN. " + "No source will be analyzed. " + "Use '--analyze-all' to analyze all code in the library.", + mainApp); + } + } else { + if (!main.isFunction()) { + reportFatalError('main is not a function', main); + } + FunctionElement mainMethod = main; + FunctionSignature parameters = mainMethod.computeSignature(this); + parameters.forEachParameter((Element parameter) { + reportFatalError('main cannot have parameters', parameter); + }); + } + } + + log('Resolving...'); + phase = PHASE_RESOLVING; + if (analyzeAll) { + libraries.forEach((_, lib) => fullyEnqueueLibrary(lib)); + } + backend.enqueueHelpers(enqueuer.resolution); + processQueue(enqueuer.resolution, main); + enqueuer.resolution.logSummary(log); + + if (compilationFailed) return; + if (analyzeOnly) return; + assert(main != null); + + log('Inferring types...'); + typesTask.onResolutionComplete(main); + + // TODO(ahe): Remove this line. Eventually, enqueuer.resolution + // should know this. + world.populate(); + + log('Compiling...'); + phase = PHASE_COMPILING; + // TODO(johnniwinther): Move these to [CodegenEnqueuer]. + if (hasIsolateSupport()) { + enqueuer.codegen.addToWorkList( + isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE)); + } + if (enabledNoSuchMethod) { + Selector selector = new Selector.noSuchMethod(); + enqueuer.codegen.registerInvocation(NO_SUCH_METHOD, selector); + enqueuer.codegen.addToWorkList(createInvocationMirrorElement); + } + processQueue(enqueuer.codegen, main); + enqueuer.codegen.logSummary(log); + + if (compilationFailed) return; + + backend.assembleProgram(); + + checkQueues(); + } + + void fullyEnqueueLibrary(LibraryElement library) { + library.forEachLocalMember(fullyEnqueueTopLevelElement); + } + + void fullyEnqueueTopLevelElement(Element element) { + if (element.isClass()) { + ClassElement cls = element; + cls.ensureResolved(this); + cls.forEachLocalMember(enqueuer.resolution.addToWorkList); + } else { + enqueuer.resolution.addToWorkList(element); + } + } + + void processQueue(Enqueuer world, Element main) { + world.nativeEnqueuer.processNativeClasses(libraries.values); + if (main != null) { + world.addToWorkList(main); + } + progress.reset(); + world.forEach((WorkItem work) { + withCurrentElement(work.element, () => work.run(this, world)); + }); + world.queueIsClosed = true; + if (compilationFailed) return; + assert(world.checkNoEnqueuedInvokedInstanceMethods()); + if (DUMP_INFERRED_TYPES && phase == PHASE_COMPILING) { + backend.dumpInferredTypes(); + } + } + + /** + * Perform various checks of the queues. This includes checking that + * the queues are empty (nothing was added after we stopped + * processing the queues). Also compute the number of methods that + * were resolved, but not compiled (aka excess resolution). + */ + checkQueues() { + for (Enqueuer world in [enqueuer.resolution, enqueuer.codegen]) { + world.forEach((WorkItem work) { + internalErrorOnElement(work.element, "Work list is not empty."); + }); + } + if (!REPORT_EXCESS_RESOLUTION) return; + var resolved = new Set.from(enqueuer.resolution.resolvedElements.keys); + for (Element e in enqueuer.codegen.generatedCode.keys) { + resolved.remove(e); + } + for (Element e in new Set.from(resolved)) { + if (e.isClass() || + e.isField() || + e.isTypeVariable() || + e.isTypedef() || + identical(e.kind, ElementKind.ABSTRACT_FIELD)) { + resolved.remove(e); + } + if (identical(e.kind, ElementKind.GENERATIVE_CONSTRUCTOR)) { + ClassElement enclosingClass = e.getEnclosingClass(); + if (enclosingClass.isInterface()) { + resolved.remove(e); + } + resolved.remove(e); + + } + if (identical(e.getLibrary(), jsHelperLibrary)) { + resolved.remove(e); + } + if (identical(e.getLibrary(), interceptorsLibrary)) { + resolved.remove(e); + } + } + log('Excess resolution work: ${resolved.length}.'); + for (Element e in resolved) { + SourceSpan span = spanFromElement(e); + reportDiagnostic(span, 'Warning: $e resolved but not compiled.', + api.Diagnostic.WARNING); + } + } + + TreeElements analyzeElement(Element element) { + assert(invariant(element, element.isDeclaration)); + TreeElements elements = enqueuer.resolution.getCachedElements(element); + if (elements != null) return elements; + assert(parser != null); + Node tree = parser.parse(element); + validator.validate(tree); + elements = resolver.resolve(element); + if (elements != null) { + // Only analyze nodes with a corresponding [TreeElements]. + checker.check(tree, elements); + typesTask.analyze(tree, elements); + } + return elements; + } + + TreeElements analyze(ResolutionWorkItem work, ResolutionEnqueuer world) { + assert(invariant(work.element, identical(world, enqueuer.resolution))); + assert(invariant(work.element, !work.isAnalyzed(), + message: 'Element ${work.element} has already been analyzed')); + if (progress.elapsedMilliseconds > 500) { + // TODO(ahe): Add structured diagnostics to the compiler API and + // use it to separate this from the --verbose option. + if (phase == PHASE_RESOLVING) { + log('Resolved ${enqueuer.resolution.resolvedElements.length} ' + 'elements.'); + progress.reset(); + } + } + Element element = work.element; + TreeElements result = world.getCachedElements(element); + if (result != null) return result; + result = analyzeElement(element); + assert(invariant(element, element.isDeclaration)); + world.resolvedElements[element] = result; + return result; + } + + void codegen(CodegenWorkItem work, CodegenEnqueuer world) { + assert(invariant(work.element, identical(world, enqueuer.codegen))); + if (progress.elapsedMilliseconds > 500) { + // TODO(ahe): Add structured diagnostics to the compiler API and + // use it to separate this from the --verbose option. + log('Compiled ${enqueuer.codegen.generatedCode.length} methods.'); + progress.reset(); + } + backend.codegen(work); + } + + DartType resolveTypeAnnotation(Element element, + TypeAnnotation annotation) { + return resolver.resolveTypeAnnotation(element, annotation); + } + + DartType resolveReturnType(Element element, + TypeAnnotation annotation) { + return resolver.resolveReturnType(element, annotation); + } + + FunctionSignature resolveSignature(FunctionElement element) { + return withCurrentElement(element, + () => resolver.resolveSignature(element)); + } + + FunctionSignature resolveFunctionExpression(Element element, + FunctionExpression node) { + return withCurrentElement(element, + () => resolver.resolveFunctionExpression(element, node)); + } + + void resolveTypedef(TypedefElement element) { + withCurrentElement(element, + () => resolver.resolveTypedef(element)); + } + + FunctionType computeFunctionType(Element element, + FunctionSignature signature) { + return withCurrentElement(element, + () => resolver.computeFunctionType(element, signature)); + } + + reportWarning(Node node, var message) { + if (message is TypeWarning) { + // TODO(ahe): Don't supress these warning when the type checker + // is more complete. + if (identical(message.message.kind, MessageKind.NOT_ASSIGNABLE)) return; + if (identical(message.message.kind, MessageKind.MISSING_RETURN)) return; + if (identical(message.message.kind, MessageKind.MAYBE_MISSING_RETURN)) return; + if (identical(message.message.kind, MessageKind.METHOD_NOT_FOUND)) return; + } + SourceSpan span = spanFromNode(node); + + reportDiagnostic(span, 'Warning: $message', api.Diagnostic.WARNING); + } + + // TODO(ahe): Remove this method. + reportError(Node node, var message) { + SourceSpan span = spanFromNode(node); + reportDiagnostic(span, 'Error: $message', api.Diagnostic.ERROR); + throw new CompilerCancelledException(message.toString()); + } + + // TODO(ahe): Rename to reportError when that method has been removed. + void reportErrorCode(Spannable node, MessageKind errorCode, + [Map arguments = const {}]) { + reportMessage(spanFromSpannable(node), + errorCode.error(arguments), + api.Diagnostic.ERROR); + } + + void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) { + // TODO(ahe): The names Diagnostic and api.Diagnostic are in + // conflict. Fix it. + reportDiagnostic(span, "$message", kind); + } + + /// Returns true if a diagnostic was emitted. + bool onDeprecatedFeature(Spannable span, String feature) { + if (currentElement == null) + throw new SpannableAssertionFailure(span, feature); + if (!checkDeprecationInSdk && + currentElement.getLibrary().isPlatformLibrary) { + return false; + } + var kind = rejectDeprecatedFeatures + ? api.Diagnostic.ERROR : api.Diagnostic.WARNING; + var message = rejectDeprecatedFeatures + ? MessageKind.DEPRECATED_FEATURE_ERROR.error({'featureName': feature}) + : MessageKind.DEPRECATED_FEATURE_WARNING.error( + {'featureName': feature}); + reportMessage(spanFromSpannable(span), message, kind); + return true; + } + + void reportDiagnostic(SourceSpan span, String message, api.Diagnostic kind); + + 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); + } + + /** + * Translates the [resolvedUri] into a readable URI. + * + * The [importingLibrary] holds the library importing [resolvedUri] or + * [:null:] if [resolvedUri] is loaded as the main library. The + * [importingLibrary] is used to grant access to internal libraries from + * platform libraries and patch libraries. + * + * If the [resolvedUri] is not accessible from [importingLibrary], this method + * is responsible for reporting errors. + * + * See [LibraryLoader] for terminology on URIs. + */ + Uri translateResolvedUri(LibraryElement importingLibrary, + Uri resolvedUri, Node node) { + unimplemented('Compiler.translateResolvedUri'); + } + + /** + * Reads the script specified by the [readableUri]. + * + * See [LibraryLoader] for terminology on URIs. + */ + Script readScript(Uri readableUri, [Node node]) { + unimplemented('Compiler.readScript'); + } + + String get legDirectory { + unimplemented('Compiler.legDirectory'); + } + + // TODO(karlklose): split into findHelperFunction and findHelperClass and + // add a check that the element has the expected kind. + Element findHelper(SourceString name) + => jsHelperLibrary.findLocal(name); + Element findInterceptor(SourceString name) + => interceptorsLibrary.findLocal(name); + + Element lookupElementIn(ScopeContainerElement container, SourceString name) { + Element element = container.localLookup(name); + if (element == null) { + throw 'Could not find ${name.slowToString()} in $container'; + } + return element; + } + + bool get isMockCompilation => false; + + Token processAndStripComments(Token currentToken) { + Token firstToken = currentToken; + Token prevToken; + while (currentToken.kind != EOF_TOKEN) { + if (identical(currentToken.kind, COMMENT_TOKEN)) { + Token firstCommentToken = currentToken; + while (identical(currentToken.kind, COMMENT_TOKEN)) { + currentToken = currentToken.next; + } + commentMap[currentToken] = firstCommentToken; + if (prevToken == null) { + firstToken = currentToken; + } else { + prevToken.next = currentToken; + } + } + prevToken = currentToken; + currentToken = currentToken.next; + } + return firstToken; + } +} + +class CompilerTask { + final Compiler compiler; + final Stopwatch watch; + + CompilerTask(this.compiler) : watch = new Stopwatch(); + + String get name => 'Unknown task'; + int get timing => watch.elapsedMilliseconds; + + measure(Function action) { + CompilerTask previous = compiler.measuredTask; + if (identical(this, previous)) return action(); + compiler.measuredTask = this; + if (previous != null) previous.watch.stop(); + watch.start(); + try { + return action(); + } finally { + watch.stop(); + if (previous != null) previous.watch.start(); + compiler.measuredTask = previous; + } + } +} + +class CompilerCancelledException implements Exception { + final String reason; + CompilerCancelledException(this.reason); + + String toString() { + String banner = 'compiler cancelled'; + return (reason != null) ? '$banner: $reason' : '$banner'; + } +} + +class Tracer { + final bool enabled = false; + + const Tracer(); + + void traceCompilation(String methodName, ItemCompilationContext context) { + } + + void traceGraph(String name, var graph) { + } + + void close() { + } +} + +class SourceSpan { + final Uri uri; + final int begin; + final int end; + + const SourceSpan(this.uri, this.begin, this.end); + + static withCharacterOffsets(Token begin, Token end, + f(int beginOffset, int endOffset)) { + final beginOffset = begin.charOffset; + final endOffset = end.charOffset + end.slowCharCount; + + // [begin] and [end] might be the same for the same empty token. This + // happens for instance when scanning '$$'. + assert(endOffset >= beginOffset); + return f(beginOffset, endOffset); + } + + String toString() => 'SourceSpan($uri, $begin, $end)'; +} + +/** + * Throws an [InvariantException] if [condition] is [:false:]. [condition] must + * be either a [:bool:] or a no-arg function returning a [:bool:]. + * + * Use this method to provide better information for assertion by calling + * [invariant] as the argument to an [:assert:] statement: + * + * assert(invariant(position, isValid)); + * + * [spannable] must be non-null and will be used to provide positional + * information in the generated error message. + */ +bool invariant(Spannable spannable, var condition, {String message: null}) { + // TODO(johnniwinther): Use [spannable] and [message] to provide better + // information on assertion errors. + if (condition is Function){ + condition = condition(); + } + if (spannable == null || !condition) { + throw new SpannableAssertionFailure(spannable, message); + } + return true; +} + +/// A sink that drains into /dev/null. +class NullSink extends StreamSink<String> { + final String name; + + NullSink(this.name); + + add(String value) {} + + void signalError(AsyncError error) {} + + void close() {} + + toString() => name; + + /// Convenience method for getting an [api.CompilerOutputProvider]. + static NullSink outputProvider(String name, String extension) { + return new NullSink('$name.$extension'); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart new file mode 100644 index 0000000..0fff580 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart
@@ -0,0 +1,80 @@ +// 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. + +part of dart2js; + +abstract class Operation { + SourceString get name; + bool isUserDefinable(); +} + +abstract class UnaryOperation extends Operation { + /** Returns [:null:] if it was unable to fold the operation. */ + Constant fold(Constant constant); + apply(value); +} + +abstract class BinaryOperation extends Operation { + /** Returns [:null:] if it was unable to fold the operation. */ + Constant fold(Constant left, Constant right); + apply(left, right); +} + +/** + * A [ConstantSystem] is responsible for creating constants and folding them. + */ +abstract class ConstantSystem { + BinaryOperation get add; + BinaryOperation get bitAnd; + UnaryOperation get bitNot; + BinaryOperation get bitOr; + BinaryOperation get bitXor; + BinaryOperation get booleanAnd; + BinaryOperation get booleanOr; + BinaryOperation get divide; + BinaryOperation get equal; + BinaryOperation get greaterEqual; + BinaryOperation get greater; + BinaryOperation get identity; + BinaryOperation get lessEqual; + BinaryOperation get less; + BinaryOperation get modulo; + BinaryOperation get multiply; + UnaryOperation get negate; + UnaryOperation get not; + BinaryOperation get shiftLeft; + BinaryOperation get shiftRight; + BinaryOperation get subtract; + BinaryOperation get truncatingDivide; + + const ConstantSystem(); + + Constant createInt(int i); + Constant createDouble(double d); + // We need a diagnostic node to report errors in case the string is malformed. + Constant createString(DartString string, Node diagnosticNode); + Constant createBool(bool value); + Constant createNull(); + + // We need to special case the subtype check for JavaScript constant + // system because an int is a double at runtime. + bool isSubtype(Compiler compiler, DartType s, DartType t); + + /** Returns true if the [constant] is an integer at runtime. */ + bool isInt(Constant constant); + /** Returns true if the [constant] is a double at runtime. */ + bool isDouble(Constant constant); + /** Returns true if the [constant] is a string at runtime. */ + bool isString(Constant constant); + /** Returns true if the [constant] is a boolean at runtime. */ + bool isBool(Constant constant); + /** Returns true if the [constant] is null at runtime. */ + bool isNull(Constant constant); + + Operation lookupUnary(SourceString operator) { + if (operator == const SourceString('-')) return negate; + if (operator == const SourceString('~')) return bitNot; + return null; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart new file mode 100644 index 0000000..6732b8c --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart
@@ -0,0 +1,380 @@ +// 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. + +part of dart2js; + +const DART_CONSTANT_SYSTEM = const DartConstantSystem(); + +class BitNotOperation implements UnaryOperation { + final SourceString name = const SourceString('~'); + bool isUserDefinable() => true; + const BitNotOperation(); + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + return DART_CONSTANT_SYSTEM.createInt(~intConstant.value); + } + return null; + } + apply(value) => ~value; +} + +class NegateOperation implements UnaryOperation { + final SourceString name = const SourceString('negate'); + bool isUserDefinable() => true; + const NegateOperation(); + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + return DART_CONSTANT_SYSTEM.createInt(-intConstant.value); + } + if (constant.isDouble()) { + DoubleConstant doubleConstant = constant; + return DART_CONSTANT_SYSTEM.createDouble(-doubleConstant.value); + } + return null; + } + apply(value) => -value; +} + +class NotOperation implements UnaryOperation { + final SourceString name = const SourceString('!'); + bool isUserDefinable() => true; + const NotOperation(); + Constant fold(Constant constant) { + if (constant.isBool()) { + BoolConstant boolConstant = constant; + return DART_CONSTANT_SYSTEM.createBool(!boolConstant.value); + } + return null; + } + apply(value) => !value; +} + +/** + * Operations that only work if both arguments are integers. + */ +abstract class BinaryBitOperation implements BinaryOperation { + bool isUserDefinable() => true; + const BinaryBitOperation(); + Constant fold(Constant left, Constant right) { + if (left.isInt() && right.isInt()) { + IntConstant leftInt = left; + IntConstant rightInt = right; + int resultValue = foldInts(leftInt.value, rightInt.value); + if (resultValue == null) return null; + return DART_CONSTANT_SYSTEM.createInt(resultValue); + } + return null; + } + + int foldInts(int left, int right); +} + +class BitOrOperation extends BinaryBitOperation { + final SourceString name = const SourceString('|'); + const BitOrOperation(); + int foldInts(int left, int right) => left | right; + apply(left, right) => left | right; +} + +class BitAndOperation extends BinaryBitOperation { + final SourceString name = const SourceString('&'); + const BitAndOperation(); + int foldInts(int left, int right) => left & right; + apply(left, right) => left & right; +} + +class BitXorOperation extends BinaryBitOperation { + final SourceString name = const SourceString('^'); + const BitXorOperation(); + int foldInts(int left, int right) => left ^ right; + apply(left, right) => left ^ right; +} + +class ShiftLeftOperation extends BinaryBitOperation { + final SourceString name = const SourceString('<<'); + const ShiftLeftOperation(); + int foldInts(int left, int right) { + // TODO(floitsch): find a better way to guard against excessive shifts to + // the left. + if (right > 100 || right < 0) return null; + return left << right; + } + apply(left, right) => left << right; +} + +class ShiftRightOperation extends BinaryBitOperation { + final SourceString name = const SourceString('>>'); + const ShiftRightOperation(); + int foldInts(int left, int right) { + if (right < 0) return null; + return left >> right; + } + apply(left, right) => left >> right; +} + +abstract class BinaryBoolOperation implements BinaryOperation { + bool isUserDefinable() => false; + const BinaryBoolOperation(); + Constant fold(Constant left, Constant right) { + if (left.isBool() && right.isBool()) { + BoolConstant leftBool = left; + BoolConstant rightBool = right; + bool resultValue = foldBools(leftBool.value, rightBool.value); + return DART_CONSTANT_SYSTEM.createBool(resultValue); + } + return null; + } + + bool foldBools(bool left, bool right); +} + +class BooleanAndOperation extends BinaryBoolOperation { + final SourceString name = const SourceString('&&'); + const BooleanAndOperation(); + bool foldBools(bool left, bool right) => left && right; + apply(left, right) => left && right; +} + +class BooleanOrOperation extends BinaryBoolOperation { + final SourceString name = const SourceString('||'); + const BooleanOrOperation(); + bool foldBools(bool left, bool right) => left || right; + apply(left, right) => left || right; +} + +abstract class ArithmeticNumOperation implements BinaryOperation { + bool isUserDefinable() => true; + const ArithmeticNumOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + num foldedValue; + if (left.isInt() && right.isInt()) { + foldedValue = foldInts(leftNum.value, rightNum.value); + } else { + foldedValue = foldNums(leftNum.value, rightNum.value); + } + // A division by 0 means that we might not have a folded value. + if (foldedValue == null) return null; + if (left.isInt() && right.isInt() && !isDivide() || + isTruncatingDivide()) { + assert(foldedValue is int); + return DART_CONSTANT_SYSTEM.createInt(foldedValue); + } else { + return DART_CONSTANT_SYSTEM.createDouble(foldedValue); + } + } + return null; + } + + bool isDivide() => false; + bool isTruncatingDivide() => false; + num foldInts(int left, int right) => foldNums(left, right); + num foldNums(num left, num right); +} + +class SubtractOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('-'); + const SubtractOperation(); + num foldNums(num left, num right) => left - right; + apply(left, right) => left - right; +} + +class MultiplyOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('*'); + const MultiplyOperation(); + num foldNums(num left, num right) => left * right; + apply(left, right) => left * right; +} + +class ModuloOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('%'); + const ModuloOperation(); + int foldInts(int left, int right) { + if (right == 0) return null; + return left % right; + } + num foldNums(num left, num right) => left % right; + apply(left, right) => left % right; +} + +class TruncatingDivideOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('~/'); + const TruncatingDivideOperation(); + int foldInts(int left, int right) { + if (right == 0) return null; + return left ~/ right; + } + num foldNums(num left, num right) { + num ratio = left / right; + if (ratio.isNaN || ratio.isInfinite) return null; + return ratio.truncate().toInt(); + } + apply(left, right) => left ~/ right; + bool isTruncatingDivide() => true; +} + +class DivideOperation extends ArithmeticNumOperation { + final SourceString name = const SourceString('/'); + const DivideOperation(); + num foldNums(num left, num right) => left / right; + bool isDivide() => true; + apply(left, right) => left / right; +} + +class AddOperation implements BinaryOperation { + final SourceString name = const SourceString('+'); + bool isUserDefinable() => true; + const AddOperation(); + Constant fold(Constant left, Constant right) { + if (left.isInt() && right.isInt()) { + IntConstant leftInt = left; + IntConstant rightInt = right; + int result = leftInt.value + rightInt.value; + return DART_CONSTANT_SYSTEM.createInt(result); + } else if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + double result = leftNum.value + rightNum.value; + return DART_CONSTANT_SYSTEM.createDouble(result); + } else { + return null; + } + } + apply(left, right) => left + right; +} + +abstract class RelationalNumOperation implements BinaryOperation { + bool isUserDefinable() => true; + const RelationalNumOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + bool foldedValue = foldNums(leftNum.value, rightNum.value); + assert(foldedValue != null); + return DART_CONSTANT_SYSTEM.createBool(foldedValue); + } + } + + bool foldNums(num left, num right); +} + +class LessOperation extends RelationalNumOperation { + final SourceString name = const SourceString('<'); + const LessOperation(); + bool foldNums(num left, num right) => left < right; + apply(left, right) => left < right; +} + +class LessEqualOperation extends RelationalNumOperation { + final SourceString name = const SourceString('<='); + const LessEqualOperation(); + bool foldNums(num left, num right) => left <= right; + apply(left, right) => left <= right; +} + +class GreaterOperation extends RelationalNumOperation { + final SourceString name = const SourceString('>'); + const GreaterOperation(); + bool foldNums(num left, num right) => left > right; + apply(left, right) => left > right; +} + +class GreaterEqualOperation extends RelationalNumOperation { + final SourceString name = const SourceString('>='); + const GreaterEqualOperation(); + bool foldNums(num left, num right) => left >= right; + apply(left, right) => left >= right; +} + +class EqualsOperation implements BinaryOperation { + final SourceString name = const SourceString('=='); + bool isUserDefinable() => true; + const EqualsOperation(); + Constant fold(Constant left, Constant right) { + if (left.isNum() && right.isNum()) { + // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0, + // and 1 == 1.0. + NumConstant leftNum = left; + NumConstant rightNum = right; + bool result = leftNum.value == rightNum.value; + return DART_CONSTANT_SYSTEM.createBool(result); + } + if (left.isConstructedObject()) { + // Unless we know that the user-defined object does not implement the + // equality operator we cannot fold here. + return null; + } + return DART_CONSTANT_SYSTEM.createBool(left == right); + } + apply(left, right) => left == right; +} + +class IdentityOperation implements BinaryOperation { + final SourceString name = const SourceString('==='); + bool isUserDefinable() => false; + const IdentityOperation(); + BoolConstant fold(Constant left, Constant right) { + // In order to preserve runtime semantics which says that NaN !== NaN don't + // constant fold NaN === NaN. Otherwise the output depends on inlined + // variables and other optimizations. + if (left.isNaN() && right.isNaN()) return null; + return DART_CONSTANT_SYSTEM.createBool(left == right); + } + apply(left, right) => identical(left, right); +} + +/** + * A constant system implementing the Dart semantics. This system relies on + * the underlying runtime-system. That is, if dart2js is run in an environment + * that doesn't correctly implement Dart's semantics this constant system will + * not return the correct values. + */ +class DartConstantSystem extends ConstantSystem { + const add = const AddOperation(); + const bitAnd = const BitAndOperation(); + const bitNot = const BitNotOperation(); + const bitOr = const BitOrOperation(); + const bitXor = const BitXorOperation(); + const booleanAnd = const BooleanAndOperation(); + const booleanOr = const BooleanOrOperation(); + const divide = const DivideOperation(); + const equal = const EqualsOperation(); + const greaterEqual = const GreaterEqualOperation(); + const greater = const GreaterOperation(); + const identity = const IdentityOperation(); + const lessEqual = const LessEqualOperation(); + const less = const LessOperation(); + const modulo = const ModuloOperation(); + const multiply = const MultiplyOperation(); + const negate = const NegateOperation(); + const not = const NotOperation(); + const shiftLeft = const ShiftLeftOperation(); + const shiftRight = const ShiftRightOperation(); + const subtract = const SubtractOperation(); + const truncatingDivide = const TruncatingDivideOperation(); + + const DartConstantSystem(); + + IntConstant createInt(int i) => new IntConstant(i); + DoubleConstant createDouble(double d) => new DoubleConstant(d); + StringConstant createString(DartString string, Node diagnosticNode) + => new StringConstant(string, diagnosticNode); + BoolConstant createBool(bool value) => new BoolConstant(value); + NullConstant createNull() => new NullConstant(); + + bool isInt(Constant constant) => constant.isInt(); + bool isDouble(Constant constant) => constant.isDouble(); + bool isString(Constant constant) => constant.isString(); + bool isBool(Constant constant) => constant.isBool(); + bool isNull(Constant constant) => constant.isNull(); + + bool isSubtype(Compiler compiler, DartType s, DartType t) { + return compiler.types.isSubtype(s, t); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart b/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart new file mode 100644 index 0000000..ad5dc74 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/constants.dart
@@ -0,0 +1,473 @@ +// 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. + +part of dart2js; + +abstract class ConstantVisitor<R> { + R visitSentinel(SentinelConstant constant); + R visitFunction(FunctionConstant constant); + R visitNull(NullConstant constant); + R visitInt(IntConstant constant); + R visitDouble(DoubleConstant constant); + R visitTrue(TrueConstant constant); + R visitFalse(FalseConstant constant); + R visitString(StringConstant constant); + R visitList(ListConstant constant); + R visitMap(MapConstant constant); + R visitConstructed(ConstructedConstant constant); + R visitType(TypeConstant constant); +} + +abstract class Constant { + const Constant(); + + bool isNull() => false; + bool isBool() => false; + bool isTrue() => false; + bool isFalse() => false; + bool isInt() => false; + bool isDouble() => false; + bool isNum() => false; + bool isString() => false; + bool isList() => false; + bool isMap() => false; + bool isConstructedObject() => false; + bool isFunction() => false; + /** Returns true if the constant is null, a bool, a number or a string. */ + bool isPrimitive() => false; + /** Returns true if the constant is a list, a map or a constructed object. */ + bool isObject() => false; + bool isType() => false; + bool isSentinel() => false; + + bool isNaN() => false; + bool isMinusZero() => false; + + DartType computeType(Compiler compiler); + + List<Constant> getDependencies(); + + accept(ConstantVisitor visitor); +} + +class SentinelConstant extends Constant { + const SentinelConstant(); + static final SENTINEL = const SentinelConstant(); + + List<Constant> getDependencies() => const <Constant>[]; + + // Just use a random value. + int get hashCode => 24297418; + + bool isSentinel() => true; + + accept(ConstantVisitor visitor) => visitor.visitSentinel(this); + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; +} + +class FunctionConstant extends Constant { + Element element; + + FunctionConstant(this.element); + + bool isFunction() => true; + + bool operator ==(var other) { + if (other is !FunctionConstant) return false; + return identical(other.element, element); + } + + String toString() => element.toString(); + List<Constant> getDependencies() => const <Constant>[]; + DartString toDartString() { + return new DartString.literal(element.name.slowToString()); + } + + DartType computeType(Compiler compiler) { + return compiler.functionClass.computeType(compiler); + } + + int get hashCode => (17 * element.hashCode) & 0x7fffffff; + + accept(ConstantVisitor visitor) => visitor.visitFunction(this); +} + +abstract class PrimitiveConstant extends Constant { + get value; + const PrimitiveConstant(); + bool isPrimitive() => true; + + bool operator ==(var other) { + if (other is !PrimitiveConstant) return false; + PrimitiveConstant otherPrimitive = other; + // We use == instead of 'identical' so that DartStrings compare correctly. + return value == otherPrimitive.value; + } + + String toString() => value.toString(); + // Primitive constants don't have dependencies. + List<Constant> getDependencies() => const <Constant>[]; + DartString toDartString(); +} + +class NullConstant extends PrimitiveConstant { + /** The value a Dart null is compiled to in JavaScript. */ + static const String JsNull = "null"; + + factory NullConstant() => const NullConstant._internal(); + const NullConstant._internal(); + bool isNull() => true; + get value => null; + + DartType computeType(Compiler compiler) { + return compiler.nullClass.computeType(compiler); + } + + void _writeJsCode(CodeBuffer buffer, ConstantHandler handler) { + buffer.add(JsNull); + } + + // The magic constant has no meaning. It is just a random value. + int get hashCode => 785965825; + DartString toDartString() => const LiteralDartString("null"); + + accept(ConstantVisitor visitor) => visitor.visitNull(this); +} + +abstract class NumConstant extends PrimitiveConstant { + num get value; + const NumConstant(); + bool isNum() => true; +} + +class IntConstant extends NumConstant { + final int value; + factory IntConstant(int value) { + switch (value) { + case 0: return const IntConstant._internal(0); + case 1: return const IntConstant._internal(1); + case 2: return const IntConstant._internal(2); + case 3: return const IntConstant._internal(3); + case 4: return const IntConstant._internal(4); + case 5: return const IntConstant._internal(5); + case 6: return const IntConstant._internal(6); + case 7: return const IntConstant._internal(7); + case 8: return const IntConstant._internal(8); + case 9: return const IntConstant._internal(9); + case 10: return const IntConstant._internal(10); + case -1: return const IntConstant._internal(-1); + case -2: return const IntConstant._internal(-2); + default: return new IntConstant._internal(value); + } + } + const IntConstant._internal(this.value); + bool isInt() => true; + + DartType computeType(Compiler compiler) { + return compiler.intClass.computeType(compiler); + } + + // We have to override the equality operator so that ints and doubles are + // treated as separate constants. + // The is [:!IntConstant:] check at the beginning of the function makes sure + // that we compare only equal to integer constants. + bool operator ==(var other) { + if (other is !IntConstant) return false; + IntConstant otherInt = other; + return value == otherInt.value; + } + + int get hashCode => value.hashCode; + DartString toDartString() => new DartString.literal(value.toString()); + + accept(ConstantVisitor visitor) => visitor.visitInt(this); +} + +class DoubleConstant extends NumConstant { + final double value; + factory DoubleConstant(double value) { + if (value.isNaN) { + return const DoubleConstant._internal(double.NAN); + } else if (value == double.INFINITY) { + return const DoubleConstant._internal(double.INFINITY); + } else if (value == -double.INFINITY) { + return const DoubleConstant._internal(-double.INFINITY); + } else if (value == 0.0 && !value.isNegative) { + return const DoubleConstant._internal(0.0); + } else if (value == 1.0) { + return const DoubleConstant._internal(1.0); + } else { + return new DoubleConstant._internal(value); + } + } + const DoubleConstant._internal(this.value); + bool isDouble() => true; + bool isNaN() => value.isNaN; + // We need to check for the negative sign since -0.0 == 0.0. + bool isMinusZero() => value == 0.0 && value.isNegative; + + DartType computeType(Compiler compiler) { + return compiler.doubleClass.computeType(compiler); + } + + bool operator ==(var other) { + if (other is !DoubleConstant) return false; + DoubleConstant otherDouble = other; + double otherValue = otherDouble.value; + if (value == 0.0 && otherValue == 0.0) { + return value.isNegative == otherValue.isNegative; + } else if (value.isNaN) { + return otherValue.isNaN; + } else { + return value == otherValue; + } + } + + int get hashCode => value.hashCode; + DartString toDartString() => new DartString.literal(value.toString()); + + accept(ConstantVisitor visitor) => visitor.visitDouble(this); +} + +abstract class BoolConstant extends PrimitiveConstant { + factory BoolConstant(value) { + return value ? new TrueConstant() : new FalseConstant(); + } + const BoolConstant._internal(); + bool isBool() => true; + + DartType computeType(Compiler compiler) { + return compiler.boolClass.computeType(compiler); + } + + BoolConstant negate(); +} + +class TrueConstant extends BoolConstant { + final bool value = true; + + factory TrueConstant() => const TrueConstant._internal(); + const TrueConstant._internal() : super._internal(); + bool isTrue() => true; + + FalseConstant negate() => new FalseConstant(); + + bool operator ==(var other) => identical(this, other); + // The magic constant is just a random value. It does not have any + // significance. + int get hashCode => 499; + DartString toDartString() => const LiteralDartString("true"); + + accept(ConstantVisitor visitor) => visitor.visitTrue(this); +} + +class FalseConstant extends BoolConstant { + final bool value = false; + + factory FalseConstant() => const FalseConstant._internal(); + const FalseConstant._internal() : super._internal(); + bool isFalse() => true; + + TrueConstant negate() => new TrueConstant(); + + bool operator ==(var other) => identical(this, other); + // The magic constant is just a random value. It does not have any + // significance. + int get hashCode => 536555975; + DartString toDartString() => const LiteralDartString("false"); + + accept(ConstantVisitor visitor) => visitor.visitFalse(this); +} + +class StringConstant extends PrimitiveConstant { + final DartString value; + final int hashCode; + final Node node; + + // TODO(floitsch): cache StringConstants. + // TODO(floitsch): compute hashcode without calling toString() on the + // DartString. + StringConstant(DartString value, this.node) + : this.value = value, + this.hashCode = value.slowToString().hashCode; + bool isString() => true; + + DartType computeType(Compiler compiler) { + return compiler.stringClass.computeType(compiler); + } + + bool operator ==(var other) { + if (other is !StringConstant) return false; + StringConstant otherString = other; + return (hashCode == otherString.hashCode) && (value == otherString.value); + } + + DartString toDartString() => value; + int get length => value.length; + + accept(ConstantVisitor visitor) => visitor.visitString(this); +} + +abstract class ObjectConstant extends Constant { + final DartType type; + + ObjectConstant(this.type); + bool isObject() => true; + + DartType computeType(Compiler compiler) => type; +} + +class TypeConstant extends ObjectConstant { + /// The user type that this constant represents. + final DartType representedType; + + TypeConstant(this.representedType, type) : super(type); + + bool isType() => true; + + bool operator ==(other) { + return other is TypeConstant && representedType == other.representedType; + } + + int get hashCode => representedType.hashCode * 13; + + List<Constant> getDependencies() => const <Constant>[]; + + accept(ConstantVisitor visitor) => visitor.visitType(this); +} + +class ListConstant extends ObjectConstant { + final List<Constant> entries; + final int hashCode; + + ListConstant(DartType type, List<Constant> entries) + : this.entries = entries, + hashCode = _computeHash(entries), + super(type); + bool isList() => true; + + static int _computeHash(List<Constant> entries) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant input in entries) hash ^= input.hashCode; + return hash; + } + + bool operator ==(var other) { + if (other is !ListConstant) return false; + ListConstant otherList = other; + if (hashCode != otherList.hashCode) return false; + // TODO(floitsch): verify that the generic types are the same. + if (entries.length != otherList.entries.length) return false; + for (int i = 0; i < entries.length; i++) { + if (entries[i] != otherList.entries[i]) return false; + } + return true; + } + + List<Constant> getDependencies() => entries; + + int get length => entries.length; + + accept(ConstantVisitor visitor) => visitor.visitList(this); +} + +class MapConstant extends ObjectConstant { + /** + * The [PROTO_PROPERTY] must not be used as normal property in any JavaScript + * object. It would change the prototype chain. + */ + static const LiteralDartString PROTO_PROPERTY = + const LiteralDartString("__proto__"); + + /** The dart class implementing constant map literals. */ + static const SourceString DART_CLASS = const SourceString("ConstantMap"); + static const SourceString DART_PROTO_CLASS = + const SourceString("ConstantProtoMap"); + static const SourceString LENGTH_NAME = const SourceString("length"); + static const SourceString JS_OBJECT_NAME = const SourceString("_jsObject"); + static const SourceString KEYS_NAME = const SourceString("_keys"); + static const SourceString PROTO_VALUE = const SourceString("_protoValue"); + + final ListConstant keys; + final List<Constant> values; + final Constant protoValue; + final int hashCode; + + MapConstant(DartType type, this.keys, List<Constant> values, this.protoValue) + : this.values = values, + this.hashCode = computeHash(values), + super(type); + bool isMap() => true; + + static int computeHash(List<Constant> values) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant value in values) hash ^= value.hashCode; + return hash; + } + + bool operator ==(var other) { + if (other is !MapConstant) return false; + MapConstant otherMap = other; + if (hashCode != otherMap.hashCode) return false; + // TODO(floitsch): verify that the generic types are the same. + if (keys != otherMap.keys) return false; + for (int i = 0; i < values.length; i++) { + if (values[i] != otherMap.values[i]) return false; + } + return true; + } + + List<Constant> getDependencies() { + List<Constant> result = <Constant>[keys]; + result.addAll(values); + return result; + } + + int get length => keys.length; + + accept(ConstantVisitor visitor) => visitor.visitMap(this); +} + +class ConstructedConstant extends ObjectConstant { + final List<Constant> fields; + final int hashCode; + + ConstructedConstant(DartType type, List<Constant> fields) + : this.fields = fields, + hashCode = computeHash(type, fields), + super(type) { + assert(type != null); + } + bool isConstructedObject() => true; + + static int computeHash(DartType type, List<Constant> fields) { + // TODO(floitsch): create a better hash. + int hash = 0; + for (Constant field in fields) { + hash ^= field.hashCode; + } + hash ^= type.element.hashCode; + return hash; + } + + bool operator ==(var otherVar) { + if (otherVar is !ConstructedConstant) return false; + ConstructedConstant other = otherVar; + if (hashCode != other.hashCode) return false; + // TODO(floitsch): verify that the (generic) types are the same. + if (type.element != other.type.element) return false; + if (fields.length != other.fields.length) return false; + for (int i = 0; i < fields.length; i++) { + if (fields[i] != other.fields[i]) return false; + } + return true; + } + + List<Constant> getDependencies() => fields; + + accept(ConstantVisitor visitor) => visitor.visitConstructed(this); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart new file mode 100644 index 0000000..ac7d422 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart
@@ -0,0 +1,497 @@ +// 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. + +library dart2js; + +import 'dart:async'; +import 'dart:collection' show Queue, LinkedHashMap; +import 'dart:io'; +import 'dart:uri'; +import 'dart:utf'; + +import '../compiler.dart' as api; +import 'source_file.dart'; +import 'source_file_provider.dart'; +import 'filenames.dart'; +import 'util/uri_extras.dart'; +import '../../libraries.dart'; + +const String LIBRARY_ROOT = '../../../../..'; +const String OUTPUT_LANGUAGE_DART = 'Dart'; + +typedef void HandleOption(String option); + +class OptionHandler { + String pattern; + HandleOption handle; + + OptionHandler(this.pattern, this.handle); +} + +/** + * Extract the parameter of an option. + * + * For example, in ['--out=fisk.js'] and ['-ohest.js'], the parameters + * are ['fisk.js'] and ['hest.js'], respectively. + */ +String extractParameter(String argument) { + // m[0] is the entire match (which will be equal to argument). m[1] + // is something like "-o" or "--out=", and m[2] is the parameter. + Match m = new RegExp('^(-[a-z]|--.+=)(.*)').firstMatch(argument); + if (m == null) helpAndFail('Error: Unknown option "$argument".'); + return m[2]; +} + +String extractPath(String argument) { + String path = nativeToUriPath(extractParameter(argument)); + return path.endsWith("/") ? path : "$path/"; +} + +void parseCommandLine(List<OptionHandler> handlers, List<String> argv) { + // TODO(ahe): Use ../../args/args.dart for parsing options instead. + var patterns = <String>[]; + for (OptionHandler handler in handlers) { + patterns.add(handler.pattern); + } + var pattern = new RegExp('^(${Strings.join(patterns, ")\$|(")})\$'); + OUTER: for (String argument in argv) { + Match match = pattern.firstMatch(argument); + assert(match.groupCount == handlers.length); + for (int i = 0; i < handlers.length; i++) { + if (match[i + 1] != null) { + handlers[i].handle(argument); + continue OUTER; + } + } + throw 'Internal error: "$argument" did not match'; + } +} + +void compile(List<String> argv) { + bool isWindows = (Platform.operatingSystem == 'windows'); + Uri cwd = getCurrentDirectory(); + Uri libraryRoot = cwd; + Uri out = cwd.resolve('out.js'); + Uri sourceMapOut = cwd.resolve('out.js.map'); + Uri packageRoot = null; + List<String> options = new List<String>(); + bool explicitOut = false; + bool wantHelp = false; + String outputLanguage = 'JavaScript'; + bool stripArgumentSet = false; + bool analyzeOnly = false; + SourceFileProvider inputProvider = new SourceFileProvider(); + FormattingDiagnosticHandler diagnosticHandler = + new FormattingDiagnosticHandler(inputProvider); + + passThrough(String argument) => options.add(argument); + + setLibraryRoot(String argument) { + libraryRoot = cwd.resolve(extractPath(argument)); + } + + setPackageRoot(String argument) { + packageRoot = cwd.resolve(extractPath(argument)); + } + + setOutput(String argument) { + explicitOut = true; + out = cwd.resolve(nativeToUriPath(extractParameter(argument))); + sourceMapOut = Uri.parse('$out.map'); + } + + setOutputType(String argument) { + if (argument == '--output-type=dart') { + outputLanguage = OUTPUT_LANGUAGE_DART; + if (!explicitOut) { + out = cwd.resolve('out.dart'); + sourceMapOut = cwd.resolve('out.dart.map'); + } + } + passThrough(argument); + } + + String getDepsOutput(Map<String, SourceFile> sourceFiles) { + var filenames = new List.from(sourceFiles.keys); + filenames.sort(); + return Strings.join(filenames, "\n"); + } + + setStrip(String argument) { + stripArgumentSet = true; + passThrough(argument); + } + + setAnalyzeOnly(String argument) { + analyzeOnly = true; + passThrough(argument); + } + + setCategories(String argument) { + List<String> categories = extractParameter(argument).split(','); + Set<String> allowedCategories = + LIBRARIES.values.map((x) => x.category).toSet(); + allowedCategories.remove('Shared'); + allowedCategories.remove('Internal'); + List<String> allowedCategoriesList = + new List<String>.from(allowedCategories); + allowedCategoriesList.sort(); + if (categories.contains('all')) { + categories = allowedCategoriesList; + } else { + String allowedCategoriesString = + Strings.join(allowedCategoriesList, ', '); + for (String category in categories) { + if (!allowedCategories.contains(category)) { + fail('Error: unsupported library category "$category", ' + 'supported categories are: $allowedCategoriesString'); + } + } + } + return passThrough('--categories=${Strings.join(categories, ",")}'); + } + + handleShortOptions(String argument) { + var shortOptions = argument.substring(1).splitChars(); + for (var shortOption in shortOptions) { + switch (shortOption) { + case 'v': + diagnosticHandler.verbose = true; + break; + case 'h': + case '?': + wantHelp = true; + break; + case 'c': + passThrough('--enable-checked-mode'); + break; + default: + throw 'Internal error: "$shortOption" did not match'; + } + } + } + + List<String> arguments = <String>[]; + List<OptionHandler> handlers = <OptionHandler>[ + new OptionHandler('-[chv?]+', handleShortOptions), + new OptionHandler('--throw-on-error', + (_) => diagnosticHandler.throwOnError = true), + new OptionHandler('--suppress-warnings', + (_) => diagnosticHandler.showWarnings = false), + new OptionHandler('--output-type=dart|--output-type=js', setOutputType), + new OptionHandler('--verbose', (_) => diagnosticHandler.verbose = true), + new OptionHandler('--library-root=.+', setLibraryRoot), + new OptionHandler('--out=.+|-o.+', setOutput), + new OptionHandler('--allow-mock-compilation', passThrough), + new OptionHandler('--minify', passThrough), + new OptionHandler('--force-strip=.*', setStrip), + // TODO(ahe): Remove the --no-colors option. + new OptionHandler('--disable-diagnostic-colors', + (_) => diagnosticHandler.enableColors = false), + new OptionHandler('--enable-diagnostic-colors', + (_) => diagnosticHandler.enableColors = true), + new OptionHandler('--enable[_-]checked[_-]mode|--checked', + (_) => passThrough('--enable-checked-mode')), + new OptionHandler('--enable-concrete-type-inference', + (_) => passThrough('--enable-concrete-type-inference')), + new OptionHandler(r'--help|/\?|/h', (_) => wantHelp = true), + new OptionHandler('--package-root=.+|-p.+', setPackageRoot), + new OptionHandler('--disallow-unsafe-eval', passThrough), + new OptionHandler('--analyze-all', passThrough), + new OptionHandler('--analyze-only', setAnalyzeOnly), + new OptionHandler('--disable-native-live-type-analysis', passThrough), + new OptionHandler('--reject-deprecated-language-features', passThrough), + new OptionHandler('--report-sdk-use-of-deprecated-language-features', + passThrough), + new OptionHandler('--categories=.*', setCategories), + + // The following two options must come last. + new OptionHandler('-.*', (String argument) { + helpAndFail('Error: Unknown option "$argument".'); + }), + new OptionHandler('.*', (String argument) { + arguments.add(nativeToUriPath(argument)); + }) + ]; + + parseCommandLine(handlers, argv); + if (wantHelp) helpAndExit(diagnosticHandler.verbose); + + if (outputLanguage != OUTPUT_LANGUAGE_DART && stripArgumentSet) { + helpAndFail('Error: --force-strip may only be used with ' + '--output-type=dart'); + } + if (arguments.isEmpty) { + helpAndFail('Error: No Dart file specified.'); + } + if (arguments.length > 1) { + var extra = arguments.getRange(1, arguments.length - 1); + helpAndFail('Error: Extra arguments: ${Strings.join(extra, " ")}'); + } + + void handler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { + diagnosticHandler.diagnosticHandler(uri, begin, end, message, kind); + } + + Uri uri = cwd.resolve(arguments[0]); + if (packageRoot == null) { + packageRoot = uri.resolve('./packages/'); + } + + diagnosticHandler.info('package root is $packageRoot'); + + int charactersWritten = 0; + + compilationDone(String code) { + if (analyzeOnly) return; + if (code == null) { + fail('Error: Compilation failed.'); + } + writeString(Uri.parse('$out.deps'), + getDepsOutput(inputProvider.sourceFiles)); + diagnosticHandler.info( + 'compiled ${inputProvider.dartCharactersRead} characters Dart ' + '-> $charactersWritten characters $outputLanguage ' + 'in ${relativize(cwd, out, isWindows)}'); + if (!explicitOut) { + String input = uriPathToNative(arguments[0]); + String output = relativize(cwd, out, isWindows); + print('Dart file $input compiled to $outputLanguage: $output'); + } + } + + StreamSink<String> outputProvider(String name, String extension) { + Uri uri; + String sourceMapFileName; + bool isPrimaryOutput = false; + if (name == '') { + if (extension == 'js' || extension == 'dart') { + isPrimaryOutput = true; + uri = out; + sourceMapFileName = + sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1); + } else if (extension == 'js.map' || extension == 'dart.map') { + uri = sourceMapOut; + } else { + fail('Error: Unknown extension: $extension'); + } + } else { + uri = out.resolve('$name.$extension'); + } + + if (uri.scheme != 'file') { + fail('Error: Unhandled scheme ${uri.scheme} in $uri.'); + } + var outputStream = new File(uriPathToNative(uri.path)).openOutputStream(); + + CountingSink sink; + + onDone() { + if (sourceMapFileName != null) { + String sourceMapTag = '//@ sourceMappingURL=$sourceMapFileName\n'; + sink.count += sourceMapTag.length; + outputStream.writeString(sourceMapTag); + } + outputStream.close(); + if (isPrimaryOutput) { + charactersWritten += sink.count; + } + } + + var controller = new StreamController<String>(); + controller.stream.listen(outputStream.writeString, onDone: onDone); + sink = new CountingSink(controller); + return sink; + } + + api.compile(uri, libraryRoot, packageRoot, + inputProvider.readStringFromUri, handler, + options, outputProvider) + .then(compilationDone); +} + +// TODO(ahe): Get rid of this class if http://dartbug.com/8118 is fixed. +class CountingSink implements StreamSink<String> { + final StreamSink<String> sink; + int count = 0; + + CountingSink(this.sink); + + add(String value) { + sink.add(value); + count += value.length; + } + + signalError(AsyncError error) => sink.signalError(error); + + close() => sink.close(); +} + +class AbortLeg { + final message; + AbortLeg(this.message); + toString() => 'Aborted due to --throw-on-error: $message'; +} + +void writeString(Uri uri, String text) { + if (uri.scheme != 'file') { + fail('Error: Unhandled scheme ${uri.scheme}.'); + } + var file = new File(uriPathToNative(uri.path)).openSync(FileMode.WRITE); + file.writeStringSync(text); + file.closeSync(); +} + +void fail(String message) { + print(message); + exit(1); +} + +void compilerMain(Options options) { + var root = uriPathToNative("/$LIBRARY_ROOT"); + List<String> argv = ['--library-root=${options.script}$root']; + argv.addAll(options.arguments); + compile(argv); +} + +void help() { + // This message should be no longer than 20 lines. The default + // terminal size normally 80x24. Two lines are used for the prompts + // before and after running the compiler. Another two lines may be + // used to print an error message. + print(''' +Usage: dart2js [options] dartfile + +Compiles Dart to JavaScript. + +Common options: + -o<file> Generate the output into <file>. + -c Insert runtime type checks and enable assertions (checked mode). + -h Display this message (add -v for information about all options).'''); +} + +void verboseHelp() { + print(''' +Usage: dart2js [options] dartfile + +Compiles Dart to JavaScript. + +Supported options: + -o<file>, --out=<file> + Generate the output into <file>. + + -c, --enable-checked-mode, --checked + Insert runtime type checks and enable assertions (checked mode). + + -h, /h, /?, --help + Display this message (add -v for information about all options). + + -v, --verbose + Display verbose information. + + -p<path>, --package-root=<path> + Where to find packages, that is, "package:..." imports. + + --analyze-all + Analyze all code. Without this option, the compiler only analyzes + code that is reachable from [main]. This option is useful for + finding errors in libraries, but using it can result in bigger and + slower output. + + --analyze-only + Analyze but do not generate code. + + --minify + Generate minified output. + + --suppress-warnings + Do not display any warnings. + + --enable-diagnostic-colors + Add colors to diagnostic messages. + +The following options are only used for compiler development and may +be removed in a future version: + + --output-type=dart + Output Dart code instead of JavaScript. + + --throw-on-error + Throw an exception if a compile-time error is detected. + + --library-root=<directory> + Where to find the Dart platform libraries. + + --allow-mock-compilation + Do not generate a call to main if either of the following + libraries are used: dart:dom, dart:html dart:io. + + --enable-concrete-type-inference + Enable experimental concrete type inference. + + --disable-native-live-type-analysis + Disable the optimization that removes unused native types from dart:html + and related libraries. + + --disallow-unsafe-eval + Disable dynamic generation of code in the generated output. This is + necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/). + This flag is not continuously tested. Please report breakages and we + will fix them as soon as possible. + + --reject-deprecated-language-features + Reject deprecated language features. Without this option, the + compiler will accept language features that are no longer valid + according to The Dart Programming Language Specification, version + 0.12, M1. + + --report-sdk-use-of-deprecated-language-features + Report use of deprecated features in Dart platform libraries. + Without this option, the compiler will silently accept use of + deprecated language features from these libraries. The option + --reject-deprecated-language-features controls if these usages are + reported as errors or warnings. + + --categories=<categories> + + A comma separated list of allowed library categories. The default + is "Client". Possible categories can be seen by providing an + unsupported category, for example, --categories=help. To enable + all categories, use --categories=all. + +'''.trim()); +} + +void helpAndExit(bool verbose) { + if (verbose) { + verboseHelp(); + } else { + help(); + } + exit(0); +} + +void helpAndFail(String message) { + help(); + print(''); + fail(message); +} + +void main() { + try { + compilerMain(new Options()); + } catch (exception, trace) { + try { + print('Internal error: $exception'); + } catch (ignored) { + print('Internal error: error while printing exception'); + } + try { + print(trace); + } finally { + exit(253); // 253 is recognized as a crash by our test scripts. + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot new file mode 100644 index 0000000..758a05a --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot Binary files differ
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart new file mode 100644 index 0000000..9b683f2 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart
@@ -0,0 +1,61 @@ +// 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. + +library dart2js; + +import 'dart:async'; +import 'dart:uri'; +import 'dart:collection' show Queue, LinkedHashMap; + +import 'closure.dart' as closureMapping; +import 'dart_backend/dart_backend.dart' as dart_backend; +import 'dart_types.dart'; +import 'elements/elements.dart'; +import 'elements/modelx.dart' + show ErroneousElementX, + CompilationUnitElementX, + LibraryElementX, + PrefixElementX, + VoidElementX; +import 'js_backend/js_backend.dart' as js_backend; +import 'native_handler.dart' as native; +import 'scanner/scanner_implementation.dart'; +import 'scanner/scannerlib.dart'; +import 'ssa/ssa.dart'; +import 'string_validator.dart'; +import 'source_file.dart'; +import 'tree/tree.dart'; +import 'universe/universe.dart'; +import 'util/characters.dart'; +import 'util/util.dart'; +import '../compiler.dart' as api; +import 'patch_parser.dart'; +import 'types/types.dart' as ti; +import 'resolution/resolution.dart'; +import 'js/js.dart' as js; + +export 'resolution/resolution.dart' show TreeElements, TreeElementMapping; +export 'scanner/scannerlib.dart' show SourceString, + isUserDefinableOperator, + isUnaryOperator, + isBinaryOperator, + isTernaryOperator, + isMinusOperator; +export 'universe/universe.dart' show Selector; + +part 'code_buffer.dart'; +part 'compile_time_constants.dart'; +part 'compiler.dart'; +part 'constants.dart'; +part 'constant_system.dart'; +part 'constant_system_dart.dart'; +part 'diagnostic_listener.dart'; +part 'enqueue.dart'; +part 'library_loader.dart'; +part 'resolved_visitor.dart'; +part 'script.dart'; +part 'tree_validator.dart'; +part 'typechecker.dart'; +part 'warnings.dart'; +part 'world.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart new file mode 100644 index 0000000..6a3b000 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart
@@ -0,0 +1,593 @@ +// 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. + +part of dart_backend; + +// TODO(ahe): This class is simply wrong. This backend should use +// elements when it can, not AST nodes. Perhaps a [Map<Element, +// TreeElements>] is what is needed. +class ElementAst { + final Node ast; + final TreeElements treeElements; + + ElementAst(this.ast, this.treeElements); + + factory ElementAst.rewrite(compiler, ast, treeElements, stripAsserts) { + final rewriter = + new FunctionBodyRewriter(compiler, treeElements, stripAsserts); + return new ElementAst(rewriter.visit(ast), rewriter.cloneTreeElements); + } + + ElementAst.forClassLike(this.ast) + : this.treeElements = new TreeElementMapping(null); +} + +// TODO(ahe): This class should not subclass [TreeElementMapping], if +// anything, it should implement TreeElements. +class AggregatedTreeElements extends TreeElementMapping { + final List<TreeElements> treeElements; + + AggregatedTreeElements() : treeElements = <TreeElements>[], super(null); + + Element operator[](Node node) { + final result = super[node]; + return result != null ? result : getFirstNotNullResult((e) => e[node]); + } + + Selector getSelector(Send send) { + final result = super.getSelector(send); + return result != null ? + result : getFirstNotNullResult((e) => e.getSelector(send)); + } + + DartType getType(Node node) { + final result = super.getType(node); + return result != null ? + result : getFirstNotNullResult((e) => e.getType(node)); + } + + getFirstNotNullResult(f(TreeElements element)) { + for (final element in treeElements) { + final result = f(element); + if (result != null) return result; + } + + return null; + } +} + +class VariableListAst extends ElementAst { + VariableListAst(ast) : super(ast, new AggregatedTreeElements()); + + add(VariableElement element, TreeElements treeElements) { + AggregatedTreeElements e = this.treeElements; + e[element.cachedNode] = element; + e.treeElements.add(treeElements); + } +} + +class FunctionBodyRewriter extends CloningVisitor { + final Compiler compiler; + final bool stripAsserts; + + FunctionBodyRewriter(this.compiler, originalTreeElements, this.stripAsserts) + : super(originalTreeElements); + + visitBlock(Block block) { + shouldOmit(Statement statement) { + if (statement is EmptyStatement) return true; + if (statement is ExpressionStatement) { + Send send = statement.expression.asSend(); + if (send != null) { + Element element = originalTreeElements[send]; + if (stripAsserts && identical(element, compiler.assertMethod)) { + return true; + } + } + } + return false; + } + + rewriteStatement(Statement statement) { + if (statement is Block) { + Link statements = statement.statements.nodes; + if (!statements.isEmpty && statements.tail.isEmpty) { + Statement single = statements.head; + bool isDeclaration = + single is VariableDefinitions || single is FunctionDeclaration; + if (!isDeclaration) return single; + } + } + return statement; + } + + NodeList statements = block.statements; + LinkBuilder<Statement> builder = new LinkBuilder<Statement>(); + for (Statement statement in statements.nodes) { + if (!shouldOmit(statement)) { + builder.addLast(visit(rewriteStatement(statement))); + } + } + return new Block(rewriteNodeList(statements, builder.toLink())); + } +} + +class DartBackend extends Backend { + final List<CompilerTask> tasks; + final bool forceStripTypes; + final bool stripAsserts; + // TODO(antonm): make available from command-line options. + final bool outputAst = false; + + Map<Element, TreeElements> get resolvedElements => + compiler.enqueuer.resolution.resolvedElements; + + /** + * Tells whether it is safe to remove type declarations from variables, + * functions parameters. It becomes not safe if: + * 1) TypeError is used somewhere in the code, + * 2) The code has typedefs in right hand side of IS checks, + * 3) The code has classes which extend typedefs, have type arguments typedefs + * or type variable bounds typedefs. + * These restrictions can be less strict. + */ + bool isSafeToRemoveTypeDeclarations( + Map<ClassElement, Set<Element>> classMembers) { + Set<DartType> processedTypes = new Set<DartType>(); + List<DartType> workQueue = new List<DartType>(); + workQueue.addAll( + classMembers.keys.map((classElement) => classElement.thisType)); + workQueue.addAll(compiler.resolverWorld.isChecks); + Element typeErrorElement = + compiler.coreLibrary.find(new SourceString('TypeError')); + DartType typeErrorType = typeErrorElement.computeType(compiler); + if (workQueue.indexOf(typeErrorType) != -1) { + return false; + } + + void processTypeArguments(Element classElement, NodeList typeArguments) { + if (typeArguments == null) return; + for (Node typeArgument in typeArguments.nodes) { + if (typeArgument is TypeVariable) { + typeArgument = typeArgument.bound; + } + if (typeArgument == null) continue; + assert(typeArgument is TypeAnnotation); + DartType argumentType = + compiler.resolveTypeAnnotation(classElement, typeArgument); + assert(argumentType != null); + workQueue.add(argumentType); + } + } + + void processTypeAnnotationList(Element classElement, NodeList annotations) { + for (Link link = annotations.nodes; !link.isEmpty; link = link.tail) { + TypeAnnotation typeAnnotation = link.head; + NodeList typeArguments = typeAnnotation.typeArguments; + processTypeArguments(classElement, typeArguments); + } + } + + void processSuperclassTypeArguments(Element classElement, Node superclass) { + if (superclass == null) return; + MixinApplication superMixinApplication = superclass.asMixinApplication(); + if (superMixinApplication != null) { + processTypeAnnotationList(classElement, superMixinApplication.mixins); + } else { + TypeAnnotation typeAnnotation = superclass; + NodeList typeArguments = typeAnnotation.typeArguments; + processTypeArguments(classElement, typeArguments); + } + } + + while (!workQueue.isEmpty) { + DartType type = workQueue.removeLast(); + if (processedTypes.contains(type)) continue; + processedTypes.add(type); + if (type is TypedefType) return false; + if (type is InterfaceType) { + ClassElement element = type.element; + Node node = element.parseNode(compiler); + if (node is ClassNode) { + ClassNode classNode = node; + processTypeArguments(element, classNode.typeParameters); + processSuperclassTypeArguments(element, classNode.superclass); + processTypeAnnotationList(element, classNode.interfaces); + } else { + MixinApplication mixinNode = node; + processSuperclassTypeArguments(element, mixinNode.superclass); + if (mixinNode is NamedMixinApplication) { + NamedMixinApplication namedMixinNode = mixinNode; + processTypeArguments(element, namedMixinNode.typeParameters); + } + } + // Check all supertypes. + if (element.allSupertypes != null) { + workQueue.addAll(element.allSupertypes.toList()); + } + } + } + return true; + } + + DartBackend(Compiler compiler, List<String> strips) + : tasks = <CompilerTask>[], + forceStripTypes = strips.indexOf('types') != -1, + stripAsserts = strips.indexOf('asserts') != -1, + super(compiler); + + void enqueueHelpers(ResolutionEnqueuer world) { + // Right now resolver doesn't always resolve interfaces needed + // for literals, so force them. TODO(antonm): fix in the resolver. + final LITERAL_TYPE_NAMES = const [ + 'Map', 'List', 'num', 'int', 'double', 'bool' + ]; + final coreLibrary = compiler.coreLibrary; + for (final name in LITERAL_TYPE_NAMES) { + ClassElement classElement = coreLibrary.findLocal(new SourceString(name)); + classElement.ensureResolved(compiler); + } + } + void codegen(CodegenWorkItem work) { } + void processNativeClasses(Enqueuer world, + Iterable<LibraryElement> libraries) { } + + bool isUserLibrary(LibraryElement lib) { + final INTERNAL_HELPERS = [ + compiler.jsHelperLibrary, + compiler.interceptorsLibrary, + ]; + return INTERNAL_HELPERS.indexOf(lib) == -1 && !lib.isPlatformLibrary; + } + + void assembleProgram() { + // Conservatively traverse all platform libraries and collect member names. + // TODO(antonm): ideally we should only collect names of used members, + // however as of today there are problems with names of some core library + // interfaces, most probably for interfaces of literals. + final fixedMemberNames = new Set<String>(); + for (final library in compiler.libraries.values) { + if (!library.isPlatformLibrary) continue; + library.implementation.forEachLocalMember((Element element) { + if (element.isClass()) { + ClassElement classElement = element; + // Make sure we parsed the class to initialize its local members. + // TODO(smok): Figure out if there is a better way to fill local + // members. + element.parseNode(compiler); + classElement.forEachLocalMember((member) { + final name = member.name.slowToString(); + // Skip operator names. + if (!name.startsWith(r'operator$')) { + // Fetch name of named constructors and factories if any, + // otherwise store regular name. + // TODO(antonm): better way to analyze the name. + fixedMemberNames.add(name.split(r'$').last); + } + }); + } + // Even class names are added due to a delicate problem we have: + // if one imports dart:core with a prefix, we cannot tell prefix.name + // from dynamic invocation (alas!). So we'd better err on preserving + // those names. + fixedMemberNames.add(element.name.slowToString()); + }); + } + // The VM will automatically invoke the call method of objects + // that are invoked as functions. Make sure to not rename that. + fixedMemberNames.add('call'); + // TODO(antonm): TypeError.srcType and TypeError.dstType are defined in + // runtime/lib/error.dart. Overall, all DartVM specific libs should be + // accounted for. + fixedMemberNames.add('srcType'); + fixedMemberNames.add('dstType'); + + /** + * Tells whether we should output given element. Corelib classes like + * Object should not be in the resulting code. + */ + bool shouldOutput(Element element) { + return !identical(element.kind, ElementKind.VOID) + && isUserLibrary(element.getLibrary()) + && !element.isSynthesized + && element is !AbstractFieldElement; + } + + final elementAsts = new Map<Element, ElementAst>(); + + parse(element) => element.parseNode(compiler); + + Set<Element> topLevelElements = new Set<Element>(); + Map<ClassElement, Set<Element>> classMembers = + new Map<ClassElement, Set<Element>>(); + + // Build all top level elements to emit and necessary class members. + var newTypedefElementCallback, newClassElementCallback; + + processElement(element, elementAst) { + new ReferencedElementCollector( + compiler, + element, elementAst.treeElements, + newTypedefElementCallback, newClassElementCallback).collect(); + elementAsts[element] = elementAst; + } + + addTopLevel(element, elementAst) { + if (topLevelElements.contains(element)) return; + topLevelElements.add(element); + processElement(element, elementAst); + } + + addClass(classElement) { + addTopLevel(classElement, + new ElementAst.forClassLike(parse(classElement))); + classMembers.putIfAbsent(classElement, () => new Set()); + } + + newTypedefElementCallback = (TypedefElement element) { + if (!shouldOutput(element)) return; + addTopLevel(element, + new ElementAst.forClassLike(parse(element))); + }; + newClassElementCallback = (ClassElement classElement) { + if (!shouldOutput(classElement)) return; + addClass(classElement); + }; + + compiler.resolverWorld.instantiatedClasses.forEach( + (ClassElement classElement) { + if (shouldOutput(classElement)) addClass(classElement); + }); + resolvedElements.forEach((element, treeElements) { + if (!shouldOutput(element) || treeElements == null) return; + var elementAst = new ElementAst.rewrite( + compiler, parse(element), treeElements, stripAsserts); + if (element.isField()) { + final list = (element as VariableElement).variables; + elementAst = elementAsts.putIfAbsent( + list, () => new VariableListAst(parse(list))); + (elementAst as VariableListAst).add(element, treeElements); + element = list; + } + + if (element.isMember()) { + ClassElement enclosingClass = element.getEnclosingClass(); + assert(enclosingClass.isClass()); + assert(enclosingClass.isTopLevel()); + assert(shouldOutput(enclosingClass)); + addClass(enclosingClass); + classMembers[enclosingClass].add(element); + processElement(element, elementAst); + } else { + if (!element.isTopLevel()) { + compiler.cancel('Cannot process $element', element: element); + } + addTopLevel(element, elementAst); + } + }); + + // Add synthesized constructors to classes with no resolved constructors, + // but which originally had any constructor. That should prevent + // those classes from being instantiable with default constructor. + Identifier synthesizedIdentifier = + new Identifier(new StringToken(IDENTIFIER_INFO, '', -1)); + + NextClassElement: + for (ClassElement classElement in classMembers.keys) { + for (Element member in classMembers[classElement]) { + if (member.isConstructor()) continue NextClassElement; + } + if (classElement.constructors.isEmpty) continue NextClassElement; + + // TODO(antonm): check with AAR team if there is better approach. + // As an idea: provide template as a Dart code---class C { C.name(); }--- + // and then overwrite necessary parts. + ClassNode classNode = classElement.parseNode(compiler); + SynthesizedConstructorElementX constructor = + new SynthesizedConstructorElementX(classElement); + constructor.type = new FunctionType( + constructor, + compiler.types.voidType, + const Link<DartType>(), + const Link<DartType>(), + const Link<SourceString>(), + const Link<DartType>() + ); + constructor.cachedNode = new FunctionExpression( + new Send(classNode.name, synthesizedIdentifier), + new NodeList(new StringToken(OPEN_PAREN_INFO, '(', -1), + const Link<Node>(), + new StringToken(CLOSE_PAREN_INFO, ')', -1)), + new EmptyStatement(new StringToken(SEMICOLON_INFO, ';', -1)), + null, Modifiers.EMPTY, null, null); + + classMembers[classElement].add(constructor); + elementAsts[constructor] = + new ElementAst(constructor.cachedNode, new TreeElementMapping(null)); + } + + // Create all necessary placeholders. + PlaceholderCollector collector = + new PlaceholderCollector(compiler, fixedMemberNames, elementAsts); + // Add synthesizedIdentifier to set of unresolved names to rename it to + // some unused identifier. + collector.unresolvedNodes.add(synthesizedIdentifier); + makePlaceholders(element) { + collector.collect(element); + if (element.isClass()) { + classMembers[element].forEach(makePlaceholders); + } + } + topLevelElements.forEach(makePlaceholders); + // Create renames. + Map<Node, String> renames = new Map<Node, String>(); + Map<LibraryElement, String> imports = new Map<LibraryElement, String>(); + bool shouldCutDeclarationTypes = forceStripTypes + || (compiler.enableMinification + && isSafeToRemoveTypeDeclarations(classMembers)); + renamePlaceholders( + compiler, collector, renames, imports, + fixedMemberNames, shouldCutDeclarationTypes); + + // Sort elements. + final sortedTopLevels = sortElements(topLevelElements); + final sortedClassMembers = new Map<ClassElement, List<Element>>(); + classMembers.forEach((classElement, members) { + sortedClassMembers[classElement] = sortElements(members); + }); + + if (outputAst) { + // TODO(antonm): Ideally XML should be a separate backend. + // TODO(antonm): obey renames and minification, at least as an option. + StringBuffer sb = new StringBuffer(); + outputElement(element) { sb.add(parse(element).toDebugString()); } + + // Emit XML for AST instead of the program. + for (final topLevel in sortedTopLevels) { + if (topLevel.isClass()) { + // TODO(antonm): add some class info. + sortedClassMembers[topLevel].forEach(outputElement); + } else { + outputElement(topLevel); + } + } + compiler.assembledCode = '<Program>\n$sb</Program>\n'; + return; + } + + final topLevelNodes = <Node>[]; + final memberNodes = new Map<ClassNode, List<Node>>(); + for (final element in sortedTopLevels) { + topLevelNodes.add(elementAsts[element].ast); + if (element.isClass() && !element.isMixinApplication) { + final members = <Node>[]; + for (final member in sortedClassMembers[element]) { + members.add(elementAsts[member].ast); + } + memberNodes[elementAsts[element].ast] = members; + } + } + + final unparser = new EmitterUnparser(renames); + emitCode(unparser, imports, topLevelNodes, memberNodes); + compiler.assembledCode = unparser.result; + + // Output verbose info about size ratio of resulting bundle to all + // referenced non-platform sources. + logResultBundleSizeInfo(topLevelElements); + } + + void logResultBundleSizeInfo(Set<Element> topLevelElements) { + Iterable<LibraryElement> referencedLibraries = + compiler.libraries.values.where(isUserLibrary); + // Sum total size of scripts in each referenced library. + int nonPlatformSize = 0; + for (LibraryElement lib in referencedLibraries) { + for (CompilationUnitElement compilationUnit in lib.compilationUnits) { + nonPlatformSize += compilationUnit.script.text.length; + } + } + int percentage = compiler.assembledCode.length * 100 ~/ nonPlatformSize; + log('Total used non-platform files size: ${nonPlatformSize} bytes, ' + 'bundle size: ${compiler.assembledCode.length} bytes (${percentage}%)'); + } + + log(String message) => compiler.log('[DartBackend] $message'); +} + +class EmitterUnparser extends Unparser { + final Map<Node, String> renames; + + EmitterUnparser(this.renames); + + visit(Node node) { + if (node != null && renames.containsKey(node)) { + sb.add(renames[node]); + } else { + super.visit(node); + } + } + + unparseSendReceiver(Send node, {bool spacesNeeded: false}) { + // TODO(smok): Remove ugly hack for library prefices. + if (node.receiver != null && renames[node.receiver] == '') return; + super.unparseSendReceiver(node, spacesNeeded: spacesNeeded); + } + + unparseFunctionName(Node name) { + if (name != null && renames.containsKey(name)) { + sb.add(renames[name]); + } else { + super.unparseFunctionName(name); + } + } +} + + +/** + * Some elements are not recorded by resolver now, + * for example, typedefs or classes which are only + * used in signatures, as/is operators or in super clauses + * (just to name a few). Retraverse AST to pick those up. + */ +class ReferencedElementCollector extends Visitor { + final Compiler compiler; + final Element rootElement; + final TreeElements treeElements; + final newTypedefElementCallback; + final newClassElementCallback; + + ReferencedElementCollector( + this.compiler, + Element rootElement, this.treeElements, + this.newTypedefElementCallback, this.newClassElementCallback) + : this.rootElement = (rootElement is VariableElement) + ? (rootElement as VariableElement).variables : rootElement; + + visitClassNode(ClassNode node) { + super.visitClassNode(node); + // Temporary hack which should go away once interfaces + // and default clauses are out. + if (node.defaultClause != null) { + // Resolver cannot resolve parameterized default clauses. + TypeAnnotation evilCousine = new TypeAnnotation( + node.defaultClause.typeName, null); + evilCousine.accept(this); + } + } + + visitNode(Node node) { node.visitChildren(this); } + + visitTypeAnnotation(TypeAnnotation typeAnnotation) { + // We call [resolveReturnType] to allow having 'void'. + final type = compiler.resolveReturnType(rootElement, typeAnnotation); + Element typeElement = type.element; + if (typeElement.isTypedef()) newTypedefElementCallback(typeElement); + if (typeElement.isClass()) newClassElementCallback(typeElement); + typeAnnotation.visitChildren(this); + } + + void collect() { + compiler.withCurrentElement(rootElement, () { + rootElement.parseNode(compiler).accept(this); + }); + } +} + +compareBy(f) => (x, y) => f(x).compareTo(f(y)); + +List sorted(Iterable l, comparison) { + final result = new List.from(l); + result.sort(comparison); + return result; +} + +compareElements(e0, e1) { + int result = compareBy((e) => e.getLibrary().canonicalUri.toString())(e0, e1); + if (result != 0) return result; + return compareBy((e) => e.position().charOffset)(e0, e1); +} + +List<Element> sortElements(Iterable<Element> elements) => + sorted(elements, compareElements);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart new file mode 100644 index 0000000..8a8abe4 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart
@@ -0,0 +1,25 @@ +// 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. + +library dart_backend; + +import '../elements/elements.dart'; +import '../elements/modelx.dart' show SynthesizedConstructorElementX; +import '../dart2jslib.dart'; +import '../dart_types.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; + +import '../scanner/scannerlib.dart' show StringToken, + Keyword, + OPEN_PAREN_INFO, + CLOSE_PAREN_INFO, + SEMICOLON_INFO, + IDENTIFIER_INFO; + +part 'backend.dart'; +part 'emitter.dart'; +part 'renamer.dart'; +part 'placeholder_collector.dart'; +part 'utils.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart new file mode 100644 index 0000000..c074af4 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart
@@ -0,0 +1,24 @@ +// 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. + +part of dart_backend; + +String emitCode( + Unparser unparser, + Map<LibraryElement, String> imports, + Collection<Node> topLevelNodes, + Map<ClassNode, Collection<Node>> classMembers) { + imports.forEach((libraryElement, prefix) { + unparser.unparseImportTag('${libraryElement.canonicalUri}', prefix); + }); + + for (final node in topLevelNodes) { + if (node is ClassNode) { + // TODO(smok): Filter out default constructors here. + unparser.unparseClassWithBody(node, classMembers[node]); + } else { + unparser.unparse(node); + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart new file mode 100644 index 0000000..d184663 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart
@@ -0,0 +1,626 @@ +// 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. + +part of dart_backend; + +class LocalPlaceholder { + final String identifier; + final Set<Node> nodes; + LocalPlaceholder(this.identifier) : nodes = new Set<Node>(); + int get hashCode => identifier.hashCode; + String toString() => + 'local_placeholder[id($identifier), nodes($nodes)]'; +} + +class FunctionScope { + final Set<String> parameterIdentifiers; + final Set<LocalPlaceholder> localPlaceholders; + FunctionScope() + : parameterIdentifiers = new Set<String>(), + localPlaceholders = new Set<LocalPlaceholder>(); + void registerParameter(Identifier node) { + parameterIdentifiers.add(node.source.slowToString()); + } +} + +class ConstructorPlaceholder { + final Node node; + final DartType type; + final bool isRedirectingCall; + ConstructorPlaceholder(this.node, this.type) + : this.isRedirectingCall = false; + // Note: factory redirection is not redirecting call! + ConstructorPlaceholder.redirectingCall(this.node) + : this.type = null, this.isRedirectingCall = true; +} + +class DeclarationTypePlaceholder { + final TypeAnnotation typeNode; + final bool requiresVar; + DeclarationTypePlaceholder(this.typeNode, this.requiresVar); +} + +class SendVisitor extends ResolvedVisitor { + final PlaceholderCollector collector; + + get compiler => collector.compiler; + + SendVisitor(this.collector, TreeElements elements) : super(elements); + + visitOperatorSend(Send node) {} + visitForeignSend(Send node) {} + + visitSuperSend(Send node) { + Element element = elements[node]; + if (element != null && element.isConstructor()) { + collector.makeRedirectingConstructorPlaceholder(node.selector, element); + } else { + collector.tryMakeMemberPlaceholder(node.selector); + } + } + + visitDynamicSend(Send node) { + final element = elements[node]; + if (element == null || !element.isErroneous()) { + collector.tryMakeMemberPlaceholder(node.selector); + } + } + + visitClosureSend(Send node) { + final element = elements[node]; + if (element != null) { + collector.tryMakeLocalPlaceholder(element, node.selector); + } + } + + visitGetterSend(Send node) { + final element = elements[node]; + // element == null means dynamic property access. + if (element == null) { + collector.tryMakeMemberPlaceholder(node.selector); + } else if (element.isErroneous()) { + return; + } else if (element.isPrefix()) { + // Node is prefix part in case of source 'lib.somesetter = 5;' + collector.makeNullPlaceholder(node); + } else if (Elements.isStaticOrTopLevel(element)) { + // Unqualified or prefixed top level or static. + collector.makeElementPlaceholder(node.selector, element); + } else if (!element.isTopLevel()) { + if (element.isInstanceMember()) { + collector.tryMakeMemberPlaceholder(node.selector); + } else { + // May get FunctionExpression here in selector + // in case of A(int this.f()); + if (node.selector is Identifier) { + collector.tryMakeLocalPlaceholder(element, node.selector); + } else { + assert(node.selector is FunctionExpression); + } + } + } + } + + visitStaticSend(Send node) { + final element = elements[node]; + if (Elements.isUnresolved(element) + || identical(element, compiler.assertMethod)) { + return; + } + if (element.isConstructor() || element.isFactoryConstructor()) { + // Rename named constructor in redirection position: + // class C { C.named(); C.redirecting() : this.named(); } + if (node.receiver is Identifier + && node.receiver.asIdentifier().isThis()) { + assert(node.selector is Identifier); + collector.makeRedirectingConstructorPlaceholder(node.selector, element); + } + return; + } + collector.makeElementPlaceholder(node.selector, element); + // Another ugly case: <lib prefix>.<top level> is represented as + // receiver: lib prefix, selector: top level. + if (element.isTopLevel() && node.receiver != null) { + assert(elements[node.receiver].isPrefix()); + // Hack: putting null into map overrides receiver of original node. + collector.makeNullPlaceholder(node.receiver); + } + } + + internalError(String reason, {Node node}) { + collector.internalError(reason, node: node); + } + + visitTypeReferenceSend(Send node) { + collector.makeElementPlaceholder(node.selector, elements[node]); + } +} + +class PlaceholderCollector extends Visitor { + final Compiler compiler; + final Set<String> fixedMemberNames; // member names which cannot be renamed. + final Map<Element, ElementAst> elementAsts; + final Set<Node> nullNodes; // Nodes that should not be in output. + final Set<Node> unresolvedNodes; + final Map<Element, Set<Node>> elementNodes; + final Map<FunctionElement, FunctionScope> functionScopes; + final Map<LibraryElement, Set<Identifier>> privateNodes; + final List<DeclarationTypePlaceholder> declarationTypePlaceholders; + final Map<String, Set<Identifier>> memberPlaceholders; + final Map<Element, List<ConstructorPlaceholder>> constructorPlaceholders; + Map<String, LocalPlaceholder> currentLocalPlaceholders; + Element currentElement; + FunctionElement topmostEnclosingFunction; + TreeElements treeElements; + + LibraryElement get coreLibrary => compiler.coreLibrary; + FunctionElement get entryFunction => compiler.mainApp.find(Compiler.MAIN); + + get currentFunctionScope => functionScopes.putIfAbsent( + topmostEnclosingFunction, () => new FunctionScope()); + + PlaceholderCollector(this.compiler, this.fixedMemberNames, this.elementAsts) : + nullNodes = new Set<Node>(), + unresolvedNodes = new Set<Node>(), + elementNodes = new Map<Element, Set<Node>>(), + functionScopes = new Map<FunctionElement, FunctionScope>(), + privateNodes = new Map<LibraryElement, Set<Identifier>>(), + declarationTypePlaceholders = new List<DeclarationTypePlaceholder>(), + memberPlaceholders = new Map<String, Set<Identifier>>(), + constructorPlaceholders = + new Map<Element, List<ConstructorPlaceholder>>(); + + void collectFunctionDeclarationPlaceholders( + FunctionElement element, FunctionExpression node) { + if (element.isGenerativeConstructor() || element.isFactoryConstructor()) { + DartType type = element.getEnclosingClass().thisType.asRaw(); + makeConstructorPlaceholder(node.name, element, type); + Return bodyAsReturn = node.body.asReturn(); + if (bodyAsReturn != null && bodyAsReturn.isRedirectingFactoryBody) { + // Factory redirection. + FunctionElement redirectTarget = element.defaultImplementation; + assert(redirectTarget != null && redirectTarget != element); + type = redirectTarget.getEnclosingClass().thisType.asRaw(); + makeConstructorPlaceholder( + bodyAsReturn.expression, redirectTarget, type); + } + } else if (Elements.isStaticOrTopLevel(element)) { + // Note: this code should only rename private identifiers for class' + // fields/getters/setters/methods. Top-level identifiers are renamed + // just to escape conflicts and that should be enough as we shouldn't + // be able to resolve private identifiers for other libraries. + makeElementPlaceholder(node.name, element); + } else if (element.isMember()) { + if (node.name is Identifier) { + tryMakeMemberPlaceholder(node.name); + } else { + assert(node.name.asSend().isOperator); + } + } + } + + void collectFieldDeclarationPlaceholders(Element element, Node node) { + Identifier name = node is Identifier ? node : node.asSend().selector; + if (Elements.isStaticOrTopLevel(element)) { + makeElementPlaceholder(name, element); + } else if (Elements.isInstanceField(element)) { + tryMakeMemberPlaceholder(name); + } + } + + void collect(Element element) { + this.currentElement = element; + this.topmostEnclosingFunction = null; + final ElementAst elementAst = elementAsts[element]; + this.treeElements = elementAst.treeElements; + Node elementNode = elementAst.ast; + if (element is FunctionElement) { + collectFunctionDeclarationPlaceholders(element, elementNode); + } else if (element is VariableListElement) { + VariableDefinitions definitions = elementNode; + for (Node definition in definitions.definitions) { + final definitionElement = treeElements[definition]; + // definitionElement == null if variable is actually unused. + if (definitionElement == null) continue; + collectFieldDeclarationPlaceholders(definitionElement, definition); + } + makeVarDeclarationTypePlaceholder(definitions); + } else { + assert(element is ClassElement || element is TypedefElement); + } + currentLocalPlaceholders = new Map<String, LocalPlaceholder>(); + compiler.withCurrentElement(element, () { + elementNode.accept(this); + }); + } + + void tryMakeLocalPlaceholder(Element element, Identifier node) { + bool isOptionalParameter() { + FunctionElement function = element.enclosingElement; + for (Element parameter in function.functionSignature.optionalParameters) { + if (identical(parameter, element)) return true; + } + return false; + } + + // TODO(smok): Maybe we should rename privates as well, their privacy + // should not matter if they are local vars. + if (node.source.isPrivate()) return; + if (element.isParameter() && isOptionalParameter()) { + currentFunctionScope.registerParameter(node); + } else if (Elements.isLocal(element)) { + makeLocalPlaceholder(node); + } + } + + void tryMakeMemberPlaceholder(Identifier node) { + assert(node != null); + if (node.source.isPrivate()) return; + if (node is Operator) return; + final identifier = node.source.slowToString(); + if (fixedMemberNames.contains(identifier)) return; + memberPlaceholders.putIfAbsent( + identifier, () => new Set<Identifier>()).add(node); + } + + void makeTypePlaceholder(Node node, DartType type) { + if (node is Send) { + // Prefix. + assert(node.receiver is Identifier); + assert(node.selector is Identifier); + makeNullPlaceholder(node.receiver); + node = node.selector; + } + makeElementPlaceholder(node, type.element); + } + + void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) { + if (type == null) return; + declarationTypePlaceholders.add( + new DeclarationTypePlaceholder(type, false)); + } + + void makeVarDeclarationTypePlaceholder(VariableDefinitions node) { + // TODO(smok): Maybe instead of calling this method and + // makeDeclaratioTypePlaceholder have type declaration placeholder + // collector logic in visitVariableDefinitions when resolver becomes better + // and/or catch syntax changes. + if (node.type == null) return; + Element definitionElement = treeElements[node.definitions.nodes.head]; + bool requiresVar = !node.modifiers.isFinalOrConst(); + declarationTypePlaceholders.add( + new DeclarationTypePlaceholder(node.type, requiresVar)); + } + + void makeNullPlaceholder(Node node) { + assert(node is Identifier || node is Send); + nullNodes.add(node); + } + + void makeElementPlaceholder(Node node, Element element) { + assert(element != null); + if (identical(element, entryFunction)) return; + if (identical(element.getLibrary(), coreLibrary)) return; + if (element.getLibrary().isPlatformLibrary && !element.isTopLevel()) { + return; + } + if (element == compiler.types.dynamicType.element) { + internalError( + 'Should never make element placeholder for dynamic type element', + node: node); + } + elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node); + } + + void makePrivateIdentifier(Identifier node) { + assert(node != null); + privateNodes.putIfAbsent( + currentElement.getLibrary(), () => new Set<Identifier>()).add(node); + } + + void makeUnresolvedPlaceholder(Node node) { + unresolvedNodes.add(node); + } + + void makeLocalPlaceholder(Identifier identifier) { + LocalPlaceholder getLocalPlaceholder() { + String name = identifier.source.slowToString(); + return currentLocalPlaceholders.putIfAbsent(name, () { + LocalPlaceholder localPlaceholder = new LocalPlaceholder(name); + currentFunctionScope.localPlaceholders.add(localPlaceholder); + return localPlaceholder; + }); + } + + getLocalPlaceholder().nodes.add(identifier); + } + + void makeConstructorPlaceholder(Node node, Element element, DartType type) { + assert(type != null); + constructorPlaceholders + .putIfAbsent(element, () => <ConstructorPlaceholder>[]) + .add(new ConstructorPlaceholder(node, type)); + } + void makeRedirectingConstructorPlaceholder(Node node, Element element) { + constructorPlaceholders + .putIfAbsent(element, () => <ConstructorPlaceholder>[]) + .add(new ConstructorPlaceholder.redirectingCall(node)); + } + + void internalError(String reason, {Node node}) { + compiler.cancel(reason, node: node); + } + + void unreachable() { internalError('Unreachable case'); } + + visit(Node node) => (node == null) ? null : node.accept(this); + + visitNode(Node node) { node.visitChildren(this); } // We must go deeper. + + visitNewExpression(NewExpression node) { + Send send = node.send; + InterfaceType type = treeElements.getType(node); + assert(type != null); + Element constructor = treeElements[send]; + assert(constructor != null); + assert(send.receiver == null); + if (!Elements.isErroneousElement(constructor)) { + makeConstructorPlaceholder(node.send.selector, constructor, type); + // TODO(smok): Should this be in visitNamedArgument? + // Field names can be exposed as names of optional arguments, e.g. + // class C { + // final field; + // C([this.field]); + // } + // Do not forget to rename them as well. + FunctionElement constructorFunction = constructor; + Link<Element> optionalParameters = + constructorFunction.functionSignature.optionalParameters; + for (final argument in send.argumentsNode) { + NamedArgument named = argument.asNamedArgument(); + if (named == null) continue; + Identifier name = named.name; + String nameAsString = name.source.slowToString(); + for (final parameter in optionalParameters) { + if (identical(parameter.kind, ElementKind.FIELD_PARAMETER)) { + if (parameter.name.slowToString() == nameAsString) { + tryMakeMemberPlaceholder(name); + break; + } + } + } + } + } else { + makeUnresolvedPlaceholder(node.send.selector); + } + visit(node.send.argumentsNode); + } + + visitSend(Send send) { + new SendVisitor(this, treeElements).visitSend(send); + send.visitChildren(this); + } + + visitSendSet(SendSet send) { + Element element = treeElements[send]; + if (Elements.isErroneousElement(element)) { + // Complicated case: constructs like receiver.selector++ can resolve + // to ErroneousElement. Fortunately, receiver.selector still + // can be resoved via treeElements[send.selector], that's all + // that is needed to rename the construct properly. + element = treeElements[send.selector]; + } + if (element == null) { + if (send.receiver != null) tryMakeMemberPlaceholder(send.selector); + } else if (!element.isErroneous()) { + if (Elements.isStaticOrTopLevel(element)) { + // TODO(smok): Worth investigating why sometimes we get getter/setter + // here and sometimes abstract field. + assert(element.isClass() || element is VariableElement || + element.isAccessor() || element.isAbstractField() || + element.isFunction() || element.isTypedef() || + element is TypeVariableElement); + makeElementPlaceholder(send.selector, element); + } else { + assert(send.selector is Identifier); + if (Elements.isInstanceField(element)) { + tryMakeMemberPlaceholder(send.selector); + } else { + tryMakeLocalPlaceholder(element, send.selector); + } + } + } + send.visitChildren(this); + } + + visitIdentifier(Identifier identifier) { + if (identifier.source.isPrivate()) makePrivateIdentifier(identifier); + } + + static bool isPlainTypeName(TypeAnnotation typeAnnotation) { + if (typeAnnotation.typeName is !Identifier) return false; + if (typeAnnotation.typeArguments == null) return true; + if (typeAnnotation.typeArguments.isEmpty) return true; + return false; + } + + static bool isDynamicType(TypeAnnotation typeAnnotation) { + if (!isPlainTypeName(typeAnnotation)) return false; + String name = typeAnnotation.typeName.asIdentifier().source.slowToString(); + // TODO(aprelev@gmail.com): Removed deprecated Dynamic keyword support. + return name == 'Dynamic' || name == 'dynamic'; + } + + visitTypeAnnotation(TypeAnnotation node) { + // Poor man generic variables resolution. + // TODO(antonm): get rid of it once resolver can deal with it. + TypeDeclarationElement typeDeclarationElement; + if (currentElement is TypeDeclarationElement) { + typeDeclarationElement = currentElement; + } else { + typeDeclarationElement = currentElement.getEnclosingClass(); + } + if (typeDeclarationElement != null && isPlainTypeName(node) + && tryResolveAndCollectTypeVariable( + typeDeclarationElement, node.typeName)) { + return; + } + // We call [resolveReturnType] to allow having 'void'. + final type = compiler.resolveReturnType(currentElement, node); + if (type is InterfaceType || type is TypedefType) { + // TODO(antonm): is there a better way to detect unresolved types? + // Corner case: dart:core type with a prefix. + // Most probably there are some additional problems with + // coreLibPrefix.topLevels. + if (!identical(type.element, compiler.types.dynamicType.element)) { + makeTypePlaceholder(node.typeName, type); + } else { + if (!isDynamicType(node)) makeUnresolvedPlaceholder(node.typeName); + } + } + // Visit only type arguments, otherwise in case of lib.Class type + // annotation typeName is Send and we go to visitGetterSend, as a result + // "Class" is added to member placeholders. + visit(node.typeArguments); + } + + visitVariableDefinitions(VariableDefinitions node) { + // Collect only local placeholders. + for (Node definition in node.definitions.nodes) { + Element definitionElement = treeElements[definition]; + // definitionElement may be null if we're inside variable definitions + // of a function that is a parameter of another function. + // TODO(smok): Fix this when resolver correctly deals with + // such cases. + if (definitionElement == null) continue; + if (definition is Send) { + // May get FunctionExpression here in definition.selector + // in case of A(int this.f()); + if (definition.selector is Identifier) { + if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) { + tryMakeMemberPlaceholder(definition.selector); + } else { + tryMakeLocalPlaceholder(definitionElement, definition.selector); + } + } else { + assert(definition.selector is FunctionExpression); + if (identical(definitionElement.kind, ElementKind.FIELD_PARAMETER)) { + tryMakeMemberPlaceholder( + definition.selector.asFunctionExpression().name); + } + } + } else if (definition is Identifier) { + tryMakeLocalPlaceholder(definitionElement, definition); + } else if (definition is FunctionExpression) { + // Skip, it will be processed in visitFunctionExpression. + } else { + internalError('Unexpected definition structure $definition'); + } + } + node.visitChildren(this); + } + + visitFunctionExpression(FunctionExpression node) { + bool isKeyword(Identifier id) => + id != null && Keyword.keywords[id.source.slowToString()] != null; + + Element element = treeElements[node]; + // May get null here in case of A(int this.f()); + if (element != null) { + // Rename only local functions. + if (topmostEnclosingFunction == null) { + topmostEnclosingFunction = element; + } + if (!identical(element, currentElement)) { + if (node.name != null) { + assert(node.name is Identifier); + tryMakeLocalPlaceholder(element, node.name); + } + } + } + node.visitChildren(this); + // Make sure we don't omit return type of methods which names are + // identifiers, because the following works fine: + // int interface() => 1; + // But omitting 'int' makes VM unhappy. + // TODO(smok): Remove it when http://dartbug.com/5278 is fixed. + if (node.name == null || !isKeyword(node.name.asIdentifier())) { + makeOmitDeclarationTypePlaceholder(node.returnType); + } + collectFunctionParameters(node.parameters); + } + + void collectFunctionParameters(NodeList parameters) { + if (parameters == null) return; + for (Node parameter in parameters.nodes) { + if (parameter is NodeList) { + // Optional parameter list. + collectFunctionParameters(parameter); + } else { + assert(parameter is VariableDefinitions); + makeOmitDeclarationTypePlaceholder( + parameter.asVariableDefinitions().type); + } + } + } + + visitClassNode(ClassNode node) { + ClassElement classElement = currentElement; + makeElementPlaceholder(node.name, classElement); + node.visitChildren(this); + if (node.defaultClause != null) { + // Can't just visit class node's default clause because of the bug in the + // resolver, it just crashes when it meets type variable. + DartType defaultType = classElement.defaultClass; + assert(defaultType != null); + makeTypePlaceholder(node.defaultClause.typeName, defaultType); + visit(node.defaultClause.typeArguments); + } + } + + bool tryResolveAndCollectTypeVariable( + TypeDeclarationElement typeDeclaration, Identifier name) { + // Hack for case when interface and default class are in different + // libraries, try to resolve type variable to default class type arg. + // Example: + // lib1: interface I<K> default C<K> {...} + // lib2: class C<K> {...} + if (typeDeclaration is ClassElement + && (typeDeclaration as ClassElement).defaultClass != null) { + typeDeclaration = (typeDeclaration as ClassElement).defaultClass.element; + } + // Another poor man type resolution. + // Find this variable in enclosing type declaration parameters. + for (DartType type in typeDeclaration.typeVariables) { + if (type.name.slowToString() == name.source.slowToString()) { + makeTypePlaceholder(name, type); + return true; + } + } + return false; + } + + visitTypeVariable(TypeVariable node) { + assert(currentElement is TypedefElement || currentElement is ClassElement); + tryResolveAndCollectTypeVariable(currentElement, node.name); + node.visitChildren(this); + } + + visitTypedef(Typedef node) { + assert(currentElement is TypedefElement); + makeElementPlaceholder(node.name, currentElement); + node.visitChildren(this); + makeOmitDeclarationTypePlaceholder(node.returnType); + collectFunctionParameters(node.formals); + } + + visitBlock(Block node) { + for (Node statement in node.statements.nodes) { + if (statement is VariableDefinitions) { + makeVarDeclarationTypePlaceholder(statement); + } + } + node.visitChildren(this); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart new file mode 100644 index 0000000..886d39e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart
@@ -0,0 +1,359 @@ +// 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. + +part of dart_backend; + +Function get _compareNodes => + compareBy((n) => n.getBeginToken().charOffset); + +typedef String _Renamer(Renamable renamable); +abstract class Renamable { + const int RENAMABLE_TYPE_ELEMENT = 1; + const int RENAMABLE_TYPE_MEMBER = 2; + const int RENAMABLE_TYPE_LOCAL = 3; + + final Set<Node> nodes; + final _Renamer renamer; + + Renamable(this.nodes, this.renamer); + int compareTo(Renamable other) { + int nodesDiff = other.nodes.length.compareTo(this.nodes.length); + if (nodesDiff != 0) return nodesDiff; + int typeDiff = this.getTypeId().compareTo(other.getTypeId()); + return typeDiff != 0 ? typeDiff : compareInternals(other); + } + + int compareInternals(Renamable other); + int getTypeId(); + + String rename() => renamer(this); +} + +class ElementRenamable extends Renamable { + final Element element; + + ElementRenamable(this.element, Set<Node> nodes, _Renamer renamer) + : super(nodes, renamer); + + int compareInternals(ElementRenamable other) => + compareElements(this.element, other.element); + int getTypeId() => RENAMABLE_TYPE_ELEMENT; +} + +class MemberRenamable extends Renamable { + final String identifier; + MemberRenamable(this.identifier, Set<Node> nodes, _Renamer renamer) + : super(nodes, renamer); + int compareInternals(MemberRenamable other) => + this.identifier.compareTo(other.identifier); + int getTypeId() => RENAMABLE_TYPE_MEMBER; +} + +class LocalRenamable extends Renamable { + LocalRenamable(Set<Node> nodes, _Renamer renamer) : super(nodes, renamer); + int compareInternals(LocalRenamable other) => + _compareNodes(sorted(this.nodes, _compareNodes)[0], + sorted(other.nodes, _compareNodes)[0]); + int getTypeId() => RENAMABLE_TYPE_LOCAL; +} + +/** + * Renames only top-level elements that would let to ambiguity if not renamed. + */ +void renamePlaceholders( + Compiler compiler, + PlaceholderCollector placeholderCollector, + Map<Node, String> renames, + Map<LibraryElement, String> imports, + Set<String> fixedMemberNames, + bool cutDeclarationTypes) { + final Map<LibraryElement, Map<String, String>> renamed + = new Map<LibraryElement, Map<String, String>>(); + + renameNodes(Collection<Node> nodes, renamer) { + for (Node node in sorted(nodes, _compareNodes)) { + renames[node] = renamer(node); + } + } + + sortedForEach(Map<Element, dynamic> map, f) { + for (Element element in sortElements(map.keys)) { + f(element, map[element]); + } + } + + String renameType(DartType type, Function renameElement) { + // TODO(smok): Do not rename type if it is in platform library or + // js-helpers. + StringBuffer result = new StringBuffer(renameElement(type.element)); + if (type is InterfaceType) { + if (!type.isRaw) { + result.add('<'); + Link<DartType> argumentsLink = type.typeArguments; + result.add(renameType(argumentsLink.head, renameElement)); + for (Link<DartType> link = argumentsLink.tail; !link.isEmpty; + link = link.tail) { + result.add(','); + result.add(renameType(link.head, renameElement)); + } + result.add('>'); + } + } + return result.toString(); + } + + String renameConstructor(Element element, ConstructorPlaceholder placeholder, + Function renameString, Function renameElement) { + assert(element.isConstructor()); + StringBuffer result = new StringBuffer(); + String name = element.name.slowToString(); + if (element.name != element.getEnclosingClass().name) { + // Named constructor or factory. Is there a more reliable way to check + // this case? + if (!placeholder.isRedirectingCall) { + result.add(renameType(placeholder.type, renameElement)); + result.add('.'); + } + String prefix = '${element.getEnclosingClass().name.slowToString()}\$'; + if (!name.startsWith(prefix)) { + // Factory for another interface (that is going away soon). + compiler.internalErrorOnElement(element, + "Factory constructors for external interfaces are not supported."); + } + name = name.substring(prefix.length); + if (!element.getLibrary().isPlatformLibrary) { + name = renameString(element.getLibrary(), name); + } + result.add(name); + } else { + assert(!placeholder.isRedirectingCall); + result.add(renameType(placeholder.type, renameElement)); + } + return result.toString(); + } + + Function makeElementRenamer(rename, generateUniqueName) => (element) { + assert(Elements.isErroneousElement(element) || + Elements.isStaticOrTopLevel(element) || + element is TypeVariableElement); + // TODO(smok): We may want to reuse class static field and method names. + String originalName = element.name.slowToString(); + LibraryElement library = element.getLibrary(); + if (identical(element.getLibrary(), compiler.coreLibrary)) { + return originalName; + } + if (library.isPlatformLibrary && !library.isInternalLibrary) { + assert(element.isTopLevel()); + final prefix = + imports.putIfAbsent(library, () => generateUniqueName('p')); + return '$prefix.$originalName'; + } + + return rename(library, originalName); + }; + + Function makeRenamer(generateUniqueName) => + (library, originalName) => + renamed.putIfAbsent(library, () => {}) + .putIfAbsent(originalName, + () => generateUniqueName(originalName)); + + // Renamer function that takes library and original name and returns a new + // name for given identifier. + Function rename; + Function renameElement; + // A function that takes original identifier name and generates a new unique + // identifier. + Function generateUniqueName; + if (compiler.enableMinification) { + MinifyingGenerator generator = new MinifyingGenerator(); + Set<String> forbiddenIdentifiers = new Set<String>.from(['main']); + forbiddenIdentifiers.addAll(Keyword.keywords.keys); + forbiddenIdentifiers.addAll(fixedMemberNames); + generateUniqueName = (_) => + generator.generate(forbiddenIdentifiers.contains); + rename = makeRenamer(generateUniqueName); + renameElement = makeElementRenamer(rename, generateUniqueName); + + Set<String> allParameterIdentifiers = new Set<String>(); + for (var functionScope in placeholderCollector.functionScopes.values) { + allParameterIdentifiers.addAll(functionScope.parameterIdentifiers); + } + // Build a sorted (by usage) list of local nodes that will be renamed to + // the same identifier. So the top-used local variables in all functions + // will be renamed first and will all share the same new identifier. + List<Set<Node>> allSortedLocals = new List<Set<Node>>(); + for (var functionScope in placeholderCollector.functionScopes.values) { + // Add current sorted local identifiers to the whole sorted list + // of all local identifiers for all functions. + List<LocalPlaceholder> currentSortedPlaceholders = + sorted(functionScope.localPlaceholders, + compareBy((LocalPlaceholder ph) => -ph.nodes.length)); + List<Set<Node>> currentSortedNodes = + currentSortedPlaceholders.map((ph) => ph.nodes).toList(); + // Make room in all sorted locals list for new stuff. + while (currentSortedNodes.length > allSortedLocals.length) { + allSortedLocals.add(new Set<Node>()); + } + for (int i = 0; i < currentSortedNodes.length; i++) { + allSortedLocals[i].addAll(currentSortedNodes[i]); + } + } + + // Rename elements, members and locals together based on their usage count, + // otherwise when we rename elements first there will be no good identifiers + // left for members even if they are used often. + String elementRenamer(ElementRenamable elementRenamable) => + renameElement(elementRenamable.element); + String memberRenamer(MemberRenamable memberRenamable) => + generator.generate(forbiddenIdentifiers.contains); + String localRenamer(LocalRenamable localRenamable) => + generator.generate((name) => + allParameterIdentifiers.contains(name) + || forbiddenIdentifiers.contains(name)); + List<Renamable> renamables = []; + placeholderCollector.elementNodes.forEach( + (Element element, Set<Node> nodes) { + renamables.add(new ElementRenamable(element, nodes, elementRenamer)); + }); + placeholderCollector.memberPlaceholders.forEach( + (String memberName, Set<Identifier> identifiers) { + renamables.add( + new MemberRenamable(memberName, identifiers, memberRenamer)); + }); + for (Set<Node> localIdentifiers in allSortedLocals) { + renamables.add(new LocalRenamable(localIdentifiers, localRenamer)); + } + renamables.sort((Renamable renamable1, Renamable renamable2) => + renamable1.compareTo(renamable2)); + for (Renamable renamable in renamables) { + String newName = renamable.rename(); + renameNodes(renamable.nodes, (_) => newName); + } + } else { + // Never rename anything to 'main'. + final usedTopLevelOrMemberIdentifiers = new Set<String>(); + usedTopLevelOrMemberIdentifiers.add('main'); + usedTopLevelOrMemberIdentifiers.addAll(fixedMemberNames); + generateUniqueName = (originalName) { + String newName = conservativeGenerator( + originalName, usedTopLevelOrMemberIdentifiers.contains); + usedTopLevelOrMemberIdentifiers.add(newName); + return newName; + }; + rename = makeRenamer(generateUniqueName); + renameElement = makeElementRenamer(rename, generateUniqueName); + // Rename elements. + sortedForEach(placeholderCollector.elementNodes, + (Element element, Set<Node> nodes) { + renameNodes(nodes, (_) => renameElement(element)); + }); + + // Rename locals. + sortedForEach(placeholderCollector.functionScopes, + (functionElement, functionScope) { + Set<LocalPlaceholder> placeholders = functionScope.localPlaceholders; + Set<String> memberIdentifiers = new Set<String>(); + if (functionElement.getEnclosingClass() != null) { + functionElement.getEnclosingClass().forEachMember( + (enclosingClass, member) { + memberIdentifiers.add(member.name.slowToString()); + }); + } + Set<String> usedLocalIdentifiers = new Set<String>(); + for (LocalPlaceholder placeholder in placeholders) { + String nextId = + conservativeGenerator(placeholder.identifier, (name) => + functionScope.parameterIdentifiers.contains(name) + || usedTopLevelOrMemberIdentifiers.contains(name) + || usedLocalIdentifiers.contains(name) + || memberIdentifiers.contains(name)); + usedLocalIdentifiers.add(nextId); + renameNodes(placeholder.nodes, (_) => nextId); + } + }); + + final usedMemberIdentifiers = new Set<String>.from(fixedMemberNames); + // Do not rename members to top-levels, that allows to avoid renaming + // members to constructors. + usedMemberIdentifiers.addAll(usedTopLevelOrMemberIdentifiers); + placeholderCollector.memberPlaceholders.forEach((identifier, nodes) { + String newIdentifier = conservativeGenerator( + identifier, usedMemberIdentifiers.contains); + renameNodes(nodes, (_) => newIdentifier); + }); + } + + // Rename constructors. + sortedForEach(placeholderCollector.constructorPlaceholders, + (Element constructor, List<ConstructorPlaceholder> placeholders) { + for (ConstructorPlaceholder ph in placeholders) { + renames[ph.node] = + renameConstructor(constructor, ph, rename, renameElement); + } + }); + sortedForEach(placeholderCollector.privateNodes, (library, nodes) { + renameNodes(nodes, (node) => rename(library, node.source.slowToString())); + }); + renameNodes(placeholderCollector.unresolvedNodes, + (_) => generateUniqueName('Unresolved')); + renameNodes(placeholderCollector.nullNodes, (_) => ''); + if (cutDeclarationTypes) { + for (DeclarationTypePlaceholder placeholder in + placeholderCollector.declarationTypePlaceholders) { + renames[placeholder.typeNode] = placeholder.requiresVar ? 'var' : ''; + } + } +} + +/** Always tries to return original identifier name unless it is forbidden. */ +String conservativeGenerator( + String originalName, bool isForbidden(String name)) { + String newName = originalName; + while (isForbidden(newName)) { + newName = 'p_$newName'; + } + return newName; +} + +/** Always tries to generate the most compact identifier. */ +class MinifyingGenerator { + static const String firstCharAlphabet = + r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + static const String otherCharsAlphabet = + r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$'; + int nextIdIndex; + + MinifyingGenerator() : nextIdIndex = 0; + + String generate(bool isForbidden(String name)) { + String newName; + do { + newName = getNextId(); + } while(isForbidden(newName)); + return newName; + } + + /** + * Generates next mini ID with current index and alphabet. + * Advances current index. + * In other words, it converts index to visual representation + * as if digits are given characters. + */ + String getNextId() { + // It's like converting index in decimal to [chars] radix. + int index = nextIdIndex++; + StringBuffer resultBuilder = new StringBuffer(); + if (index < firstCharAlphabet.length) return firstCharAlphabet[index]; + resultBuilder.add(firstCharAlphabet[index % firstCharAlphabet.length]); + index ~/= firstCharAlphabet.length; + int length = otherCharsAlphabet.length; + while (index >= length) { + resultBuilder.add(otherCharsAlphabet[index % length]); + index ~/= length; + } + resultBuilder.add(otherCharsAlphabet[index]); + return resultBuilder.toString(); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart new file mode 100644 index 0000000..c30a2cf --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart
@@ -0,0 +1,292 @@ +// 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. + +part of dart_backend; + +class CloningVisitor implements Visitor<Node> { + final TreeElements originalTreeElements; + final TreeElementMapping cloneTreeElements; + + CloningVisitor(originalTreeElements) + : cloneTreeElements = + new TreeElementMapping(originalTreeElements.currentElement), + this.originalTreeElements = originalTreeElements; + + visit(Node node) { + if (node == null) return null; + final clone = node.accept(this); + + final originalElement = originalTreeElements[node]; + if (originalElement != null) cloneTreeElements[clone] = originalElement; + + final originalType = originalTreeElements.getType(node); + if (originalType != null) cloneTreeElements.setType(clone, originalType); + return clone; + } + + visitBlock(Block node) => new Block(visit(node.statements)); + + visitBreakStatement(BreakStatement node) => new BreakStatement( + visit(node.target), node.keywordToken, node.semicolonToken); + + visitCascade(Cascade node) => new Cascade(visit(node.expression)); + + visitCascadeReceiver(CascadeReceiver node) => new CascadeReceiver( + visit(node.expression), node.cascadeOperator); + + visitCaseMatch(CaseMatch node) => new CaseMatch( + node.caseKeyword, visit(node.expression), node.colonToken); + + visitCatchBlock(CatchBlock node) => new CatchBlock( + visit(node.type), visit(node.formals), visit(node.block), + node.onKeyword, node.catchKeyword); + + visitClassNode(ClassNode node) => new ClassNode( + visit(node.modifiers), visit(node.name), visit(node.typeParameters), + visit(node.superclass), visit(node.interfaces), visit(node.defaultClause), + node.beginToken, node.extendsKeyword, visit(node.body), node.endToken); + + visitConditional(Conditional node) => new Conditional( + visit(node.condition), visit(node.thenExpression), + visit(node.elseExpression), node.questionToken, node.colonToken); + + visitContinueStatement(ContinueStatement node) => new ContinueStatement( + visit(node.target), node.keywordToken, node.semicolonToken); + + visitDoWhile(DoWhile node) => new DoWhile( + visit(node.body), visit(node.condition), + node.doKeyword, node.whileKeyword, node.endToken); + + visitEmptyStatement(EmptyStatement node) => new EmptyStatement( + node.semicolonToken); + + visitExpressionStatement(ExpressionStatement node) => new ExpressionStatement( + visit(node.expression), node.endToken); + + visitFor(For node) => new For( + visit(node.initializer), visit(node.conditionStatement), + visit(node.update), visit(node.body), node.forToken); + + visitForIn(ForIn node) => new ForIn( + visit(node.declaredIdentifier), visit(node.expression), visit(node.body), + node.forToken, node.inToken); + + visitFunctionDeclaration(FunctionDeclaration node) => new FunctionDeclaration( + visit(node.function)); + + rewriteFunctionExpression(FunctionExpression node, Statement body) => + new FunctionExpression( + visit(node.name), visit(node.parameters), body, + visit(node.returnType), visit(node.modifiers), + visit(node.initializers), node.getOrSet); + + visitFunctionExpression(FunctionExpression node) => + rewriteFunctionExpression(node, visit(node.body)); + + visitIdentifier(Identifier node) => new Identifier(node.token); + + visitIf(If node) => new If( + visit(node.condition), visit(node.thenPart), visit(node.elsePart), + node.ifToken, node.elseToken); + + visitLabel(Label node) => new Label(visit(node.identifier), node.colonToken); + + visitLabeledStatement(LabeledStatement node) => new LabeledStatement( + visit(node.labels), visit(node.statement)); + + visitLiteralBool(LiteralBool node) => new LiteralBool( + node.token, node.handler); + + visitLiteralDouble(LiteralDouble node) => new LiteralDouble( + node.token, node.handler); + + visitLiteralInt(LiteralInt node) => new LiteralInt(node.token, node.handler); + + visitLiteralList(LiteralList node) => new LiteralList( + visit(node.typeArguments), visit(node.elements), node.constKeyword); + + visitLiteralMap(LiteralMap node) => new LiteralMap( + visit(node.typeArguments), visit(node.entries), node.constKeyword); + + visitLiteralMapEntry(LiteralMapEntry node) => new LiteralMapEntry( + visit(node.key), node.colonToken, visit(node.value)); + + visitLiteralNull(LiteralNull node) => new LiteralNull(node.token); + + visitLiteralString(LiteralString node) => new LiteralString( + node.token, node.dartString); + + visitMixinApplication(MixinApplication node) => new MixinApplication( + visit(node.superclass), visit(node.mixins)); + + visitNamedMixinApplication(NamedMixinApplication node) => + new NamedMixinApplication(visit(node.name), + visit(node.typeParameters), + visit(node.modifiers), + visit(node.mixinApplication), + visit(node.interfaces), + node.typedefKeyword, + node.endToken); + + visitModifiers(Modifiers node) => new Modifiers(visit(node.nodes)); + + visitNamedArgument(NamedArgument node) => new NamedArgument( + visit(node.name), node.colonToken, visit(node.expression)); + + visitNewExpression(NewExpression node) => new NewExpression( + node.newToken, visit(node.send)); + + rewriteNodeList(NodeList node, Link link) => + new NodeList(node.beginToken, link, node.endToken, node.delimiter); + + visitNodeList(NodeList node) { + // Special case for classes which exist in hierarchy, but not + // in the visitor. + if (node is Prefix) { + return node.nodes.isEmpty ? + new Prefix() : new Prefix.singleton(visit(node.nodes.head)); + } + if (node is Postfix) { + return node.nodes.isEmpty ? + new Postfix() : new Postfix.singleton(visit(node.nodes.head)); + } + LinkBuilder<Node> builder = new LinkBuilder<Node>(); + for (Node n in node.nodes) { + builder.addLast(visit(n)); + } + return rewriteNodeList(node, builder.toLink()); + } + + visitOperator(Operator node) => new Operator(node.token); + + visitParenthesizedExpression(ParenthesizedExpression node) => + new ParenthesizedExpression(visit(node.expression), node.beginToken); + + visitReturn(Return node) => new Return( + node.beginToken, node.endToken, visit(node.expression)); + + visitScriptTag(ScriptTag node) => new ScriptTag( + visit(node.tag), visit(node.argument), + visit(node.prefixIdentifier), visit(node.prefix), + node.beginToken, node.endToken); + + visitSend(Send node) => new Send( + visit(node.receiver), visit(node.selector), visit(node.argumentsNode)); + + visitSendSet(SendSet node) => new SendSet( + visit(node.receiver), visit(node.selector), + visit(node.assignmentOperator), visit(node.argumentsNode)); + + visitStringInterpolation(StringInterpolation node) => + new StringInterpolation(visit(node.string), visit(node.parts)); + + visitStringInterpolationPart(StringInterpolationPart node) => + new StringInterpolationPart(visit(node.expression), visit(node.string)); + + visitStringJuxtaposition(StringJuxtaposition node) => + new StringJuxtaposition(visit(node.first), visit(node.second)); + + visitSwitchCase(SwitchCase node) => new SwitchCase( + visit(node.labelsAndCases), node.defaultKeyword, visit(node.statements), + node.startToken); + + visitSwitchStatement(SwitchStatement node) => new SwitchStatement( + visit(node.parenthesizedExpression), visit(node.cases), + node.switchKeyword); + + visitThrow(Throw node) => new Throw( + visit(node.expression), node.throwToken, node.endToken); + + visitTryStatement(TryStatement node) => new TryStatement( + visit(node.tryBlock), visit(node.catchBlocks), visit(node.finallyBlock), + node.tryKeyword, node.finallyKeyword); + + visitTypeAnnotation(TypeAnnotation node) => new TypeAnnotation( + visit(node.typeName), visit(node.typeArguments)); + + visitTypedef(Typedef node) => new Typedef( + visit(node.returnType), visit(node.name), visit(node.typeParameters), + visit(node.formals), node.typedefKeyword, node.endToken); + + visitTypeVariable(TypeVariable node) => new TypeVariable( + visit(node.name), visit(node.bound)); + + visitVariableDefinitions(VariableDefinitions node) => new VariableDefinitions( + visit(node.type), visit(node.modifiers), visit(node.definitions)); + + visitWhile(While node) => new While( + visit(node.condition), visit(node.body), node.whileKeyword); + + Node visitNode(Node node) { + unimplemented('visitNode', node: node); + } + + Node visitCombinator(Combinator node) { + unimplemented('visitNode', node: node); + } + + Node visitExport(Export node) { + unimplemented('visitNode', node: node); + } + + Node visitExpression(Expression node) { + unimplemented('visitNode', node: node); + } + + Node visitGotoStatement(GotoStatement node) { + unimplemented('visitNode', node: node); + } + + Node visitImport(Import node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryDependency(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryName(LibraryName node) { + unimplemented('visitNode', node: node); + } + + Node visitLibraryTag(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + Node visitLiteral(Literal node) { + unimplemented('visitNode', node: node); + } + + Node visitLoop(Loop node) { + unimplemented('visitNode', node: node); + } + + Node visitPart(Part node) { + unimplemented('visitNode', node: node); + } + + Node visitPartOf(PartOf node) { + unimplemented('visitNode', node: node); + } + + Node visitPostfix(Postfix node) { + unimplemented('visitNode', node: node); + } + + Node visitPrefix(Prefix node) { + unimplemented('visitNode', node: node); + } + + Node visitStatement(Statement node) { + unimplemented('visitNode', node: node); + } + + Node visitStringNode(StringNode node) { + unimplemented('visitNode', node: node); + } + + unimplemented(String message, {Node node}) { + throw message; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart new file mode 100644 index 0000000..7c78bd5 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart
@@ -0,0 +1,806 @@ +// 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. + +library dart_types; + +import 'dart2jslib.dart' show Compiler, invariant, Script, Message; +import 'elements/modelx.dart' show VoidElementX, LibraryElementX; +import 'elements/elements.dart'; +import 'scanner/scannerlib.dart' show SourceString; +import 'util/util.dart' show Link, LinkBuilder; + +class TypeKind { + final String id; + + const TypeKind(String this.id); + + static const TypeKind FUNCTION = const TypeKind('function'); + static const TypeKind INTERFACE = const TypeKind('interface'); + static const TypeKind STATEMENT = const TypeKind('statement'); + static const TypeKind TYPEDEF = const TypeKind('typedef'); + static const TypeKind TYPE_VARIABLE = const TypeKind('type variable'); + static const TypeKind MALFORMED_TYPE = const TypeKind('malformed'); + static const TypeKind VOID = const TypeKind('void'); + + String toString() => id; +} + +abstract class DartType { + SourceString get name; + + TypeKind get kind; + + const DartType(); + + /** + * Returns the [Element] which declared this type. + * + * This can be [ClassElement] for classes, [TypedefElement] for typedefs, + * [TypeVariableElement] for type variables and [FunctionElement] for + * function types. + * + * Invariant: [element] must be a declaration element. + */ + Element get element; + + /** + * Performs the substitution [: [arguments[i]/parameters[i]]this :]. + * + * The notation is known from this lambda calculus rule: + * + * (lambda x.e0)e1 -> [e1/x]e0. + * + * See [TypeVariableType] for a motivation for this method. + * + * Invariant: There must be the same number of [arguments] and [parameters]. + */ + DartType subst(Link<DartType> arguments, Link<DartType> parameters); + + /** + * Returns the unaliased type of this type. + * + * The unaliased type of a typedef'd type is the unaliased type to which its + * name is bound. The unaliased version of any other type is the type itself. + * + * For example, the unaliased type of [: typedef A Func<A,B>(B b) :] is the + * function type [: (B) -> A :] and the unaliased type of + * [: Func<int,String> :] is the function type [: (String) -> int :]. + */ + DartType unalias(Compiler compiler); + + /** + * A type is malformed if it is itself a malformed type or contains a + * malformed type. + */ + bool get isMalformed => false; + + /** + * Calls [f] with each [MalformedType] within this type. + * + * If [f] returns [: false :], the traversal stops prematurely. + * + * [forEachMalformedType] returns [: false :] if the traversal was stopped + * prematurely. + */ + bool forEachMalformedType(bool f(MalformedType type)) => true; + + bool operator ==(other); + + /** + * Is [: true :] if this type has no explict type arguments. + */ + bool get isRaw => true; + + DartType asRaw() => this; +} + +/** + * Represents a type variable, that is the type parameters of a class type. + * + * For example, in [: class Array<E> { ... } :], E is a type variable. + * + * Each class should have its own unique type variables, one for each type + * parameter. A class with type parameters is said to be parameterized or + * generic. + * + * Non-static members, constructors, and factories of generic + * class/interface can refer to type variables of the current class + * (not of supertypes). + * + * When using a generic type, also known as an application or + * instantiation of the type, the actual type arguments should be + * substituted for the type variables in the class declaration. + * + * For example, given a box, [: class Box<T> { T value; } :], the + * type of the expression [: new Box<String>().value :] is + * [: String :] because we must substitute [: String :] for the + * the type variable [: T :]. + */ +class TypeVariableType extends DartType { + final TypeVariableElement element; + + TypeVariableType(this.element); + + TypeKind get kind => TypeKind.TYPE_VARIABLE; + + SourceString get name => element.name; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + Link<DartType> parameterLink = parameters; + Link<DartType> argumentLink = arguments; + while (!argumentLink.isEmpty && !parameterLink.isEmpty) { + TypeVariableType parameter = parameterLink.head; + DartType argument = argumentLink.head; + if (parameter == this) { + assert(argumentLink.tail.isEmpty == parameterLink.tail.isEmpty); + return argument; + } + parameterLink = parameterLink.tail; + argumentLink = argumentLink.tail; + } + assert(argumentLink.isEmpty && parameterLink.isEmpty); + // The type variable was not substituted. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 17 * element.hashCode; + + bool operator ==(other) { + if (other is !TypeVariableType) return false; + return identical(other.element, element); + } + + String toString() => name.slowToString(); +} + +/** + * A statement type tracks whether a statement returns or may return. + */ +class StatementType extends DartType { + final String stringName; + + Element get element => null; + + TypeKind get kind => TypeKind.STATEMENT; + + SourceString get name => new SourceString(stringName); + + const StatementType(this.stringName); + + static const RETURNING = const StatementType('<returning>'); + static const NOT_RETURNING = const StatementType('<not returning>'); + static const MAYBE_RETURNING = const StatementType('<maybe returning>'); + + /** Combine the information about two control-flow edges that are joined. */ + StatementType join(StatementType other) { + return (identical(this, other)) ? this : MAYBE_RETURNING; + } + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Statement types are not substitutable. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 17 * stringName.hashCode; + + bool operator ==(other) { + if (other is !StatementType) return false; + return other.stringName == stringName; + } + + String toString() => stringName; +} + +class VoidType extends DartType { + const VoidType(this.element); + + TypeKind get kind => TypeKind.VOID; + + SourceString get name => element.name; + + final Element element; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Void cannot be substituted. + return this; + } + + DartType unalias(Compiler compiler) => this; + + int get hashCode => 1729; + + bool operator ==(other) => other is VoidType; + + String toString() => name.slowToString(); +} + +class MalformedType extends DartType { + final ErroneousElement element; + + /** + * [declaredType] holds the type which the user wrote in code. + * + * For instance, for a resolved but malformed type like [: Map<String> :] the + * [declaredType] is [: Map<String> :] whereas for an unresolved type + */ + final DartType userProvidedBadType; + + /** + * Type arguments for the malformed typed, if these cannot fit in the + * [declaredType]. + * + * This field is for instance used for [: dynamic<int> :] and [: T<int> :] + * where [: T :] is a type variable, in which case [declaredType] holds + * [: dynamic :] and [: T :], respectively, or for [: X<int> :] where [: X :] + * is not resolved or does not imply a type. + */ + final Link<DartType> typeArguments; + + MalformedType(this.element, this.userProvidedBadType, + [this.typeArguments = null]); + + TypeKind get kind => TypeKind.MALFORMED_TYPE; + + SourceString get name => element.name; + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + // Malformed types are not substitutable. + return this; + } + + bool get isMalformed => true; + + bool forEachMalformedType(bool f(MalformedType type)) => f(this); + + DartType unalias(Compiler compiler) => this; + + String toString() { + var sb = new StringBuffer(); + if (typeArguments != null) { + if (userProvidedBadType != null) { + sb.add(userProvidedBadType.name.slowToString()); + } else { + sb.add(element.name.slowToString()); + } + if (!typeArguments.isEmpty) { + sb.add('<'); + typeArguments.printOn(sb, ', '); + sb.add('>'); + } + } else { + sb.add(userProvidedBadType.toString()); + } + return sb.toString(); + } +} + +bool hasMalformed(Link<DartType> types) { + for (DartType typeArgument in types) { + if (typeArgument.isMalformed) { + return true; + } + } + return false; +} + +abstract class GenericType extends DartType { + final Link<DartType> typeArguments; + final bool isMalformed; + + GenericType(Link<DartType> this.typeArguments, bool this.isMalformed); + + TypeDeclarationElement get element; + + /// Creates a new instance of this type using the provided type arguments. + GenericType _createType(Link<DartType> newTypeArguments); + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (typeArguments.isEmpty) { + // Return fast on non-generic types. + return this; + } + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + Link<DartType> newTypeArguments = + Types.substTypes(typeArguments, arguments, parameters); + if (!identical(typeArguments, newTypeArguments)) { + // Create a new type only if necessary. + return _createType(newTypeArguments); + } + return this; + } + + bool forEachMalformedType(bool f(MalformedType type)) { + for (DartType typeArgument in typeArguments) { + if (!typeArgument.forEachMalformedType(f)) { + return false; + } + } + return true; + } + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add(name.slowToString()); + if (!isRaw) { + sb.add('<'); + typeArguments.printOn(sb, ', '); + sb.add('>'); + } + return sb.toString(); + } + + int get hashCode { + int hash = element.hashCode; + for (Link<DartType> arguments = this.typeArguments; + !arguments.isEmpty; + arguments = arguments.tail) { + int argumentHash = arguments.head != null ? arguments.head.hashCode : 0; + hash = 17 * hash + 3 * argumentHash; + } + return hash; + } + + bool operator ==(other) { + if (!identical(element, other.element)) return false; + return typeArguments == other.typeArguments; + } + + bool get isRaw => typeArguments.isEmpty || identical(this, element.rawType); + + GenericType asRaw() => element.rawType; +} + +// TODO(johnniwinther): Add common supertype for InterfaceType and TypedefType. +class InterfaceType extends GenericType { + final ClassElement element; + + InterfaceType(this.element, + [Link<DartType> typeArguments = const Link<DartType>()]) + : super(typeArguments, hasMalformed(typeArguments)) { + assert(invariant(element, element.isDeclaration)); + } + + TypeKind get kind => TypeKind.INTERFACE; + + SourceString get name => element.name; + + InterfaceType _createType(Link<DartType> newTypeArguments) { + return new InterfaceType(element, newTypeArguments); + } + + /** + * Returns the type as an instance of class [other], if possible, null + * otherwise. + */ + DartType asInstanceOf(ClassElement other) { + if (element == other) return this; + for (InterfaceType supertype in element.allSupertypes) { + ClassElement superclass = supertype.element; + if (superclass == other) { + Link<DartType> arguments = Types.substTypes(supertype.typeArguments, + typeArguments, + element.typeVariables); + return new InterfaceType(superclass, arguments); + } + } + return null; + } + + DartType unalias(Compiler compiler) => this; + + bool operator ==(other) { + if (other is !InterfaceType) return false; + return super == other; + } + + InterfaceType asRaw() => super.asRaw(); +} + +class FunctionType extends DartType { + final Element element; + final DartType returnType; + final Link<DartType> parameterTypes; + final Link<DartType> optionalParameterTypes; + + /** + * The names of the named parameters ordered lexicographically. + */ + final Link<SourceString> namedParameters; + + /** + * The types of the named parameters in the order corresponding to the + * [namedParameters]. + */ + final Link<DartType> namedParameterTypes; + final bool isMalformed; + + factory FunctionType(Element element, + DartType returnType, + Link<DartType> parameterTypes, + Link<DartType> optionalParameterTypes, + Link<SourceString> namedParameters, + Link<DartType> namedParameterTypes) { + // Compute [isMalformed] eagerly since it is faster than a lazy computation + // and since [isMalformed] most likely will be accessed in [Types.isSubtype] + // anyway. + bool isMalformed = returnType != null && + returnType.isMalformed || + hasMalformed(parameterTypes) || + hasMalformed(optionalParameterTypes) || + hasMalformed(namedParameterTypes); + return new FunctionType.internal(element, + returnType, + parameterTypes, + optionalParameterTypes, + namedParameters, + namedParameterTypes, + isMalformed); + } + + FunctionType.internal(Element this.element, + DartType this.returnType, + Link<DartType> this.parameterTypes, + Link<DartType> this.optionalParameterTypes, + Link<SourceString> this.namedParameters, + Link<DartType> this.namedParameterTypes, + bool this.isMalformed) { + assert(element == null || invariant(element, element.isDeclaration)); + // Assert that optional and named parameters are not used at the same time. + assert(optionalParameterTypes.isEmpty || namedParameterTypes.isEmpty); + assert(namedParameters.slowLength() == namedParameterTypes.slowLength()); + } + + TypeKind get kind => TypeKind.FUNCTION; + + DartType getNamedParameterType(SourceString name) { + Link<SourceString> namedParameter = namedParameters; + Link<DartType> namedParameterType = namedParameterTypes; + while (!namedParameter.isEmpty && !namedParameterType.isEmpty) { + if (namedParameter.head == name) { + return namedParameterType.head; + } + namedParameter = namedParameter.tail; + namedParameterType = namedParameterType.tail; + } + return null; + } + + DartType subst(Link<DartType> arguments, Link<DartType> parameters) { + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + var newReturnType = returnType.subst(arguments, parameters); + bool changed = !identical(newReturnType, returnType); + var newParameterTypes = + Types.substTypes(parameterTypes, arguments, parameters); + var newOptionalParameterTypes = + Types.substTypes(optionalParameterTypes, arguments, parameters); + var newNamedParameterTypes = + Types.substTypes(namedParameterTypes, arguments, parameters); + if (!changed && + (!identical(parameterTypes, newParameterTypes) || + !identical(optionalParameterTypes, newOptionalParameterTypes) || + !identical(namedParameterTypes, newNamedParameterTypes))) { + changed = true; + } + if (changed) { + // Create a new type only if necessary. + return new FunctionType(element, + newReturnType, + newParameterTypes, + newOptionalParameterTypes, + namedParameters, + newNamedParameterTypes); + } + return this; + } + + bool forEachMalformedType(bool f(MalformedType type)) { + if (!returnType.forEachMalformedType(f)) { + return false; + } + for (DartType parameterType in parameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + for (DartType parameterType in optionalParameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + for (DartType parameterType in namedParameterTypes) { + if (!parameterType.forEachMalformedType(f)) { + return false; + } + } + return true; + } + + DartType unalias(Compiler compiler) => this; + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add('('); + parameterTypes.printOn(sb, ', '); + bool first = parameterTypes.isEmpty; + if (!optionalParameterTypes.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add('['); + optionalParameterTypes.printOn(sb, ', '); + sb.add(']'); + first = false; + } + if (!namedParameterTypes.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add('{'); + Link<SourceString> namedParameter = namedParameters; + Link<DartType> namedParameterType = namedParameterTypes; + first = true; + while (!namedParameter.isEmpty && !namedParameterType.isEmpty) { + if (!first) { + sb.add(', '); + } + sb.add(namedParameterType.head); + sb.add(' '); + sb.add(namedParameter.head.slowToString()); + namedParameter = namedParameter.tail; + namedParameterType = namedParameterType.tail; + first = false; + } + sb.add('}'); + } + sb.add(') -> ${returnType}'); + return sb.toString(); + } + + SourceString get name => const SourceString('Function'); + + int computeArity() { + int arity = 0; + parameterTypes.forEach((_) { arity++; }); + return arity; + } + + int get hashCode { + int hash = 17 * element.hashCode + 3 * returnType.hashCode; + for (DartType parameter in parameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + for (DartType parameter in optionalParameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + for (SourceString name in namedParameters) { + hash = 17 * hash + 3 * name.hashCode; + } + for (DartType parameter in namedParameterTypes) { + hash = 17 * hash + 3 * parameter.hashCode; + } + return hash; + } + + bool operator ==(other) { + if (other is !FunctionType) return false; + return returnType == other.returnType + && parameterTypes == other.parameterTypes + && optionalParameterTypes == other.optionalParameterTypes + && namedParameters == other.namedParameters + && namedParameterTypes == other.namedParameterTypes; + } +} + +class TypedefType extends GenericType { + final TypedefElement element; + + TypedefType(this.element, + [Link<DartType> typeArguments = const Link<DartType>()]) + : super(typeArguments, hasMalformed(typeArguments)); + + TypedefType _createType(Link<DartType> newTypeArguments) { + return new TypedefType(element, newTypeArguments); + } + + TypeKind get kind => TypeKind.TYPEDEF; + + SourceString get name => element.name; + + DartType unalias(Compiler compiler) { + // TODO(ahe): This should be [ensureResolved]. + compiler.resolveTypedef(element); + DartType definition = element.alias.unalias(compiler); + TypedefType declaration = element.computeType(compiler); + return definition.subst(typeArguments, declaration.typeArguments); + } + + bool operator ==(other) { + if (other is !TypedefType) return false; + return super == other; + } + + TypedefType asRaw() => super.asRaw(); +} + +/** + * Special type to hold the [dynamic] type. Used for correctly returning + * 'dynamic' on [toString]. + */ +class DynamicType extends InterfaceType { + DynamicType(ClassElement element) : super(element); + + SourceString get name => const SourceString('dynamic'); +} + +class Types { + final Compiler compiler; + // TODO(karlklose): should we have a class Void? + final VoidType voidType; + final DynamicType dynamicType; + + factory Types(Compiler compiler, ClassElement dynamicElement) { + LibraryElement library = new LibraryElementX(new Script(null, null)); + VoidType voidType = new VoidType(new VoidElementX(library)); + DynamicType dynamicType = new DynamicType(dynamicElement); + dynamicElement.rawType = dynamicElement.thisType = dynamicType; + return new Types.internal(compiler, voidType, dynamicType); + } + + Types.internal(this.compiler, this.voidType, this.dynamicType); + + /** Returns true if t is a subtype of s */ + bool isSubtype(DartType t, DartType s) { + if (identical(t, s) || + identical(t, dynamicType) || + identical(s, dynamicType) || + t.isMalformed || + s.isMalformed || + identical(s.element, compiler.objectClass) || + identical(t.element, compiler.nullClass)) { + return true; + } + t = t.unalias(compiler); + s = s.unalias(compiler); + + if (t is VoidType) { + return false; + } else if (t is InterfaceType) { + if (s is !InterfaceType) return false; + ClassElement tc = t.element; + if (identical(tc, s.element)) return true; + for (Link<DartType> supertypes = tc.allSupertypes; + supertypes != null && !supertypes.isEmpty; + supertypes = supertypes.tail) { + DartType supertype = supertypes.head; + if (identical(supertype.element, s.element)) return true; + } + return false; + } else if (t is FunctionType) { + if (identical(s.element, compiler.functionClass)) return true; + if (s is !FunctionType) return false; + FunctionType tf = t; + FunctionType sf = s; + Link<DartType> tps = tf.parameterTypes; + Link<DartType> sps = sf.parameterTypes; + while (!tps.isEmpty && !sps.isEmpty) { + if (!isAssignable(tps.head, sps.head)) return false; + tps = tps.tail; + sps = sps.tail; + } + if (!tps.isEmpty || !sps.isEmpty) return false; + if (!isAssignable(sf.returnType, tf.returnType)) return false; + if (!sf.namedParameters.isEmpty) { + // Since named parameters are globally ordered we can determine the + // subset relation with a linear search for [:sf.NamedParameters:] + // within [:tf.NamedParameters:]. + Link<SourceString> tNames = tf.namedParameters; + Link<DartType> tTypes = tf.namedParameterTypes; + Link<SourceString> sNames = sf.namedParameters; + Link<DartType> sTypes = sf.namedParameterTypes; + while (!tNames.isEmpty && !sNames.isEmpty) { + if (sNames.head == tNames.head) { + if (!isAssignable(tTypes.head, sTypes.head)) return false; + + sNames = sNames.tail; + sTypes = sTypes.tail; + } + tNames = tNames.tail; + tTypes = tTypes.tail; + } + if (!sNames.isEmpty) { + // We didn't find all names. + return false; + } + } + if (!sf.optionalParameterTypes.isEmpty) { + Link<DartType> tOptionalParameterType = tf.optionalParameterTypes; + Link<DartType> sOptionalParameterType = sf.optionalParameterTypes; + while (!tOptionalParameterType.isEmpty && + !sOptionalParameterType.isEmpty) { + if (!isAssignable(tOptionalParameterType.head, + sOptionalParameterType.head)) { + return false; + } + sOptionalParameterType = sOptionalParameterType.tail; + tOptionalParameterType = tOptionalParameterType.tail; + } + if (!sOptionalParameterType.isEmpty) { + // We didn't find enough optional parameters. + return false; + } + } + return true; + } else if (t is TypeVariableType) { + if (s is !TypeVariableType) return false; + return (identical(t.element, s.element)); + } else { + throw 'internal error: unknown type kind'; + } + } + + bool isAssignable(DartType r, DartType s) { + return isSubtype(r, s) || isSubtype(s, r); + } + + + /** + * Helper method for performing substitution of a linked list of types. + * + * If no types are changed by the substitution, the [types] is returned + * instead of a newly created linked list. + */ + static Link<DartType> substTypes(Link<DartType> types, + Link<DartType> arguments, + Link<DartType> parameters) { + bool changed = false; + var builder = new LinkBuilder<DartType>(); + Link<DartType> typeLink = types; + while (!typeLink.isEmpty) { + var argument = typeLink.head.subst(arguments, parameters); + if (!changed && !identical(argument, typeLink.head)) { + changed = true; + } + builder.addLast(argument); + typeLink = typeLink.tail; + } + if (changed) { + // Create a new link only if necessary. + return builder.toLink(); + } + return types; + } + + /** + * Combine error messages in a malformed type to a single message string. + */ + static String fetchReasonsFromMalformedType(DartType type) { + // TODO(johnniwinther): Figure out how to produce good error message in face + // of multiple errors, and how to ensure non-localized error messages. + var reasons = new List<String>(); + type.forEachMalformedType((MalformedType malformedType) { + ErroneousElement error = malformedType.element; + Message message = error.messageKind.message(error.messageArguments); + reasons.add(message.toString()); + return true; + }); + return Strings.join(reasons, ', '); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart b/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart new file mode 100644 index 0000000..1d0c2b9 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart
@@ -0,0 +1,28 @@ +// 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. + +part of dart2js; + +abstract class DiagnosticListener { + // TODO(karlklose): replace cancel with better error reporting mechanism. + void cancel(String reason, {node, token, instruction, element}); + // TODO(karlklose): rename log to something like reportInfo. + void log(message); + // TODO(karlklose): add reportWarning and reportError to this interface. + + void internalErrorOnElement(Element element, String message); + void internalError(String message, + {Node node, Token token, HInstruction instruction, + Element element}); + + SourceSpan spanFromSpannable(Spannable node, [Uri uri]); + + void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind); + + // TODO(ahe): Rename to reportError when that method has been removed. + void reportErrorCode(Spannable node, MessageKind errorCode, [Map arguments]); + + /// Returns true if a diagnostic was emitted. + bool onDeprecatedFeature(Spannable span, String feature); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart b/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart new file mode 100644 index 0000000..2798f95 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart
@@ -0,0 +1,792 @@ +// 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. + +library elements; + +import 'dart:uri'; + +import 'modelx.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../resolution/resolution.dart'; + +import '../dart2jslib.dart' show InterfaceType, + DartType, + TypeVariableType, + TypedefType, + MessageKind, + DiagnosticListener, + Script, + FunctionType, + SourceString, + Selector, + Constant, + Compiler; + +import '../dart_types.dart'; + +import '../scanner/scannerlib.dart' show Token, + isUserDefinableOperator, + isMinusOperator; + +const int STATE_NOT_STARTED = 0; +const int STATE_STARTED = 1; +const int STATE_DONE = 2; + +class ElementCategory { + /** + * Represents things that we don't expect to find when looking in a + * scope. + */ + static const int NONE = 0; + + /** Field, parameter, or variable. */ + static const int VARIABLE = 1; + + /** Function, method, or foreign function. */ + static const int FUNCTION = 2; + + static const int CLASS = 4; + + static const int PREFIX = 8; + + /** Constructor or factory. */ + static const int FACTORY = 16; + + static const int ALIAS = 32; + + static const int SUPER = 64; + + /** Type variable */ + static const int TYPE_VARIABLE = 128; + + static const int IMPLIES_TYPE = CLASS | ALIAS | TYPE_VARIABLE; +} + +class ElementKind { + final String id; + final int category; + + const ElementKind(String this.id, this.category); + + static const ElementKind VARIABLE = + const ElementKind('variable', ElementCategory.VARIABLE); + static const ElementKind PARAMETER = + const ElementKind('parameter', ElementCategory.VARIABLE); + // Parameters in constructors that directly initialize fields. For example: + // [:A(this.field):]. + static const ElementKind FIELD_PARAMETER = + const ElementKind('field_parameter', ElementCategory.VARIABLE); + static const ElementKind FUNCTION = + const ElementKind('function', ElementCategory.FUNCTION); + static const ElementKind CLASS = + const ElementKind('class', ElementCategory.CLASS); + static const ElementKind GENERATIVE_CONSTRUCTOR = + const ElementKind('generative_constructor', ElementCategory.FACTORY); + static const ElementKind FIELD = + const ElementKind('field', ElementCategory.VARIABLE); + static const ElementKind VARIABLE_LIST = + const ElementKind('variable_list', ElementCategory.NONE); + static const ElementKind FIELD_LIST = + const ElementKind('field_list', ElementCategory.NONE); + static const ElementKind GENERATIVE_CONSTRUCTOR_BODY = + const ElementKind('generative_constructor_body', ElementCategory.NONE); + static const ElementKind COMPILATION_UNIT = + const ElementKind('compilation_unit', ElementCategory.NONE); + static const ElementKind GETTER = + const ElementKind('getter', ElementCategory.NONE); + static const ElementKind SETTER = + const ElementKind('setter', ElementCategory.NONE); + static const ElementKind TYPE_VARIABLE = + const ElementKind('type_variable', ElementCategory.TYPE_VARIABLE); + static const ElementKind ABSTRACT_FIELD = + const ElementKind('abstract_field', ElementCategory.VARIABLE); + static const ElementKind LIBRARY = + const ElementKind('library', ElementCategory.NONE); + static const ElementKind PREFIX = + const ElementKind('prefix', ElementCategory.PREFIX); + static const ElementKind TYPEDEF = + const ElementKind('typedef', ElementCategory.ALIAS); + + static const ElementKind STATEMENT = + const ElementKind('statement', ElementCategory.NONE); + static const ElementKind LABEL = + const ElementKind('label', ElementCategory.NONE); + static const ElementKind VOID = + const ElementKind('void', ElementCategory.NONE); + + static const ElementKind AMBIGUOUS = + const ElementKind('ambiguous', ElementCategory.NONE); + static const ElementKind ERROR = + const ElementKind('error', ElementCategory.NONE); + static const ElementKind MALFORMED_TYPE = + const ElementKind('malformed', ElementCategory.NONE); + + toString() => id; +} + +abstract class Element implements Spannable { + SourceString get name; + ElementKind get kind; + Modifiers get modifiers; + Element get enclosingElement; + Link<MetadataAnnotation> get metadata; + + Node parseNode(DiagnosticListener listener); + DartType computeType(Compiler compiler); + + bool isFunction(); + bool isConstructor(); + bool isClosure(); + bool isMember(); + bool isInstanceMember(); + bool isInStaticMember(); + + bool isFactoryConstructor(); + bool isGenerativeConstructor(); + bool isGenerativeConstructorBody(); + bool isCompilationUnit(); + bool isClass(); + bool isPrefix(); + bool isVariable(); + bool isParameter(); + bool isStatement(); + bool isTypedef(); + bool isTypeVariable(); + bool isField(); + bool isAbstractField(); + bool isGetter(); + bool isSetter(); + bool isAccessor(); + bool isLibrary(); + bool isErroneous(); + bool isAmbiguous(); + + bool isTopLevel(); + bool isAssignable(); + bool isNative(); + + bool impliesType(); + + Token position(); + + CompilationUnitElement getCompilationUnit(); + LibraryElement getLibrary(); + LibraryElement getImplementationLibrary(); + ClassElement getEnclosingClass(); + Element getEnclosingClassOrCompilationUnit(); + Element getEnclosingMember(); + Element getOutermostEnclosingMemberOrTopLevel(); + + FunctionElement asFunctionElement(); + + bool get isPatched; + bool get isPatch; + bool get isImplementation; + bool get isDeclaration; + bool get isSynthesized; + + Element get implementation; + Element get declaration; + Element get patch; + Element get origin; + + bool hasFixedBackendName(); + String fixedBackendName(); + + bool isAbstract(Compiler compiler); + bool isForeign(Compiler compiler); + + void addMetadata(MetadataAnnotation annotation); + void setNative(String name); + void setFixedBackendName(String name); + + Scope buildScope(); +} + +class Elements { + static bool isUnresolved(Element e) { + return e == null || e.isErroneous(); + } + static bool isErroneousElement(Element e) => e != null && e.isErroneous(); + + static bool isClass(Element e) => e != null && e.kind == ElementKind.CLASS; + static bool isTypedef(Element e) { + return e != null && e.kind == ElementKind.TYPEDEF; + } + + static bool isLocal(Element element) { + return !Elements.isUnresolved(element) + && !element.isInstanceMember() + && !isStaticOrTopLevelField(element) + && !isStaticOrTopLevelFunction(element) + && (identical(element.kind, ElementKind.VARIABLE) || + identical(element.kind, ElementKind.PARAMETER) || + identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceField(Element element) { + return !Elements.isUnresolved(element) + && element.isInstanceMember() + && (identical(element.kind, ElementKind.FIELD) + || identical(element.kind, ElementKind.GETTER) + || identical(element.kind, ElementKind.SETTER)); + } + + static bool isStaticOrTopLevel(Element element) { + // TODO(ager): This should not be necessary when patch support has + // been reworked. + if (!Elements.isUnresolved(element) + && element.modifiers.isStatic()) { + return true; + } + return !Elements.isUnresolved(element) + && !element.isInstanceMember() + && !element.isPrefix() + && element.enclosingElement != null + && (element.enclosingElement.kind == ElementKind.CLASS || + element.enclosingElement.kind == ElementKind.COMPILATION_UNIT || + element.enclosingElement.kind == ElementKind.LIBRARY); + } + + static bool isStaticOrTopLevelField(Element element) { + return isStaticOrTopLevel(element) + && (identical(element.kind, ElementKind.FIELD) + || identical(element.kind, ElementKind.GETTER) + || identical(element.kind, ElementKind.SETTER)); + } + + static bool isStaticOrTopLevelFunction(Element element) { + return isStaticOrTopLevel(element) + && (identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceMethod(Element element) { + return !Elements.isUnresolved(element) + && element.isInstanceMember() + && (identical(element.kind, ElementKind.FUNCTION)); + } + + static bool isInstanceSend(Send send, TreeElements elements) { + Element element = elements[send]; + if (element == null) return !isClosureSend(send, element); + return isInstanceMethod(element) || isInstanceField(element); + } + + static bool isClosureSend(Send send, Element element) { + if (send.isPropertyAccess) return false; + if (send.receiver != null) return false; + // (o)() or foo()(). + if (element == null && send.selector.asIdentifier() == null) return true; + if (element == null) return false; + // foo() with foo a local or a parameter. + return isLocal(element); + } + + static SourceString constructConstructorName(SourceString receiver, + SourceString selector) { + String r = receiver.slowToString(); + String s = selector.slowToString(); + return new SourceString('$r\$$s'); + } + + static SourceString deconstructConstructorName(SourceString name, + ClassElement holder) { + String r = '${holder.name.slowToString()}\$'; + String s = name.slowToString(); + if (s.startsWith(r)) { + return new SourceString(s.substring(r.length)); + } + return null; + } + + /** + * Map an operator-name to a valid Dart identifier. + * + * For non-operator names, this metod just returns its input. + * + * The results returned from this method are guaranteed to be valid + * JavaScript identifers, except it may include reserved words for + * non-operator names. + */ + static SourceString operatorNameToIdentifier(SourceString name) { + if (name == null) return null; + String value = name.stringValue; + if (value == null) { + return name; + } else if (identical(value, '==')) { + return const SourceString(r'operator$eq'); + } else if (identical(value, '~')) { + return const SourceString(r'operator$not'); + } else if (identical(value, '[]')) { + return const SourceString(r'operator$index'); + } else if (identical(value, '[]=')) { + return const SourceString(r'operator$indexSet'); + } else if (identical(value, '*')) { + return const SourceString(r'operator$mul'); + } else if (identical(value, '/')) { + return const SourceString(r'operator$div'); + } else if (identical(value, '%')) { + return const SourceString(r'operator$mod'); + } else if (identical(value, '~/')) { + return const SourceString(r'operator$tdiv'); + } else if (identical(value, '+')) { + return const SourceString(r'operator$add'); + } else if (identical(value, '<<')) { + return const SourceString(r'operator$shl'); + } else if (identical(value, '>>')) { + return const SourceString(r'operator$shr'); + } else if (identical(value, '>=')) { + return const SourceString(r'operator$ge'); + } else if (identical(value, '>')) { + return const SourceString(r'operator$gt'); + } else if (identical(value, '<=')) { + return const SourceString(r'operator$le'); + } else if (identical(value, '<')) { + return const SourceString(r'operator$lt'); + } else if (identical(value, '&')) { + return const SourceString(r'operator$and'); + } else if (identical(value, '^')) { + return const SourceString(r'operator$xor'); + } else if (identical(value, '|')) { + return const SourceString(r'operator$or'); + } else if (identical(value, '-')) { + return const SourceString(r'operator$sub'); + } else if (identical(value, 'unary-')) { + return const SourceString(r'operator$negate'); + } else { + return name; + } + } + + static SourceString constructOperatorNameOrNull(SourceString op, + bool isUnary) { + String value = op.stringValue; + if (isMinusOperator(value)) { + return isUnary ? const SourceString('unary-') : op; + } else if (isUserDefinableOperator(value)) { + return op; + } else { + return null; + } + } + + static SourceString constructOperatorName(SourceString op, bool isUnary) { + SourceString operatorName = constructOperatorNameOrNull(op, isUnary); + if (operatorName == null) throw 'Unhandled operator: ${op.slowToString()}'; + else return operatorName; + } + + static SourceString mapToUserOperatorOrNull(SourceString op) { + String value = op.stringValue; + + if (identical(value, '!=')) return const SourceString('=='); + if (identical(value, '*=')) return const SourceString('*'); + if (identical(value, '/=')) return const SourceString('/'); + if (identical(value, '%=')) return const SourceString('%'); + if (identical(value, '~/=')) return const SourceString('~/'); + if (identical(value, '+=')) return const SourceString('+'); + if (identical(value, '-=')) return const SourceString('-'); + if (identical(value, '<<=')) return const SourceString('<<'); + if (identical(value, '>>=')) return const SourceString('>>'); + if (identical(value, '&=')) return const SourceString('&'); + if (identical(value, '^=')) return const SourceString('^'); + if (identical(value, '|=')) return const SourceString('|'); + + return null; + } + + static SourceString mapToUserOperator(SourceString op) { + SourceString userOperator = mapToUserOperatorOrNull(op); + if (userOperator == null) throw 'Unhandled operator: ${op.slowToString()}'; + else return userOperator; + } + + static bool isNumberOrStringSupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return (element == coreLibrary.find(const SourceString('Comparable'))); + } + + static bool isStringOnlySupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return element == coreLibrary.find(const SourceString('Pattern')); + } + + static bool isListSupertype(Element element, Compiler compiler) { + LibraryElement coreLibrary = compiler.coreLibrary; + return (element == coreLibrary.find(const SourceString('Collection'))) + || (element == coreLibrary.find(const SourceString('Iterable'))); + } + + /// A `compareTo` function that places [Element]s in a consistent order based + /// on the source code order. + static int compareByPosition(Element a, Element b) { + CompilationUnitElement unitA = a.getCompilationUnit(); + CompilationUnitElement unitB = b.getCompilationUnit(); + if (!identical(unitA, unitB)) { + int r = unitA.script.uri.path.compareTo(unitB.script.uri.path); + if (r != 0) return r; + } + Token positionA = a.position(); + Token positionB = b.position(); + int r = positionA.charOffset.compareTo(positionB.charOffset); + if (r != 0) return r; + r = a.name.slowToString().compareTo(b.name.slowToString()); + if (r != 0) return r; + // Same file, position and name. If this happens, we should find out why + // and make the order total and independent of hashCode. + return a.hashCode.compareTo(b.hashCode); + } + + static List<Element> sortedByPosition(Iterable<Element> elements) { + return elements.toList()..sort(compareByPosition); + } +} + +abstract class ErroneousElement extends Element implements FunctionElement { + MessageKind get messageKind; + Map get messageArguments; +} + +abstract class AmbiguousElement extends Element { + MessageKind get messageKind; + Map get messageArguments; + Element get existingElement; + Element get newElement; +} + +// TODO(kasperl): This probably shouldn't be called an element. It's +// just an interface shared by classes and libraries. +abstract class ScopeContainerElement { + Element localLookup(SourceString elementName); +} + +abstract class CompilationUnitElement extends Element { + Script get script; + PartOf get partTag; + + void addMember(Element element, DiagnosticListener listener); + void setPartOf(PartOf tag, DiagnosticListener listener); + bool get hasMembers; +} + +abstract class LibraryElement extends Element implements ScopeContainerElement { + /** + * The canonical uri for this library. + * + * For user libraries the canonical uri is the script uri. For platform + * libraries the canonical uri is of the form [:dart:x:]. + */ + Uri get canonicalUri; + CompilationUnitElement get entryCompilationUnit; + Link<CompilationUnitElement> get compilationUnits; + Link<LibraryTag> get tags; + LibraryName get libraryTag; + Link<Element> get exports; + + /** + * [:true:] if this library is part of the platform, that is its canonical + * uri has the scheme 'dart'. + */ + bool get isPlatformLibrary; + + /** + * [:true:] if this library is a platform library whose path starts with + * an underscore. + */ + bool get isInternalLibrary; + bool get canUseNative; + bool get exportsHandled; + + // TODO(kasperl): We should try to get rid of these. + void set canUseNative(bool value); + void set libraryTag(LibraryName value); + + LibraryElement get implementation; + + void addCompilationUnit(CompilationUnitElement element); + void addTag(LibraryTag tag, DiagnosticListener listener); + void addImport(Element element, DiagnosticListener listener); + + void addMember(Element element, DiagnosticListener listener); + void addToScope(Element element, DiagnosticListener listener); + + // TODO(kasperl): Get rid of this method. + Iterable<Element> getNonPrivateElementsInScope(); + + void setExports(Iterable<Element> exportedElements); + + Element find(SourceString elementName); + Element findLocal(SourceString elementName); + void forEachExport(f(Element element)); + + void forEachLocalMember(f(Element element)); + + bool hasLibraryName(); + String getLibraryOrScriptName(); +} + +abstract class PrefixElement extends Element { + Map<SourceString, Element> get imported; + Element lookupLocalMember(SourceString memberName); +} + +abstract class TypedefElement extends Element + implements TypeDeclarationElement { + TypedefType get rawType; + DartType get alias; + FunctionSignature get functionSignature; + Link<DartType> get typeVariables; + + bool get isResolved; + bool get isBeingResolved; + + // TODO(kasperl): Try to get rid of these setters. + void set alias(DartType value); + void set isResolved(bool value); + void set isBeingResolved(bool value); + void set functionSignature(FunctionSignature value); +} + +abstract class VariableElement extends Element { + VariableListElement get variables; + + // TODO(kasperl): Try to get rid of this. + Expression get cachedNode; +} + +abstract class FieldParameterElement extends VariableElement { + VariableElement get fieldElement; +} + +abstract class VariableListElement extends Element { + DartType get type; + FunctionSignature get functionSignature; + + // TODO(kasperl): Try to get rid of this. + void set type(DartType value); +} + +abstract class AbstractFieldElement extends Element { + FunctionElement get getter; + FunctionElement get setter; +} + +abstract class FunctionSignature { + DartType get returnType; + Link<Element> get requiredParameters; + Link<Element> get optionalParameters; + + int get requiredParameterCount; + int get optionalParameterCount; + bool get optionalParametersAreNamed; + + int get parameterCount; + List<Element> get orderedOptionalParameters; + + void forEachParameter(void function(Element parameter)); + void forEachRequiredParameter(void function(Element parameter)); + void forEachOptionalParameter(void function(Element parameter)); + + void orderedForEachParameter(void function(Element parameter)); +} + +abstract class FunctionElement extends Element { + FunctionExpression get cachedNode; + DartType get type; + FunctionSignature get functionSignature; + FunctionElement get redirectionTarget; + FunctionElement get defaultImplementation; + + FunctionElement get patch; + FunctionElement get origin; + + // TODO(kasperl): These are bit fishy. Do we really need them? + void set patch(FunctionElement value); + void set origin(FunctionElement value); + void set defaultImplementation(FunctionElement value); + + void setPatch(FunctionElement patchElement); + FunctionSignature computeSignature(Compiler compiler); + int requiredParameterCount(Compiler compiler); + int optionalParameterCount(Compiler compiler); + int parameterCount(Compiler compiler); + + FunctionExpression parseNode(DiagnosticListener listener); +} + +abstract class ConstructorBodyElement extends FunctionElement { + FunctionElement get constructor; +} + +/** + * [TypeDeclarationElement] defines the common interface for class/interface + * declarations and typedefs. + */ +abstract class TypeDeclarationElement extends Element { + GenericType get rawType; + + /** + * The type variables declared on this declaration. The type variables are not + * available until the type of the element has been computed through + * [computeType]. + */ + Link<DartType> get typeVariables; +} + +abstract class ClassElement extends TypeDeclarationElement + implements ScopeContainerElement { + int get id; + + InterfaceType get rawType; + InterfaceType get thisType; + + ClassElement get superclass; + + DartType get supertype; + Link<DartType> get allSupertypes; + Link<DartType> get interfaces; + + bool get hasConstructor; + Link<Element> get constructors; + + ClassElement get patch; + ClassElement get origin; + ClassElement get declaration; + ClassElement get implementation; + + int get supertypeLoadState; + int get resolutionState; + SourceString get nativeTagInfo; + + bool get isMixinApplication; + bool get hasBackendMembers; + bool get hasLocalScopeMembers; + + // TODO(kasperl): These are bit fishy. Do we really need them? + void set rawType(InterfaceType value); + void set thisType(InterfaceType value); + void set supertype(DartType value); + void set allSupertypes(Link<DartType> value); + void set interfaces(Link<DartType> value); + void set patch(ClassElement value); + void set origin(ClassElement value); + void set supertypeLoadState(int value); + void set resolutionState(int value); + void set nativeTagInfo(SourceString value); + + // TODO(kasperl): These seem outdated. + bool isInterface(); + DartType get defaultClass; + void set defaultClass(DartType value); + + bool isObject(Compiler compiler); + bool isSubclassOf(ClassElement cls); + bool implementsInterface(ClassElement intrface); + bool isShadowedByField(Element fieldMember); + + ClassElement ensureResolved(Compiler compiler); + + void addMember(Element element, DiagnosticListener listener); + void addToScope(Element element, DiagnosticListener listener); + + /** + * Add a synthetic nullary constructor if there are no other + * constructors. + */ + void addDefaultConstructorIfNeeded(Compiler compiler); + + void addBackendMember(Element element); + void reverseBackendMembers(); + + Element lookupMember(SourceString memberName); + Element lookupSelector(Selector selector); + + Element lookupLocalMember(SourceString memberName); + Element lookupBackendMember(SourceString memberName); + Element lookupSuperMember(SourceString memberName); + + Element lookupSuperMemberInLibrary(SourceString memberName, + LibraryElement library); + + Element lookupSuperInterfaceMember(SourceString memberName, + LibraryElement fromLibrary); + + Element validateConstructorLookupResults(Selector selector, + Element result, + Element noMatch(Element)); + + Element lookupConstructor(Selector selector, [Element noMatch(Element)]); + Element lookupFactoryConstructor(Selector selector, + [Element noMatch(Element)]); + + void forEachMember(void f(ClassElement enclosingClass, Element member), + {includeBackendMembers: false, + includeSuperMembers: false}); + + void forEachInstanceField(void f(ClassElement enclosingClass, Element field), + {includeBackendMembers: false, + includeSuperMembers: false}); + + void forEachLocalMember(void f(Element member)); + void forEachBackendMember(void f(Element member)); +} + +abstract class MixinApplicationElement extends ClassElement { + ClassElement get mixin; + void set mixin(ClassElement value); +} + +abstract class LabelElement extends Element { + Label get label; + String get labelName; + TargetElement get target; + + bool get isTarget; + bool get isBreakTarget; + bool get isContinueTarget; + + void setBreakTarget(); + void setContinueTarget(); +} + +abstract class TargetElement extends Element { + Node get statement; + int get nestingLevel; + Link<LabelElement> get labels; + + bool get isTarget; + bool get isBreakTarget; + bool get isContinueTarget; + bool get isSwitch; + + // TODO(kasperl): Try to get rid of these. + void set isBreakTarget(bool value); + void set isContinueTarget(bool value); + + LabelElement addLabel(Label label, String labelName); +} + +abstract class TypeVariableElement extends Element { + TypeVariableType get type; + DartType get bound; + + // TODO(kasperl): Try to get rid of these. + void set type(TypeVariableType value); + void set bound(DartType value); +} + +abstract class MetadataAnnotation implements Spannable { + Constant get value; + Element get annotatedElement; + int get resolutionState; + Token get beginToken; + Token get endToken; + + // TODO(kasperl): Try to get rid of these. + void set annotatedElement(Element value); + void set resolutionState(int value); + + MetadataAnnotation ensureResolved(Compiler compiler); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart b/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart new file mode 100644 index 0000000..f007946 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart
@@ -0,0 +1,1981 @@ +// Copyright (c) 2013, 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. + +library elements.modelx; + +import 'dart:uri'; + +import 'elements.dart'; +import '../../compiler.dart' as api; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../resolution/resolution.dart'; + +import '../dart2jslib.dart' show invariant, + InterfaceType, + DartType, + TypeVariableType, + TypedefType, + MessageKind, + DiagnosticListener, + Script, + FunctionType, + SourceString, + Selector, + Constant, + Compiler; + +import '../dart_types.dart'; + +import '../scanner/scannerlib.dart' show Token, EOF_TOKEN; + + +class ElementX implements Element { + static int elementHashCode = 0; + + final SourceString name; + final ElementKind kind; + final Element enclosingElement; + final int hashCode = ++elementHashCode; + Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>(); + + ElementX(this.name, this.kind, this.enclosingElement) { + assert(isErroneous() || getImplementationLibrary() != null); + } + + Modifiers get modifiers => Modifiers.EMPTY; + + Node parseNode(DiagnosticListener listener) { + listener.internalErrorOnElement(this, 'not implemented'); + } + + DartType computeType(Compiler compiler) { + compiler.internalError("$this.computeType.", token: position()); + } + + void addMetadata(MetadataAnnotation annotation) { + assert(annotation.annotatedElement == null); + annotation.annotatedElement = this; + metadata = metadata.prepend(annotation); + } + + bool isFunction() => identical(kind, ElementKind.FUNCTION); + bool isConstructor() => isFactoryConstructor() || isGenerativeConstructor(); + bool isClosure() => false; + bool isMember() { + // Check that this element is defined in the scope of a Class. + return enclosingElement != null && enclosingElement.isClass(); + } + bool isInstanceMember() => false; + + /** + * Returns [:true:] if this element is enclosed in a static member or is + * itself a static member. + */ + bool isInStaticMember() { + Element member = getEnclosingMember(); + return member != null && member.modifiers.isStatic(); + } + + bool isFactoryConstructor() => modifiers.isFactory(); + bool isGenerativeConstructor() => + identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR); + bool isGenerativeConstructorBody() => + identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY); + bool isCompilationUnit() => identical(kind, ElementKind.COMPILATION_UNIT); + bool isClass() => identical(kind, ElementKind.CLASS); + bool isPrefix() => identical(kind, ElementKind.PREFIX); + bool isVariable() => identical(kind, ElementKind.VARIABLE); + bool isParameter() => identical(kind, ElementKind.PARAMETER); + bool isStatement() => identical(kind, ElementKind.STATEMENT); + bool isTypedef() => identical(kind, ElementKind.TYPEDEF); + bool isTypeVariable() => identical(kind, ElementKind.TYPE_VARIABLE); + bool isField() => identical(kind, ElementKind.FIELD); + bool isAbstractField() => identical(kind, ElementKind.ABSTRACT_FIELD); + bool isGetter() => identical(kind, ElementKind.GETTER); + bool isSetter() => identical(kind, ElementKind.SETTER); + bool isAccessor() => isGetter() || isSetter(); + bool isLibrary() => identical(kind, ElementKind.LIBRARY); + bool impliesType() => (kind.category & ElementCategory.IMPLIES_TYPE) != 0; + + /** See [ErroneousElement] for documentation. */ + bool isErroneous() => false; + + /** See [AmbiguousElement] for documentation. */ + bool isAmbiguous() => false; + + /** + * Is [:true:] if this element has a corresponding patch. + * + * If [:true:] this element has a non-null [patch] field. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isPatched => false; + + /** + * Is [:true:] if this element is a patch. + * + * If [:true:] this element has a non-null [origin] field. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isPatch => false; + + /** + * Is [:true:] if this element defines the implementation for the entity of + * this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isImplementation => !isPatched; + + /** + * Is [:true:] if this element introduces the entity of this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + bool get isDeclaration => !isPatch; + + bool get isSynthesized => false; + + /** + * Returns the element which defines the implementation for the entity of this + * element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + Element get implementation => isPatched ? patch : this; + + /** + * Returns the element which introduces the entity of this element. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + Element get declaration => isPatch ? origin : this; + + Element get patch { + throw new UnsupportedError('patch is not supported on $this'); + } + + Element get origin { + throw new UnsupportedError('origin is not supported on $this'); + } + + // TODO(johnniwinther): This breaks for libraries (for which enclosing + // elements are null) and is invalid for top level variable declarations for + // which the enclosing element is a VariableDeclarations and not a compilation + // unit. + bool isTopLevel() { + return enclosingElement != null && enclosingElement.isCompilationUnit(); + } + + bool isAssignable() { + if (modifiers.isFinalOrConst()) return false; + if (isFunction() || isGenerativeConstructor()) return false; + return true; + } + + Token position() => null; + + Token findMyName(Token token) { + for (Token t = token; !identical(t.kind, EOF_TOKEN); t = t.next) { + if (t.value == name) return t; + } + return token; + } + + CompilationUnitElement getCompilationUnit() { + Element element = this; + while (!element.isCompilationUnit()) { + element = element.enclosingElement; + } + return element; + } + + LibraryElement getLibrary() => enclosingElement.getLibrary(); + + LibraryElement getImplementationLibrary() { + Element element = this; + while (!identical(element.kind, ElementKind.LIBRARY)) { + element = element.enclosingElement; + } + return element; + } + + ClassElement getEnclosingClass() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isClass()) return e; + } + return null; + } + + Element getEnclosingClassOrCompilationUnit() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isClass() || e.isCompilationUnit()) return e; + } + return null; + } + + /** + * Returns the member enclosing this element or the element itself if it is a + * member. If no enclosing element is found, [:null:] is returned. + */ + Element getEnclosingMember() { + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isMember()) return e; + } + return null; + } + + Element getOutermostEnclosingMemberOrTopLevel() { + // TODO(lrn): Why is this called "Outermost"? + for (Element e = this; e != null; e = e.enclosingElement) { + if (e.isMember() || e.isTopLevel()) { + return e; + } + } + return null; + } + + /** + * Creates the scope for this element. + */ + Scope buildScope() => enclosingElement.buildScope(); + + String toString() { + // TODO(johnniwinther): Test for nullness of name, or make non-nullness an + // invariant for all element types? + var nameText = name != null ? name.slowToString() : '?'; + if (enclosingElement != null && !isTopLevel()) { + String holderName = enclosingElement.name != null + ? enclosingElement.name.slowToString() + : '${enclosingElement.kind}?'; + return '$kind($holderName#${nameText})'; + } else { + return '$kind(${nameText})'; + } + } + + String _fixedBackendName = null; + bool _isNative = false; + bool isNative() => _isNative; + bool hasFixedBackendName() => _fixedBackendName != null; + String fixedBackendName() => _fixedBackendName; + // Marks this element as a native element. + void setNative(String name) { + _isNative = true; + _fixedBackendName = name; + } + void setFixedBackendName(String name) { + _fixedBackendName = name; + } + + FunctionElement asFunctionElement() => null; + + static bool isInvalid(Element e) => e == null || e.isErroneous(); + + bool isAbstract(Compiler compiler) => modifiers.isAbstract(); + bool isForeign(Compiler compiler) => getLibrary() == compiler.foreignLibrary; +} + +/** + * Represents an unresolvable or duplicated element. + * + * An [ErroneousElement] is used instead of [null] to provide additional + * information about the error that caused the element to be unresolvable + * or otherwise invalid. + * + * Accessing any field or calling any method defined on [ErroneousElement] + * except [isErroneous] will currently throw an exception. (This might + * change when we actually want more information on the erroneous element, + * e.g., the name of the element we were trying to resolve.) + * + * Code that cannot not handle an [ErroneousElement] should use + * [: Element.isInvalid(element) :] + * to check for unresolvable elements instead of + * [: element == null :]. + */ +class ErroneousElementX extends ElementX implements ErroneousElement { + final MessageKind messageKind; + final Map messageArguments; + + ErroneousElementX(this.messageKind, this.messageArguments, + SourceString name, Element enclosing) + : super(name, ElementKind.ERROR, enclosing); + + isErroneous() => true; + + unsupported() { + throw 'unsupported operation on erroneous element'; + } + + Link<MetadataAnnotation> get metadata => unsupported(); + get type => unsupported(); + get cachedNode => unsupported(); + get functionSignature => unsupported(); + get patch => unsupported(); + get origin => unsupported(); + get defaultImplementation => unsupported(); + + bool get isPatched => unsupported(); + bool get isPatch => unsupported(); + + setPatch(patch) => unsupported(); + computeSignature(compiler) => unsupported(); + requiredParameterCount(compiler) => unsupported(); + optionalParameterCount(compiler) => unsupported(); + parameterCount(compiler) => unsupported(); + + // TODO(kasperl): These seem unnecessary. + set patch(value) => unsupported(); + set origin(value) => unsupported(); + set defaultImplementation(value) => unsupported(); + + get redirectionTarget => this; + + getLibrary() => enclosingElement.getLibrary(); + + String toString() { + String n = name.slowToString(); + return '<$n: ${messageKind.message(messageArguments)}>'; + } +} + +/** + * An ambiguous element represents multiple elements accessible by the same name. + * + * Ambiguous elements are created during handling of import/export scopes. If an + * ambiguous element is encountered during resolution a warning/error should be + * reported. + */ +class AmbiguousElementX extends ElementX implements AmbiguousElement { + /** + * The message to report on resolving this element. + */ + final MessageKind messageKind; + + /** + * The message arguments to report on resolving this element. + */ + final Map messageArguments; + + /** + * The first element that this ambiguous element might refer to. + */ + final Element existingElement; + + /** + * The second element that this ambiguous element might refer to. + */ + final Element newElement; + + AmbiguousElementX(this.messageKind, this.messageArguments, + Element enclosingElement, Element existingElement, Element newElement) + : this.existingElement = existingElement, + this.newElement = newElement, + super(existingElement.name, ElementKind.AMBIGUOUS, enclosingElement); + + bool isAmbiguous() => true; +} + +class ScopeX { + final Map<SourceString, Element> contents = new Map<SourceString, Element>(); + + bool get isEmpty => contents.isEmpty; + Iterable<Element> get values => contents.values; + + Element lookup(SourceString name) { + return contents[name]; + } + + void add(Element element, DiagnosticListener listener) { + if (element.isAccessor()) { + addAccessor(element, contents[element.name], listener); + } else { + Element existing = contents.putIfAbsent(element.name, () => element); + if (!identical(existing, element)) { + // TODO(ahe): Do something similar to Resolver.reportErrorWithContext. + listener.cancel('duplicate definition', token: element.position()); + listener.cancel('existing definition', token: existing.position()); + } + } + } + + /** + * Adds a definition for an [accessor] (getter or setter) to a scope. + * The definition binds to an abstract field that can hold both a getter + * and a setter. + * + * The abstract field is added once, for the first getter or setter, and + * reused if the other one is also added. + * The abstract field should not be treated as a proper member of the + * container, it's simply a way to return two results for one lookup. + * That is, the getter or setter does not have the abstract field as enclosing + * element, they are enclosed by the class or compilation unit, as is the + * abstract field. + */ + void addAccessor(Element accessor, + Element existing, + DiagnosticListener listener) { + void reportError(Element other) { + // TODO(ahe): Do something similar to Resolver.reportErrorWithContext. + listener.cancel('duplicate definition of ${accessor.name.slowToString()}', + element: accessor); + listener.cancel('existing definition', element: other); + } + + if (existing != null) { + if (!identical(existing.kind, ElementKind.ABSTRACT_FIELD)) { + reportError(existing); + } else { + AbstractFieldElementX field = existing; + if (accessor.isGetter()) { + if (field.getter != null && field.getter != accessor) { + reportError(field.getter); + } + field.getter = accessor; + } else { + assert(accessor.isSetter()); + if (field.setter != null && field.setter != accessor) { + reportError(field.setter); + } + field.setter = accessor; + } + } + } else { + Element container = accessor.getEnclosingClassOrCompilationUnit(); + AbstractFieldElementX field = + new AbstractFieldElementX(accessor.name, container); + if (accessor.isGetter()) { + field.getter = accessor; + } else { + field.setter = accessor; + } + add(field, listener); + } + } +} + +class CompilationUnitElementX extends ElementX + implements CompilationUnitElement { + final Script script; + PartOf partTag; + Link<Element> localMembers = const Link<Element>(); + + CompilationUnitElementX(Script script, LibraryElement library) + : this.script = script, + super(new SourceString(script.name), + ElementKind.COMPILATION_UNIT, + library) { + library.addCompilationUnit(this); + } + + void addMember(Element element, DiagnosticListener listener) { + // Keep a list of top level members. + localMembers = localMembers.prepend(element); + // Provide the member to the library to build scope. + if (enclosingElement.isPatch) { + getImplementationLibrary().addMember(element, listener); + } else { + getLibrary().addMember(element, listener); + } + } + + void setPartOf(PartOf tag, DiagnosticListener listener) { + LibraryElementX library = enclosingElement; + if (library.entryCompilationUnit == this) { + listener.reportMessage( + listener.spanFromSpannable(tag), + MessageKind.ILLEGAL_DIRECTIVE.error(), + api.Diagnostic.WARNING); + return; + } + if (!localMembers.isEmpty) { + listener.reportErrorCode(tag, MessageKind.BEFORE_TOP_LEVEL); + return; + } + if (partTag != null) { + listener.reportMessage( + listener.spanFromSpannable(tag), + MessageKind.DUPLICATED_PART_OF.error(), + api.Diagnostic.WARNING); + return; + } + partTag = tag; + LibraryName libraryTag = getLibrary().libraryTag; + if (libraryTag != null) { + String actualName = tag.name.toString(); + String expectedName = libraryTag.name.toString(); + if (expectedName != actualName) { + listener.reportMessage( + listener.spanFromSpannable(tag.name), + MessageKind.LIBRARY_NAME_MISMATCH.error( + {'libraryName': expectedName}), + api.Diagnostic.WARNING); + } + } + } + + bool get hasMembers => !localMembers.isEmpty; +} + +class LibraryElementX extends ElementX implements LibraryElement { + final Uri canonicalUri; + CompilationUnitElement entryCompilationUnit; + Link<CompilationUnitElement> compilationUnits = + const Link<CompilationUnitElement>(); + Link<LibraryTag> tags = const Link<LibraryTag>(); + LibraryName libraryTag; + bool canUseNative = false; + Link<Element> localMembers = const Link<Element>(); + final ScopeX localScope = new ScopeX(); + + /** + * If this library is patched, [patch] points to the patch library. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + LibraryElementX patch = null; + + /** + * If this is a patch library, [origin] points to the origin library. + * + * See [:patch_parser.dart:] for a description of the terminology. + */ + final LibraryElementX origin; + + /** + * Map for elements imported through import declarations. + * + * Addition to the map is performed by [addImport]. Lookup is done trough + * [find]. + */ + final Map<SourceString, Element> importScope; + + /** + * Link for elements exported either through export declarations or through + * declaration. This field should not be accessed directly but instead through + * the [exports] getter. + * + * [LibraryDependencyHandler] sets this field through [setExports] when the + * library is loaded. + */ + Link<Element> slotForExports; + + LibraryElementX(Script script, [Uri canonicalUri, LibraryElement this.origin]) + : this.canonicalUri = ((canonicalUri == null) ? script.uri : canonicalUri), + importScope = new Map<SourceString, Element>(), + super(new SourceString(script.name), ElementKind.LIBRARY, null) { + entryCompilationUnit = new CompilationUnitElementX(script, this); + if (isPatch) { + origin.patch = this; + } + } + + bool get isPatched => patch != null; + bool get isPatch => origin != null; + + LibraryElement get declaration => super.declaration; + LibraryElement get implementation => super.implementation; + + CompilationUnitElement getCompilationUnit() => entryCompilationUnit; + + void addCompilationUnit(CompilationUnitElement element) { + compilationUnits = compilationUnits.prepend(element); + } + + void addTag(LibraryTag tag, DiagnosticListener listener) { + tags = tags.prepend(tag); + } + + /** + * Adds [element] to the import scope of this library. + * + * If an element by the same name is already in the imported scope, an + * [ErroneousElement] will be put in the imported scope, allowing for the + * detection of ambiguous uses of imported names. + */ + void addImport(Element element, DiagnosticListener listener) { + Element existing = importScope[element.name]; + if (existing != null) { + // TODO(johnniwinther): Provide access to the import tags from which + // the elements came. + importScope[element.name] = new AmbiguousElementX( + MessageKind.DUPLICATE_IMPORT, {'name': element.name}, + this, existing, element); + } else { + importScope[element.name] = element; + } + } + + void addMember(Element element, DiagnosticListener listener) { + localMembers = localMembers.prepend(element); + addToScope(element, listener); + } + + void addToScope(Element element, DiagnosticListener listener) { + localScope.add(element, listener); + } + + Element localLookup(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result == null && isPatch) { + result = origin.localLookup(elementName); + } + return result; + } + + /** + * Returns [:true:] if the export scope has already been computed for this + * library. + */ + bool get exportsHandled => slotForExports != null; + + Link<Element> get exports { + assert(invariant(this, exportsHandled, + message: 'Exports not handled on $this')); + return slotForExports; + } + + /** + * Sets the export scope of this library. This method can only be called once. + */ + void setExports(Iterable<Element> exportedElements) { + assert(invariant(this, !exportsHandled, + message: 'Exports already set to $slotForExports on $this')); + assert(invariant(this, exportedElements != null)); + var builder = new LinkBuilder<Element>(); + for (Element export in exportedElements) { + builder.addLast(export); + } + slotForExports = builder.toLink(); + } + + LibraryElement getLibrary() => isPatch ? origin : this; + + /** + * Look up a top-level element in this library. The element could + * potentially have been imported from another library. Returns + * null if no such element exist and an [ErroneousElement] if multiple + * elements have been imported. + */ + Element find(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result != null) return result; + if (origin != null) { + result = origin.localScope.lookup(elementName); + if (result != null) return result; + } + result = importScope[elementName]; + if (result != null) return result; + if (origin != null) { + result = origin.importScope[elementName]; + if (result != null) return result; + } + return null; + } + + /** Look up a top-level element in this library, but only look for + * non-imported elements. Returns null if no such element exist. */ + Element findLocal(SourceString elementName) { + // TODO(johnniwinther): How to handle injected elements in the patch + // library? + Element result = localScope.lookup(elementName); + if (result == null || result.getLibrary() != this) return null; + return result; + } + + void forEachExport(f(Element element)) { + exports.forEach((Element e) => f(e)); + } + + void forEachLocalMember(f(Element element)) { + if (isPatch) { + // Patch libraries traverse both origin and injected members. + origin.localMembers.forEach(f); + + void filterPatch(Element element) { + if (!element.isPatch) { + // Do not traverse the patch members. + f(element); + } + } + localMembers.forEach(filterPatch); + } else { + localMembers.forEach(f); + } + } + + Iterable<Element> getNonPrivateElementsInScope() { + return localScope.values.where((Element element) { + // At this point [localScope] only contains members so we don't need + // to check for foreign or prefix elements. + return !element.name.isPrivate(); + }); + } + + bool hasLibraryName() => libraryTag != null; + + /** + * Returns the library name (as defined by the #library tag) or for script + * (which have no #library tag) the script file name. The latter case is used + * to private 'library name' for scripts to use for instance in dartdoc. + */ + String getLibraryOrScriptName() { + if (libraryTag != null) { + return libraryTag.name.toString(); + } else { + // Use the file name as script name. + String path = canonicalUri.path; + return path.substring(path.lastIndexOf('/') + 1); + } + } + + Scope buildScope() => new LibraryScope(this); + + bool get isPlatformLibrary => canonicalUri.scheme == "dart"; + + bool get isInternalLibrary => + isPlatformLibrary && canonicalUri.path.startsWith('_'); + + String toString() { + if (origin != null) { + return 'patch library(${getLibraryOrScriptName()})'; + } else if (patch != null) { + return 'origin library(${getLibraryOrScriptName()})'; + } else { + return 'library(${getLibraryOrScriptName()})'; + } + } +} + +class PrefixElementX extends ElementX implements PrefixElement { + Map<SourceString, Element> imported; + Token firstPosition; + + PrefixElementX(SourceString prefix, Element enclosing, this.firstPosition) + : imported = new Map<SourceString, Element>(), + super(prefix, ElementKind.PREFIX, enclosing); + + Element lookupLocalMember(SourceString memberName) => imported[memberName]; + + DartType computeType(Compiler compiler) => compiler.types.dynamicType; + + Token position() => firstPosition; +} + +class TypedefElementX extends ElementX implements TypedefElement { + Typedef cachedNode; + TypedefType cachedType; + + /** + * Canonicalize raw version of [cachedType]. + * + * See [ClassElement.rawType] for motivation. + * + * The [rawType] is computed together with [cachedType] in [computeType]. + */ + TypedefType rawType; + + /** + * The type annotation which defines this typedef. + */ + DartType alias; + + bool isResolved = false; + bool isBeingResolved = false; + + TypedefElementX(SourceString name, Element enclosing) + : super(name, ElementKind.TYPEDEF, enclosing); + + /** + * Function signature for a typedef of a function type. The signature is + * kept to provide full information about parameter names through the mirror + * system. + * + * The [functionSignature] is not available until the typedef element has been + * resolved. + */ + FunctionSignature functionSignature; + + TypedefType computeType(Compiler compiler) { + if (cachedType != null) return cachedType; + Typedef node = parseNode(compiler); + Link<DartType> parameters = + TypeDeclarationElementX.createTypeVariables(this, node.typeParameters); + cachedType = new TypedefType(this, parameters); + if (parameters.isEmpty) { + rawType = cachedType; + } else { + var dynamicParameters = const Link<DartType>(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new TypedefType(this, dynamicParameters); + } + compiler.resolveTypedef(this); + return cachedType; + } + + Link<DartType> get typeVariables => cachedType.typeArguments; + + Scope buildScope() { + return new TypeDeclarationScope(enclosingElement.buildScope(), this); + } +} + +class VariableElementX extends ElementX implements VariableElement { + final VariableListElement variables; + Expression cachedNode; // The send or the identifier in the variables list. + + Modifiers get modifiers => variables.modifiers; + + VariableElementX(SourceString name, + VariableListElement variables, + ElementKind kind, + this.cachedNode) + : this.variables = variables, + super(name, kind, variables.enclosingElement); + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + VariableDefinitions definitions = variables.parseNode(listener); + for (Link<Node> link = definitions.definitions.nodes; + !link.isEmpty; link = link.tail) { + Expression initializedIdentifier = link.head; + Identifier identifier = initializedIdentifier.asIdentifier(); + if (identifier == null) { + identifier = initializedIdentifier.asSendSet().selector.asIdentifier(); + } + if (identical(name, identifier.source)) { + cachedNode = initializedIdentifier; + return cachedNode; + } + } + listener.cancel('internal error: could not find $name', node: variables); + } + + DartType computeType(Compiler compiler) { + return variables.computeType(compiler); + } + + DartType get type => variables.type; + + bool isInstanceMember() => variables.isInstanceMember(); + + // Note: cachedNode.getBeginToken() will not be correct in all + // cases, for example, for function typed parameters. + Token position() => findMyName(variables.position()); +} + +/** + * Parameters in constructors that directly initialize fields. For example: + * [:A(this.field):]. + */ +class FieldParameterElementX extends VariableElementX + implements FieldParameterElement { + VariableElement fieldElement; + + FieldParameterElementX(SourceString name, + this.fieldElement, + VariableListElement variables, + Node node) + : super(name, variables, ElementKind.FIELD_PARAMETER, node); +} + +// This element represents a list of variable or field declaration. +// It contains the node, and the type. A [VariableElement] always +// references its [VariableListElement]. It forwards its +// [computeType] and [parseNode] methods to this element. +class VariableListElementX extends ElementX implements VariableListElement { + VariableDefinitions cachedNode; + DartType type; + final Modifiers modifiers; + + /** + * Function signature for a variable with a function type. The signature is + * kept to provide full information about parameter names through the mirror + * system. + */ + FunctionSignature functionSignature; + + VariableListElementX(ElementKind kind, + Modifiers this.modifiers, + Element enclosing) + : super(null, kind, enclosing); + + VariableListElementX.node(VariableDefinitions node, + ElementKind kind, + Element enclosing) + : super(null, kind, enclosing), + this.cachedNode = node, + this.modifiers = node.modifiers { + assert(modifiers != null); + } + + VariableDefinitions parseNode(DiagnosticListener listener) { + return cachedNode; + } + + DartType computeType(Compiler compiler) { + if (type != null) return type; + compiler.withCurrentElement(this, () { + VariableDefinitions node = parseNode(compiler); + if (node.type != null) { + type = compiler.resolveTypeAnnotation(this, node.type); + } else { + // Is node.definitions exactly one FunctionExpression? + Link<Node> link = node.definitions.nodes; + if (!link.isEmpty && + link.head.asFunctionExpression() != null && + link.tail.isEmpty) { + FunctionExpression functionExpression = link.head; + // We found exactly one FunctionExpression + functionSignature = + compiler.resolveFunctionExpression(this, functionExpression); + type = compiler.computeFunctionType(compiler.functionClass, + functionSignature); + } else { + type = compiler.types.dynamicType; + } + } + }); + assert(type != null); + return type; + } + + Token position() => cachedNode.getBeginToken(); + + bool isInstanceMember() { + return isMember() && !modifiers.isStatic(); + } +} + +class AbstractFieldElementX extends ElementX implements AbstractFieldElement { + FunctionElement getter; + FunctionElement setter; + + AbstractFieldElementX(SourceString name, Element enclosing) + : super(name, ElementKind.ABSTRACT_FIELD, enclosing); + + DartType computeType(Compiler compiler) { + throw "internal error: AbstractFieldElement has no type"; + } + + Node parseNode(DiagnosticListener listener) { + throw "internal error: AbstractFieldElement has no node"; + } + + position() { + // The getter and setter may be defined in two different + // compilation units. However, we know that one of them is + // non-null and defined in the same compilation unit as the + // abstract element. + // TODO(lrn): No we don't know that if the element from the same + // compilation unit is patched. + // + // We need to make sure that the position returned is relative to + // the compilation unit of the abstract element. + if (getter != null + && identical(getter.getCompilationUnit(), getCompilationUnit())) { + return getter.position(); + } else { + return setter.position(); + } + } + + Modifiers get modifiers { + // The resolver ensures that the flags match (ignoring abstract). + if (getter != null) { + return new Modifiers.withFlags( + getter.modifiers.nodes, + getter.modifiers.flags | Modifiers.FLAG_ABSTRACT); + } else { + return new Modifiers.withFlags( + setter.modifiers.nodes, + setter.modifiers.flags | Modifiers.FLAG_ABSTRACT); + } + } +} + +// TODO(johnniwinther): [FunctionSignature] should be merged with +// [FunctionType]. +class FunctionSignatureX implements FunctionSignature { + final Link<Element> requiredParameters; + final Link<Element> optionalParameters; + final DartType returnType; + final int requiredParameterCount; + final int optionalParameterCount; + final bool optionalParametersAreNamed; + + List<Element> _orderedOptionalParameters; + + FunctionSignatureX(this.requiredParameters, + this.optionalParameters, + this.requiredParameterCount, + this.optionalParameterCount, + this.optionalParametersAreNamed, + this.returnType); + + void forEachRequiredParameter(void function(Element parameter)) { + for (Link<Element> link = requiredParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + void forEachOptionalParameter(void function(Element parameter)) { + for (Link<Element> link = optionalParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + List<Element> get orderedOptionalParameters { + if (_orderedOptionalParameters != null) return _orderedOptionalParameters; + List<Element> list = new List<Element>.from(optionalParameters); + if (optionalParametersAreNamed) { + list.sort((Element a, Element b) { + return a.name.slowToString().compareTo(b.name.slowToString()); + }); + } + _orderedOptionalParameters = list; + return list; + } + + void forEachParameter(void function(Element parameter)) { + forEachRequiredParameter(function); + forEachOptionalParameter(function); + } + + void orderedForEachParameter(void function(Element parameter)) { + forEachRequiredParameter(function); + orderedOptionalParameters.forEach(function); + } + + int get parameterCount => requiredParameterCount + optionalParameterCount; +} + +class FunctionElementX extends ElementX implements FunctionElement { + FunctionExpression cachedNode; + DartType type; + final Modifiers modifiers; + + FunctionSignature functionSignature; + + /** + * A function declaration that should be parsed instead of the current one. + * The patch should be parsed as if it was in the current scope. Its + * signature must match this function's signature. + */ + // TODO(lrn): Consider using [defaultImplementation] to store the patch. + FunctionElement patch = null; + FunctionElement origin = null; + + /** + * If this is a redirecting factory, [defaultImplementation] will be + * changed by the resolver to point to the redirection target. If + * this is an interface constructor, [defaultImplementation] will be + * changed by the resolver to point to the default implementation. + * Otherwise, [:identical(defaultImplementation, this):]. + */ + // TODO(ahe): Rename this field to redirectionTarget and remove + // mention of interface constructors above. + FunctionElement defaultImplementation; + + FunctionElementX(SourceString name, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : this.tooMuchOverloading(name, null, kind, modifiers, enclosing, null); + + FunctionElementX.node(SourceString name, + FunctionExpression node, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : this.tooMuchOverloading(name, node, kind, modifiers, enclosing, null); + + FunctionElementX.from(SourceString name, + FunctionElement other, + Element enclosing) + : this.tooMuchOverloading(name, other.cachedNode, other.kind, + other.modifiers, enclosing, + other.functionSignature); + + FunctionElementX.tooMuchOverloading(SourceString name, + FunctionExpression this.cachedNode, + ElementKind kind, + Modifiers this.modifiers, + Element enclosing, + FunctionSignature this.functionSignature) + : super(name, kind, enclosing) { + assert(modifiers != null); + defaultImplementation = this; + } + + bool get isPatched => patch != null; + bool get isPatch => origin != null; + + FunctionElement get redirectionTarget { + if (this == defaultImplementation) return this; + var target = defaultImplementation; + Set<Element> seen = new Set<Element>(); + seen.add(target); + while (!target.isErroneous() && target != target.defaultImplementation) { + target = target.defaultImplementation; + if (seen.contains(target)) { + // TODO(ahe): This is expedient for now, but it should be + // checked by the resolver. Keeping http://dartbug.com/3970 + // open to track this. + throw new SpannableAssertionFailure( + target, 'redirecting factory leads to cycle'); + } + } + return target; + } + + /** + * Applies a patch function to this function. The patch function's body + * is used as replacement when parsing this function's body. + * This method must not be called after the function has been parsed, + * and it must be called at most once. + */ + void setPatch(FunctionElement patchElement) { + // Sanity checks. The caller must check these things before calling. + assert(patch == null); + this.patch = patchElement; + } + + bool isInstanceMember() { + return isMember() + && !isConstructor() + && !modifiers.isStatic(); + } + + FunctionSignature computeSignature(Compiler compiler) { + if (functionSignature != null) return functionSignature; + compiler.withCurrentElement(this, () { + functionSignature = compiler.resolveSignature(this); + }); + return functionSignature; + } + + int requiredParameterCount(Compiler compiler) { + return computeSignature(compiler).requiredParameterCount; + } + + int optionalParameterCount(Compiler compiler) { + return computeSignature(compiler).optionalParameterCount; + } + + int parameterCount(Compiler compiler) { + return computeSignature(compiler).parameterCount; + } + + FunctionType computeType(Compiler compiler) { + if (type != null) return type; + type = compiler.computeFunctionType(declaration, + computeSignature(compiler)); + return type; + } + + FunctionExpression parseNode(DiagnosticListener listener) { + if (patch == null) { + if (modifiers.isExternal()) { + listener.cancel("Compiling external function with no implementation.", + element: this); + } + } + return cachedNode; + } + + Token position() => cachedNode.getBeginToken(); + + FunctionElement asFunctionElement() => this; + + String toString() { + if (isPatch) { + return 'patch ${super.toString()}'; + } else if (isPatched) { + return 'origin ${super.toString()}'; + } else { + return super.toString(); + } + } + + bool isAbstract(Compiler compiler) { + if (super.isAbstract(compiler)) return true; + if (modifiers.isExternal()) return false; + if (isFunction() || isAccessor()) { + return !parseNode(compiler).hasBody(); + } + return false; + } +} + +class ConstructorBodyElementX extends FunctionElementX + implements ConstructorBodyElement { + FunctionElement constructor; + + ConstructorBodyElementX(FunctionElement constructor) + : this.constructor = constructor, + super(constructor.name, + ElementKind.GENERATIVE_CONSTRUCTOR_BODY, + Modifiers.EMPTY, + constructor.enclosingElement) { + functionSignature = constructor.functionSignature; + } + + bool isInstanceMember() => true; + + FunctionType computeType(Compiler compiler) { + compiler.reportFatalError('Internal error: $this.computeType', this); + } + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = constructor.parseNode(listener); + assert(cachedNode != null); + return cachedNode; + } + + Token position() => constructor.position(); +} + +class SynthesizedConstructorElementX extends FunctionElementX { + SynthesizedConstructorElementX(Element enclosing) + : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR, + Modifiers.EMPTY, enclosing); + + SynthesizedConstructorElementX.forDefault(Element enclosing, + Compiler compiler) + : super(enclosing.name, ElementKind.GENERATIVE_CONSTRUCTOR, + Modifiers.EMPTY, enclosing) { + type = new FunctionType(this, + compiler.types.voidType, + const Link<DartType>(), + const Link<DartType>(), + const Link<SourceString>(), + const Link<DartType>()); + cachedNode = new FunctionExpression( + new Identifier(enclosing.position()), + new NodeList.empty(), + new Block(new NodeList.empty()), + null, Modifiers.EMPTY, null, null); + } + + bool get isSynthesized => true; + + Token position() => enclosingElement.position(); +} + +class VoidElementX extends ElementX { + VoidElementX(Element enclosing) + : super(const SourceString('void'), ElementKind.VOID, enclosing); + DartType computeType(compiler) => compiler.types.voidType; + Node parseNode(_) { + throw 'internal error: parseNode on void'; + } + bool impliesType() => true; +} + +class TypeDeclarationElementX { + /** + * Creates the type variables, their type and corresponding element, for the + * type variables declared in [parameter] on [element]. The bounds of the type + * variables are not set until [element] has been resolved. + */ + static Link<DartType> createTypeVariables(TypeDeclarationElement element, + NodeList parameters) { + if (parameters == null) return const Link<DartType>(); + + // Create types and elements for type variable. + var arguments = new LinkBuilder<DartType>(); + for (Link link = parameters.nodes; !link.isEmpty; link = link.tail) { + TypeVariable node = link.head; + SourceString variableName = node.name.source; + TypeVariableElement variableElement = + new TypeVariableElementX(variableName, element, node); + TypeVariableType variableType = new TypeVariableType(variableElement); + variableElement.type = variableType; + arguments.addLast(variableType); + } + return arguments.toLink(); + } +} + +abstract class BaseClassElementX extends ElementX implements ClassElement { + final int id; + + /** + * The type of [:this:] for this class declaration. + * + * The type of [:this:] is the interface type based on this element in which + * the type arguments are the declared type variables. For instance, + * [:List<E>:] for [:List:] and [:Map<K,V>:] for [:Map:]. + * + * This type is computed in [computeType]. + */ + InterfaceType thisType; + + /** + * The raw type for this class declaration. + * + * The raw type is the interface type base on this element in which the type + * arguments are all [dynamic]. For instance [:List<dynamic>:] for [:List:] + * and [:Map<dynamic,dynamic>:] for [:Map:]. For non-generic classes [rawType] + * is the same as [thisType]. + * + * The [rawType] field is a canonicalization of the raw type and should be + * used to distinguish explicit and implicit uses of the [dynamic] + * type arguments. For instance should [:List:] be the [rawType] of the + * [:List:] class element whereas [:List<dynamic>:] should be its own + * instantiation of [InterfaceType] with [:dynamic:] as type argument. Using + * this distinction, we can print the raw type with type arguments only when + * the input source has used explicit type arguments. + * + * This type is computed together with [thisType] in [computeType]. + */ + InterfaceType rawType; + DartType supertype; + DartType defaultClass; + Link<DartType> interfaces; + SourceString nativeTagInfo; + int supertypeLoadState; + int resolutionState; + + // backendMembers are members that have been added by the backend to simplify + // compilation. They don't have any user-side counter-part. + Link<Element> backendMembers = const Link<Element>(); + + Link<DartType> allSupertypes; + + BaseClassElementX(SourceString name, + Element enclosing, + this.id, + int initialState) + : supertypeLoadState = initialState, + resolutionState = initialState, + super(name, ElementKind.CLASS, enclosing); + + int get hashCode => id; + ClassElement get patch => super.patch; + ClassElement get origin => super.origin; + ClassElement get declaration => super.declaration; + ClassElement get implementation => super.implementation; + + bool get hasBackendMembers => !backendMembers.isEmpty; + + InterfaceType computeType(Compiler compiler) { + if (thisType == null) { + if (origin == null) { + Link<DartType> parameters = computeTypeParameters(compiler); + thisType = new InterfaceType(this, parameters); + if (parameters.isEmpty) { + rawType = thisType; + } else { + var dynamicParameters = const Link<DartType>(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new InterfaceType(this, dynamicParameters); + } + } else { + thisType = origin.computeType(compiler); + rawType = origin.rawType; + } + } + return thisType; + } + + Link<DartType> computeTypeParameters(Compiler compiler); + + /** + * Return [:true:] if this element is the [:Object:] class for the [compiler]. + */ + bool isObject(Compiler compiler) => + identical(declaration, compiler.objectClass); + + Link<DartType> get typeVariables => thisType.typeArguments; + + ClassElement ensureResolved(Compiler compiler) { + if (resolutionState == STATE_NOT_STARTED) { + compiler.resolver.resolveClass(this); + } + return this; + } + + void addDefaultConstructorIfNeeded(Compiler compiler) { + if (hasConstructor) return; + FunctionElement constructor = + new SynthesizedConstructorElementX.forDefault(this, compiler); + setDefaultConstructor(constructor, compiler); + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler); + + void addBackendMember(Element member) { + backendMembers = backendMembers.prepend(member); + } + + void reverseBackendMembers() { + backendMembers = backendMembers.reverse(); + } + + /** + * Lookup local members in the class. This will ignore constructors. + */ + Element lookupLocalMember(SourceString memberName) { + var result = localLookup(memberName); + if (result != null && result.isConstructor()) return null; + return result; + } + + /// Lookup a synthetic element created by the backend. + Element lookupBackendMember(SourceString memberName) { + for (Element element in backendMembers) { + if (element.name == memberName) { + return element; + } + } + } + /** + * Lookup super members for the class. This will ignore constructors. + */ + Element lookupSuperMember(SourceString memberName) { + return lookupSuperMemberInLibrary(memberName, getLibrary()); + } + + /** + * Lookup super members for the class that is accessible in [library]. + * This will ignore constructors. + */ + Element lookupSuperMemberInLibrary(SourceString memberName, + LibraryElement library) { + bool includeInjectedMembers = isPatch; + bool isPrivate = memberName.isPrivate(); + for (ClassElement s = superclass; s != null; s = s.superclass) { + // Private members from a different library are not visible. + if (isPrivate && !identical(library, s.getLibrary())) continue; + s = includeInjectedMembers ? s.implementation : s; + Element e = s.lookupLocalMember(memberName); + if (e == null) continue; + // Static members are not inherited. + if (e.modifiers.isStatic()) continue; + return e; + } + if (isInterface()) { + return lookupSuperInterfaceMember(memberName, getLibrary()); + } + return null; + } + + Element lookupSuperInterfaceMember(SourceString memberName, + LibraryElement fromLibrary) { + bool includeInjectedMembers = isPatch; + bool isPrivate = memberName.isPrivate(); + for (InterfaceType t in interfaces) { + ClassElement cls = t.element; + cls = includeInjectedMembers ? cls.implementation : cls; + Element e = cls.lookupLocalMember(memberName); + if (e == null) continue; + // Private members from a different library are not visible. + if (isPrivate && !identical(fromLibrary, e.getLibrary())) continue; + // Static members are not inherited. + if (e.modifiers.isStatic()) continue; + return e; + } + return null; + } + + /** + * Find the first member in the class chain with the given [selector]. + * + * This method is NOT to be used for resolving + * unqualified sends because it does not implement the scoping + * rules, where library scope comes before superclass scope. + * + * When called on the implementation element both members declared in the + * origin and the patch class are returned. + */ + Element lookupSelector(Selector selector) { + SourceString memberName = selector.name; + LibraryElement library = selector.library; + Element localMember = lookupLocalMember(memberName); + if (localMember != null && + (!memberName.isPrivate() || getLibrary() == library)) { + return localMember; + } + return lookupSuperMemberInLibrary(memberName, library); + } + + /** + * Find the first member in the class chain with the given + * [memberName]. This method is NOT to be used for resolving + * unqualified sends because it does not implement the scoping + * rules, where library scope comes before superclass scope. + */ + Element lookupMember(SourceString memberName) { + Element localMember = lookupLocalMember(memberName); + return localMember == null ? lookupSuperMember(memberName) : localMember; + } + + /** + * Returns true if the [fieldMember] is shadowed by another field. The given + * [fieldMember] must be a member of this class. + * + * This method also works if the [fieldMember] is private. + */ + bool isShadowedByField(Element fieldMember) { + assert(fieldMember.isField()); + // Note that we cannot use [lookupMember] or [lookupSuperMember] since it + // will not do the right thing for private elements. + ClassElement lookupClass = this; + LibraryElement memberLibrary = fieldMember.getLibrary(); + if (fieldMember.name.isPrivate()) { + // We find a super class in the same library as the field. This way the + // lookupMember will work. + while (lookupClass.getLibrary() != memberLibrary) { + lookupClass = lookupClass.superclass; + } + } + SourceString fieldName = fieldMember.name; + while (true) { + Element foundMember = lookupClass.lookupMember(fieldName); + if (foundMember == fieldMember) return false; + if (foundMember.isField()) return true; + lookupClass = foundMember.getEnclosingClass().superclass; + } + } + + Element validateConstructorLookupResults(Selector selector, + Element result, + Element noMatch(Element)) { + if (result == null + || !result.isConstructor() + || (selector.name.isPrivate() + && result.getLibrary() != selector.library)) { + result = noMatch != null ? noMatch(result) : null; + } + return result; + } + + // TODO(aprelev@gmail.com): Peter believes that it would be great to + // make noMatch a required argument. Peter's suspicion is that most + // callers of this method would benefit from using the noMatch method. + Element lookupConstructor(Selector selector, [Element noMatch(Element)]) { + SourceString normalizedName; + SourceString className = this.name; + SourceString constructorName = selector.name; + if (constructorName != const SourceString('')) { + normalizedName = Elements.constructConstructorName(className, + constructorName); + } else { + normalizedName = className; + } + Element result = localLookup(normalizedName); + return validateConstructorLookupResults(selector, result, noMatch); + } + + Element lookupFactoryConstructor(Selector selector, + [Element noMatch(Element)]) { + SourceString constructorName = selector.name; + Element result = localLookup(constructorName); + return validateConstructorLookupResults(selector, result, noMatch); + } + + Link<Element> get constructors { + // TODO(ajohnsen): See if we can avoid this method at some point. + Link<Element> result = const Link<Element>(); + // TODO(johnniwinther): Should we include injected constructors? + forEachMember((_, Element member) { + if (member.isConstructor()) result = result.prepend(member); + }); + return result; + } + + /** + * Returns the super class, if any. + * + * The returned element may not be resolved yet. + */ + ClassElement get superclass { + assert(supertypeLoadState == STATE_DONE); + return supertype == null ? null : supertype.element; + } + + /** + * Runs through all members of this class. + * + * The enclosing class is passed to the callback. This is useful when + * [includeSuperMembers] is [:true:]. + * + * When called on an implementation element both the members in the origin + * and patch class are included. + */ + // TODO(johnniwinther): Clean up lookup to get rid of the include predicates. + void forEachMember(void f(ClassElement enclosingClass, Element member), + {includeBackendMembers: false, + includeSuperMembers: false}) { + bool includeInjectedMembers = isPatch; + Set<ClassElement> seen = new Set<ClassElement>(); + ClassElement classElement = declaration; + do { + if (seen.contains(classElement)) return; + seen.add(classElement); + + // Iterate through the members in textual order, which requires + // to reverse the data structure [localMembers] we created. + // Textual order may be important for certain operations, for + // example when emitting the initializers of fields. + classElement.forEachLocalMember((e) => f(classElement, e)); + if (includeBackendMembers) { + classElement.forEachBackendMember((e) => f(classElement, e)); + } + if (includeInjectedMembers) { + if (classElement.patch != null) { + classElement.patch.forEachLocalMember((e) { + if (!e.isPatch) f(classElement, e); + }); + } + } + classElement = includeSuperMembers ? classElement.superclass : null; + } while(classElement != null); + } + + /** + * Runs through all instance-field members of this class. + * + * The enclosing class is passed to the callback. This is useful when + * [includeSuperMembers] is [:true:]. + * + * When [includeBackendMembers] and [includeSuperMembers] are both [:true:] + * then the fields are visited in the same order as they need to be given + * to the JavaScript constructor. + * + * When called on the implementation element both the fields declared in the + * origin and in the patch are included. + */ + void forEachInstanceField(void f(ClassElement enclosingClass, Element field), + {includeBackendMembers: false, + includeSuperMembers: false}) { + // Filters so that [f] is only invoked with instance fields. + void fieldFilter(ClassElement enclosingClass, Element member) { + if (member.isInstanceMember() && member.kind == ElementKind.FIELD) { + f(enclosingClass, member); + } + } + + forEachMember(fieldFilter, + includeBackendMembers: includeBackendMembers, + includeSuperMembers: includeSuperMembers); + } + + void forEachBackendMember(void f(Element member)) { + backendMembers.forEach(f); + } + + bool implementsInterface(ClassElement intrface) { + for (DartType implementedInterfaceType in allSupertypes) { + ClassElement implementedInterface = implementedInterfaceType.element; + if (identical(implementedInterface, intrface)) { + return true; + } + } + return false; + } + + /** + * Returns true if [this] is a subclass of [cls]. + * + * This method is not to be used for checking type hierarchy and + * assignments, because it does not take parameterized types into + * account. + */ + bool isSubclassOf(ClassElement cls) { + // Use [declaration] for both [this] and [cls], because + // declaration classes hold the superclass hierarchy. + cls = cls.declaration; + for (ClassElement s = declaration; s != null; s = s.superclass) { + if (identical(s, cls)) return true; + } + return false; + } + + bool isInterface() => false; + bool isNative() => nativeTagInfo != null; + void setNative(String name) { + nativeTagInfo = new SourceString(name); + } +} + +abstract class ClassElementX extends BaseClassElementX { + // Lazily applied patch of class members. + ClassElement patch = null; + ClassElement origin = null; + + Link<Element> localMembers = const Link<Element>(); + final ScopeX localScope = new ScopeX(); + + ClassElementX(SourceString name, Element enclosing, int id, int initialState) + : super(name, enclosing, id, initialState); + + ClassNode parseNode(Compiler compiler); + + bool get isMixinApplication => false; + bool get isPatched => patch != null; + bool get isPatch => origin != null; + bool get hasLocalScopeMembers => !localScope.isEmpty; + + void addMember(Element element, DiagnosticListener listener) { + localMembers = localMembers.prepend(element); + addToScope(element, listener); + } + + void addToScope(Element element, DiagnosticListener listener) { + localScope.add(element, listener); + } + + Element localLookup(SourceString elementName) { + Element result = localScope.lookup(elementName); + if (result == null && isPatch) { + result = origin.localLookup(elementName); + } + return result; + } + + void forEachLocalMember(void f(Element member)) { + localMembers.reverse().forEach(f); + } + + bool get hasConstructor { + // Search in scope to be sure we search patched constructors. + for (var element in localScope.values) { + if (element.isConstructor()) return true; + } + return false; + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler) { + addToScope(constructor, compiler); + } + + Link<DartType> computeTypeParameters(Compiler compiler) { + ClassNode node = parseNode(compiler); + return TypeDeclarationElementX.createTypeVariables( + this, node.typeParameters); + } + + Scope buildScope() => new ClassScope(enclosingElement.buildScope(), this); + + String toString() { + if (origin != null) { + return 'patch ${super.toString()}'; + } else if (patch != null) { + return 'origin ${super.toString()}'; + } else { + return super.toString(); + } + } +} + +class MixinApplicationElementX extends BaseClassElementX + implements MixinApplicationElement { + final Node node; + final Modifiers modifiers; + + FunctionElement constructor; + ClassElement mixin; + + // TODO(kasperl): The analyzer complains when I don't have these two + // fields. This is pretty weird. I cannot replace them with getters. + final ClassElement patch = null; + final ClassElement origin = null; + + MixinApplicationElementX(SourceString name, Element enclosing, int id, + this.node, this.modifiers) + : super(name, enclosing, id, STATE_NOT_STARTED); + + bool get isMixinApplication => true; + bool get hasConstructor => constructor != null; + bool get hasLocalScopeMembers => false; + + Token position() => node.getBeginToken(); + + Node parseNode(DiagnosticListener listener) => node; + + Element localLookup(SourceString name) { + if (this.name == name) return constructor; + if (mixin == null) return null; + Element mixedInElement = mixin.localLookup(name); + if (mixedInElement == null) return null; + return mixedInElement.isInstanceMember() ? mixedInElement : null; + } + + void forEachLocalMember(void f(Element member)) { + if (mixin != null) mixin.forEachLocalMember((Element mixedInElement) { + if (mixedInElement.isInstanceMember()) f(mixedInElement); + }); + } + + void addMember(Element element, DiagnosticListener listener) { + throw new UnsupportedError("cannot add member to $this"); + } + + void addToScope(Element element, DiagnosticListener listener) { + throw new UnsupportedError("cannot add to scope of $this"); + } + + void setDefaultConstructor(FunctionElement constructor, Compiler compiler) { + assert(!hasConstructor); + this.constructor = constructor; + } + + Link<DartType> computeTypeParameters(Compiler compiler) { + NamedMixinApplication named = node.asNamedMixinApplication(); + if (named == null) return const Link<DartType>(); + return TypeDeclarationElementX.createTypeVariables( + this, named.typeParameters); + } +} + +class LabelElementX extends ElementX implements LabelElement { + + // We store the original label here so it can be returned by [parseNode]. + final Label label; + final String labelName; + final TargetElement target; + bool isBreakTarget = false; + bool isContinueTarget = false; + LabelElementX(Label label, String labelName, this.target, + Element enclosingElement) + : this.label = label, + this.labelName = labelName, + // In case of a synthetic label, just use [labelName] for + // identifying the element. + super(label == null + ? new SourceString(labelName) + : label.identifier.source, + ElementKind.LABEL, + enclosingElement); + + void setBreakTarget() { + isBreakTarget = true; + target.isBreakTarget = true; + } + void setContinueTarget() { + isContinueTarget = true; + target.isContinueTarget = true; + } + + bool get isTarget => isBreakTarget || isContinueTarget; + Node parseNode(DiagnosticListener l) => label; + + Token position() => label.getBeginToken(); + String toString() => "${labelName}:"; +} + +// Represents a reference to a statement or switch-case, either by label or the +// default target of a break or continue. +class TargetElementX extends ElementX implements TargetElement { + final Node statement; + final int nestingLevel; + Link<LabelElement> labels = const Link<LabelElement>(); + bool isBreakTarget = false; + bool isContinueTarget = false; + + TargetElementX(this.statement, this.nestingLevel, Element enclosingElement) + : super(const SourceString(""), ElementKind.STATEMENT, enclosingElement); + bool get isTarget => isBreakTarget || isContinueTarget; + + LabelElement addLabel(Label label, String labelName) { + LabelElement result = new LabelElementX(label, labelName, this, + enclosingElement); + labels = labels.prepend(result); + return result; + } + + Node parseNode(DiagnosticListener l) => statement; + + bool get isSwitch => statement is SwitchStatement; + + Token position() => statement.getBeginToken(); + String toString() => statement.toString(); +} + +class TypeVariableElementX extends ElementX implements TypeVariableElement { + final Node cachedNode; + TypeVariableType type; + DartType bound; + + TypeVariableElementX(name, Element enclosing, this.cachedNode, + [this.type, this.bound]) + : super(name, ElementKind.TYPE_VARIABLE, enclosing); + + TypeVariableType computeType(compiler) => type; + + Node parseNode(compiler) => cachedNode; + + String toString() => "${enclosingElement.toString()}.${name.slowToString()}"; + + Token position() => cachedNode.getBeginToken(); +} + +/** + * A single metadata annotation. + * + * For example, consider: + * + * [: + * class Data { + * const Data(); + * } + * + * const data = const Data(); + * + * @data + * class Foo {} + * + * @data @data + * class Bar {} + * :] + * + * In this example, there are three instances of [MetadataAnnotation] + * and they correspond each to a location in the source code where + * there is an at-sign, '@'. The [value] of each of these instances + * are the same compile-time constant, [: const Data() :]. + * + * The mirror system does not have a concept matching this class. + */ +abstract class MetadataAnnotationX implements MetadataAnnotation { + /** + * The compile-time constant which this annotation resolves to. + * In the mirror system, this would be an object mirror. + */ + Constant get value; + Element annotatedElement; + int resolutionState; + + /** + * The beginning token of this annotation, or [:null:] if it is synthetic. + */ + Token get beginToken; + + MetadataAnnotationX([this.resolutionState = STATE_NOT_STARTED]); + + MetadataAnnotation ensureResolved(Compiler compiler) { + if (resolutionState == STATE_NOT_STARTED) { + compiler.resolver.resolveMetadataAnnotation(this); + } + return this; + } + + String toString() => 'MetadataAnnotation($value, $resolutionState)'; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart b/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart new file mode 100644 index 0000000..4a4fea8 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart
@@ -0,0 +1,552 @@ +// 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. + +part of dart2js; + +class EnqueueTask extends CompilerTask { + final ResolutionEnqueuer resolution; + final CodegenEnqueuer codegen; + + String get name => 'Enqueue'; + + EnqueueTask(Compiler compiler) + : resolution = new ResolutionEnqueuer( + compiler, compiler.backend.createItemCompilationContext), + codegen = new CodegenEnqueuer( + compiler, compiler.backend.createItemCompilationContext), + super(compiler) { + codegen.task = this; + resolution.task = this; + + codegen.nativeEnqueuer = compiler.backend.nativeCodegenEnqueuer(codegen); + resolution.nativeEnqueuer = + compiler.backend.nativeResolutionEnqueuer(resolution); + } +} + +abstract class Enqueuer { + final String name; + final Compiler compiler; // TODO(ahe): Remove this dependency. + final Function itemCompilationContextCreator; + final Map<String, Link<Element>> instanceMembersByName; + final Set<ClassElement> seenClasses; + final Universe universe; + + bool queueIsClosed = false; + EnqueueTask task; + native.NativeEnqueuer nativeEnqueuer; // Set by EnqueueTask + + Enqueuer(this.name, this.compiler, + ItemCompilationContext itemCompilationContextCreator()) + : this.itemCompilationContextCreator = itemCompilationContextCreator, + instanceMembersByName = new Map<String, Link<Element>>(), + universe = new Universe(), + seenClasses = new Set<ClassElement>(); + + /// Returns [:true:] if this enqueuer is the resolution enqueuer. + bool get isResolutionQueue => false; + + /// Returns [:true:] if [member] has been processed by this enqueuer. + bool isProcessed(Element member); + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void addToWorkList(Element element, [TreeElements elements]) { + assert(invariant(element, element.isDeclaration)); + if (element.isForeign(compiler)) return; + + if (!addElementToWorkList(element, elements)) return; + + // Enable runtime type support if we discover a getter called runtimeType. + // We have to enable runtime type before hitting the codegen, so + // that constructors know whether they need to generate code for + // runtime type. + if (element.isGetter() && element.name == Compiler.RUNTIME_TYPE) { + compiler.enabledRuntimeType = true; + } else if (element == compiler.functionApplyMethod) { + compiler.enabledFunctionApply = true; + } else if (element == compiler.invokeOnMethod) { + compiler.enabledInvokeOn = true; + } + + nativeEnqueuer.registerElement(element); + } + + /** + * Adds [element] to the work list if it has not already been processed. + * + * Returns [:true:] if the [element] should be processed. + */ + // TODO(johnniwinther): Change to 'Returns true if the element was added to + // the work list'? + bool addElementToWorkList(Element element, [TreeElements elements]); + + void registerInstantiatedClass(ClassElement cls) { + if (universe.instantiatedClasses.contains(cls)) return; + if (!cls.isAbstract(compiler)) { + universe.instantiatedClasses.add(cls); + onRegisterInstantiatedClass(cls); + } + compiler.backend.registerInstantiatedClass(cls, this); + } + + bool checkNoEnqueuedInvokedInstanceMethods() { + task.measure(() { + // Run through the classes and see if we need to compile methods. + for (ClassElement classElement in universe.instantiatedClasses) { + for (ClassElement currentClass = classElement; + currentClass != null; + currentClass = currentClass.superclass) { + processInstantiatedClass(currentClass); + } + } + }); + return true; + } + + void processInstantiatedClass(ClassElement cls) { + cls.implementation.forEachMember(processInstantiatedClassMember); + } + + /** + * Documentation wanted -- johnniwinther + */ + void processInstantiatedClassMember(ClassElement cls, Element member) { + assert(invariant(member, member.isDeclaration)); + if (isProcessed(member)) return; + if (!member.isInstanceMember()) return; + if (member.isField()) { + // Native fields need to go into instanceMembersByName as they are virtual + // instantiation points and escape points. + // Test the enclosing class, since the metadata has not been parsed yet. + if (!member.enclosingElement.isNative()) return; + } + + String memberName = member.name.slowToString(); + Link<Element> members = instanceMembersByName.putIfAbsent( + memberName, () => const Link<Element>()); + instanceMembersByName[memberName] = members.prepend(member); + + if (member.kind == ElementKind.FUNCTION) { + if (member.name == Compiler.NO_SUCH_METHOD) { + enableNoSuchMethod(member); + } + if (universe.hasInvocation(member, compiler)) { + return addToWorkList(member); + } + // If there is a property access with the same name as a method we + // need to emit the method. + if (universe.hasInvokedGetter(member, compiler)) { + // We will emit a closure, so make sure the closure class is + // generated. + compiler.closureClass.ensureResolved(compiler); + registerInstantiatedClass(compiler.closureClass); + return addToWorkList(member); + } + } else if (member.kind == ElementKind.GETTER) { + if (universe.hasInvokedGetter(member, compiler)) { + return addToWorkList(member); + } + // We don't know what selectors the returned closure accepts. If + // the set contains any selector we have to assume that it matches. + if (universe.hasInvocation(member, compiler)) { + return addToWorkList(member); + } + } else if (member.kind == ElementKind.SETTER) { + if (universe.hasInvokedSetter(member, compiler)) { + return addToWorkList(member); + } + } else if (member.kind == ElementKind.FIELD && + member.enclosingElement.isNative()) { + nativeEnqueuer.handleFieldAnnotations(member); + if (universe.hasInvokedGetter(member, compiler) || + universe.hasInvocation(member, compiler)) { + nativeEnqueuer.registerFieldLoad(member); + // In handleUnseenSelector we can't tell if the field is loaded or + // stored. We need the basic algorithm to be Church-Rosser, since the + // resolution 'reduction' order is different to the codegen order. So + // register that the field is also stored. In other words: if we don't + // register the store here during resolution, the store could be + // registered during codegen on the handleUnseenSelector path, and cause + // the set of codegen elements to include unresolved elements. + nativeEnqueuer.registerFieldStore(member); + } + if (universe.hasInvokedSetter(member, compiler)) { + nativeEnqueuer.registerFieldStore(member); + // See comment after registerFieldLoad above. + nativeEnqueuer.registerFieldLoad(member); + } + } + } + + void enableNoSuchMethod(Element element) {} + + void onRegisterInstantiatedClass(ClassElement cls) { + task.measure(() { + // The class must be resolved to compute the set of all + // supertypes. + cls.ensureResolved(compiler); + + void processClass(ClassElement cls) { + if (seenClasses.contains(cls)) return; + + seenClasses.add(cls); + cls.ensureResolved(compiler); + cls.implementation.forEachMember(processInstantiatedClassMember); + if (isResolutionQueue) { + compiler.resolver.checkClass(cls); + } + + if (compiler.enableTypeAssertions) { + // We need to register is checks and helpers for checking + // assignments to fields. + // TODO(ngeoffray): This should really move to the backend. + cls.forEachLocalMember((Element member) { + if (!member.isInstanceMember() || !member.isField()) return; + DartType type = member.computeType(compiler); + registerIsCheck(type); + SourceString helper = compiler.backend.getCheckedModeHelper(type); + if (helper != null) { + Element helperElement = compiler.findHelper(helper); + registerStaticUse(helperElement); + } + }); + } + } + processClass(cls); + for (Link<DartType> supertypes = cls.allSupertypes; + !supertypes.isEmpty; supertypes = supertypes.tail) { + processClass(supertypes.head.element); + } + }); + } + + void registerNewSelector(SourceString name, + Selector selector, + Map<SourceString, Set<Selector>> selectorsMap) { + if (name != selector.name) { + String message = "$name != ${selector.name} (${selector.kind})"; + compiler.internalError("Wrong selector name: $message."); + } + Set<Selector> selectors = + selectorsMap.putIfAbsent(name, () => new Set<Selector>()); + if (!selectors.contains(selector)) { + selectors.add(selector); + handleUnseenSelector(name, selector); + } + } + + void registerInvocation(SourceString methodName, Selector selector) { + task.measure(() { + registerNewSelector(methodName, selector, universe.invokedNames); + }); + } + + void registerInvokedGetter(SourceString getterName, Selector selector) { + task.measure(() { + registerNewSelector(getterName, selector, universe.invokedGetters); + }); + } + + void registerInvokedSetter(SourceString setterName, Selector selector) { + task.measure(() { + registerNewSelector(setterName, selector, universe.invokedSetters); + }); + } + + processInstanceMembers(SourceString n, bool f(Element e)) { + String memberName = n.slowToString(); + Link<Element> members = instanceMembersByName[memberName]; + if (members != null) { + LinkBuilder<Element> remaining = new LinkBuilder<Element>(); + for (; !members.isEmpty; members = members.tail) { + if (!f(members.head)) remaining.addLast(members.head); + } + instanceMembersByName[memberName] = remaining.toLink(); + } + } + + void handleUnseenSelector(SourceString methodName, Selector selector) { + processInstanceMembers(methodName, (Element member) { + if (selector.appliesUnnamed(member, compiler)) { + if (member.isField() && member.enclosingElement.isNative()) { + if (selector.isGetter() || selector.isCall()) { + nativeEnqueuer.registerFieldLoad(member); + // We have to also handle storing to the field because we only get + // one look at each member and there might be a store we have not + // seen yet. + // TODO(sra): Process fields for storing separately. + nativeEnqueuer.registerFieldStore(member); + } else { + nativeEnqueuer.registerFieldStore(member); + // We have to also handle loading from the field because we only get + // one look at each member and there might be a load we have not + // seen yet. + // TODO(sra): Process fields for storing separately. + nativeEnqueuer.registerFieldLoad(member); + } + } else { + addToWorkList(member); + } + return true; + } + return false; + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void registerStaticUse(Element element) { + if (element == null) return; + assert(invariant(element, element.isDeclaration)); + addToWorkList(element); + } + + void registerGetOfStaticFunction(FunctionElement element) { + registerStaticUse(element); + universe.staticFunctionsNeedingGetter.add(element); + } + + void registerDynamicInvocation(SourceString methodName, Selector selector) { + assert(selector != null); + registerInvocation(methodName, selector); + } + + void registerDynamicInvocationOf(Element element, Selector selector) { + assert(selector.isCall() + || selector.isOperator() + || selector.isIndex() + || selector.isIndexSet()); + if (element.isFunction()) { + addToWorkList(element); + } else if (element.isAbstractField()) { + AbstractFieldElement field = element; + // Since the invocation is a dynamic call on a getter, we only + // need to schedule the getter on the work list. + addToWorkList(field.getter); + } else { + assert(element.isField()); + } + // We also need to add the selector to the invoked names map, + // because the emitter uses that map to generate parameter stubs. + Set<Selector> selectors = universe.invokedNames.putIfAbsent( + element.name, () => new Set<Selector>()); + selectors.add(selector); + } + + void registerDynamicGetter(SourceString methodName, Selector selector) { + registerInvokedGetter(methodName, selector); + } + + void registerDynamicSetter(SourceString methodName, Selector selector) { + registerInvokedSetter(methodName, selector); + } + + void registerFieldGetter(SourceString getterName, + LibraryElement library, + DartType type) { + task.measure(() { + Selector getter = new Selector.getter(getterName, library); + registerNewSelector(getterName, + new TypedSelector(type, getter), + universe.fieldGetters); + }); + } + + void registerFieldSetter(SourceString setterName, + LibraryElement library, + DartType type) { + task.measure(() { + Selector setter = new Selector.setter(setterName, library); + registerNewSelector(setterName, + new TypedSelector(type, setter), + universe.fieldSetters); + }); + } + + void registerIsCheck(DartType type) { + universe.isChecks.add(type); + } + + void forEach(f(WorkItem work)); + + void logSummary(log(message)) { + _logSpecificSummary(log); + nativeEnqueuer.logSummary(log); + } + + /// Log summary specific to the concrete enqueuer. + void _logSpecificSummary(log(message)); + + String toString() => 'Enqueuer($name)'; +} + +/// [Enqueuer] which is specific to resolution. +class ResolutionEnqueuer extends Enqueuer { + /** + * Map from declaration elements to the [TreeElements] object holding the + * resolution mapping for the element implementation. + * + * Invariant: Key elements are declaration elements. + */ + final Map<Element, TreeElements> resolvedElements; + + final Queue<ResolutionWorkItem> queue; + + ResolutionEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('resolution enqueuer', compiler, itemCompilationContextCreator), + resolvedElements = new Map<Element, TreeElements>(), + queue = new Queue<ResolutionWorkItem>(); + + bool get isResolutionQueue => true; + + bool isProcessed(Element member) => resolvedElements.containsKey(member); + + TreeElements getCachedElements(Element element) { + // TODO(ngeoffray): Get rid of this check. + if (element.enclosingElement.isClosure()) { + closureMapping.ClosureClassElement cls = element.enclosingElement; + element = cls.methodElement; + } + Element owner = element.getOutermostEnclosingMemberOrTopLevel(); + return resolvedElements[owner.declaration]; + } + + /** + * Sets the resolved elements of [element] to [elements], or if [elements] is + * [:null:], to the elements found through [getCachedElements]. + * + * Returns the resolved elements. + */ + TreeElements ensureCachedElements(Element element, TreeElements elements) { + if (elements == null) { + elements = getCachedElements(element); + } + resolvedElements[element] = elements; + return elements; + } + + bool addElementToWorkList(Element element, [TreeElements elements]) { + if (queueIsClosed) { + if (getCachedElements(element) != null) return false; + throw new SpannableAssertionFailure(element, + "Resolution work list is closed."); + } + if (elements == null) { + elements = getCachedElements(element); + } + compiler.world.registerUsedElement(element); + + if (elements == null) { + queue.add( + new ResolutionWorkItem(element, itemCompilationContextCreator())); + } + + // Enable isolate support if we start using something from the + // isolate library, or timers for the async library. + LibraryElement library = element.getLibrary(); + if (!compiler.hasIsolateSupport()) { + String uri = library.canonicalUri.toString(); + if (uri == 'dart:isolate') { + enableIsolateSupport(library); + } else if (uri == 'dart:async') { + ClassElement cls = element.getEnclosingClass(); + if (cls != null && cls.name == const SourceString('Timer')) { + // The [:Timer:] class uses the event queue of the isolate + // library, so we make sure that event queue is generated. + enableIsolateSupport(library); + } + } + } + + return true; + } + + void enableIsolateSupport(LibraryElement element) { + compiler.isolateLibrary = element.patch; + addToWorkList( + compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE)); + addToWorkList(compiler.isolateHelperLibrary.find( + const SourceString('_currentIsolate'))); + addToWorkList(compiler.isolateHelperLibrary.find( + const SourceString('_callInIsolate'))); + } + + void enableNoSuchMethod(Element element) { + if (compiler.enabledNoSuchMethod) return; + Selector selector = new Selector.noSuchMethod(); + if (identical(element.getEnclosingClass(), compiler.objectClass)) { + registerDynamicInvocationOf(element, selector); + return; + } + compiler.enabledNoSuchMethod = true; + registerInvocation(Compiler.NO_SUCH_METHOD, selector); + + compiler.createInvocationMirrorElement = + compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR); + addToWorkList(compiler.createInvocationMirrorElement); + } + + void forEach(f(WorkItem work)) { + while (!queue.isEmpty) { + // TODO(johnniwinther): Find an optimal process order for resolution. + f(queue.removeLast()); + } + } + + void registerJsCall(Send node, ResolverVisitor resolver) { + nativeEnqueuer.registerJsCall(node, resolver); + } + + void _logSpecificSummary(log(message)) { + log('Resolved ${resolvedElements.length} elements.'); + } +} + +/// [Enqueuer] which is specific to code generation. +class CodegenEnqueuer extends Enqueuer { + final Queue<CodegenWorkItem> queue; + final Map<Element, js.Expression> generatedCode = + new Map<Element, js.Expression>(); + + CodegenEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('codegen enqueuer', compiler, itemCompilationContextCreator), + queue = new Queue<CodegenWorkItem>(); + + bool isProcessed(Element member) => generatedCode.containsKey(member); + + bool addElementToWorkList(Element element, [TreeElements elements]) { + if (queueIsClosed) { + throw new SpannableAssertionFailure(element, + "Codegen work list is closed."); + } + elements = + compiler.enqueuer.resolution.ensureCachedElements(element, elements); + + CodegenWorkItem workItem = new CodegenWorkItem( + element, elements, itemCompilationContextCreator()); + queue.add(workItem); + + return true; + } + + void forEach(f(WorkItem work)) { + while(!queue.isEmpty) { + // TODO(johnniwinther): Find an optimal process order for codegen. + f(queue.removeLast()); + } + } + + void _logSpecificSummary(log(message)) { + log('Compiled ${generatedCode.length} methods.'); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart b/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart new file mode 100644 index 0000000..198a28f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart
@@ -0,0 +1,29 @@ +// 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. + +library filenames; + +import 'dart:io'; +import 'dart:uri'; + +// TODO(ahe): This library should be replaced by a general +// path-munging library. +// +// See also: +// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx + +String nativeToUriPath(String filename) { + return new Path(filename).toString(); +} + +String uriPathToNative(String path) { + return new Path(path).toNativePath(); +} + +Uri getCurrentDirectory() { + final String dir = nativeToUriPath(new File('.').fullPathSync()); + return new Uri.fromComponents(scheme: 'file', path: appendSlash(dir)); +} + +String appendSlash(String path) => path.endsWith('/') ? path : '$path/';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart new file mode 100644 index 0000000..b7df642 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart
@@ -0,0 +1,15 @@ +// 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. + +library js; + +import 'precedence.dart'; +import '../util/characters.dart' as charCodes; + +// TODO(floitsch): remove this dependency (currently necessary for the +// CodeBuffer). +import '../dart2jslib.dart' as leg; + +part 'nodes.dart'; +part 'printer.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart new file mode 100644 index 0000000..da8cd44 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart
@@ -0,0 +1,906 @@ +// 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. + +part of js; + +abstract class NodeVisitor<T> { + T visitProgram(Program node); + + T visitBlock(Block node); + T visitExpressionStatement(ExpressionStatement node); + T visitEmptyStatement(EmptyStatement node); + T visitIf(If node); + T visitFor(For node); + T visitForIn(ForIn node); + T visitWhile(While node); + T visitDo(Do node); + T visitContinue(Continue node); + T visitBreak(Break node); + T visitReturn(Return node); + T visitThrow(Throw node); + T visitTry(Try node); + T visitCatch(Catch node); + T visitSwitch(Switch node); + T visitCase(Case node); + T visitDefault(Default node); + T visitFunctionDeclaration(FunctionDeclaration node); + T visitLabeledStatement(LabeledStatement node); + T visitLiteralStatement(LiteralStatement node); + + T visitLiteralExpression(LiteralExpression node); + T visitVariableDeclarationList(VariableDeclarationList node); + T visitSequence(Sequence node); + T visitAssignment(Assignment node); + T visitVariableInitialization(VariableInitialization node); + T visitConditional(Conditional cond); + T visitNew(New node); + T visitCall(Call node); + T visitBinary(Binary node); + T visitPrefix(Prefix node); + T visitPostfix(Postfix node); + + T visitVariableUse(VariableUse node); + T visitThis(This node); + T visitVariableDeclaration(VariableDeclaration node); + T visitParameter(Parameter node); + T visitAccess(PropertyAccess node); + + T visitNamedFunction(NamedFunction node); + T visitFun(Fun node); + + T visitLiteralBool(LiteralBool node); + T visitLiteralString(LiteralString node); + T visitLiteralNumber(LiteralNumber node); + T visitLiteralNull(LiteralNull node); + + T visitArrayInitializer(ArrayInitializer node); + T visitArrayElement(ArrayElement node); + T visitObjectInitializer(ObjectInitializer node); + T visitProperty(Property node); + T visitRegExpLiteral(RegExpLiteral node); +} + +class BaseVisitor<T> implements NodeVisitor<T> { + T visitNode(Node node) { + node.visitChildren(this); + return null; + } + + T visitProgram(Program node) => visitNode(node); + + T visitStatement(Statement node) => visitNode(node); + T visitLoop(Loop node) => visitStatement(node); + T visitJump(Statement node) => visitStatement(node); + + T visitBlock(Block node) => visitStatement(node); + T visitExpressionStatement(ExpressionStatement node) + => visitStatement(node); + T visitEmptyStatement(EmptyStatement node) => visitStatement(node); + T visitIf(If node) => visitStatement(node); + T visitFor(For node) => visitLoop(node); + T visitForIn(ForIn node) => visitLoop(node); + T visitWhile(While node) => visitLoop(node); + T visitDo(Do node) => visitLoop(node); + T visitContinue(Continue node) => visitJump(node); + T visitBreak(Break node) => visitJump(node); + T visitReturn(Return node) => visitJump(node); + T visitThrow(Throw node) => visitJump(node); + T visitTry(Try node) => visitStatement(node); + T visitSwitch(Switch node) => visitStatement(node); + T visitFunctionDeclaration(FunctionDeclaration node) + => visitStatement(node); + T visitLabeledStatement(LabeledStatement node) => visitStatement(node); + T visitLiteralStatement(LiteralStatement node) => visitStatement(node); + + T visitCatch(Catch node) => visitNode(node); + T visitCase(Case node) => visitNode(node); + T visitDefault(Default node) => visitNode(node); + + T visitExpression(Expression node) => visitNode(node); + T visitVariableReference(VariableReference node) => visitExpression(node); + + T visitLiteralExpression(LiteralExpression node) => visitExpression(node); + T visitVariableDeclarationList(VariableDeclarationList node) + => visitExpression(node); + T visitSequence(Sequence node) => visitExpression(node); + T visitAssignment(Assignment node) => visitExpression(node); + T visitVariableInitialization(VariableInitialization node) { + if (node.value != null) { + visitAssignment(node); + } else { + visitExpression(node); + } + } + T visitConditional(Conditional node) => visitExpression(node); + T visitNew(New node) => visitExpression(node); + T visitCall(Call node) => visitExpression(node); + T visitBinary(Binary node) => visitCall(node); + T visitPrefix(Prefix node) => visitCall(node); + T visitPostfix(Postfix node) => visitCall(node); + T visitAccess(PropertyAccess node) => visitExpression(node); + + T visitVariableUse(VariableUse node) => visitVariableReference(node); + T visitVariableDeclaration(VariableDeclaration node) + => visitVariableReference(node); + T visitParameter(Parameter node) => visitVariableDeclaration(node); + T visitThis(This node) => visitParameter(node); + + T visitNamedFunction(NamedFunction node) => visitExpression(node); + T visitFun(Fun node) => visitExpression(node); + + T visitLiteral(Literal node) => visitExpression(node); + + T visitLiteralBool(LiteralBool node) => visitLiteral(node); + T visitLiteralString(LiteralString node) => visitLiteral(node); + T visitLiteralNumber(LiteralNumber node) => visitLiteral(node); + T visitLiteralNull(LiteralNull node) => visitLiteral(node); + + T visitArrayInitializer(ArrayInitializer node) => visitExpression(node); + T visitArrayElement(ArrayElement node) => visitNode(node); + T visitObjectInitializer(ObjectInitializer node) => visitExpression(node); + T visitProperty(Property node) => visitNode(node); + T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node); +} + +abstract class Node { + var sourcePosition; + var endSourcePosition; + + accept(NodeVisitor visitor); + void visitChildren(NodeVisitor visitor); + + VariableUse asVariableUse() => null; +} + +class Program extends Node { + final List<Statement> body; + Program(this.body); + + accept(NodeVisitor visitor) => visitor.visitProgram(this); + void visitChildren(NodeVisitor visitor) { + for (Statement statement in body) statement.accept(visitor); + } +} + +abstract class Statement extends Node { +} + +class Block extends Statement { + final List<Statement> statements; + Block(this.statements); + Block.empty() : this.statements = <Statement>[]; + + accept(NodeVisitor visitor) => visitor.visitBlock(this); + void visitChildren(NodeVisitor visitor) { + for (Statement statement in statements) statement.accept(visitor); + } +} + +class ExpressionStatement extends Statement { + final Expression expression; + ExpressionStatement(this.expression); + + accept(NodeVisitor visitor) => visitor.visitExpressionStatement(this); + void visitChildren(NodeVisitor visitor) { expression.accept(visitor); } +} + +class EmptyStatement extends Statement { + EmptyStatement(); + + accept(NodeVisitor visitor) => visitor.visitEmptyStatement(this); + void visitChildren(NodeVisitor visitor) {} +} + +class If extends Statement { + final Expression condition; + final Node then; + final Node otherwise; + + If(this.condition, this.then, this.otherwise); + If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement(); + + bool get hasElse => otherwise is !EmptyStatement; + + accept(NodeVisitor visitor) => visitor.visitIf(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + then.accept(visitor); + otherwise.accept(visitor); + } +} + +abstract class Loop extends Statement { + final Statement body; + Loop(this.body); +} + +class For extends Loop { + final Expression init; + final Expression condition; + final Expression update; + + For(this.init, this.condition, this.update, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitFor(this); + + void visitChildren(NodeVisitor visitor) { + if (init != null) init.accept(visitor); + if (condition != null) condition.accept(visitor); + if (update != null) update.accept(visitor); + body.accept(visitor); + } +} + +class ForIn extends Loop { + // Note that [VariableDeclarationList] is a subclass of [Expression]. + // Therefore we can type the leftHandSide as [Expression]. + final Expression leftHandSide; + final Expression object; + + ForIn(this.leftHandSide, this.object, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitForIn(this); + + void visitChildren(NodeVisitor visitor) { + leftHandSide.accept(visitor); + object.accept(visitor); + body.accept(visitor); + } +} + +class While extends Loop { + final Node condition; + + While(this.condition, Statement body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitWhile(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + body.accept(visitor); + } +} + +class Do extends Loop { + final Expression condition; + + Do(Statement body, this.condition) : super(body); + + accept(NodeVisitor visitor) => visitor.visitDo(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + condition.accept(visitor); + } +} + +class Continue extends Statement { + final String targetLabel; // Can be null. + + Continue(this.targetLabel); + + accept(NodeVisitor visitor) => visitor.visitContinue(this); + void visitChildren(NodeVisitor visitor) {} +} + +class Break extends Statement { + final String targetLabel; // Can be null. + + Break(this.targetLabel); + + accept(NodeVisitor visitor) => visitor.visitBreak(this); + void visitChildren(NodeVisitor visitor) {} +} + +class Return extends Statement { + final Expression value; // Can be null. + + Return([this.value = null]); + + accept(NodeVisitor visitor) => visitor.visitReturn(this); + + void visitChildren(NodeVisitor visitor) { + if (value != null) value.accept(visitor); + } +} + +class Throw extends Statement { + final Expression expression; + + Throw(this.expression); + + accept(NodeVisitor visitor) => visitor.visitThrow(this); + + void visitChildren(NodeVisitor visitor) { + expression.accept(visitor); + } +} + +class Try extends Statement { + final Block body; + final Catch catchPart; // Can be null if [finallyPart] is non-null. + final Block finallyPart; // Can be null if [catchPart] is non-null. + + Try(this.body, this.catchPart, this.finallyPart) { + assert(catchPart != null || finallyPart != null); + } + + accept(NodeVisitor visitor) => visitor.visitTry(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + if (catchPart != null) catchPart.accept(visitor); + if (finallyPart != null) finallyPart.accept(visitor); + } +} + +class Catch extends Node { + final VariableDeclaration declaration; + final Block body; + + Catch(this.declaration, this.body); + + accept(NodeVisitor visitor) => visitor.visitCatch(this); + + void visitChildren(NodeVisitor visitor) { + declaration.accept(visitor); + body.accept(visitor); + } +} + +class Switch extends Statement { + final Expression key; + final List<SwitchClause> cases; + + Switch(this.key, this.cases); + + accept(NodeVisitor visitor) => visitor.visitSwitch(this); + + void visitChildren(NodeVisitor visitor) { + key.accept(visitor); + for (SwitchClause clause in cases) clause.accept(visitor); + } +} + +abstract class SwitchClause extends Node { + final Block body; + + SwitchClause(this.body); +} + +class Case extends SwitchClause { + final Expression expression; + + Case(this.expression, Block body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitCase(this); + + void visitChildren(NodeVisitor visitor) { + expression.accept(visitor); + body.accept(visitor); + } +} + +class Default extends SwitchClause { + Default(Block body) : super(body); + + accept(NodeVisitor visitor) => visitor.visitDefault(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + } +} + +class FunctionDeclaration extends Statement { + final VariableDeclaration name; + final Fun function; + + FunctionDeclaration(this.name, this.function); + + accept(NodeVisitor visitor) => visitor.visitFunctionDeclaration(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + function.accept(visitor); + } +} + +class LabeledStatement extends Statement { + final String label; + final Statement body; + + LabeledStatement(this.label, this.body); + + accept(NodeVisitor visitor) => visitor.visitLabeledStatement(this); + + void visitChildren(NodeVisitor visitor) { + body.accept(visitor); + } +} + +class LiteralStatement extends Statement { + final String code; + + LiteralStatement(this.code); + + accept(NodeVisitor visitor) => visitor.visitLiteralStatement(this); + void visitChildren(NodeVisitor visitor) { } +} + +abstract class Expression extends Node { + int get precedenceLevel; + + PropertyAccess dot(String name) => new PropertyAccess.field(this, name); + Call callWith(List<Expression> arguments) => new Call(this, arguments); +} + +class LiteralExpression extends Expression { + final String template; + final List<Expression> inputs; + + LiteralExpression(this.template) : inputs = const []; + LiteralExpression.withData(this.template, this.inputs); + + accept(NodeVisitor visitor) => visitor.visitLiteralExpression(this); + + void visitChildren(NodeVisitor visitor) { + for (Expression expr in inputs) expr.accept(visitor); + } + + // Code that uses JS must take care of operator precedences, and + // put parenthesis if needed. + int get precedenceLevel => PRIMARY; +} + +/** + * [VariableDeclarationList] is a subclass of [Expression] to simplify the + * AST. + */ +class VariableDeclarationList extends Expression { + final List<VariableInitialization> declarations; + + VariableDeclarationList(this.declarations); + + accept(NodeVisitor visitor) => visitor.visitVariableDeclarationList(this); + + void visitChildren(NodeVisitor visitor) { + for (VariableInitialization declaration in declarations) { + declaration.accept(visitor); + } + } + + int get precedenceLevel => EXPRESSION; +} + +class Sequence extends Expression { + final List<Expression> expressions; + + Sequence(this.expressions); + + accept(NodeVisitor visitor) => visitor.visitSequence(this); + + void visitChildren(NodeVisitor visitor) { + for (Expression expr in expressions) expr.accept(visitor); + } + + int get precedenceLevel => EXPRESSION; +} + +class Assignment extends Expression { + final Expression leftHandSide; + // Null, if the assignment is not compound. + final VariableReference compoundTarget; + final Expression value; // May be null, for [VariableInitialization]s. + + Assignment(this.leftHandSide, this.value) : compoundTarget = null; + Assignment.compound(this.leftHandSide, String op, this.value) + : compoundTarget = new VariableUse(op); + + int get precedenceLevel => ASSIGNMENT; + + bool get isCompound => compoundTarget != null; + String get op => compoundTarget == null ? null : compoundTarget.name; + + accept(NodeVisitor visitor) => visitor.visitAssignment(this); + + void visitChildren(NodeVisitor visitor) { + leftHandSide.accept(visitor); + if (compoundTarget != null) compoundTarget.accept(visitor); + if (value != null) value.accept(visitor); + } +} + +class VariableInitialization extends Assignment { + /** [value] may be null. */ + VariableInitialization(VariableDeclaration declaration, Expression value) + : super(declaration, value); + + VariableDeclaration get declaration => leftHandSide; + + accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this); +} + +class Conditional extends Expression { + final Expression condition; + final Expression then; + final Expression otherwise; + + Conditional(this.condition, this.then, this.otherwise); + + accept(NodeVisitor visitor) => visitor.visitConditional(this); + + void visitChildren(NodeVisitor visitor) { + condition.accept(visitor); + then.accept(visitor); + otherwise.accept(visitor); + } + + int get precedenceLevel => ASSIGNMENT; +} + +class Call extends Expression { + Expression target; + List<Expression> arguments; + + Call(this.target, this.arguments); + + accept(NodeVisitor visitor) => visitor.visitCall(this); + + void visitChildren(NodeVisitor visitor) { + target.accept(visitor); + for (Expression arg in arguments) arg.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class New extends Call { + New(Expression cls, List<Expression> arguments) : super(cls, arguments); + + accept(NodeVisitor visitor) => visitor.visitNew(this); +} + +class Binary extends Call { + Binary(String op, Expression left, Expression right) + : super(new VariableUse(op), <Expression>[left, right]); + + String get op { + VariableUse use = target; + return use.name; + } + + Expression get left => arguments[0]; + Expression get right => arguments[1]; + + accept(NodeVisitor visitor) => visitor.visitBinary(this); + + int get precedenceLevel { + // TODO(floitsch): switch to constant map. + switch (op) { + case "*": + case "/": + case "%": + return MULTIPLICATIVE; + case "+": + case "-": + return ADDITIVE; + case "<<": + case ">>": + case ">>>": + return SHIFT; + case "<": + case ">": + case "<=": + case ">=": + case "instanceof": + case "in": + return RELATIONAL; + case "==": + case "===": + case "!=": + case "!==": + return EQUALITY; + case "&": + return BIT_AND; + case "^": + return BIT_XOR; + case "|": + return BIT_OR; + case "&&": + return LOGICAL_AND; + case "||": + return LOGICAL_OR; + default: + throw new leg.CompilerCancelledException( + "Internal Error: Unhandled binary operator: $op"); + } + } +} + +class Prefix extends Call { + Prefix(String op, Expression arg) + : super(new VariableUse(op), <Expression>[arg]); + + String get op => (target as VariableUse).name; + Expression get argument => arguments[0]; + + accept(NodeVisitor visitor) => visitor.visitPrefix(this); + + int get precedenceLevel => UNARY; +} + +class Postfix extends Call { + Postfix(String op, Expression arg) + : super(new VariableUse(op), <Expression>[arg]); + + String get op => (target as VariableUse).name; + Expression get argument => arguments[0]; + + accept(NodeVisitor visitor) => visitor.visitPostfix(this); + + int get precedenceLevel => UNARY; +} + +abstract class VariableReference extends Expression { + final String name; + + // We treat operators as if they were special functions. They can thus be + // referenced like other variables. + VariableReference(this.name); + + accept(NodeVisitor visitor); + int get precedenceLevel => PRIMARY; + void visitChildren(NodeVisitor visitor) {} +} + +class VariableUse extends VariableReference { + VariableUse(String name) : super(name); + + accept(NodeVisitor visitor) => visitor.visitVariableUse(this); + + VariableUse asVariableUse() => this; +} + +class VariableDeclaration extends VariableReference { + VariableDeclaration(String name) : super(name); + + accept(NodeVisitor visitor) => visitor.visitVariableDeclaration(this); +} + +class Parameter extends VariableDeclaration { + Parameter(String id) : super(id); + + accept(NodeVisitor visitor) => visitor.visitParameter(this); +} + +class This extends Parameter { + This() : super("this"); + + accept(NodeVisitor visitor) => visitor.visitThis(this); +} + +class NamedFunction extends Expression { + final VariableDeclaration name; + final Fun function; + + NamedFunction(this.name, this.function); + + accept(NodeVisitor visitor) => visitor.visitNamedFunction(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + function.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class Fun extends Expression { + final List<Parameter> params; + final Block body; + + Fun(this.params, this.body); + + accept(NodeVisitor visitor) => visitor.visitFun(this); + + void visitChildren(NodeVisitor visitor) { + for (Parameter param in params) param.accept(visitor); + body.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +class PropertyAccess extends Expression { + final Expression receiver; + final Expression selector; + + PropertyAccess(this.receiver, this.selector); + PropertyAccess.field(this.receiver, String fieldName) + : selector = new LiteralString("'$fieldName'"); + PropertyAccess.indexed(this.receiver, int index) + : selector = new LiteralNumber('$index'); + + accept(NodeVisitor visitor) => visitor.visitAccess(this); + + void visitChildren(NodeVisitor visitor) { + receiver.accept(visitor); + selector.accept(visitor); + } + + int get precedenceLevel => CALL; +} + +abstract class Literal extends Expression { + void visitChildren(NodeVisitor visitor) {} + + int get precedenceLevel => PRIMARY; +} + +class LiteralBool extends Literal { + final bool value; + + LiteralBool(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralBool(this); + // [visitChildren] inherited from [Literal]. +} + +class LiteralNull extends Literal { + LiteralNull(); + + accept(NodeVisitor visitor) => visitor.visitLiteralNull(this); +} + +class LiteralString extends Literal { + final String value; + + LiteralString(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralString(this); +} + +class LiteralNumber extends Literal { + final String value; + + LiteralNumber(this.value); + + accept(NodeVisitor visitor) => visitor.visitLiteralNumber(this); +} + +class ArrayInitializer extends Expression { + final int length; + // We represent the array as sparse list of elements. Each element knows its + // position in the array. + final List<ArrayElement> elements; + + ArrayInitializer(this.length, this.elements); + + factory ArrayInitializer.from(Iterable<Expression> expressions) => + new ArrayInitializer(expressions.length, _convert(expressions)); + + accept(NodeVisitor visitor) => visitor.visitArrayInitializer(this); + + void visitChildren(NodeVisitor visitor) { + for (ArrayElement element in elements) element.accept(visitor); + } + + int get precedenceLevel => PRIMARY; + + static List<ArrayElement> _convert(Iterable<Expression> expressions) { + int index = 0; + return expressions.map( + (expression) => new ArrayElement(index++, expression)) + .toList(); + } +} + +/** + * An expression inside an [ArrayInitialization]. An [ArrayElement] knows + * its position in the containing [ArrayInitialization]. + */ +class ArrayElement extends Node { + int index; + Expression value; + + ArrayElement(this.index, this.value); + + accept(NodeVisitor visitor) => visitor.visitArrayElement(this); + + void visitChildren(NodeVisitor visitor) { + value.accept(visitor); + } +} + +class ObjectInitializer extends Expression { + List<Property> properties; + + ObjectInitializer(this.properties); + + accept(NodeVisitor visitor) => visitor.visitObjectInitializer(this); + + void visitChildren(NodeVisitor visitor) { + for (Property init in properties) init.accept(visitor); + } + + int get precedenceLevel => PRIMARY; +} + +class Property extends Node { + Literal name; + Expression value; + + Property(this.name, this.value); + + accept(NodeVisitor visitor) => visitor.visitProperty(this); + + void visitChildren(NodeVisitor visitor) { + name.accept(visitor); + value.accept(visitor); + } +} + +/** + * [RegExpLiteral]s, despite being called "Literal", are not inheriting from + * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and + * are thus not in the same category as numbers or strings. + */ +class RegExpLiteral extends Expression { + /** Contains the pattern and the flags.*/ + String pattern; + + RegExpLiteral(this.pattern); + + accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this); + void visitChildren(NodeVisitor visitor) {} + + int get precedenceLevel => PRIMARY; +} + +Prefix typeOf(Expression argument) => new Prefix('typeof', argument); + +Binary equals(Expression left, Expression right) { + return new Binary('==', left, right); +} + +Binary strictEquals(Expression left, Expression right) { + return new Binary('===', left, right); +} + +LiteralString string(String value) => new LiteralString('"$value"'); + +If if_(Expression condition, Node then, [Node otherwise]) { + return (otherwise == null) + ? new If.noElse(condition, then) + : new If(condition, then, otherwise); +} + +Return return_([Expression value]) => new Return(value); + +VariableUse use(String name) => new VariableUse(name); + +PropertyAccess fieldAccess(Expression receiver, String fieldName) { + return new PropertyAccess.field(receiver, fieldName); +} + +Block emptyBlock() => new Block.empty(); + +Block block1(Statement statement) => new Block(<Statement>[statement]); + +Block block2(Statement s1, Statement s2) => new Block(<Statement>[s1, s2]); + +Call call(Expression target, List<Expression> arguments) { + return new Call(target, arguments); +} + +Fun fun(List<String> parameterNames, Block body) { + return new Fun(parameterNames.map((n) => new Parameter(n)).toList(), body); +} + +Assignment assign(Expression leftHandSide, Expression value) { + return new Assignment(leftHandSide, value); +} + +Expression undefined() => new Prefix('void', new LiteralNumber('0'));
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart new file mode 100644 index 0000000..6d66f1f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart
@@ -0,0 +1,25 @@ +// 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. + +library precedence; + +const EXPRESSION = 0; +const ASSIGNMENT = EXPRESSION + 1; +const LOGICAL_OR = ASSIGNMENT + 1; +const LOGICAL_AND = LOGICAL_OR + 1; +const BIT_OR = LOGICAL_AND + 1; +const BIT_XOR = BIT_OR + 1; +const BIT_AND = BIT_XOR + 1; +const EQUALITY = BIT_AND + 1; +const RELATIONAL = EQUALITY + 1; +const SHIFT = RELATIONAL + 1; +const ADDITIVE = SHIFT + 1; +const MULTIPLICATIVE = ADDITIVE + 1; +const UNARY = MULTIPLICATIVE + 1; +const LEFT_HAND_SIDE = UNARY + 1; +// We merge new, call and member expressions. +// This means that we have to emit parenthesis for 'new's. For example `new X;` +// should be printed as `new X();`. This simplifies the requirements. +const CALL = LEFT_HAND_SIDE; +const PRIMARY = CALL + 1;
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart new file mode 100644 index 0000000..504933d --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart
@@ -0,0 +1,1111 @@ +// 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. + +part of js; + +class Printer implements NodeVisitor { + final bool shouldCompressOutput; + leg.Compiler compiler; + leg.CodeBuffer outBuffer; + int indentLevel = 0; + bool inForInit = false; + bool atStatementBegin = false; + final DanglingElseVisitor danglingElseVisitor; + final LocalNamer localNamer; + bool pendingSemicolon = false; + bool pendingSpace = false; + static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]'); + static final expressionContinuationRegExp = new RegExp(r'^[-+([]'); + + Printer(leg.Compiler compiler, { allowVariableMinification: true }) + : shouldCompressOutput = compiler.enableMinification, + this.compiler = compiler, + outBuffer = new leg.CodeBuffer(), + danglingElseVisitor = new DanglingElseVisitor(compiler), + localNamer = determineRenamer(compiler.enableMinification, + allowVariableMinification); + + static LocalNamer determineRenamer(bool shouldCompressOutput, + bool allowVariableMinification) { + return (shouldCompressOutput && allowVariableMinification) + ? new MinifyRenamer() : new IdentityNamer(); + } + + /// Always emit a newline, even under `enableMinification`. + void forceLine() { + out("\n"); + } + /// Emits a newline for readability. + void lineOut() { + if (!shouldCompressOutput) forceLine(); + } + void spaceOut() { + if (!shouldCompressOutput) out(" "); + } + + String lastAddedString = null; + int get lastCharCode { + if (lastAddedString == null) return 0; + assert(lastAddedString.length != ""); + return lastAddedString.charCodeAt(lastAddedString.length - 1); + } + + void out(String str) { + if (str != "") { + if (pendingSemicolon) { + if (!shouldCompressOutput) { + outBuffer.add(";"); + } else if (str != "}") { + // We want to output newline instead of semicolon because it makes + // the raw stack traces much easier to read and it also makes line- + // based tools like diff work much better. JavaScript will + // automatically insert the semicolon at the newline if it means a + // parsing error is avoided, so we can only do this trick if the + // next line is not something that can be glued onto a valid + // expression to make a new valid expression. + if (expressionContinuationRegExp.hasMatch(str)) { + outBuffer.add(";"); + } else { + outBuffer.add("\n"); + } + } + } + if (pendingSpace && + (!shouldCompressOutput || identifierCharacterRegExp.hasMatch(str))) { + outBuffer.add(" "); + } + pendingSpace = false; + pendingSemicolon = false; + outBuffer.add(str); + lastAddedString = str; + } + } + + void outLn(String str) { + out(str); + lineOut(); + } + + void outSemicolonLn() { + if (shouldCompressOutput) { + pendingSemicolon = true; + } else { + out(";"); + forceLine(); + } + } + + void outIndent(String str) { indent(); out(str); } + void outIndentLn(String str) { indent(); outLn(str); } + void indent() { + if (!shouldCompressOutput) { + for (int i = 0; i < indentLevel; i++) out(" "); + } + } + + void recordSourcePosition(var position) { + if (position != null) { + outBuffer.setSourceLocation(position); + } + } + + visit(Node node) { + if (node.sourcePosition != null) outBuffer.beginMappedRange(); + recordSourcePosition(node.sourcePosition); + node.accept(this); + recordSourcePosition(node.endSourcePosition); + if (node.sourcePosition != null) outBuffer.endMappedRange(); + } + + visitCommaSeparated(List<Node> nodes, int hasRequiredType, + {bool newInForInit, bool newAtStatementBegin}) { + for (int i = 0; i < nodes.length; i++) { + if (i != 0) { + atStatementBegin = false; + out(","); + spaceOut(); + } + visitNestedExpression(nodes[i], hasRequiredType, + newInForInit: newInForInit, + newAtStatementBegin: newAtStatementBegin); + } + } + + visitAll(List<Node> nodes) { + nodes.forEach(visit); + } + + visitProgram(Program program) { + visitAll(program.body); + } + + bool blockBody(Node body, {bool needsSeparation, bool needsNewline}) { + if (body is Block) { + spaceOut(); + blockOut(body, false, needsNewline); + return true; + } + if (shouldCompressOutput && needsSeparation) { + // If [shouldCompressOutput] is false, then the 'lineOut' will insert + // the separation. + out(" "); + } else { + lineOut(); + } + indentLevel++; + visit(body); + indentLevel--; + return false; + } + + void blockOutWithoutBraces(Node node) { + if (node is Block) { + node.statements.forEach(blockOutWithoutBraces); + } else { + visit(node); + } + } + + void blockOut(Block node, bool shouldIndent, bool needsNewline) { + if (shouldIndent) indent(); + out("{"); + lineOut(); + indentLevel++; + node.statements.forEach(blockOutWithoutBraces); + indentLevel--; + indent(); + out("}"); + if (needsNewline) lineOut(); + } + + visitBlock(Block block) { + blockOut(block, true, true); + } + + visitExpressionStatement(ExpressionStatement expressionStatement) { + indent(); + visitNestedExpression(expressionStatement.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: true); + outSemicolonLn(); + } + + visitEmptyStatement(EmptyStatement nop) { + outIndentLn(";"); + } + + void ifOut(If node, bool shouldIndent) { + Node then = node.then; + Node elsePart = node.otherwise; + bool hasElse = node.hasElse; + + // Handle dangling elses. + if (hasElse) { + bool needsBraces = node.then.accept(danglingElseVisitor); + if (needsBraces) { + then = new Block(<Statement>[then]); + } + } + if (shouldIndent) indent(); + out("if"); + spaceOut(); + out("("); + visitNestedExpression(node.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + bool thenWasBlock = + blockBody(then, needsSeparation: false, needsNewline: !hasElse); + if (hasElse) { + if (thenWasBlock) { + spaceOut(); + } else { + indent(); + } + out("else"); + if (elsePart is If) { + pendingSpace = true; + ifOut(elsePart, false); + } else { + blockBody(elsePart, needsSeparation: true, needsNewline: true); + } + } + } + + visitIf(If node) { + ifOut(node, true); + } + + visitFor(For loop) { + outIndent("for"); + spaceOut(); + out("("); + if (loop.init != null) { + visitNestedExpression(loop.init, EXPRESSION, + newInForInit: true, newAtStatementBegin: false); + } + out(";"); + if (loop.condition != null) { + spaceOut(); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + out(";"); + if (loop.update != null) { + spaceOut(); + visitNestedExpression(loop.update, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitForIn(ForIn loop) { + outIndent("for"); + spaceOut(); + out("("); + visitNestedExpression(loop.leftHandSide, EXPRESSION, + newInForInit: true, newAtStatementBegin: false); + out(" in"); + pendingSpace = true; + visitNestedExpression(loop.object, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitWhile(While loop) { + outIndent("while"); + spaceOut(); + out("("); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(loop.body, needsSeparation: false, needsNewline: true); + } + + visitDo(Do loop) { + outIndent("do"); + if (blockBody(loop.body, needsSeparation: true, needsNewline: false)) { + spaceOut(); + } else { + indent(); + } + out("while"); + spaceOut(); + out("("); + visitNestedExpression(loop.condition, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + outSemicolonLn(); + } + + visitContinue(Continue node) { + if (node.targetLabel == null) { + outIndent("continue"); + } else { + outIndent("continue ${node.targetLabel}"); + } + outSemicolonLn(); + } + + visitBreak(Break node) { + if (node.targetLabel == null) { + outIndent("break"); + } else { + outIndent("break ${node.targetLabel}"); + } + outSemicolonLn(); + } + + visitReturn(Return node) { + if (node.value == null) { + outIndent("return"); + } else { + outIndent("return"); + pendingSpace = true; + visitNestedExpression(node.value, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + } + outSemicolonLn(); + } + + visitThrow(Throw node) { + outIndent("throw"); + pendingSpace = true; + visitNestedExpression(node.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + outSemicolonLn(); + } + + visitTry(Try node) { + outIndent("try"); + blockBody(node.body, needsSeparation: true, needsNewline: false); + if (node.catchPart != null) { + visit(node.catchPart); + } + if (node.finallyPart != null) { + spaceOut(); + out("finally"); + blockBody(node.finallyPart, needsSeparation: true, needsNewline: true); + } else { + lineOut(); + } + } + + visitCatch(Catch node) { + spaceOut(); + out("catch"); + spaceOut(); + out("("); + visitNestedExpression(node.declaration, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + blockBody(node.body, needsSeparation: false, needsNewline: true); + } + + visitSwitch(Switch node) { + outIndent("switch"); + spaceOut(); + out("("); + visitNestedExpression(node.key, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out(")"); + spaceOut(); + outLn("{"); + indentLevel++; + visitAll(node.cases); + indentLevel--; + outIndentLn("}"); + } + + visitCase(Case node) { + outIndent("case"); + pendingSpace = true; + visitNestedExpression(node.expression, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + outLn(":"); + if (!node.body.statements.isEmpty) { + indentLevel++; + blockOutWithoutBraces(node.body); + indentLevel--; + } + } + + visitDefault(Default node) { + outIndentLn("default:"); + if (!node.body.statements.isEmpty) { + indentLevel++; + blockOutWithoutBraces(node.body); + indentLevel--; + } + } + + visitLabeledStatement(LabeledStatement node) { + outIndent("${node.label}:"); + blockBody(node.body, needsSeparation: false, needsNewline: true); + } + + void functionOut(Fun fun, Node name, VarCollector vars) { + out("function"); + if (name != null) { + out(" "); + // Name must be a [Decl]. Therefore only test for primary expressions. + visitNestedExpression(name, PRIMARY, + newInForInit: false, newAtStatementBegin: false); + } + localNamer.enterScope(vars); + out("("); + if (fun.params != null) { + visitCommaSeparated(fun.params, PRIMARY, + newInForInit: false, newAtStatementBegin: false); + } + out(")"); + blockBody(fun.body, needsSeparation: false, needsNewline: false); + localNamer.leaveScope(); + } + + visitFunctionDeclaration(FunctionDeclaration declaration) { + VarCollector vars = new VarCollector(); + vars.visitFunctionDeclaration(declaration); + indent(); + functionOut(declaration.function, declaration.name, vars); + lineOut(); + } + + visitNestedExpression(Expression node, int requiredPrecedence, + {bool newInForInit, bool newAtStatementBegin}) { + bool needsParentheses = + // a - (b + c). + (requiredPrecedence != EXPRESSION && + node.precedenceLevel < requiredPrecedence) || + // for (a = (x in o); ... ; ... ) { ... } + (newInForInit && node is Binary && (node as Binary).op == "in") || + // (function() { ... })(). + // ({a: 2, b: 3}.toString()). + (newAtStatementBegin && (node is NamedFunction || + node is Fun || + node is ObjectInitializer)); + if (needsParentheses) { + inForInit = false; + atStatementBegin = false; + out("("); + visit(node); + out(")"); + } else { + inForInit = newInForInit; + atStatementBegin = newAtStatementBegin; + visit(node); + } + } + + visitVariableDeclarationList(VariableDeclarationList list) { + out("var "); + visitCommaSeparated(list.declarations, ASSIGNMENT, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitSequence(Sequence sequence) { + // Note that we only require that the entries are expressions and not + // assignments. This means that nested sequences are not put into + // parenthesis. + visitCommaSeparated(sequence.expressions, EXPRESSION, + newInForInit: false, + newAtStatementBegin: atStatementBegin); + } + + visitAssignment(Assignment assignment) { + visitNestedExpression(assignment.leftHandSide, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + if (assignment.value != null) { + spaceOut(); + String op = assignment.op; + if (op != null) out(op); + out("="); + spaceOut(); + visitNestedExpression(assignment.value, ASSIGNMENT, + newInForInit: inForInit, + newAtStatementBegin: false); + } + } + + visitVariableInitialization(VariableInitialization initialization) { + visitAssignment(initialization); + } + + visitConditional(Conditional cond) { + visitNestedExpression(cond.condition, LOGICAL_OR, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + spaceOut(); + out("?"); + spaceOut(); + // The then part is allowed to have an 'in'. + visitNestedExpression(cond.then, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + spaceOut(); + out(":"); + spaceOut(); + visitNestedExpression(cond.otherwise, ASSIGNMENT, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitNew(New node) { + out("new "); + visitNestedExpression(node.target, CALL, + newInForInit: inForInit, newAtStatementBegin: false); + out("("); + visitCommaSeparated(node.arguments, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + out(")"); + } + + visitCall(Call call) { + visitNestedExpression(call.target, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + out("("); + visitCommaSeparated(call.arguments, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + out(")"); + } + + visitBinary(Binary binary) { + Expression left = binary.left; + Expression right = binary.right; + String op = binary.op; + int leftPrecedenceRequirement; + int rightPrecedenceRequirement; + switch (op) { + case "||": + leftPrecedenceRequirement = LOGICAL_OR; + // x || (y || z) <=> (x || y) || z. + rightPrecedenceRequirement = LOGICAL_OR; + break; + case "&&": + leftPrecedenceRequirement = LOGICAL_AND; + // x && (y && z) <=> (x && y) && z. + rightPrecedenceRequirement = LOGICAL_AND; + break; + case "|": + leftPrecedenceRequirement = BIT_OR; + // x | (y | z) <=> (x | y) | z. + rightPrecedenceRequirement = BIT_OR; + break; + case "^": + leftPrecedenceRequirement = BIT_XOR; + // x ^ (y ^ z) <=> (x ^ y) ^ z. + rightPrecedenceRequirement = BIT_XOR; + break; + case "&": + leftPrecedenceRequirement = BIT_AND; + // x & (y & z) <=> (x & y) & z. + rightPrecedenceRequirement = BIT_AND; + break; + case "==": + case "!=": + case "===": + case "!==": + leftPrecedenceRequirement = EQUALITY; + rightPrecedenceRequirement = RELATIONAL; + break; + case "<": + case ">": + case "<=": + case ">=": + case "instanceof": + case "in": + leftPrecedenceRequirement = RELATIONAL; + rightPrecedenceRequirement = SHIFT; + break; + case ">>": + case "<<": + case ">>>": + leftPrecedenceRequirement = SHIFT; + rightPrecedenceRequirement = ADDITIVE; + break; + case "+": + case "-": + leftPrecedenceRequirement = ADDITIVE; + // We cannot remove parenthesis for "+" because + // x + (y + z) <!=> (x + y) + z: + // Example: + // "a" + (1 + 2) => "a3"; + // ("a" + 1) + 2 => "a12"; + rightPrecedenceRequirement = MULTIPLICATIVE; + break; + case "*": + case "/": + case "%": + leftPrecedenceRequirement = MULTIPLICATIVE; + // We cannot remove parenthesis for "*" because of precision issues. + rightPrecedenceRequirement = UNARY; + break; + default: + compiler.internalError("Forgot operator: $op"); + } + + visitNestedExpression(left, leftPrecedenceRequirement, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + + if (op == "in" || op == "instanceof") { + // There are cases where the space is not required but without further + // analysis we cannot know. + out(" "); + out(op); + out(" "); + } else { + spaceOut(); + out(op); + spaceOut(); + } + visitNestedExpression(right, rightPrecedenceRequirement, + newInForInit: inForInit, + newAtStatementBegin: false); + } + + visitPrefix(Prefix unary) { + String op = unary.op; + switch (op) { + case "delete": + case "void": + case "typeof": + // There are cases where the space is not required but without further + // analysis we cannot know. + out(op); + out(" "); + break; + case "+": + case "++": + if (lastCharCode == charCodes.$PLUS) out(" "); + out(op); + break; + case "-": + case "--": + if (lastCharCode == charCodes.$MINUS) out(" "); + out(op); + break; + default: + out(op); + } + visitNestedExpression(unary.argument, UNARY, + newInForInit: inForInit, newAtStatementBegin: false); + } + + visitPostfix(Postfix postfix) { + visitNestedExpression(postfix.argument, LEFT_HAND_SIDE, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + out(postfix.op); + } + + visitVariableUse(VariableUse ref) { + out(localNamer.getName(ref.name)); + } + + visitThis(This node) { + out("this"); + } + + visitVariableDeclaration(VariableDeclaration decl) { + out(localNamer.getName(decl.name)); + } + + visitParameter(Parameter param) { + out(localNamer.getName(param.name)); + } + + bool isDigit(int charCode) { + return charCodes.$0 <= charCode && charCode <= charCodes.$9; + } + + bool isValidJavaScriptId(String field) { + if (field.length < 3) return false; + // Ignore the leading and trailing string-delimiter. + for (int i = 1; i < field.length - 1; i++) { + // TODO(floitsch): allow more characters. + int charCode = field.charCodeAt(i); + if (!(charCodes.$a <= charCode && charCode <= charCodes.$z || + charCodes.$A <= charCode && charCode <= charCodes.$Z || + charCode == charCodes.$$ || + charCode == charCodes.$_ || + i != 1 && isDigit(charCode))) { + return false; + } + } + // TODO(floitsch): normally we should also check that the field is not a + // reserved word. We don't generate fields with reserved word names except + // for 'super'. + if (field == '"super"') return false; + return true; + } + + visitAccess(PropertyAccess access) { + visitNestedExpression(access.receiver, CALL, + newInForInit: inForInit, + newAtStatementBegin: atStatementBegin); + Node selector = access.selector; + if (selector is LiteralString) { + LiteralString selectorString = selector; + String fieldWithQuotes = selectorString.value; + if (isValidJavaScriptId(fieldWithQuotes)) { + if (access.receiver is LiteralNumber) out(" "); + out("."); + out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)); + return; + } + } + out("["); + visitNestedExpression(selector, EXPRESSION, + newInForInit: false, newAtStatementBegin: false); + out("]"); + } + + visitNamedFunction(NamedFunction namedFunction) { + VarCollector vars = new VarCollector(); + vars.visitNamedFunction(namedFunction); + functionOut(namedFunction.function, namedFunction.name, vars); + } + + visitFun(Fun fun) { + VarCollector vars = new VarCollector(); + vars.visitFun(fun); + functionOut(fun, null, vars); + } + + visitLiteralBool(LiteralBool node) { + out(node.value ? "true" : "false"); + } + + visitLiteralString(LiteralString node) { + out(node.value); + } + + visitLiteralNumber(LiteralNumber node) { + int charCode = node.value.charCodeAt(0); + if (charCode == charCodes.$MINUS && lastCharCode == charCodes.$MINUS) { + out(" "); + } + out(node.value); + } + + visitLiteralNull(LiteralNull node) { + out("null"); + } + + visitArrayInitializer(ArrayInitializer node) { + out("["); + List<ArrayElement> elements = node.elements; + int elementIndex = 0; + for (int i = 0; i < node.length; i++) { + if (elementIndex < elements.length && + elements[elementIndex].index == i) { + visitNestedExpression(elements[elementIndex].value, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + elementIndex++; + // We can avoid a trailing "," if there was an element just before. So + // `[1]` and `[1,]` are the same, but `[,]` and `[]` are not. + if (i != node.length - 1) { + out(","); + spaceOut(); + } + } else { + out(","); + } + } + out("]"); + } + + visitArrayElement(ArrayElement node) { + throw "Unreachable"; + } + + visitObjectInitializer(ObjectInitializer node) { + // Print all the properties on one line until we see a function-valued + // property. Ideally, we would use a proper pretty-printer to make the + // decision based on layout. + bool onePerLine = false; + List<Property> properties = node.properties; + out("{"); + ++indentLevel; + for (int i = 0; i < properties.length; i++) { + Expression value = properties[i].value; + if (value is Fun || value is NamedFunction) onePerLine = true; + if (i != 0) { + out(","); + if (!onePerLine) spaceOut(); + } + if (onePerLine) { + forceLine(); + indent(); + } + visitProperty(properties[i]); + } + --indentLevel; + if (onePerLine) lineOut(); + out("}"); + } + + visitProperty(Property node) { + if (node.name is LiteralString) { + LiteralString nameString = node.name; + String name = nameString.value; + if (isValidJavaScriptId(name)) { + out(name.substring(1, name.length - 1)); + } else { + out(name); + } + } else { + assert(node.name is LiteralNumber); + LiteralNumber nameNumber = node.name; + out(nameNumber.value); + } + out(":"); + spaceOut(); + visitNestedExpression(node.value, ASSIGNMENT, + newInForInit: false, newAtStatementBegin: false); + } + + visitRegExpLiteral(RegExpLiteral node) { + out(node.pattern); + } + + visitLiteralExpression(LiteralExpression node) { + String template = node.template; + List<Expression> inputs = node.inputs; + + List<String> parts = template.split('#'); + if (parts.length != inputs.length + 1) { + compiler.internalError('Wrong number of arguments for JS: $template'); + } + // Code that uses JS must take care of operator precedences, and + // put parenthesis if needed. + out(parts[0]); + for (int i = 0; i < inputs.length; i++) { + visit(inputs[i]); + out(parts[i + 1]); + } + } + + visitLiteralStatement(LiteralStatement node) { + outLn(node.code); + } +} + + +class OrderedSet<T> { + final Set<T> set; + final List<T> list; + + OrderedSet() : set = new Set<T>(), list = <T>[]; + + void add(T x) { + if (!set.contains(x)) { + set.add(x); + list.add(x); + } + } + + void forEach(void fun(T x)) { + list.forEach(fun); + } +} + +// Collects all the var declarations in the function. We need to do this in a +// separate pass because JS vars are lifted to the top of the function. +class VarCollector extends BaseVisitor { + bool nested; + final OrderedSet<String> vars; + final OrderedSet<String> params; + + VarCollector() : nested = false, + vars = new OrderedSet<String>(), + params = new OrderedSet<String>(); + + void forEachVar(void fn(String v)) => vars.forEach(fn); + void forEachParam(void fn(String p)) => params.forEach(fn); + + void collectVarsInFunction(Fun fun) { + if (!nested) { + nested = true; + if (fun.params != null) { + for (int i = 0; i < fun.params.length; i++) { + params.add(fun.params[i].name); + } + } + visitBlock(fun.body); + nested = false; + } + } + + void visitFunctionDeclaration(FunctionDeclaration declaration) { + // Note that we don't bother collecting the name of the function. + collectVarsInFunction(declaration.function); + } + + void visitNamedFunction(NamedFunction namedFunction) { + // Note that we don't bother collecting the name of the function. + collectVarsInFunction(namedFunction.function); + } + + void visitFun(Fun fun) { + collectVarsInFunction(fun); + } + + void visitThis(This node) {} + + void visitVariableDeclaration(VariableDeclaration decl) { + vars.add(decl.name); + } +} + + +/** + * Returns true, if the given node must be wrapped into braces when used + * as then-statement in an [If] that has an else branch. + */ +class DanglingElseVisitor extends BaseVisitor<bool> { + leg.Compiler compiler; + + DanglingElseVisitor(this.compiler); + + bool visitProgram(Program node) => false; + + bool visitNode(Statement node) { + compiler.internalError("Forgot node: $node"); + } + + bool visitBlock(Block node) => false; + bool visitExpressionStatement(ExpressionStatement node) => false; + bool visitEmptyStatement(EmptyStatement node) => false; + bool visitIf(If node) { + if (!node.hasElse) return true; + return node.otherwise.accept(this); + } + bool visitFor(For node) => node.body.accept(this); + bool visitForIn(ForIn node) => node.body.accept(this); + bool visitWhile(While node) => node.body.accept(this); + bool visitDo(Do node) => false; + bool visitContinue(Continue node) => false; + bool visitBreak(Break node) => false; + bool visitReturn(Return node) => false; + bool visitThrow(Throw node) => false; + bool visitTry(Try node) { + if (node.finallyPart != null) { + return node.finallyPart.accept(this); + } else { + return node.catchPart.accept(this); + } + } + bool visitCatch(Catch node) => node.body.accept(this); + bool visitSwitch(Switch node) => false; + bool visitCase(Case node) => false; + bool visitDefault(Default node) => false; + bool visitFunctionDeclaration(FunctionDeclaration node) => false; + bool visitLabeledStatement(LabeledStatement node) + => node.body.accept(this); + bool visitLiteralStatement(LiteralStatement node) => true; + + bool visitExpression(Expression node) => false; +} + + +leg.CodeBuffer prettyPrint(Node node, leg.Compiler compiler, + { allowVariableMinification: true }) { + Printer printer = + new Printer(compiler, + allowVariableMinification: allowVariableMinification); + printer.visit(node); + return printer.outBuffer; +} + + +abstract class LocalNamer { + String getName(String oldName); + String declareVariable(String oldName); + String declareParameter(String oldName); + void enterScope(VarCollector vars); + void leaveScope(); +} + + +class IdentityNamer implements LocalNamer { + String getName(String oldName) => oldName; + String declareVariable(String oldName) => oldName; + String declareParameter(String oldName) => oldName; + void enterScope(VarCollector vars) {} + void leaveScope() {} +} + + +class MinifyRenamer implements LocalNamer { + final List<Map<String, String>> maps = []; + final List<int> parameterNumberStack = []; + final List<int> variableNumberStack = []; + int parameterNumber = 0; + int variableNumber = 0; + + MinifyRenamer(); + + void enterScope(VarCollector vars) { + maps.add(new Map<String, String>()); + variableNumberStack.add(variableNumber); + parameterNumberStack.add(parameterNumber); + vars.forEachVar(declareVariable); + vars.forEachParam(declareParameter); + } + + void leaveScope() { + maps.removeLast(); + variableNumber = variableNumberStack.removeLast(); + parameterNumber = parameterNumberStack.removeLast(); + } + + String getName(String oldName) { + // Go from inner scope to outer looking for mapping of name. + for (int i = maps.length - 1; i >= 0; i--) { + var map = maps[i]; + var replacement = map[oldName]; + if (replacement != null) return replacement; + } + return oldName; + } + + static const LOWER_CASE_LETTERS = 26; + static const LETTERS = 52; + static const DIGITS = 10; + + static int nthLetter(int n) { + return (n < LOWER_CASE_LETTERS) ? + charCodes.$a + n : + charCodes.$A + n - LOWER_CASE_LETTERS; + } + + // Parameters go from a to z and variables go from z to a. This makes each + // argument list and each top-of-function var declaration look similar and + // helps gzip compress the file. If we have more than 26 arguments and + // variables then we meet somewhere in the middle of the alphabet. After + // that we give up trying to be nice to the compression algorithm and just + // use the same namespace for arguments and variables, starting with A, and + // moving on to a0, a1, etc. + String declareVariable(String oldName) { + var newName; + if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { + // Variables start from z and go backwards, for better gzipability. + newName = getNameNumber(oldName, LOWER_CASE_LETTERS - 1 - variableNumber); + } else { + // After 26 variables and parameters we allocate them in the same order. + newName = getNameNumber(oldName, variableNumber + parameterNumber); + } + variableNumber++; + return newName; + } + + String declareParameter(String oldName) { + var newName; + if (variableNumber + parameterNumber < LOWER_CASE_LETTERS) { + newName = getNameNumber(oldName, parameterNumber); + } else { + newName = getNameNumber(oldName, variableNumber + parameterNumber); + } + parameterNumber++; + return newName; + } + + String getNameNumber(String oldName, int n) { + if (maps.isEmpty) return oldName; + + String newName; + if (n < LETTERS) { + // Start naming variables a, b, c, ..., z, A, B, C, ..., Z. + newName = new String.fromCharCodes([nthLetter(n)]); + } else { + // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ... + // For all functions with fewer than 500 locals this is just as compact + // as using aa, ab, etc. but avoids clashes with keywords. + n -= LETTERS; + int digit = n % DIGITS; + n ~/= DIGITS; + int alphaChars = 1; + int nameSpaceSize = LETTERS; + // Find out whether we should use the 1-character namespace (size 52), the + // 2-character namespace (size 52*52), etc. + while (n >= nameSpaceSize) { + n -= nameSpaceSize; + alphaChars++; + nameSpaceSize *= LETTERS; + } + var codes = <int>[]; + for (var i = 0; i < alphaChars; i++) { + nameSpaceSize ~/= LETTERS; + codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS)); + } + codes.add(charCodes.$0 + digit); + newName = new String.fromCharCodes(codes); + } + assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName)); + maps.last[oldName] = newName; + return newName; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart new file mode 100644 index 0000000..2a681d8 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart
@@ -0,0 +1,1262 @@ +// 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. + +part of js_backend; + +typedef void Recompile(Element element); + +class ReturnInfo { + HType returnType; + List<Element> compiledFunctions; + + ReturnInfo(HType this.returnType) + : compiledFunctions = new List<Element>(); + + ReturnInfo.unknownType() : this(null); + + void update(HType type, Recompile recompile, Compiler compiler) { + HType newType = + returnType != null ? returnType.union(type, compiler) : type; + if (newType != returnType) { + if (returnType == null && identical(newType, HType.UNKNOWN)) { + // If the first actual piece of information is not providing any type + // information there is no need to recompile callers. + compiledFunctions.clear(); + } + returnType = newType; + if (recompile != null) { + compiledFunctions.forEach(recompile); + } + compiledFunctions.clear(); + } + } + + // Note that lazy initializers are treated like functions (but are not + // of type [FunctionElement]. + addCompiledFunction(Element function) => compiledFunctions.add(function); +} + +class OptionalParameterTypes { + final List<SourceString> names; + final List<HType> types; + + OptionalParameterTypes(int optionalArgumentsCount) + : names = new List<SourceString>.fixedLength(optionalArgumentsCount), + types = new List<HType>.fixedLength(optionalArgumentsCount); + + int get length => names.length; + SourceString name(int index) => names[index]; + HType type(int index) => types[index]; + int indexOf(SourceString name) => names.indexOf(name); + + HType typeFor(SourceString name) { + int index = indexOf(name); + if (index == -1) return null; + return type(index); + } + + void update(int index, SourceString name, HType type) { + names[index] = name; + types[index] = type; + } + + String toString() => "OptionalParameterTypes($names, $types)"; +} + +class HTypeList { + final List<HType> types; + final List<SourceString> namedArguments; + + HTypeList(int length) + : types = new List<HType>.fixedLength(length), + namedArguments = null; + HTypeList.withNamedArguments(int length, this.namedArguments) + : types = new List<HType>.fixedLength(length); + const HTypeList.withAllUnknown() + : types = null, + namedArguments = null; + + factory HTypeList.fromStaticInvocation(HInvokeStatic node, HTypeMap types) { + bool allUnknown = true; + for (int i = 1; i < node.inputs.length; i++) { + if (types[node.inputs[i]] != HType.UNKNOWN) { + allUnknown = false; + break; + } + } + if (allUnknown) return HTypeList.ALL_UNKNOWN; + + HTypeList result = new HTypeList(node.inputs.length - 1); + for (int i = 0; i < result.types.length; i++) { + result.types[i] = types[node.inputs[i + 1]]; + } + return result; + } + + factory HTypeList.fromDynamicInvocation(HInvokeDynamic node, + Selector selector, + HTypeMap types) { + HTypeList result; + int argumentsCount = node.inputs.length - 1; + int startInvokeIndex = HInvoke.ARGUMENTS_OFFSET; + + if (node.isInterceptorCall) { + argumentsCount--; + startInvokeIndex++; + } + + if (selector.namedArgumentCount > 0) { + result = + new HTypeList.withNamedArguments( + argumentsCount, selector.namedArguments); + } else { + result = new HTypeList(argumentsCount); + } + + for (int i = 0; i < result.types.length; i++) { + result.types[i] = types[node.inputs[i + startInvokeIndex]]; + } + return result; + } + + static const HTypeList ALL_UNKNOWN = const HTypeList.withAllUnknown(); + + bool get allUnknown => types == null; + bool get hasNamedArguments => namedArguments != null; + int get length => types.length; + HType operator[](int index) => types[index]; + void operator[]=(int index, HType type) { types[index] = type; } + + HTypeList union(HTypeList other, Compiler compiler) { + if (allUnknown) return this; + if (other.allUnknown) return other; + if (length != other.length) return HTypeList.ALL_UNKNOWN; + bool onlyUnknown = true; + HTypeList result = this; + for (int i = 0; i < length; i++) { + HType newType = this[i].union(other[i], compiler); + if (result == this && newType != this[i]) { + // Create a new argument types object with the matching types copied. + result = new HTypeList(length); + result.types.setRange(0, i, this.types); + } + if (result != this) { + result.types[i] = newType; + } + if (result[i] != HType.UNKNOWN) onlyUnknown = false; + } + return onlyUnknown ? HTypeList.ALL_UNKNOWN : result; + } + + HTypeList unionWithOptionalParameters( + Selector selector, + FunctionSignature signature, + OptionalParameterTypes defaultValueTypes) { + assert(allUnknown || selector.argumentCount == this.length); + // Create a new HTypeList for holding types for all parameters. + HTypeList result = new HTypeList(signature.parameterCount); + + // First fill in the type of the positional arguments. + int nextTypeIndex = -1; + if (allUnknown) { + for (int i = 0; i < selector.positionalArgumentCount; i++) { + result.types[i] = HType.UNKNOWN; + } + } else { + result.types.setRange(0, selector.positionalArgumentCount, this.types); + nextTypeIndex = selector.positionalArgumentCount; + } + + // Next fill the type of the optional arguments. + // As the selector can pass optional arguments positionally some of the + // optional arguments might already have a type set. We only need to look + // at the optional arguments not passed positionally. + // The variable 'index' is counting the signatures optional arguments, the + // variable 'next' is set to the next optional arguments to look at and + // is used to skip some optional arguments. + int next = selector.positionalArgumentCount; + int index = signature.requiredParameterCount; + signature.forEachOptionalParameter((Element element) { + // If some optional parameters were passed positionally these have + // already been filled. + if (index == next) { + assert(result.types[index] == null); + HType type = null; + if (hasNamedArguments && + selector.namedArguments.indexOf(element.name) >= 0) { + type = types[nextTypeIndex++]; + } else { + type = defaultValueTypes.typeFor(element.name); + } + result.types[index] = type; + next++; + } + index++; + }); + return result; + } + + String toString() => + allUnknown ? "HTypeList.ALL_UNKNOWN" : "HTypeList $types"; +} + +class FieldTypesRegistry { + final JavaScriptBackend backend; + + /** + * For each class, [constructors] holds the set of constructors. If there is + * more than one constructor for a class it is currently not possible to + * infer the field types from construction, as the information collected does + * not correlate the generative constructors and generative constructor + * body/bodies. + */ + final Map<ClassElement, Set<Element>> constructors; + + /** + * The collected type information is stored in three maps. One for types + * assigned in the initializer list(s) [fieldInitializerTypeMap], one for + * types assigned in the constructor(s) [fieldConstructorTypeMap], and one + * for types assigned in the rest of the code, where the field can be + * resolved [fieldTypeMap]. + * + * If a field has a type both from constructors and from the initializer + * list(s), then the type from the constructor(s) will owerride the one from + * the initializer list(s). + * + * Because the order in which generative constructors, generative constructor + * bodies and normal method/function bodies are compiled is undefined, and + * because they can all be recompiled, it is not possible to combine this + * information into one map at the moment. + */ + final Map<Element, HType> fieldInitializerTypeMap; + final Map<Element, HType> fieldConstructorTypeMap; + final Map<Element, HType> fieldTypeMap; + + /** + * The set of current names setter selectors used. If a named selector is + * used it is currently not possible to infer the type of the field. + */ + final Set<SourceString> setterSelectorsUsed; + + final Map<Element, Set<Element>> optimizedStaticFunctions; + final Map<Element, FunctionSet> optimizedFunctions; + + FieldTypesRegistry(JavaScriptBackend backend) + : constructors = new Map<ClassElement, Set<Element>>(), + fieldInitializerTypeMap = new Map<Element, HType>(), + fieldConstructorTypeMap = new Map<Element, HType>(), + fieldTypeMap = new Map<Element, HType>(), + setterSelectorsUsed = new Set<SourceString>(), + optimizedStaticFunctions = new Map<Element, Set<Element>>(), + optimizedFunctions = new Map<Element, FunctionSet>(), + this.backend = backend; + + Compiler get compiler => backend.compiler; + + void scheduleRecompilation(Element field) { + Set optimizedStatics = optimizedStaticFunctions[field]; + if (optimizedStatics != null) { + optimizedStatics.forEach(backend.scheduleForRecompilation); + optimizedStaticFunctions.remove(field); + } + FunctionSet optimized = optimizedFunctions[field]; + if (optimized != null) { + optimized.forEach(backend.scheduleForRecompilation); + optimizedFunctions.remove(field); + } + } + + int constructorCount(Element element) { + assert(element.isClass()); + Set<Element> ctors = constructors[element]; + return ctors == null ? 0 : ctors.length; + } + + void registerFieldType(Map<Element, HType> typeMap, + Element field, + HType type) { + assert(field.isField()); + HType before = optimisticFieldType(field); + + HType oldType = typeMap[field]; + HType newType; + + if (oldType != null) { + newType = oldType.union(type, compiler); + } else { + newType = type; + } + typeMap[field] = newType; + if (oldType != newType) { + scheduleRecompilation(field); + } + } + + void registerConstructor(Element element) { + assert(element.isGenerativeConstructor()); + Element cls = element.getEnclosingClass(); + constructors.putIfAbsent(cls, () => new Set<Element>()); + Set<Element> ctors = constructors[cls]; + if (ctors.contains(element)) return; + ctors.add(element); + // We cannot infer field types for classes with more than one constructor. + // When the second constructor is seen, recompile all functions relying on + // optimistic field types for that class. + // TODO(sgjesse): Handle field types for classes with more than one + // constructor. + if (ctors.length == 2) { + optimizedFunctions.forEach((Element field, _) { + if (identical(field.enclosingElement, cls)) { + scheduleRecompilation(field); + } + }); + } + } + + void registerFieldInitializer(Element field, HType type) { + registerFieldType(fieldInitializerTypeMap, field, type); + } + + void registerFieldConstructor(Element field, HType type) { + registerFieldType(fieldConstructorTypeMap, field, type); + } + + void registerFieldSetter(FunctionElement element, Element field, HType type) { + HType initializerType = fieldInitializerTypeMap[field]; + HType constructorType = fieldConstructorTypeMap[field]; + HType setterType = fieldTypeMap[field]; + if (type == HType.UNKNOWN + && initializerType == null + && constructorType == null + && setterType == null) { + // Don't register UNKONWN if there is currently no type information + // present for the field. Instead register the function holding the + // setter for recompilation if better type information for the field + // becomes available. + registerOptimizedFunction(element, field, type); + return; + } + registerFieldType(fieldTypeMap, field, type); + } + + void addedDynamicSetter(Selector setter, HType type) { + // Field type optimizations are disabled for all fields matching a + // setter selector. + assert(setter.isSetter()); + // TODO(sgjesse): Take the type of the setter into account. + if (setterSelectorsUsed.contains(setter.name)) return; + setterSelectorsUsed.add(setter.name); + optimizedStaticFunctions.forEach((Element field, _) { + if (field.name == setter.name) { + scheduleRecompilation(field); + } + }); + optimizedFunctions.forEach((Element field, _) { + if (field.name == setter.name) { + scheduleRecompilation(field); + } + }); + } + + HType optimisticFieldType(Element field) { + assert(field.isField()); + if (constructorCount(field.getEnclosingClass()) > 1) { + return HType.UNKNOWN; + } + if (setterSelectorsUsed.contains(field.name)) { + return HType.UNKNOWN; + } + HType initializerType = fieldInitializerTypeMap[field]; + HType constructorType = fieldConstructorTypeMap[field]; + if (initializerType == null && constructorType == null) { + // If there are no constructor type information return UNKNOWN. This + // ensures that the function will be recompiled if useful constructor + // type information becomes available. + return HType.UNKNOWN; + } + // A type set through the constructor overrides the type from the + // initializer list. + HType result = constructorType != null ? constructorType : initializerType; + HType type = fieldTypeMap[field]; + if (type != null) result = result.union(type, compiler); + return result; + } + + void registerOptimizedFunction(FunctionElement element, + Element field, + HType type) { + assert(field.isField()); + if (Elements.isStaticOrTopLevel(element)) { + optimizedStaticFunctions.putIfAbsent( + field, () => new Set<Element>()); + optimizedStaticFunctions[field].add(element); + } else { + optimizedFunctions.putIfAbsent( + field, () => new FunctionSet(backend.compiler)); + optimizedFunctions[field].add(element); + } + } + + void dump() { + Set<Element> allFields = new Set<Element>(); + fieldInitializerTypeMap.keys.forEach(allFields.add); + fieldConstructorTypeMap.keys.forEach(allFields.add); + fieldTypeMap.keys.forEach(allFields.add); + allFields.forEach((Element field) { + print("Inferred $field has type ${optimisticFieldType(field)}"); + }); + } +} + +class ArgumentTypesRegistry { + final JavaScriptBackend backend; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Keys must be declaration elements. + */ + final Map<Element, HTypeList> staticTypeMap; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final Set<Element> optimizedStaticFunctions; + final SelectorMap<HTypeList> selectorTypeMap; + final FunctionSet optimizedFunctions; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Keys must be declaration elements. + */ + final Map<Element, HTypeList> optimizedTypes; + final Map<Element, OptionalParameterTypes> optimizedDefaultValueTypes; + + ArgumentTypesRegistry(JavaScriptBackend backend) + : staticTypeMap = new Map<Element, HTypeList>(), + optimizedStaticFunctions = new Set<Element>(), + selectorTypeMap = new SelectorMap<HTypeList>(backend.compiler), + optimizedFunctions = new FunctionSet(backend.compiler), + optimizedTypes = new Map<Element, HTypeList>(), + optimizedDefaultValueTypes = + new Map<Element, OptionalParameterTypes>(), + this.backend = backend; + + Compiler get compiler => backend.compiler; + + bool updateTypes(HTypeList oldTypes, HTypeList newTypes, var key, var map) { + if (oldTypes.allUnknown) return false; + newTypes = oldTypes.union(newTypes, backend.compiler); + if (identical(newTypes, oldTypes)) return false; + map[key] = newTypes; + return true; + } + + void registerStaticInvocation(HInvokeStatic node, HTypeMap types) { + Element element = node.element; + assert(invariant(node, element.isDeclaration)); + HTypeList oldTypes = staticTypeMap[element]; + HTypeList newTypes = new HTypeList.fromStaticInvocation(node, types); + if (oldTypes == null) { + staticTypeMap[element] = newTypes; + } else if (updateTypes(oldTypes, newTypes, element, staticTypeMap)) { + if (optimizedStaticFunctions.contains(element)) { + backend.scheduleForRecompilation(element); + } + } + } + + void registerNonCallStaticUse(HStatic node) { + // When a static is used for anything else than a call target we cannot + // infer anything about its parameter types. + Element element = node.element; + assert(invariant(node, element.isDeclaration)); + if (optimizedStaticFunctions.contains(element)) { + backend.scheduleForRecompilation(element); + } + staticTypeMap[element] = HTypeList.ALL_UNKNOWN; + } + + void registerDynamicInvocation(HTypeList providedTypes, Selector selector) { + if (selector.isClosureCall()) { + // We cannot use the current framework to do optimizations based + // on the 'call' selector because we are also generating closure + // calls during the emitter phase, which at this point, does not + // track parameter types, nor invalidates optimized methods. + return; + } + if (!selectorTypeMap.containsKey(selector)) { + selectorTypeMap[selector] = providedTypes; + } else { + HTypeList oldTypes = selectorTypeMap[selector]; + updateTypes(oldTypes, providedTypes, selector, selectorTypeMap); + } + + // If we're not compiling, we don't have to do anything. + if (compiler.phase != Compiler.PHASE_COMPILING) return; + + // Run through all optimized functions and figure out if they need + // to be recompiled because of this new invocation. + optimizedFunctions.filterBySelector(selector).forEach((Element element) { + // TODO(kasperl): Maybe check if the element is already marked for + // recompilation? Could be pretty cheap compared to computing + // union types. + HTypeList newTypes = + parameterTypes(element, optimizedDefaultValueTypes[element]); + bool recompile = false; + if (newTypes.allUnknown) { + recompile = true; + } else { + HTypeList oldTypes = optimizedTypes[element]; + assert(newTypes.length == oldTypes.length); + for (int i = 0; i < oldTypes.length; i++) { + if (newTypes[i] != oldTypes[i]) { + recompile = true; + break; + } + } + } + if (recompile) backend.scheduleForRecompilation(element); + }); + } + + HTypeList parameterTypes(FunctionElement element, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + // Handle static functions separately. + if (Elements.isStaticOrTopLevelFunction(element) || + element.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { + HTypeList types = staticTypeMap[element]; + if (types != null) { + if (!optimizedStaticFunctions.contains(element)) { + optimizedStaticFunctions.add(element); + } + return types; + } else { + return HTypeList.ALL_UNKNOWN; + } + } + + // Getters have no parameters. + if (element.isGetter()) return HTypeList.ALL_UNKNOWN; + + // TODO(kasperl): What kind of non-members do we get here? + if (!element.isMember()) return HTypeList.ALL_UNKNOWN; + + // If there are any getters for this method we cannot know anything about + // the types of the provided parameters. Use resolverWorld for now as that + // information does not change during compilation. + // TODO(ngeoffray): These checks should use the codegenWorld and keep track + // of changes to this information. + if (compiler.resolverWorld.hasInvokedGetter(element, compiler)) { + return HTypeList.ALL_UNKNOWN; + } + + FunctionSignature signature = element.computeSignature(compiler); + HTypeList found = null; + selectorTypeMap.visitMatching(element, + (Selector selector, HTypeList types) { + if (selector.argumentCount != signature.parameterCount || + selector.namedArgumentCount > 0) { + types = types.unionWithOptionalParameters(selector, + signature, + defaultValueTypes); + } + assert(types.allUnknown || types.length == signature.parameterCount); + found = (found == null) ? types : found.union(types, compiler); + return !found.allUnknown; + }); + return found != null ? found : HTypeList.ALL_UNKNOWN; + } + + void registerOptimizedFunction(Element element, + HTypeList parameterTypes, + OptionalParameterTypes defaultValueTypes) { + if (Elements.isStaticOrTopLevelFunction(element)) { + if (parameterTypes.allUnknown) { + optimizedStaticFunctions.remove(element); + } else { + optimizedStaticFunctions.add(element); + } + } + + // TODO(kasperl): What kind of non-members do we get here? + if (!element.isMember()) return; + + if (parameterTypes.allUnknown) { + optimizedFunctions.remove(element); + optimizedTypes.remove(element); + optimizedDefaultValueTypes.remove(element); + } else { + optimizedFunctions.add(element); + optimizedTypes[element] = parameterTypes; + optimizedDefaultValueTypes[element] = defaultValueTypes; + } + } + + void dump() { + optimizedFunctions.forEach((Element element) { + HTypeList types = optimizedTypes[element]; + print("Inferred $element has argument types ${types.types}"); + }); + } +} + +class JavaScriptItemCompilationContext extends ItemCompilationContext { + final HTypeMap types; + final Set<HInstruction> boundsChecked; + + JavaScriptItemCompilationContext() + : types = new HTypeMap(), + boundsChecked = new Set<HInstruction>(); +} + +class JavaScriptBackend extends Backend { + SsaBuilderTask builder; + SsaOptimizerTask optimizer; + SsaCodeGeneratorTask generator; + CodeEmitterTask emitter; + + /** + * The generated code as a js AST for compiled methods. + */ + Map<Element, js.Expression> get generatedCode { + return compiler.enqueuer.codegen.generatedCode; + } + + /** + * The generated code as a js AST for compiled bailout methods. + */ + final Map<Element, js.Expression> generatedBailoutCode = + new Map<Element, js.Expression>(); + + ClassElement jsStringClass; + ClassElement jsArrayClass; + ClassElement jsNumberClass; + ClassElement jsIntClass; + ClassElement jsDoubleClass; + ClassElement jsFunctionClass; + ClassElement jsNullClass; + ClassElement jsBoolClass; + ClassElement objectInterceptorClass; + Element jsArrayLength; + Element jsStringLength; + Element jsArrayRemoveLast; + Element jsArrayAdd; + Element jsStringSplit; + Element jsStringConcat; + Element jsStringToString; + Element getInterceptorMethod; + Element fixedLengthListConstructor; + bool seenAnyClass = false; + + final Namer namer; + + /** + * Interface used to determine if an object has the JavaScript + * indexing behavior. The interface is only visible to specific + * libraries. + */ + ClassElement jsIndexingBehaviorInterface; + + final Map<Element, ReturnInfo> returnInfo; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final List<Element> invalidateAfterCodegen; + ArgumentTypesRegistry argumentTypes; + FieldTypesRegistry fieldTypes; + + /** + * A collection of selectors of intercepted method calls. The + * emitter uses this set to generate the [:ObjectInterceptor:] class + * whose members just forward the call to the intercepted receiver. + */ + final Set<Selector> usedInterceptors; + + /** + * A collection of selectors that must have a one shot interceptor + * generated. + */ + final Set<Selector> oneShotInterceptors; + + /** + * The members of instantiated interceptor classes: maps a member + * name to the list of members that have that name. This map is used + * by the codegen to know whether a send must be intercepted or not. + */ + final Map<SourceString, Set<Element>> interceptedElements; + + /** + * A map of specialized versions of the [getInterceptorMethod]. + * Since [getInterceptorMethod] is a hot method at runtime, we're + * always specializing it based on the incoming type. The keys in + * the map are the names of these specialized versions. Note that + * the generic version that contains all possible type checks is + * also stored in this map. + */ + final Map<String, Collection<ClassElement>> specializedGetInterceptors; + + /** + * Set of classes whose instances are intercepted. Implemented as a + * [LinkedHashMap] to preserve the insertion order. + * TODO(ngeoffray): No need to preserve order anymore. + */ + final Map<ClassElement, ClassElement> interceptedClasses; + + List<CompilerTask> get tasks { + return <CompilerTask>[builder, optimizer, generator, emitter]; + } + + final RuntimeTypeInformation rti; + + JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval) + : namer = determineNamer(compiler), + returnInfo = new Map<Element, ReturnInfo>(), + invalidateAfterCodegen = new List<Element>(), + usedInterceptors = new Set<Selector>(), + oneShotInterceptors = new Set<Selector>(), + interceptedElements = new Map<SourceString, Set<Element>>(), + rti = new RuntimeTypeInformation(compiler), + specializedGetInterceptors = + new Map<String, Collection<ClassElement>>(), + interceptedClasses = new LinkedHashMap<ClassElement, ClassElement>(), + super(compiler, JAVA_SCRIPT_CONSTANT_SYSTEM) { + emitter = disableEval + ? new CodeEmitterNoEvalTask(compiler, namer, generateSourceMap) + : new CodeEmitterTask(compiler, namer, generateSourceMap); + builder = new SsaBuilderTask(this); + optimizer = new SsaOptimizerTask(this); + generator = new SsaCodeGeneratorTask(this); + argumentTypes = new ArgumentTypesRegistry(this); + fieldTypes = new FieldTypesRegistry(this); + } + + static Namer determineNamer(Compiler compiler) { + return compiler.enableMinification ? + new MinifyNamer(compiler) : + new Namer(compiler); + } + + bool isInterceptorClass(Element element) { + if (element == null) return false; + return interceptedClasses.containsKey(element); + } + + void addInterceptedSelector(Selector selector) { + usedInterceptors.add(selector); + } + + void addOneShotInterceptor(Selector selector) { + oneShotInterceptors.add(selector); + } + + /** + * Returns a set of interceptor classes that contain a member whose + * signature matches the given [selector]. Returns [:null:] if there + * is no class. + */ + Set<ClassElement> getInterceptedClassesOn(Selector selector) { + Set<Element> intercepted = interceptedElements[selector.name]; + if (intercepted == null) return null; + Set<ClassElement> result = new Set<ClassElement>(); + for (Element element in intercepted) { + if (selector.applies(element, compiler)) { + result.add(element.getEnclosingClass()); + } + } + if (result.isEmpty) return null; + return result; + } + + List<ClassElement> getListOfInterceptedClasses() { + return <ClassElement>[jsStringClass, jsArrayClass, jsIntClass, + jsDoubleClass, jsNumberClass, jsNullClass, + jsFunctionClass, jsBoolClass]; + } + + void initializeInterceptorElements() { + objectInterceptorClass = + compiler.findInterceptor(const SourceString('ObjectInterceptor')); + getInterceptorMethod = + compiler.findInterceptor(const SourceString('getInterceptor')); + List<ClassElement> classes = [ + jsStringClass = compiler.findInterceptor(const SourceString('JSString')), + jsArrayClass = compiler.findInterceptor(const SourceString('JSArray')), + // The int class must be before the double class, because the + // emitter relies on this list for the order of type checks. + jsIntClass = compiler.findInterceptor(const SourceString('JSInt')), + jsDoubleClass = compiler.findInterceptor(const SourceString('JSDouble')), + jsNumberClass = compiler.findInterceptor(const SourceString('JSNumber')), + jsNullClass = compiler.findInterceptor(const SourceString('JSNull')), + jsFunctionClass = + compiler.findInterceptor(const SourceString('JSFunction')), + jsBoolClass = compiler.findInterceptor(const SourceString('JSBool'))]; + + jsArrayClass.ensureResolved(compiler); + jsArrayLength = compiler.lookupElementIn( + jsArrayClass, const SourceString('length')); + jsArrayRemoveLast = compiler.lookupElementIn( + jsArrayClass, const SourceString('removeLast')); + jsArrayAdd = compiler.lookupElementIn( + jsArrayClass, const SourceString('add')); + + jsStringClass.ensureResolved(compiler); + jsStringLength = compiler.lookupElementIn( + jsStringClass, const SourceString('length')); + jsStringSplit = compiler.lookupElementIn( + jsStringClass, const SourceString('split')); + jsStringConcat = compiler.lookupElementIn( + jsStringClass, const SourceString('concat')); + jsStringToString = compiler.lookupElementIn( + jsStringClass, const SourceString('toString')); + + for (ClassElement cls in classes) { + if (cls != null) interceptedClasses[cls] = null; + } + } + + void addInterceptors(ClassElement cls, Enqueuer enqueuer) { + if (enqueuer.isResolutionQueue) { + cls.ensureResolved(compiler); + cls.forEachMember((ClassElement classElement, Element member) { + Set<Element> set = interceptedElements.putIfAbsent( + member.name, () => new Set<Element>()); + set.add(member); + }, + includeSuperMembers: true); + } + enqueuer.registerInstantiatedClass(cls); + } + + void registerSpecializedGetInterceptor(Set<ClassElement> classes) { + compiler.enqueuer.codegen.registerInstantiatedClass(objectInterceptorClass); + String name = namer.getInterceptorName(getInterceptorMethod, classes); + if (classes.contains(compiler.objectClass)) { + // We can't use a specialized [getInterceptorMethod], so we make + // sure we emit the one with all checks. + specializedGetInterceptors.putIfAbsent(name, () { + // It is important to take the order provided by the map, + // because we want the int type check to happen before the + // double type check: the double type check covers the int + // type check. Also we don't need to do a number type check + // because that is covered by the double type check. + List<ClassElement> keys = <ClassElement>[]; + interceptedClasses.forEach((ClassElement cls, _) { + if (cls != jsNumberClass) keys.add(cls); + }); + return keys; + }); + } else { + specializedGetInterceptors[name] = classes; + } + } + + void initializeNoSuchMethod() { + // In case the emitter generates noSuchMethod calls, we need to + // make sure all [noSuchMethod] methods know they might take a + // [JsInvocationMirror] as parameter. + HTypeList types = new HTypeList(1); + types[0] = new HType.fromBoundedType( + compiler.jsInvocationMirrorClass.computeType(compiler), + compiler, + false); + argumentTypes.registerDynamicInvocation(types, new Selector.noSuchMethod()); + } + + void registerInstantiatedClass(ClassElement cls, Enqueuer enqueuer) { + if (!seenAnyClass) { + initializeInterceptorElements(); + initializeNoSuchMethod(); + seenAnyClass = true; + } + ClassElement result = null; + if (cls == compiler.stringClass) { + addInterceptors(jsStringClass, enqueuer); + } else if (cls == compiler.listClass) { + addInterceptors(jsArrayClass, enqueuer); + // The backend will try to optimize array access and use the + // `ioore` and `iae` helpers directly. + if (enqueuer.isResolutionQueue) { + enqueuer.registerStaticUse( + compiler.findHelper(const SourceString('ioore'))); + enqueuer.registerStaticUse( + compiler.findHelper(const SourceString('iae'))); + } + } else if (cls == compiler.intClass) { + addInterceptors(jsIntClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.doubleClass) { + addInterceptors(jsDoubleClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.functionClass) { + addInterceptors(jsFunctionClass, enqueuer); + } else if (cls == compiler.boolClass) { + addInterceptors(jsBoolClass, enqueuer); + } else if (cls == compiler.nullClass) { + addInterceptors(jsNullClass, enqueuer); + } else if (cls == compiler.numClass) { + addInterceptors(jsIntClass, enqueuer); + addInterceptors(jsDoubleClass, enqueuer); + addInterceptors(jsNumberClass, enqueuer); + } else if (cls == compiler.mapClass) { + // The backend will use a literal list to initialize the entries + // of the map. + if (enqueuer.isResolutionQueue) { + enqueuer.registerInstantiatedClass(compiler.listClass); + } + } + } + + Element get cyclicThrowHelper { + return compiler.findHelper(const SourceString("throwCyclicInit")); + } + + JavaScriptItemCompilationContext createItemCompilationContext() { + return new JavaScriptItemCompilationContext(); + } + + void enqueueHelpers(ResolutionEnqueuer world) { + enqueueAllTopLevelFunctions(compiler.jsHelperLibrary, world); + + jsIndexingBehaviorInterface = + compiler.findHelper(const SourceString('JavaScriptIndexingBehavior')); + if (jsIndexingBehaviorInterface != null) { + world.registerIsCheck(jsIndexingBehaviorInterface.computeType(compiler)); + } + + for (var helper in [const SourceString('Closure'), + const SourceString('ConstantMap'), + const SourceString('ConstantProtoMap')]) { + var e = compiler.findHelper(helper); + if (e != null) world.registerInstantiatedClass(e); + } + } + + void codegen(CodegenWorkItem work) { + Element element = work.element; + if (element.kind.category == ElementCategory.VARIABLE) { + Constant initialValue = compiler.constantHandler.compileWorkItem(work); + if (initialValue != null) { + return; + } else { + // If the constant-handler was not able to produce a result we have to + // go through the builder (below) to generate the lazy initializer for + // the static variable. + // We also need to register the use of the cyclic-error helper. + compiler.enqueuer.codegen.registerStaticUse(cyclicThrowHelper); + } + } + + HGraph graph = builder.build(work); + optimizer.optimize(work, graph, false); + if (work.allowSpeculativeOptimization + && optimizer.trySpeculativeOptimizations(work, graph)) { + js.Expression code = generator.generateBailoutMethod(work, graph); + generatedBailoutCode[element] = code; + optimizer.prepareForSpeculativeOptimizations(work, graph); + optimizer.optimize(work, graph, true); + } + js.Expression code = generator.generateCode(work, graph); + generatedCode[element] = code; + invalidateAfterCodegen.forEach(eagerRecompile); + invalidateAfterCodegen.clear(); + } + + native.NativeEnqueuer nativeResolutionEnqueuer(Enqueuer world) { + return new native.NativeResolutionEnqueuer(world, compiler); + } + + native.NativeEnqueuer nativeCodegenEnqueuer(Enqueuer world) { + return new native.NativeCodegenEnqueuer(world, compiler, emitter); + } + + /** + * Unit test hook that returns code of an element as a String. + * + * Invariant: [element] must be a declaration element. + */ + String assembleCode(Element element) { + assert(invariant(element, element.isDeclaration)); + return js.prettyPrint(generatedCode[element], compiler).getText(); + } + + void assembleProgram() { + emitter.assembleProgram(); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void scheduleForRecompilation(Element element) { + assert(invariant(element, element.isDeclaration)); + if (compiler.phase == Compiler.PHASE_COMPILING) { + invalidateAfterCodegen.add(element); + } + } + + /** + * Register a dynamic invocation and collect the provided types for the + * named selector. + */ + void registerDynamicInvocation(HInvokeDynamic node, + Selector selector, + HTypeMap types) { + HTypeList providedTypes = + new HTypeList.fromDynamicInvocation(node, selector, types); + argumentTypes.registerDynamicInvocation(providedTypes, selector); + } + + /** + * Register a static invocation and collect the provided types for the + * named selector. + */ + void registerStaticInvocation(HInvokeStatic node, HTypeMap types) { + argumentTypes.registerStaticInvocation(node, types); + } + + /** + * Register that a static is used for something else than a direct call + * target. + */ + void registerNonCallStaticUse(HStatic node) { + argumentTypes.registerNonCallStaticUse(node); + } + + /** + * Retrieve the types of the parameters used for calling the [element] + * function. The types are optimistic in the sense as they are based on the + * possible invocations of the function seen so far. + * + * Invariant: [element] must be a declaration element. + */ + HTypeList optimisticParameterTypes( + FunctionElement element, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + if (element.parameterCount(compiler) == 0) return HTypeList.ALL_UNKNOWN; + return argumentTypes.parameterTypes(element, defaultValueTypes); + } + + /** + * Register that the function [element] has been optimized under the + * assumptions that the types [parameterType] will be used for calling it. + * The passed [defaultValueTypes] holds the types of default values for + * the optional parameters. If this assumption fail the function will be + * scheduled for recompilation. + * + * Invariant: [element] must be a declaration element. + */ + registerParameterTypesOptimization( + FunctionElement element, + HTypeList parameterTypes, + OptionalParameterTypes defaultValueTypes) { + assert(invariant(element, element.isDeclaration)); + if (element.parameterCount(compiler) == 0) return; + argumentTypes.registerOptimizedFunction( + element, parameterTypes, defaultValueTypes); + } + + registerFieldTypesOptimization(FunctionElement element, + Element field, + HType type) { + fieldTypes.registerOptimizedFunction(element, field, type); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [element] must be a declaration element. + */ + void registerReturnType(FunctionElement element, HType returnType) { + assert(invariant(element, element.isDeclaration)); + ReturnInfo info = returnInfo[element]; + if (info != null) { + info.update(returnType, scheduleForRecompilation, compiler); + } else { + returnInfo[element] = new ReturnInfo(returnType); + } + } + + /** + * Retrieve the return type of the function [callee]. The type is optimistic + * in the sense that is is based on the compilation of [callee]. If [callee] + * is recompiled the return type might change to someting broader. For that + * reason [caller] is registered for recompilation if this happens. If the + * function [callee] has not yet been compiled the returned type is [null]. + * + * Invariant: Both [caller] and [callee] must be declaration elements. + */ + HType optimisticReturnTypesWithRecompilationOnTypeChange( + Element caller, FunctionElement callee) { + assert(invariant(callee, callee.isDeclaration)); + returnInfo.putIfAbsent(callee, () => new ReturnInfo.unknownType()); + ReturnInfo info = returnInfo[callee]; + HType returnType = info.returnType; + if (returnType != HType.UNKNOWN && returnType != null && caller != null) { + assert(invariant(caller, caller.isDeclaration)); + info.addCompiledFunction(caller); + } + return info.returnType; + } + + void dumpReturnTypes() { + returnInfo.forEach((Element element, ReturnInfo info) { + if (info.returnType != HType.UNKNOWN) { + print("Inferred $element has return type ${info.returnType}"); + } + }); + } + + void registerConstructor(Element element) { + fieldTypes.registerConstructor(element); + } + + void registerFieldInitializer(Element field, HType type) { + fieldTypes.registerFieldInitializer(field, type); + } + + void registerFieldConstructor(Element field, HType type) { + fieldTypes.registerFieldConstructor(field, type); + } + + void registerFieldSetter(FunctionElement element, Element field, HType type) { + fieldTypes.registerFieldSetter(element, field, type); + } + + void addedDynamicSetter(Selector setter, HType type) { + fieldTypes.addedDynamicSetter(setter, type); + } + + HType optimisticFieldType(Element element) { + return fieldTypes.optimisticFieldType(element); + } + + /** + * Return the checked mode helper name that will be needed to do a + * type check on [type] at runtime. Note that this method is being + * called both by the resolver with interface types (int, String, + * ...), and by the SSA backend with implementation types (JSInt, + * JSString, ...). + */ + SourceString getCheckedModeHelper(DartType type) { + Element element = type.element; + bool nativeCheck = + emitter.nativeEmitter.requiresNativeIsCheck(element); + if (type.isMalformed) { + // Check for malformed types first, because the type may be a list type + // with a malformed argument type. + return const SourceString('malformedTypeCheck'); + } else if (type == compiler.types.voidType) { + return const SourceString('voidTypeCheck'); + } else if (element == jsStringClass || element == compiler.stringClass) { + return const SourceString('stringTypeCheck'); + } else if (element == jsDoubleClass || element == compiler.doubleClass) { + return const SourceString('doubleTypeCheck'); + } else if (element == jsNumberClass || element == compiler.numClass) { + return const SourceString('numTypeCheck'); + } else if (element == jsBoolClass || element == compiler.boolClass) { + return const SourceString('boolTypeCheck'); + } else if (element == jsFunctionClass + || element == compiler.functionClass) { + return const SourceString('functionTypeCheck'); + } else if (element == jsIntClass || element == compiler.intClass) { + return const SourceString('intTypeCheck'); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + return nativeCheck + ? const SourceString('numberOrStringSuperNativeTypeCheck') + : const SourceString('numberOrStringSuperTypeCheck'); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + return nativeCheck + ? const SourceString('stringSuperNativeTypeCheck') + : const SourceString('stringSuperTypeCheck'); + } else if (element == compiler.listClass || element == jsArrayClass) { + return const SourceString('listTypeCheck'); + } else { + if (Elements.isListSupertype(element, compiler)) { + return nativeCheck + ? const SourceString('listSuperNativeTypeCheck') + : const SourceString('listSuperTypeCheck'); + } else { + return nativeCheck + ? const SourceString('callTypeCheck') + : const SourceString('propertyTypeCheck'); + } + } + } + + void dumpInferredTypes() { + print("Inferred argument types:"); + print("------------------------"); + argumentTypes.dump(); + print(""); + print("Inferred return types:"); + print("----------------------"); + dumpReturnTypes(); + print(""); + print("Inferred field types:"); + print("------------------------"); + fieldTypes.dump(); + print(""); + } + + Element getExceptionUnwrapper() { + return compiler.findHelper(const SourceString('unwrapException')); + } + + Element getThrowRuntimeError() { + return compiler.findHelper(const SourceString('throwRuntimeError')); + } + + Element getThrowMalformedSubtypeError() { + return compiler.findHelper( + const SourceString('throwMalformedSubtypeError')); + } + + Element getThrowAbstractClassInstantiationError() { + return compiler.findHelper( + const SourceString('throwAbstractClassInstantiationError')); + } + + Element getClosureConverter() { + return compiler.findHelper(const SourceString('convertDartClosureToJS')); + } + + Element getTraceFromException() { + return compiler.findHelper(const SourceString('getTraceFromException')); + } + + Element getMapMaker() { + return compiler.findHelper(const SourceString('makeLiteralMap')); + } + + Element getSetRuntimeTypeInfo() { + return compiler.findHelper(const SourceString('setRuntimeTypeInfo')); + } + + Element getGetRuntimeTypeInfo() { + return compiler.findHelper(const SourceString('getRuntimeTypeInfo')); + } + + /** + * Remove [element] from the set of generated code, and put it back + * into the worklist. + * + * Invariant: [element] must be a declaration element. + */ + void eagerRecompile(Element element) { + assert(invariant(element, element.isDeclaration)); + generatedCode.remove(element); + generatedBailoutCode.remove(element); + compiler.enqueuer.codegen.addToWorkList(element); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart new file mode 100644 index 0000000..86717ae --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart
@@ -0,0 +1,325 @@ +// 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. + +part of js_backend; + +class ConstantEmitter { + ConstantReferenceEmitter _referenceEmitter; + ConstantInitializerEmitter _initializerEmitter; + + ConstantEmitter(Compiler compiler, Namer namer) { + _referenceEmitter = new ConstantReferenceEmitter(compiler, namer); + _initializerEmitter = new ConstantInitializerEmitter( + compiler, namer, _referenceEmitter); + } + + /** + * Constructs an expression that is a reference to the constant. Uses a + * canonical name unless the constant can be emitted multiple times (as for + * numbers and strings). + */ + js.Expression reference(Constant constant) { + return _referenceEmitter.generate(constant); + } + + /** + * Constructs an expression like [reference], but the expression is valid + * during isolate initialization. + */ + js.Expression referenceInInitializationContext(Constant constant) { + return _referenceEmitter.generateInInitializationContext(constant); + } + + /** + * Constructs an expression used to initialize a canonicalized constant. + */ + js.Expression initializationExpression(Constant constant) { + return _initializerEmitter.generate(constant); + } +} + +/** + * Visitor for generating JavaScript expressions to refer to [Constant]s. + * Do not use directly, use methods from [ConstantEmitter]. + */ +class ConstantReferenceEmitter implements ConstantVisitor<js.Expression> { + final Compiler compiler; + final Namer namer; + bool inIsolateInitializationContext = false; + + ConstantReferenceEmitter(this.compiler, this.namer); + + js.Expression generate(Constant constant) { + inIsolateInitializationContext = false; + return _visit(constant); + } + + js.Expression generateInInitializationContext(Constant constant) { + inIsolateInitializationContext = true; + return _visit(constant); + } + + js.Expression _visit(Constant constant) { + return constant.accept(this); + } + + js.Expression visitSentinel(SentinelConstant constant) { + return new js.VariableUse(namer.CURRENT_ISOLATE); + } + + js.Expression visitFunction(FunctionConstant constant) { + return inIsolateInitializationContext + ? new js.VariableUse(namer.isolatePropertiesAccess(constant.element)) + : new js.VariableUse(namer.isolateAccess(constant.element)); + } + + js.Expression visitNull(NullConstant constant) { + return new js.LiteralNull(); + } + + js.Expression visitInt(IntConstant constant) { + return new js.LiteralNumber('${constant.value}'); + } + + js.Expression visitDouble(DoubleConstant constant) { + double value = constant.value; + if (value.isNaN) { + return new js.LiteralNumber("(0/0)"); + } else if (value == double.INFINITY) { + return new js.LiteralNumber("(1/0)"); + } else if (value == -double.INFINITY) { + return new js.LiteralNumber("(-1/0)"); + } else { + return new js.LiteralNumber("$value"); + } + } + + js.Expression visitTrue(TrueConstant constant) { + if (compiler.enableMinification) { + // Use !0 for true. + return new js.Prefix("!", new js.LiteralNumber("0")); + } else { + return new js.LiteralBool(true); + } + + } + + js.Expression visitFalse(FalseConstant constant) { + if (compiler.enableMinification) { + // Use !1 for false. + return new js.Prefix("!", new js.LiteralNumber("1")); + } else { + return new js.LiteralBool(false); + } + } + + /** + * Write the contents of the quoted string to a [CodeBuffer] in + * a form that is valid as JavaScript string literal content. + * The string is assumed quoted by double quote characters. + */ + js.Expression visitString(StringConstant constant) { + // TODO(sra): If the string is long *and repeated* (and not on a hot path) + // then it should be assigned to a name. We don't have reference counts (or + // profile information) here, so this is the wrong place. + StringBuffer sb = new StringBuffer(); + writeJsonEscapedCharsOn(constant.value.slowToString(), sb); + return new js.LiteralString('"$sb"'); + } + + js.Expression emitCanonicalVersion(Constant constant) { + String name = namer.constantName(constant); + if (inIsolateInitializationContext) { + // $isolateName.$isolatePropertiesName.$name + return new js.PropertyAccess.field( + new js.PropertyAccess.field( + new js.VariableUse(namer.isolateName), + namer.isolatePropertiesName), + name); + } else { + return new js.PropertyAccess.field( + new js.VariableUse(namer.CURRENT_ISOLATE), + name); + } + } + + js.Expression visitList(ListConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitMap(MapConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitType(TypeConstant constant) { + return emitCanonicalVersion(constant); + } + + js.Expression visitConstructed(ConstructedConstant constant) { + return emitCanonicalVersion(constant); + } +} + +/** + * Visitor for generating JavaScript expressions to initialize [Constant]s. + * Do not use directly; use methods from [ConstantEmitter]. + */ +class ConstantInitializerEmitter implements ConstantVisitor<js.Expression> { + final Compiler compiler; + final Namer namer; + final ConstantReferenceEmitter referenceEmitter; + + ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter); + + js.Expression generate(Constant constant) { + return _visit(constant); + } + + js.Expression _visit(Constant constant) { + return constant.accept(this); + } + + js.Expression _reference(Constant constant) { + return referenceEmitter.generateInInitializationContext(constant); + } + + js.Expression visitSentinel(SentinelConstant constant) { + compiler.internalError( + "The parameter sentinel constant does not need specific JS code"); + } + + js.Expression visitFunction(FunctionConstant constant) { + compiler.internalError( + "The function constant does not need specific JS code"); + } + + js.Expression visitNull(NullConstant constant) { + return _reference(constant); + } + + js.Expression visitInt(IntConstant constant) { + return _reference(constant); + } + + js.Expression visitDouble(DoubleConstant constant) { + return _reference(constant); + } + + js.Expression visitTrue(TrueConstant constant) { + return _reference(constant); + } + + js.Expression visitFalse(FalseConstant constant) { + return _reference(constant); + } + + js.Expression visitString(StringConstant constant) { + // TODO(sra): Some larger strings are worth sharing. + return _reference(constant); + } + + js.Expression visitList(ListConstant constant) { + return new js.Call( + new js.PropertyAccess.field( + new js.VariableUse(namer.isolateName), + 'makeConstantList'), + [new js.ArrayInitializer.from(_array(constant.entries))]); + } + + String getJsConstructor(ClassElement element) { + return namer.isolatePropertiesAccess(element); + } + + js.Expression visitMap(MapConstant constant) { + js.Expression jsMap() { + List<js.Property> properties = <js.Property>[]; + int valueIndex = 0; + for (int i = 0; i < constant.keys.entries.length; i++) { + StringConstant key = constant.keys.entries[i]; + if (key.value == MapConstant.PROTO_PROPERTY) continue; + + // Keys in literal maps must be emitted in place. + js.Literal keyExpression = _visit(key); + js.Expression valueExpression = + _reference(constant.values[valueIndex++]); + properties.add(new js.Property(keyExpression, valueExpression)); + } + if (valueIndex != constant.values.length) { + compiler.internalError("Bad value count."); + } + return new js.ObjectInitializer(properties); + } + + void badFieldCountError() { + compiler.internalError( + "Compiler and ConstantMap disagree on number of fields."); + } + + ClassElement classElement = constant.type.element; + + List<js.Expression> arguments = <js.Expression>[]; + + // The arguments of the JavaScript constructor for any given Dart class + // are in the same order as the members of the class element. + int emittedArgumentCount = 0; + classElement.implementation.forEachInstanceField( + (ClassElement enclosing, Element field) { + if (field.name == MapConstant.LENGTH_NAME) { + arguments.add( + new js.LiteralNumber('${constant.keys.entries.length}')); + } else if (field.name == MapConstant.JS_OBJECT_NAME) { + arguments.add(jsMap()); + } else if (field.name == MapConstant.KEYS_NAME) { + arguments.add(_reference(constant.keys)); + } else if (field.name == MapConstant.PROTO_VALUE) { + assert(constant.protoValue != null); + arguments.add(_reference(constant.protoValue)); + } else { + badFieldCountError(); + } + emittedArgumentCount++; + }, + includeBackendMembers: true, + includeSuperMembers: true); + + if ((constant.protoValue == null && emittedArgumentCount != 3) || + (constant.protoValue != null && emittedArgumentCount != 4)) { + badFieldCountError(); + } + + return new js.New( + new js.VariableUse(getJsConstructor(classElement)), + arguments); + } + + js.Expression visitType(TypeConstant constant) { + SourceString helperSourceName = const SourceString('createRuntimeType'); + Element helper = compiler.findHelper(helperSourceName); + JavaScriptBackend backend = compiler.backend; + String helperName = backend.namer.getName(helper); + DartType type = constant.representedType; + Element element = type.element; + String name = backend.rti.getRawTypeRepresentation(type); + js.Expression typeName = new js.LiteralString("'$name'"); + return new js.Call( + new js.PropertyAccess.field( + new js.VariableUse(namer.CURRENT_ISOLATE), + helperName), + [typeName]); + } + + js.Expression visitConstructed(ConstructedConstant constant) { + return new js.New( + new js.VariableUse(getJsConstructor(constant.type.element)), + _array(constant.fields)); + } + + List<js.Expression> _array(List<Constant> values) { + List<js.Expression> valueList = <js.Expression>[]; + for (int i = 0; i < values.length; i++) { + valueList.add(_reference(values[i])); + } + return valueList; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart new file mode 100644 index 0000000..30c8bc5 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart
@@ -0,0 +1,240 @@ +// 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. + +part of js_backend; + +const JAVA_SCRIPT_CONSTANT_SYSTEM = const JavaScriptConstantSystem(); + +class JavaScriptBitNotOperation extends BitNotOperation { + const JavaScriptBitNotOperation(); + + Constant fold(Constant constant) { + if (JAVA_SCRIPT_CONSTANT_SYSTEM.isInt(constant)) { + // In JavaScript we don't check for -0 and treat it as if it was zero. + if (constant.isMinusZero()) constant = DART_CONSTANT_SYSTEM.createInt(0); + IntConstant intConstant = constant; + // We convert the result of bit-operations to 32 bit unsigned integers. + return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(~intConstant.value); + } + return null; + } +} + +/** + * In JavaScript we truncate the result to an unsigned 32 bit integer. Also, -0 + * is treated as if it was the integer 0. + */ +class JavaScriptBinaryBitOperation implements BinaryOperation { + final BinaryBitOperation dartBitOperation; + + const JavaScriptBinaryBitOperation(this.dartBitOperation); + + bool isUserDefinable() => dartBitOperation.isUserDefinable(); + SourceString get name => dartBitOperation.name; + + Constant fold(Constant left, Constant right) { + // In JavaScript we don't check for -0 and treat it as if it was zero. + if (left.isMinusZero()) left = DART_CONSTANT_SYSTEM.createInt(0); + if (right.isMinusZero()) right = DART_CONSTANT_SYSTEM.createInt(0); + IntConstant result = dartBitOperation.fold(left, right); + if (result != null) { + // We convert the result of bit-operations to 32 bit unsigned integers. + return JAVA_SCRIPT_CONSTANT_SYSTEM.createInt32(result.value); + } + return result; + } + + apply(left, right) => dartBitOperation.apply(left, right); +} + +class JavaScriptShiftRightOperation extends JavaScriptBinaryBitOperation { + const JavaScriptShiftRightOperation() : super(const ShiftRightOperation()); + + Constant fold(Constant left, Constant right) { + // Truncate the input value to 32 bits if necessary. + if (left.isInt()) { + IntConstant intConstant = left; + int value = intConstant.value; + int truncatedValue = value & JAVA_SCRIPT_CONSTANT_SYSTEM.BITS32; + // TODO(floitsch): we should treat the input to right shifts as unsigned. + + // Sign-extend. A 32 bit complement-two value x can be computed by: + // x_u - 2^32 (where x_u is its unsigned representation). + // Example: 0xFFFFFFFF - 0x100000000 => -1. + // We simply and with the sign-bit and multiply by two. If the sign-bit + // was set, then the result is 0. Otherwise it will become 2^32. + final int SIGN_BIT = 0x80000000; + truncatedValue -= 2 * (truncatedValue & SIGN_BIT); + if (value != truncatedValue) { + left = DART_CONSTANT_SYSTEM.createInt(truncatedValue); + } + } + return super.fold(left, right); + } +} + +class JavaScriptNegateOperation implements UnaryOperation { + final NegateOperation dartNegateOperation = const NegateOperation(); + + const JavaScriptNegateOperation(); + + bool isUserDefinable() => dartNegateOperation.isUserDefinable(); + SourceString get name => dartNegateOperation.name; + + Constant fold(Constant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + if (intConstant.value == 0) { + return JAVA_SCRIPT_CONSTANT_SYSTEM.createDouble(-0.0); + } + } + return dartNegateOperation.fold(constant); + } + apply(value) => -value; +} + +class JavaScriptBinaryArithmeticOperation implements BinaryOperation { + final BinaryOperation dartArithmeticOperation; + + const JavaScriptBinaryArithmeticOperation(this.dartArithmeticOperation); + + bool isUserDefinable() => dartArithmeticOperation.isUserDefinable(); + SourceString get name => dartArithmeticOperation.name; + + Constant fold(Constant left, Constant right) { + Constant result = dartArithmeticOperation.fold(left, right); + if (result == null) return result; + return JAVA_SCRIPT_CONSTANT_SYSTEM.convertToJavaScriptConstant(result); + } + + apply(left, right) => dartArithmeticOperation.apply(left, right); +} + +class JavaScriptIdentityOperation implements BinaryOperation { + final IdentityOperation dartIdentityOperation = const IdentityOperation(); + + const JavaScriptIdentityOperation(); + + bool isUserDefinable() => dartIdentityOperation.isUserDefinable(); + SourceString get name => dartIdentityOperation.name; + + BoolConstant fold(Constant left, Constant right) { + BoolConstant result = dartIdentityOperation.fold(left, right); + if (result == null || result.value) return result; + // In JavaScript -0.0 === 0 and all doubles are equal to their integer + // values. Furthermore NaN !== NaN. + if (left.isNum() && right.isNum()) { + NumConstant leftNum = left; + NumConstant rightNum = right; + double leftDouble = leftNum.value.toDouble(); + double rightDouble = rightNum.value.toDouble(); + return new BoolConstant(leftDouble == rightDouble); + } + return result; + } + + apply(left, right) => identical(left, right); +} + +/** + * Constant system following the semantics for Dart code that has been + * compiled to JavaScript. + */ +class JavaScriptConstantSystem extends ConstantSystem { + const int BITS31 = 0x8FFFFFFF; + const int BITS32 = 0xFFFFFFFF; + // The maximum integer value a double can represent without losing + // precision. + const int BITS53 = 0x1FFFFFFFFFFFFF; + + final add = const JavaScriptBinaryArithmeticOperation(const AddOperation()); + final bitAnd = const JavaScriptBinaryBitOperation(const BitAndOperation()); + final bitNot = const JavaScriptBitNotOperation(); + final bitOr = const JavaScriptBinaryBitOperation(const BitOrOperation()); + final bitXor = const JavaScriptBinaryBitOperation(const BitXorOperation()); + final booleanAnd = const BooleanAndOperation(); + final booleanOr = const BooleanOrOperation(); + final divide = + const JavaScriptBinaryArithmeticOperation(const DivideOperation()); + final equal = const EqualsOperation(); + final greaterEqual = const GreaterEqualOperation(); + final greater = const GreaterOperation(); + final identity = const JavaScriptIdentityOperation(); + final lessEqual = const LessEqualOperation(); + final less = const LessOperation(); + final modulo = + const JavaScriptBinaryArithmeticOperation(const ModuloOperation()); + final multiply = + const JavaScriptBinaryArithmeticOperation(const MultiplyOperation()); + final negate = const JavaScriptNegateOperation(); + final not = const NotOperation(); + final shiftLeft = + const JavaScriptBinaryBitOperation(const ShiftLeftOperation()); + final shiftRight = const JavaScriptShiftRightOperation(); + final subtract = + const JavaScriptBinaryArithmeticOperation(const SubtractOperation()); + final truncatingDivide = const JavaScriptBinaryArithmeticOperation( + const TruncatingDivideOperation()); + + const JavaScriptConstantSystem(); + + /** + * Returns true if the given [value] fits into a double without losing + * precision. + */ + bool integerFitsIntoDouble(int value) { + int absValue = value.abs(); + return (absValue & BITS53) == absValue; + } + + NumConstant convertToJavaScriptConstant(NumConstant constant) { + if (constant.isInt()) { + IntConstant intConstant = constant; + int intValue = intConstant.value; + if (!integerFitsIntoDouble(intValue)) { + return new DoubleConstant(intValue.toDouble()); + } + } else if (constant.isDouble()) { + DoubleConstant doubleResult = constant; + double doubleValue = doubleResult.value; + if (!doubleValue.isInfinite && !doubleValue.isNaN && + !constant.isMinusZero()) { + int intValue = doubleValue.toInt(); + if (intValue == doubleValue && integerFitsIntoDouble(intValue)) { + return new IntConstant(intValue); + } + } + } + return constant; + } + + NumConstant createInt(int i) + => convertToJavaScriptConstant(new IntConstant(i)); + NumConstant createInt32(int i) => new IntConstant(i & BITS32); + NumConstant createDouble(double d) + => convertToJavaScriptConstant(new DoubleConstant(d)); + StringConstant createString(DartString string, Node diagnosticNode) + => new StringConstant(string, diagnosticNode); + BoolConstant createBool(bool value) => new BoolConstant(value); + NullConstant createNull() => new NullConstant(); + + // Integer checks don't verify that the number is not -0.0. + bool isInt(Constant constant) => constant.isInt() || constant.isMinusZero(); + bool isDouble(Constant constant) + => constant.isDouble() && !constant.isMinusZero(); + bool isString(Constant constant) => constant.isString(); + bool isBool(Constant constant) => constant.isBool(); + bool isNull(Constant constant) => constant.isNull(); + + bool isSubtype(Compiler compiler, DartType s, DartType t) { + // At runtime, an integer is both an integer and a double: the + // integer type check is Math.floor, which will return true only + // for real integers, and our double type check is 'typeof number' + // which will return true for both integers and doubles. + if (s.element == compiler.intClass && t.element == compiler.doubleClass) { + return true; + } + return compiler.types.isSubtype(s, t); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart new file mode 100644 index 0000000..f401235 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart
@@ -0,0 +1,2359 @@ +// 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. + +part of js_backend; + +/** + * A function element that represents a closure call. The signature is copied + * from the given element. + */ +class ClosureInvocationElement extends FunctionElementX { + ClosureInvocationElement(SourceString name, + FunctionElement other) + : super.from(name, other, other.enclosingElement), + methodElement = other; + + isInstanceMember() => true; + + Element getOutermostEnclosingMemberOrTopLevel() => methodElement; + + /** + * The [member] this invocation refers to. + */ + Element methodElement; +} + +/** + * A convenient type alias for some functions that emit keyed values. + */ +typedef void DefineStubFunction(String invocationName, js.Expression value); + +/** + * A data structure for collecting fragments of a class definition. + */ +class ClassBuilder { + final List<js.Property> properties = <js.Property>[]; + + // Has the same signature as [DefineStubFunction]. + void addProperty(String name, js.Expression value) { + properties.add(new js.Property(js.string(name), value)); + } + + js.Expression toObjectInitializer() => new js.ObjectInitializer(properties); +} + +/** + * Generates the code for all used classes in the program. Static fields (even + * in classes) are ignored, since they can be treated as non-class elements. + * + * The code for the containing (used) methods must exist in the [:universe:]. + */ +class CodeEmitterTask extends CompilerTask { + bool needsInheritFunction = false; + bool needsDefineClass = false; + bool needsClosureClass = false; + bool needsLazyInitializer = false; + final Namer namer; + ConstantEmitter constantEmitter; + NativeEmitter nativeEmitter; + CodeBuffer boundClosureBuffer; + CodeBuffer mainBuffer; + /** Shorter access to [isolatePropertiesName]. Both here in the code, as + well as in the generated code. */ + String isolateProperties; + String classesCollector; + Set<ClassElement> neededClasses; + // TODO(ngeoffray): remove this field. + Set<ClassElement> instantiatedClasses; + + String get _ => compiler.enableMinification ? "" : " "; + String get n => compiler.enableMinification ? "" : "\n"; + String get N => compiler.enableMinification ? "\n" : ";\n"; + + /** + * A cache of closures that are used to closurize instance methods. + * A closure is dynamically bound to the instance used when + * closurized. + */ + final Map<int, String> boundClosureCache; + + /** + * A cache of closures that are used to closurize instance methods + * of interceptors. These closures are dynamically bound to the + * interceptor instance, and the actual receiver of the method. + */ + final Map<int, String> interceptorClosureCache; + + /** + * Raw ClassElement symbols occuring in is-checks and type assertions. If the + * program contains parameterized checks `x is Set<int>` and + * `x is Set<String>` then the ClassElement `Set` will occur once in + * [checkedClasses]. + */ + Set<ClassElement> checkedClasses; + + /** + * Raw Typedef symbols occuring in is-checks and type assertions. If the + * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement + * `F` will occur once in [checkedTypedefs]. + */ + Set<TypedefElement> checkedTypedefs; + + final bool generateSourceMap; + + CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap) + : boundClosureBuffer = new CodeBuffer(), + mainBuffer = new CodeBuffer(), + this.namer = namer, + boundClosureCache = new Map<int, String>(), + interceptorClosureCache = new Map<int, String>(), + constantEmitter = new ConstantEmitter(compiler, namer), + super(compiler) { + nativeEmitter = new NativeEmitter(this); + } + + void computeRequiredTypeChecks() { + assert(checkedClasses == null); + checkedClasses = new Set<ClassElement>(); + checkedTypedefs = new Set<TypedefElement>(); + compiler.codegenWorld.isChecks.forEach((DartType t) { + if (t is InterfaceType) { + checkedClasses.add(t.element); + } else if (t is TypedefType) { + checkedTypedefs.add(t.element); + } + }); + } + + js.Expression constantReference(Constant value) { + return constantEmitter.reference(value); + } + + js.Expression constantInitializerExpression(Constant value) { + return constantEmitter.initializationExpression(value); + } + + String get name => 'CodeEmitter'; + + String get defineClassName + => '${namer.isolateName}.\$defineClass'; + String get currentGenerateAccessorName + => '${namer.CURRENT_ISOLATE}.\$generateAccessor'; + String get generateAccessorHolder + => '$isolatePropertiesName.\$generateAccessor'; + String get finishClassesName + => '${namer.isolateName}.\$finishClasses'; + String get finishIsolateConstructorName + => '${namer.isolateName}.\$finishIsolateConstructor'; + String get pendingClassesName + => '${namer.isolateName}.\$pendingClasses'; + String get isolatePropertiesName + => '${namer.isolateName}.${namer.isolatePropertiesName}'; + String get supportsProtoName + => 'supportsProto'; + String get lazyInitializerName + => '${namer.isolateName}.\$lazy'; + + // Property name suffixes. If the accessors are renaming then the format + // is <accessorName>:<fieldName><suffix>. We use the suffix to know whether + // to look for the ':' separator in order to avoid doing the indexOf operation + // on every single property (they are quite rare). None of these characters + // are legal in an identifier and they are related by bit patterns. + // setter < 0x3c + // both = 0x3d + // getter > 0x3e + // renaming setter | 0x7c + // renaming both } 0x7d + // renaming getter ~ 0x7e + const SUFFIX_MASK = 0x3f; + const FIRST_SUFFIX_CODE = 0x3c; + const SETTER_CODE = 0x3c; + const GETTER_SETTER_CODE = 0x3d; + const GETTER_CODE = 0x3e; + const RENAMING_FLAG = 0x40; + String needsGetterCode(String variable) => '($variable & 3) > 0'; + String needsSetterCode(String variable) => '($variable & 2) == 0'; + String isRenaming(String variable) => '($variable & $RENAMING_FLAG) != 0'; + + String get generateAccessorFunction { + return """ +function generateAccessor(field, prototype) { + var len = field.length; + var lastCharCode = field.charCodeAt(len - 1); + var needsAccessor = (lastCharCode & $SUFFIX_MASK) >= $FIRST_SUFFIX_CODE; + if (needsAccessor) { + var needsGetter = ${needsGetterCode('lastCharCode')}; + var needsSetter = ${needsSetterCode('lastCharCode')}; + var renaming = ${isRenaming('lastCharCode')}; + var accessorName = field = field.substring(0, len - 1); + if (renaming) { + var divider = field.indexOf(":"); + accessorName = field.substring(0, divider); + field = field.substring(divider + 1); + } + if (needsGetter) { + var getterString = "return this." + field + ";"; + prototype["get\$" + accessorName] = new Function(getterString); + } + if (needsSetter) { + var setterString = "this." + field + " = v;"; + prototype["set\$" + accessorName] = new Function("v", setterString); + } + } + return field; +}"""; + } + + String get defineClassFunction { + // First the class name, then the field names in an array and the members + // (inside an Object literal). + // The caller can also pass in the constructor as a function if needed. + // + // Example: + // defineClass("A", ["x", "y"], { + // foo$1: function(y) { + // print(this.x + y); + // }, + // bar$2: function(t, v) { + // this.x = t - v; + // }, + // }); + return """ +function(cls, fields, prototype) { + var constructor; + if (typeof fields == 'function') { + constructor = fields; + } else { + var str = "function " + cls + "("; + var body = ""; + for (var i = 0; i < fields.length; i++) { + if (i != 0) str += ", "; + var field = fields[i]; + field = generateAccessor(field, prototype); + str += field; + body += "this." + field + " = " + field + ";\\n"; + } + str += ") {" + body + "}\\n"; + str += "return " + cls + ";"; + constructor = new Function(str)(); + } + constructor.prototype = prototype; + constructor.builtin\$cls = cls; + return constructor; +}"""; + } + + /** Needs defineClass to be defined. */ + String get protoSupportCheck { + // On Firefox and Webkit browsers we can manipulate the __proto__ + // directly. Opera claims to have __proto__ support, but it is buggy. + // So we have to do more checks. + // Opera bug was filed as DSK-370158, and fixed as CORE-47615 + // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes). + // If the browser does not support __proto__ we need to instantiate an + // object with the correct (internal) prototype set up correctly, and then + // copy the members. + + return ''' +var $supportsProtoName = false; +var tmp = $defineClassName('c', ['f?'], {}).prototype; +if (tmp.__proto__) { + tmp.__proto__ = {}; + if (typeof tmp.get\$f !== 'undefined') $supportsProtoName = true; +} +'''; + } + + String get finishClassesFunction { + // 'defineClass' does not require the classes to be constructed in order. + // Classes are initially just stored in the 'pendingClasses' field. + // 'finishClasses' takes all pending classes and sets up the prototype. + // Once set up, the constructors prototype field satisfy: + // - it contains all (local) members. + // - its internal prototype (__proto__) points to the superclass' + // prototype field. + // - the prototype's constructor field points to the JavaScript + // constructor. + // For engines where we have access to the '__proto__' we can manipulate + // the object literal directly. For other engines we have to create a new + // object and copy over the members. + return ''' +function(collectedClasses) { + var hasOwnProperty = Object.prototype.hasOwnProperty; + for (var cls in collectedClasses) { + if (hasOwnProperty.call(collectedClasses, cls)) { + var desc = collectedClasses[cls]; +'''/* The 'fields' are either a constructor function or a string encoding + fields, constructor and superclass. Get the superclass and the fields + in the format Super;field1,field2 from the null-string property on the + descriptor. */''' + var fields = desc[''], supr; + if (typeof fields == 'string') { + var s = fields.split(';'); supr = s[0]; + fields = s[1] == '' ? [] : s[1].split(','); + } else { + supr = desc['super']; + } + $isolatePropertiesName[cls] = $defineClassName(cls, fields, desc); + if (supr) $pendingClassesName[cls] = supr; + } + } + var pendingClasses = $pendingClassesName; +'''/* FinishClasses can be called multiple times. This means that we need to + clear the pendingClasses property. */''' + $pendingClassesName = {}; + var finishedClasses = {}; + function finishClass(cls) { +'''/* Opera does not support 'getOwnPropertyNames'. Therefore we use + hasOwnProperty instead. */''' + var hasOwnProperty = Object.prototype.hasOwnProperty; + if (hasOwnProperty.call(finishedClasses, cls)) return; + finishedClasses[cls] = true; + var superclass = pendingClasses[cls]; +'''/* The superclass is only false (empty string) for Dart's Object class. */''' + if (!superclass) return; + finishClass(superclass); + var constructor = $isolatePropertiesName[cls]; + var superConstructor = $isolatePropertiesName[superclass]; + var prototype = constructor.prototype; + if ($supportsProtoName) { + prototype.__proto__ = superConstructor.prototype; + prototype.constructor = constructor; + } else { + function tmp() {}; + tmp.prototype = superConstructor.prototype; + var newPrototype = new tmp(); + constructor.prototype = newPrototype; + newPrototype.constructor = constructor; + for (var member in prototype) { + if (!member) continue; '''/* Short version of: if (member == '') */''' + if (hasOwnProperty.call(prototype, member)) { + newPrototype[member] = prototype[member]; + } + } + } + } + for (var cls in pendingClasses) finishClass(cls); +}'''; + } + + String get finishIsolateConstructorFunction { + String isolate = namer.isolateName; + // We replace the old Isolate function with a new one that initializes + // all its field with the initial (and often final) value of all globals. + // This has two advantages: + // 1. the properties are in the object itself (thus avoiding to go through + // the prototype when looking up globals. + // 2. a new isolate goes through a (usually well optimized) constructor + // function of the form: "function() { this.x = ...; this.y = ...; }". + // + // Example: If [isolateProperties] is an object containing: x = 3 and + // A = function A() { /* constructor of class A. */ }, then we generate: + // str = "{ + // var isolateProperties = Isolate.$isolateProperties; + // this.x = isolateProperties.x; + // this.A = isolateProperties.A; + // }"; + // which is then dynamically evaluated: + // var newIsolate = new Function(str); + // + // We also copy over old values like the prototype, and the + // isolateProperties themselves. + return """function(oldIsolate) { + var isolateProperties = oldIsolate.${namer.isolatePropertiesName}; + var isolatePrototype = oldIsolate.prototype; + var str = "{\\n"; + str += "var properties = $isolate.${namer.isolatePropertiesName};\\n"; + for (var staticName in isolateProperties) { + if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) { + str += "this." + staticName + "= properties." + staticName + ";\\n"; + } + } + str += "}\\n"; + var newIsolate = new Function(str); + newIsolate.prototype = isolatePrototype; + isolatePrototype.constructor = newIsolate; + newIsolate.${namer.isolatePropertiesName} = isolateProperties; + return newIsolate; +}"""; + } + + String get lazyInitializerFunction { + String isolate = namer.CURRENT_ISOLATE; + return """ +function(prototype, staticName, fieldName, getterName, lazyValue) { + var getter = new Function("{ return $isolate." + fieldName + ";}"); +$lazyInitializerLogic +}"""; + } + + String get lazyInitializerLogic { + String isolate = namer.CURRENT_ISOLATE; + JavaScriptBackend backend = compiler.backend; + String cyclicThrow = namer.isolateAccess(backend.cyclicThrowHelper); + return """ + var sentinelUndefined = {}; + var sentinelInProgress = {}; + prototype[fieldName] = sentinelUndefined; + prototype[getterName] = function() { + var result = $isolate[fieldName]; + try { + if (result === sentinelUndefined) { + $isolate[fieldName] = sentinelInProgress; + try { + result = $isolate[fieldName] = lazyValue(); + } finally { +""" // Use try-finally, not try-catch/throw as it destroys the stack trace. +""" + if (result === sentinelUndefined) { + if ($isolate[fieldName] === sentinelInProgress) { + $isolate[fieldName] = null; + } + } + } + } else if (result === sentinelInProgress) { + $cyclicThrow(staticName); + } + return result; + } finally { + $isolate[getterName] = getter; + } + };"""; + } + + void addDefineClassAndFinishClassFunctionsIfNecessary(CodeBuffer buffer) { + if (needsDefineClass) { + // Declare function called generateAccessor. This is used in + // defineClassFunction (it's a local declaration in init()). + buffer.add("$generateAccessorFunction$N"); + buffer.add("$generateAccessorHolder = generateAccessor$N"); + buffer.add("$defineClassName = $defineClassFunction$N"); + buffer.add(protoSupportCheck); + buffer.add("$pendingClassesName = {}$N"); + buffer.add("$finishClassesName = $finishClassesFunction$N"); + } + } + + void addLazyInitializerFunctionIfNecessary(CodeBuffer buffer) { + if (needsLazyInitializer) { + buffer.add("$lazyInitializerName = $lazyInitializerFunction$N"); + } + } + + void emitFinishIsolateConstructor(CodeBuffer buffer) { + String name = finishIsolateConstructorName; + String value = finishIsolateConstructorFunction; + buffer.add("$name = $value$N"); + } + + void emitFinishIsolateConstructorInvocation(CodeBuffer buffer) { + String isolate = namer.isolateName; + buffer.add("$isolate = $finishIsolateConstructorName($isolate)$N"); + } + + /** + * Generate stubs to handle invocation of methods with optional + * arguments. + * + * A method like [: foo([x]) :] may be invoked by the following + * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this + * function for detailed examples. + */ + void addParameterStub(FunctionElement member, + Selector selector, + DefineStubFunction defineStub, + Set<String> alreadyGenerated) { + FunctionSignature parameters = member.computeSignature(compiler); + int positionalArgumentCount = selector.positionalArgumentCount; + if (positionalArgumentCount == parameters.parameterCount) { + assert(selector.namedArgumentCount == 0); + return; + } + if (parameters.optionalParametersAreNamed + && selector.namedArgumentCount == parameters.optionalParameterCount) { + // If the selector has the same number of named arguments as + // the element, we don't need to add a stub. The call site will + // hit the method directly. + return; + } + ConstantHandler handler = compiler.constantHandler; + List<SourceString> names = selector.getOrderedNamedArguments(); + + String invocationName = namer.invocationName(selector); + if (alreadyGenerated.contains(invocationName)) return; + alreadyGenerated.add(invocationName); + + JavaScriptBackend backend = compiler.backend; + bool isInterceptorClass = + backend.isInterceptorClass(member.getEnclosingClass()); + + // If the method is in an interceptor class, we need to also pass + // the actual receiver. + int extraArgumentCount = isInterceptorClass ? 1 : 0; + // Use '$receiver' to avoid clashes with other parameter names. Using + // '$receiver' works because [:namer.safeName:] used for getting parameter + // names never returns a name beginning with a single '$'. + String receiverArgumentName = r'$receiver'; + + // The parameters that this stub takes. + List<js.Parameter> parametersBuffer = + new List<js.Parameter>.fixedLength( + selector.argumentCount + extraArgumentCount); + // The arguments that will be passed to the real method. + List<js.Expression> argumentsBuffer = + new List<js.Expression>.fixedLength( + parameters.parameterCount + extraArgumentCount); + + int count = 0; + if (isInterceptorClass) { + count++; + parametersBuffer[0] = new js.Parameter(receiverArgumentName); + argumentsBuffer[0] = new js.VariableUse(receiverArgumentName); + } + + int indexOfLastOptionalArgumentInParameters = positionalArgumentCount - 1; + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(member); + + parameters.orderedForEachParameter((Element element) { + String jsName = backend.namer.safeName(element.name.slowToString()); + assert(jsName != receiverArgumentName); + int optionalParameterStart = positionalArgumentCount + extraArgumentCount; + if (count < optionalParameterStart) { + parametersBuffer[count] = new js.Parameter(jsName); + argumentsBuffer[count] = new js.VariableUse(jsName); + } else { + int index = names.indexOf(element.name); + if (index != -1) { + indexOfLastOptionalArgumentInParameters = count; + // The order of the named arguments is not the same as the + // one in the real method (which is in Dart source order). + argumentsBuffer[count] = new js.VariableUse(jsName); + parametersBuffer[optionalParameterStart + index] = + new js.Parameter(jsName); + // Note that [elements] may be null for a synthesized [member]. + } else if (elements != null && elements.isParameterChecked(element)) { + argumentsBuffer[count] = constantReference(SentinelConstant.SENTINEL); + } else { + Constant value = handler.initialVariableValues[element]; + if (value == null) { + argumentsBuffer[count] = constantReference(new NullConstant()); + } else { + if (!value.isNull()) { + // If the value is the null constant, we should not pass it + // down to the native method. + indexOfLastOptionalArgumentInParameters = count; + } + argumentsBuffer[count] = constantReference(value); + } + } + } + count++; + }); + + List<js.Statement> body; + if (member.hasFixedBackendName()) { + body = nativeEmitter.generateParameterStubStatements( + member, invocationName, parametersBuffer, argumentsBuffer, + indexOfLastOptionalArgumentInParameters); + } else { + body = <js.Statement>[ + new js.Return( + new js.VariableUse('this') + .dot(namer.getName(member)) + .callWith(argumentsBuffer))]; + } + + js.Fun function = new js.Fun(parametersBuffer, new js.Block(body)); + + defineStub(invocationName, function); + } + + void addParameterStubs(FunctionElement member, + DefineStubFunction defineStub) { + // We fill the lists depending on the selector. For example, + // take method foo: + // foo(a, b, {c, d}); + // + // We may have multiple ways of calling foo: + // (1) foo(1, 2); + // (2) foo(1, 2, c: 3); + // (3) foo(1, 2, d: 4); + // (4) foo(1, 2, c: 3, d: 4); + // (5) foo(1, 2, d: 4, c: 3); + // + // What we generate at the call sites are: + // (1) foo$2(1, 2); + // (2) foo$3$c(1, 2, 3); + // (3) foo$3$d(1, 2, 4); + // (4) foo$4$c$d(1, 2, 3, 4); + // (5) foo$4$c$d(1, 2, 3, 4); + // + // The stubs we generate are (expressed in Dart): + // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) + // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); + // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); + // (4) No stub generated, call is direct. + // (5) No stub generated, call is direct. + + // Keep a cache of which stubs have already been generated, to + // avoid duplicates. Note that even if selectors are + // canonicalized, we would still need this cache: a typed selector + // on A and a typed selector on B could yield the same stub. + Set<String> generatedStubNames = new Set<String>(); + if (compiler.enabledFunctionApply + && member.name == namer.closureInvocationSelectorName) { + // If [Function.apply] is called, we pessimistically compile all + // possible stubs for this closure. + FunctionSignature signature = member.computeSignature(compiler); + Set<Selector> selectors = signature.optionalParametersAreNamed + ? computeNamedSelectors(signature, member) + : computeOptionalSelectors(signature, member); + for (Selector selector in selectors) { + addParameterStub(member, selector, defineStub, generatedStubNames); + } + } else { + Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; + if (selectors == null) return; + for (Selector selector in selectors) { + if (!selector.applies(member, compiler)) continue; + addParameterStub(member, selector, defineStub, generatedStubNames); + } + } + } + + /** + * Compute the set of possible selectors in the presence of named + * parameters. + */ + Set<Selector> computeNamedSelectors(FunctionSignature signature, + FunctionElement element) { + Set<Selector> selectors = new Set<Selector>(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + <SourceString>[])); + + // For each optional parameter, we iterator over the set of + // already computed selectors and create new selectors with that + // parameter now being passed. + signature.forEachOptionalParameter((Element element) { + Set<Selector> newSet = new Set<Selector>(); + selectors.forEach((Selector other) { + List<SourceString> namedArguments = [element.name]; + namedArguments.addAll(other.namedArguments); + newSet.add(new Selector(other.kind, + other.name, + other.library, + other.argumentCount + 1, + namedArguments)); + }); + selectors.addAll(newSet); + }); + return selectors; + } + + /** + * Compute the set of possible selectors in the presence of optional + * non-named parameters. + */ + Set<Selector> computeOptionalSelectors(FunctionSignature signature, + FunctionElement element) { + Set<Selector> selectors = new Set<Selector>(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + <SourceString>[])); + + // For each optional parameter, we increment the number of passed + // argument. + for (int i = 1; i <= signature.optionalParameterCount; i++) { + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount + i, + <SourceString>[])); + } + return selectors; + } + + bool instanceFieldNeedsGetter(Element member) { + assert(member.isField()); + return compiler.codegenWorld.hasInvokedGetter(member, compiler); + } + + bool instanceFieldNeedsSetter(Element member) { + assert(member.isField()); + return (!member.modifiers.isFinalOrConst()) + && compiler.codegenWorld.hasInvokedSetter(member, compiler); + } + + String compiledFieldName(Element member) { + assert(member.isField()); + return member.hasFixedBackendName() + ? member.fixedBackendName() + : namer.getName(member); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void addInstanceMember(Element member, ClassBuilder builder) { + assert(invariant(member, member.isDeclaration)); + // TODO(floitsch): we don't need to deal with members of + // uninstantiated classes, that have been overwritten by subclasses. + + if (member.isFunction() + || member.isGenerativeConstructorBody() + || member.isAccessor()) { + if (member.isAbstract(compiler)) return; + JavaScriptBackend backend = compiler.backend; + js.Expression code = backend.generatedCode[member]; + if (code == null) return; + builder.addProperty(namer.getName(member), code); + code = backend.generatedBailoutCode[member]; + if (code != null) { + builder.addProperty(namer.getBailoutName(member), code); + } + FunctionElement function = member; + FunctionSignature parameters = function.computeSignature(compiler); + if (!parameters.optionalParameters.isEmpty) { + addParameterStubs(member, builder.addProperty); + } + } else if (!member.isField()) { + compiler.internalError('unexpected kind: "${member.kind}"', + element: member); + } + emitExtraAccessors(member, builder); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void emitInstanceMembers(ClassElement classElement, + ClassBuilder builder) { + assert(invariant(classElement, classElement.isDeclaration)); + JavaScriptBackend backend = compiler.backend; + if (classElement == backend.objectInterceptorClass) { + emitInterceptorMethods(builder); + // The ObjectInterceptor does not have any instance methods. + return; + } + + void visitMember(ClassElement enclosing, Element member) { + assert(invariant(classElement, member.isDeclaration)); + if (member.isInstanceMember()) { + addInstanceMember(member, builder); + } + } + + // TODO(kasperl): We should make sure to only emit one version of + // overridden methods. Right now, we rely on the ordering so the + // methods pulled in from mixins are replaced with the members + // from the class definition. + + // If the class is a native class, we have to add the instance + // members defined in the non-native mixin applications used by + // the class. + visitNativeMixins(classElement, (MixinApplicationElement mixin) { + mixin.forEachMember( + visitMember, + includeBackendMembers: true, + includeSuperMembers: false); + }); + + classElement.implementation.forEachMember( + visitMember, + includeBackendMembers: true, + includeSuperMembers: false); + + generateIsTestsOn(classElement, (Element other) { + js.Expression code; + if (compiler.objectClass == other) return; + if (nativeEmitter.requiresNativeIsCheck(other)) { + code = js.fun([], js.block1(js.return_(new js.LiteralBool(true)))); + } else { + code = new js.LiteralBool(true); + } + builder.addProperty(namer.operatorIs(other), code); + }); + + if (identical(classElement, compiler.objectClass) + && compiler.enabledNoSuchMethod) { + // Emit the noSuchMethod handlers on the Object prototype now, + // so that the code in the dynamicFunction helper can find + // them. Note that this helper is invoked before analyzing the + // full JS script. + if (!nativeEmitter.handleNoSuchMethod) { + emitNoSuchMethodHandlers(builder.addProperty); + } + } + + if (backend.isInterceptorClass(classElement)) { + // The operator== method in [:Object:] does not take the same + // number of arguments as an intercepted method, therefore we + // explicitely add one to all interceptor classes. Note that we + // would not have do do that if all intercepted methods had + // a calling convention where the receiver is the first + // parameter. + String name = backend.namer.publicInstanceMethodNameByArity( + const SourceString('=='), 1); + Function kind = (classElement == backend.jsNullClass) + ? js.equals + : js.strictEquals; + builder.addProperty(name, js.fun(['receiver', 'a'], + js.block1(js.return_(kind(js.use('receiver'), js.use('a')))))); + } + } + + void emitRuntimeClassesAndTests(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + RuntimeTypeInformation rti = backend.rti; + + TypeChecks typeChecks = rti.computeRequiredChecks(); + + bool needsHolder(ClassElement cls) { + return !neededClasses.contains(cls) || cls.isNative() || + rti.isJsNative(cls); + } + + void maybeGenerateHolder(ClassElement cls) { + if (!needsHolder(cls)) return; + + String holder = namer.isolateAccess(cls); + String name = namer.getName(cls); + buffer.add("$holder$_=$_{builtin\$cls:$_'$name'"); + for (ClassElement check in typeChecks[cls]) { + buffer.add(',$_${namer.operatorIs(check)}:${_}true'); + }; + buffer.add('}$N'); + } + + // Create representation objects for classes that we do not have a class + // definition for (because they are uninstantiated or native). + for (ClassElement cls in rti.allArguments) { + maybeGenerateHolder(cls); + } + + // Add checks to the constructors of instantiated classes. + for (ClassElement cls in typeChecks) { + if (needsHolder(cls)) { + // We already emitted the is-checks in the object definition for this + // class. + continue; + } + String holder = namer.isolateAccess(cls); + for (ClassElement check in typeChecks[cls]) { + buffer.add('$holder.${namer.operatorIs(check)}$_=${_}true$N'); + }; + } + } + + void visitNativeMixins(ClassElement classElement, + void visit(MixinApplicationElement mixinApplication)) { + if (!classElement.isNative()) return; + // Use recursion to make sure to visit the superclasses before the + // subclasses. Once we start keeping track of the emitted fields + // and members, we're going to want to visit these in the other + // order so we get the most specialized definition first. + void recurse(ClassElement cls) { + if (cls == null || !cls.isMixinApplication) return; + recurse(cls.superclass); + assert(!cls.isNative()); + visit(cls); + } + recurse(classElement.superclass); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void visitClassFields(ClassElement classElement, + void addField(Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter)) { + assert(invariant(classElement, classElement.isDeclaration)); + // If the class is never instantiated we still need to set it up for + // inheritance purposes, but we can simplify its JavaScript constructor. + bool isInstantiated = + compiler.codegenWorld.instantiatedClasses.contains(classElement); + + void visitField(ClassElement enclosingClass, Element member) { + assert(invariant(classElement, member.isDeclaration)); + LibraryElement library = member.getLibrary(); + SourceString name = member.name; + bool isPrivate = name.isPrivate(); + + // Keep track of whether or not we're dealing with a field mixin + // into a native class. + bool isMixinNativeField = + classElement.isNative() && enclosingClass.isMixinApplication; + + // See if we can dynamically create getters and setters. + // We can only generate getters and setters for [classElement] since + // the fields of super classes could be overwritten with getters or + // setters. + bool needsGetter = false; + bool needsSetter = false; + // We need to name shadowed fields differently, so they don't clash with + // the non-shadowed field. + bool isShadowed = false; + if (isMixinNativeField || identical(enclosingClass, classElement)) { + needsGetter = instanceFieldNeedsGetter(member); + needsSetter = instanceFieldNeedsSetter(member); + } else { + isShadowed = classElement.isShadowedByField(member); + } + + if ((isInstantiated && !enclosingClass.isNative()) + || needsGetter + || needsSetter) { + String accessorName = isShadowed + ? namer.shadowedFieldName(member) + : namer.getName(member); + String fieldName = member.hasFixedBackendName() + ? member.fixedBackendName() + : (isMixinNativeField ? member.name.slowToString() : accessorName); + bool needsCheckedSetter = false; + if (needsSetter && compiler.enableTypeAssertions + && canGenerateCheckedSetter(member)) { + needsCheckedSetter = true; + needsSetter = false; + } + // Getters and setters with suffixes will be generated dynamically. + addField(member, + fieldName, + accessorName, + needsGetter, + needsSetter, + needsCheckedSetter); + } + } + + // TODO(kasperl): We should make sure to only emit one version of + // overridden fields. Right now, we rely on the ordering so the + // fields pulled in from mixins are replaced with the fields from + // the class definition. + + // If the class is a native class, we have to add the fields + // defined in the non-native mixin applications used by the class. + visitNativeMixins(classElement, (MixinApplicationElement mixin) { + mixin.forEachInstanceField( + visitField, + includeBackendMembers: true, + includeSuperMembers: false); + }); + + // If a class is not instantiated then we add the field just so we can + // generate the field getter/setter dynamically. Since this is only + // allowed on fields that are in [classElement] we don't need to visit + // superclasses for non-instantiated classes. + classElement.implementation.forEachInstanceField( + visitField, + includeBackendMembers: true, + includeSuperMembers: isInstantiated && !classElement.isNative()); + } + + void generateGetter(Element member, String fieldName, String accessorName, + ClassBuilder builder) { + String getterName = namer.getterNameFromAccessorName(accessorName); + builder.addProperty(getterName, + js.fun([], js.block1(js.return_(js.use('this').dot(fieldName))))); + } + + void generateSetter(Element member, String fieldName, String accessorName, + ClassBuilder builder) { + String setterName = namer.setterNameFromAccessorName(accessorName); + builder.addProperty(setterName, + js.fun(['v'], + js.block1( + new js.ExpressionStatement( + js.assign(js.use('this').dot(fieldName), js.use('v')))))); + } + + bool canGenerateCheckedSetter(Element member) { + DartType type = member.computeType(compiler); + if (type.element.isTypeVariable() + || type.element == compiler.dynamicClass + || type.element == compiler.objectClass) { + // TODO(ngeoffray): Support type checks on type parameters. + return false; + } + return true; + } + + void generateCheckedSetter(Element member, + String fieldName, + String accessorName, + ClassBuilder builder) { + assert(canGenerateCheckedSetter(member)); + DartType type = member.computeType(compiler); + // TODO(ahe): Generate a dynamic type error here. + if (type.element.isErroneous()) return; + SourceString helper = compiler.backend.getCheckedModeHelper(type); + FunctionElement helperElement = compiler.findHelper(helper); + String helperName = namer.isolateAccess(helperElement); + List<js.Expression> arguments = <js.Expression>[js.use('v')]; + if (helperElement.computeSignature(compiler).parameterCount != 1) { + arguments.add(js.string(namer.operatorIs(type.element))); + } + + String setterName = namer.setterNameFromAccessorName(accessorName); + builder.addProperty(setterName, + js.fun(['v'], + js.block1( + new js.ExpressionStatement( + js.assign( + js.use('this').dot(fieldName), + js.call(js.use(helperName), arguments)))))); + } + + void emitClassConstructor(ClassElement classElement, ClassBuilder builder) { + /* Do nothing. */ + } + + void emitSuper(String superName, ClassBuilder builder) { + /* Do nothing. */ + } + + void emitClassFields(ClassElement classElement, + ClassBuilder builder, + { String superClass: "", + bool classIsNative: false}) { + bool isFirstField = true; + StringBuffer buffer = new StringBuffer(); + if (!classIsNative) { + buffer.add('$superClass;'); + } + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + // Ignore needsCheckedSetter - that is handled below. + bool needsAccessor = (needsGetter || needsSetter); + // We need to output the fields for non-native classes so we can auto- + // generate the constructor. For native classes there are no + // constructors, so we don't need the fields unless we are generating + // accessors at runtime. + if (!classIsNative || needsAccessor) { + // Emit correct commas. + if (isFirstField) { + isFirstField = false; + } else { + buffer.add(','); + } + int flag = 0; + if (!needsAccessor) { + // Emit field for constructor generation. + assert(!classIsNative); + buffer.add(name); + } else { + // Emit (possibly renaming) field name so we can add accessors at + // runtime. + buffer.add(accessorName); + if (name != accessorName) { + buffer.add(':$name'); + // Only the native classes can have renaming accessors. + assert(classIsNative); + flag = RENAMING_FLAG; + } + } + if (needsGetter && needsSetter) { + buffer.addCharCode(GETTER_SETTER_CODE + flag); + } else if (needsGetter) { + buffer.addCharCode(GETTER_CODE + flag); + } else if (needsSetter) { + buffer.addCharCode(SETTER_CODE + flag); + } + } + }); + + String compactClassData = buffer.toString(); + if (compactClassData.length > 0) { + builder.addProperty('', js.string(compactClassData)); + } + } + + void emitClassGettersSetters(ClassElement classElement, + ClassBuilder builder) { + + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + compiler.withCurrentElement(member, () { + if (needsCheckedSetter) { + assert(!needsSetter); + generateCheckedSetter(member, name, accessorName, builder); + } + if (!getterAndSetterCanBeImplementedByFieldSpec) { + if (needsGetter) { + generateGetter(member, name, accessorName, builder); + } + if (needsSetter) { + generateSetter(member, name, accessorName, builder); + } + } + }); + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [classElement] must be a declaration element. + */ + void generateClass(ClassElement classElement, CodeBuffer buffer) { + assert(invariant(classElement, classElement.isDeclaration)); + if (classElement.isNative()) { + nativeEmitter.generateNativeClass(classElement); + return; + } + + needsDefineClass = true; + String className = namer.getName(classElement); + + // Find the first non-native superclass. + ClassElement superclass = classElement.superclass; + while (superclass != null && superclass.isNative()) { + superclass = superclass.superclass; + } + + String superName = ""; + if (superclass != null) { + superName = namer.getName(superclass); + } + + ClassBuilder builder = new ClassBuilder(); + + emitClassConstructor(classElement, builder); + emitSuper(superName, builder); + emitClassFields(classElement, builder, + superClass: superName, classIsNative: false); + emitClassGettersSetters(classElement, builder); + emitInstanceMembers(classElement, builder); + + js.Expression init = + js.assign( + js.use(classesCollector).dot(className), + builder.toObjectInitializer()); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N$n'); + } + + bool get getterAndSetterCanBeImplementedByFieldSpec => true; + + int _selectorRank(Selector selector) { + int arity = selector.argumentCount * 3; + if (selector.isGetter()) return arity + 2; + if (selector.isSetter()) return arity + 1; + return arity; + } + + int _compareSelectorNames(Selector selector1, Selector selector2) { + String name1 = selector1.name.toString(); + String name2 = selector2.name.toString(); + if (name1 != name2) return Comparable.compare(name1, name2); + return _selectorRank(selector1) - _selectorRank(selector2); + } + + void emitInterceptorMethods(ClassBuilder builder) { + JavaScriptBackend backend = compiler.backend; + // Emit forwarders for the ObjectInterceptor class. We need to + // emit all possible sends on intercepted methods. + for (Selector selector in + backend.usedInterceptors.toList()..sort(_compareSelectorNames)) { + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + parameters.add(new js.Parameter('receiver')); + + String name = backend.namer.invocationName(selector); + if (selector.isSetter()) { + parameters.add(new js.Parameter('value')); + arguments.add(new js.VariableUse('value')); + } else { + for (int i = 0; i < selector.argumentCount; i++) { + String argName = 'a$i'; + parameters.add(new js.Parameter(argName)); + arguments.add(new js.VariableUse(argName)); + } + } + js.Fun function = + new js.Fun(parameters, + new js.Block( + <js.Statement>[ + new js.Return( + new js.VariableUse('receiver') + .dot(name) + .callWith(arguments))])); + builder.addProperty(name, function); + } + } + + Iterable<Element> getTypedefChecksOn(DartType type) { + bool isSubtype(TypedefElement typedef) { + FunctionType typedefType = + typedef.computeType(compiler).unalias(compiler); + return compiler.types.isSubtype(type, typedefType); + } + return checkedTypedefs.where(isSubtype).toList() + ..sort(Elements.compareByPosition); + } + + /** + * Generate "is tests" for [cls]: itself, and the "is tests" for the + * classes it implements. We don't need to add the "is tests" of the + * super class because they will be inherited at runtime. + */ + void generateIsTestsOn(ClassElement cls, + void emitIsTest(Element element)) { + if (checkedClasses.contains(cls)) { + emitIsTest(cls); + } + + Set<Element> generated = new Set<Element>(); + // A class that defines a [:call:] method implicitly implements + // [Function] and needs checks for all typedefs that are used in is-checks. + if (checkedClasses.contains(compiler.functionClass) || + !checkedTypedefs.isEmpty) { + FunctionElement call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); + if (call == null) { + // If [cls] is a closure, it has a synthetic call operator method. + call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); + } + if (call != null) { + generateInterfacesIsTests(compiler.functionClass, + emitIsTest, + generated); + getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest); + } + } + + for (DartType interfaceType in cls.interfaces) { + generateInterfacesIsTests(interfaceType.element, emitIsTest, generated); + } + + // For native classes, we also have to run through their mixin + // applications and make sure we deal with 'is' tests correctly + // for those. + visitNativeMixins(cls, (MixinApplicationElement mixin) { + for (DartType interfaceType in mixin.interfaces) { + ClassElement interfaceElement = interfaceType.element; + generateInterfacesIsTests(interfaceType.element, emitIsTest, generated); + } + }); + } + + /** + * Generate "is tests" where [cls] is being implemented. + */ + void generateInterfacesIsTests(ClassElement cls, + void emitIsTest(ClassElement element), + Set<Element> alreadyGenerated) { + void tryEmitTest(ClassElement cls) { + if (!alreadyGenerated.contains(cls) && checkedClasses.contains(cls)) { + alreadyGenerated.add(cls); + emitIsTest(cls); + } + }; + + tryEmitTest(cls); + + for (DartType interfaceType in cls.interfaces) { + Element element = interfaceType.element; + tryEmitTest(element); + generateInterfacesIsTests(element, emitIsTest, alreadyGenerated); + } + + // We need to also emit "is checks" for the superclass and its supertypes. + ClassElement superclass = cls.superclass; + if (superclass != null) { + tryEmitTest(superclass); + generateInterfacesIsTests(superclass, emitIsTest, alreadyGenerated); + } + } + + /** + * Return a function that returns true if its argument is a class + * that needs to be emitted. + */ + Function computeClassFilter() { + Set<ClassElement> unneededClasses = new Set<ClassElement>(); + // The [Bool] class is not marked as abstract, but has a factory + // constructor that always throws. We never need to emit it. + unneededClasses.add(compiler.boolClass); + + JavaScriptBackend backend = compiler.backend; + + // Go over specialized interceptors and then constants to know which + // interceptors are needed. + Set<ClassElement> needed = new Set<ClassElement>(); + backend.specializedGetInterceptors.forEach( + (_, Collection<ClassElement> elements) { + needed.addAll(elements); + } + ); + + ConstantHandler handler = compiler.constantHandler; + List<Constant> constants = handler.getConstantsForEmission(); + for (Constant constant in constants) { + if (constant is ConstructedConstant) { + Element element = constant.computeType(compiler).element; + if (backend.isInterceptorClass(element)) { + needed.add(element); + } + } + } + + // Add unneeded interceptors to the [unneededClasses] set. + for (ClassElement interceptor in backend.interceptedClasses.keys) { + if (!needed.contains(interceptor)) { + unneededClasses.add(interceptor); + } + } + + return (ClassElement cls) => !unneededClasses.contains(cls); + } + + void emitClasses(CodeBuffer buffer) { + // Compute the required type checks to know which classes need a + // 'is$' method. + computeRequiredTypeChecks(); + List<ClassElement> sortedClasses = + new List<ClassElement>.from(neededClasses); + sortedClasses.sort((ClassElement class1, ClassElement class2) { + // We sort by the ids of the classes. There is no guarantee that these + // ids are meaningful (or even deterministic), but in the current + // implementation they are increasing within a source file. + return class1.id - class2.id; + }); + + // If we need noSuchMethod support, we run through all needed + // classes to figure out if we need the support on any native + // class. If so, we let the native emitter deal with it. + if (compiler.enabledNoSuchMethod) { + SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; + Selector noSuchMethodSelector = new Selector.noSuchMethod(); + for (ClassElement element in sortedClasses) { + if (!element.isNative()) continue; + Element member = element.lookupLocalMember(noSuchMethodName); + if (member == null) continue; + if (noSuchMethodSelector.applies(member, compiler)) { + nativeEmitter.handleNoSuchMethod = true; + break; + } + } + } + + for (ClassElement element in sortedClasses) { + generateClass(element, buffer); + } + + // The closure class could have become necessary because of the generation + // of stubs. + ClassElement closureClass = compiler.closureClass; + if (needsClosureClass && !instantiatedClasses.contains(closureClass)) { + generateClass(closureClass, buffer); + } + } + + void emitFinishClassesInvocationIfNecessary(CodeBuffer buffer) { + if (needsDefineClass) { + buffer.add("$finishClassesName($classesCollector)$N"); + // Reset the map. + buffer.add("$classesCollector$_=$_{}$N"); + } + } + + void emitStaticFunction(CodeBuffer buffer, + String name, + js.Expression functionExpression) { + js.Expression assignment = + js.assign(js.use(isolateProperties).dot(name), functionExpression); + buffer.add(js.prettyPrint(assignment, compiler)); + buffer.add('$N$n'); + } + + void emitStaticFunctions(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + bool isStaticFunction(Element element) => + !element.isInstanceMember() && !element.isField(); + + Iterable<Element> elements = + backend.generatedCode.keys.where(isStaticFunction); + Set<Element> pendingElementsWithBailouts = + backend.generatedBailoutCode.keys + .where(isStaticFunction) + .toSet(); + + for (Element element in Elements.sortedByPosition(elements)) { + js.Expression code = backend.generatedCode[element]; + emitStaticFunction(buffer, namer.getName(element), code); + js.Expression bailoutCode = backend.generatedBailoutCode[element]; + if (bailoutCode != null) { + pendingElementsWithBailouts.remove(element); + emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode); + } + } + + // Is it possible the primary function was inlined but the bailout was not? + for (Element element in + Elements.sortedByPosition(pendingElementsWithBailouts)) { + js.Expression bailoutCode = backend.generatedBailoutCode[element]; + emitStaticFunction(buffer, namer.getBailoutName(element), bailoutCode); + } + } + + void emitStaticFunctionGetters(CodeBuffer buffer) { + Set<FunctionElement> functionsNeedingGetter = + compiler.codegenWorld.staticFunctionsNeedingGetter; + for (FunctionElement element in + Elements.sortedByPosition(functionsNeedingGetter)) { + // The static function does not have the correct name. Since + // [addParameterStubs] use the name to create its stubs we simply + // create a fake element with the correct name. + // Note: the callElement will not have any enclosingElement. + FunctionElement callElement = + new ClosureInvocationElement(namer.closureInvocationSelectorName, + element); + String staticName = namer.getName(element); + String invocationName = namer.instanceMethodName(callElement); + String fieldAccess = '$isolateProperties.$staticName'; + buffer.add("$fieldAccess.$invocationName$_=$_$fieldAccess$N"); + + addParameterStubs(callElement, (String name, js.Expression value) { + js.Expression assignment = + js.assign( + js.use(isolateProperties).dot(staticName).dot(name), + value); + buffer.add( + js.prettyPrint(new js.ExpressionStatement(assignment), compiler)); + buffer.add('$N'); + }); + + // If a static function is used as a closure we need to add its name + // in case it is used in spawnFunction. + String fieldName = namer.STATIC_CLOSURE_NAME_NAME; + buffer.add('$fieldAccess.$fieldName$_=$_"$staticName"$N'); + getTypedefChecksOn(element.computeType(compiler)).forEach( + (Element typedef) { + String operator = namer.operatorIs(typedef); + buffer.add('$fieldAccess.$operator$_=${_}true$N'); + } + ); + } + } + + void emitBoundClosureClassHeader(String mangledName, + String superName, + List<String> fieldNames, + ClassBuilder builder) { + builder.addProperty('', + js.string("$superName;${Strings.join(fieldNames,',')}")); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitDynamicFunctionGetter(FunctionElement member, + DefineStubFunction defineStub) { + assert(invariant(member, member.isDeclaration)); + // For every method that has the same name as a property-get we create a + // getter that returns a bound closure. Say we have a class 'A' with method + // 'foo' and somewhere in the code there is a dynamic property get of + // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript): + // + // class A { + // foo(x, y, z) { ... } // Original function. + // get foo { return new BoundClosure499(this, "foo"); } + // } + // class BoundClosure499 extends Closure { + // var self; + // BoundClosure499(this.self, this.name); + // $call3(x, y, z) { return self[name](x, y, z); } + // } + + // TODO(floitsch): share the closure classes with other classes + // if they share methods with the same signature. Currently we do this only + // if there are no optional parameters. Closures with optional parameters + // are more difficult to canonicalize because they would need to have the + // same default values. + + bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0; + int parameterCount = member.parameterCount(compiler); + + Map<int, String> cache; + String extraArg = null; + // Methods on interceptor classes take an extra parameter, which is the + // actual receiver of the call. + JavaScriptBackend backend = compiler.backend; + bool inInterceptor = backend.isInterceptorClass(member.getEnclosingClass()); + if (inInterceptor) { + cache = interceptorClosureCache; + extraArg = 'receiver'; + } else { + cache = boundClosureCache; + } + List<String> fieldNames = compiler.enableMinification + ? inInterceptor ? const ['a', 'b', 'c'] + : const ['a', 'b'] + : inInterceptor ? const ['self', 'target', 'receiver'] + : const ['self', 'target']; + + Iterable<Element> typedefChecks = + getTypedefChecksOn(member.computeType(compiler)); + bool hasTypedefChecks = !typedefChecks.isEmpty; + + bool canBeShared = !hasOptionalParameters && !hasTypedefChecks; + + String closureClass = canBeShared ? cache[parameterCount] : null; + if (closureClass == null) { + // Either the class was not cached yet, or there are optional parameters. + // Create a new closure class. + String name; + if (canBeShared) { + if (inInterceptor) { + name = 'BoundClosure\$i${parameterCount}'; + } else { + name = 'BoundClosure\$${parameterCount}'; + } + } else { + name = 'Bound_${member.name.slowToString()}' + '_${member.enclosingElement.name.slowToString()}'; + } + + ClassElement closureClassElement = new ClosureClassElement( + new SourceString(name), compiler, member, member.getCompilationUnit()); + String mangledName = namer.getName(closureClassElement); + String superName = namer.getName(closureClassElement.superclass); + needsClosureClass = true; + + // Define the constructor with a name so that Object.toString can + // find the class name of the closure class. + ClassBuilder boundClosureBuilder = new ClassBuilder(); + emitBoundClosureClassHeader( + mangledName, superName, fieldNames, boundClosureBuilder); + // Now add the methods on the closure class. The instance method does not + // have the correct name. Since [addParameterStubs] use the name to create + // its stubs we simply create a fake element with the correct name. + // Note: the callElement will not have any enclosingElement. + FunctionElement callElement = + new ClosureInvocationElement(namer.closureInvocationSelectorName, + member); + + String invocationName = namer.instanceMethodName(callElement); + + List<String> parameters = <String>[]; + List<js.Expression> arguments = <js.Expression>[]; + if (inInterceptor) { + arguments.add(js.use('this').dot(fieldNames[2])); + } + for (int i = 0; i < parameterCount; i++) { + String name = 'p$i'; + parameters.add(name); + arguments.add(js.use(name)); + } + + js.Expression fun = + js.fun(parameters, + js.block1( + js.return_( + new js.PropertyAccess( + js.use('this').dot(fieldNames[0]), + js.use('this').dot(fieldNames[1])) + .callWith(arguments)))); + boundClosureBuilder.addProperty(invocationName, fun); + + addParameterStubs(callElement, boundClosureBuilder.addProperty); + typedefChecks.forEach((Element typedef) { + String operator = namer.operatorIs(typedef); + boundClosureBuilder.addProperty(operator, new js.LiteralBool(true)); + }); + + js.Expression init = + js.assign( + js.use(classesCollector).dot(mangledName), + boundClosureBuilder.toObjectInitializer()); + boundClosureBuffer.add(js.prettyPrint(init, compiler)); + boundClosureBuffer.add("$N"); + + closureClass = namer.isolateAccess(closureClassElement); + + // Cache it. + if (canBeShared) { + cache[parameterCount] = closureClass; + } + } + + // And finally the getter. + String getterName = namer.getterName(member); + String targetName = namer.instanceMethodName(member); + + List<String> parameters = <String>[]; + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(js.use('this')); + arguments.add(js.string(targetName)); + if (inInterceptor) { + parameters.add(extraArg); + arguments.add(js.use(extraArg)); + } + + js.Expression getterFunction = + js.fun(parameters, + js.block1( + js.return_( + new js.New(js.use(closureClass), arguments)))); + + defineStub(getterName, getterFunction); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitCallStubForGetter(Element member, + Set<Selector> selectors, + DefineStubFunction defineStub) { + assert(invariant(member, member.isDeclaration)); + LibraryElement memberLibrary = member.getLibrary(); + JavaScriptBackend backend = compiler.backend; + // If the class is an interceptor class, the stub gets the + // receiver explicitely and we need to pass it to the getter call. + bool isInterceptorClass = + backend.isInterceptorClass(member.getEnclosingClass()); + + const String receiverArgumentName = r'$receiver'; + + js.Expression buildGetter() { + if (member.isGetter()) { + String getterName = namer.getterName(member); + return new js.VariableUse('this').dot(getterName).callWith( + isInterceptorClass + ? <js.Expression>[new js.VariableUse(receiverArgumentName)] + : <js.Expression>[]); + } else { + String fieldName = member.hasFixedBackendName() + ? member.fixedBackendName() + : namer.instanceFieldName(member); + return new js.VariableUse('this').dot(fieldName); + } + } + + // Two selectors may match but differ only in type. To avoid generating + // identical stubs for each we track untyped selectors which already have + // stubs. + Set<Selector> generatedSelectors = new Set<Selector>(); + + for (Selector selector in selectors) { + if (selector.applies(member, compiler)) { + selector = selector.asUntyped; + if (generatedSelectors.contains(selector)) continue; + generatedSelectors.add(selector); + + String invocationName = namer.invocationName(selector); + Selector callSelector = new Selector.callClosureFrom(selector); + String closureCallName = namer.invocationName(callSelector); + + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + if (isInterceptorClass) { + parameters.add(new js.Parameter(receiverArgumentName)); + } + + for (int i = 0; i < selector.argumentCount; i++) { + String name = 'arg$i'; + parameters.add(new js.Parameter(name)); + arguments.add(new js.VariableUse(name)); + } + + js.Fun function = + new js.Fun(parameters, + new js.Block( + <js.Statement>[ + new js.Return( + buildGetter().dot(closureCallName) + .callWith(arguments))])); + + defineStub(invocationName, function); + } + } + } + + void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + Iterable<VariableElement> staticNonFinalFields = + handler.getStaticNonFinalFieldsForEmission(); + for (Element element in Elements.sortedByPosition(staticNonFinalFields)) { + compiler.withCurrentElement(element, () { + Constant initialValue = handler.getInitialValueFor(element); + js.Expression init = + new js.Assignment( + new js.PropertyAccess.field( + new js.VariableUse(isolateProperties), + namer.getName(element)), + constantEmitter.referenceInInitializationContext(initialValue)); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N'); + }); + } + } + + void emitLazilyInitializedStaticFields(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + List<VariableElement> lazyFields = + handler.getLazilyInitializedFieldsForEmission(); + JavaScriptBackend backend = compiler.backend; + if (!lazyFields.isEmpty) { + needsLazyInitializer = true; + for (VariableElement element in Elements.sortedByPosition(lazyFields)) { + assert(backend.generatedBailoutCode[element] == null); + js.Expression code = backend.generatedCode[element]; + assert(code != null); + // The code only computes the initial value. We build the lazy-check + // here: + // lazyInitializer(prototype, 'name', fieldName, getterName, initial); + // The name is used for error reporting. The 'initial' must be a + // closure that constructs the initial value. + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(js.use(isolateProperties)); + arguments.add(js.string(element.name.slowToString())); + arguments.add(js.string(namer.getName(element))); + arguments.add(js.string(namer.getLazyInitializerName(element))); + arguments.add(code); + js.Expression getter = buildLazyInitializedGetter(element); + if (getter != null) { + arguments.add(getter); + } + js.Expression init = js.call(js.use(lazyInitializerName), arguments); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add("$N"); + } + } + } + + js.Expression buildLazyInitializedGetter(VariableElement element) { + // Nothing to do, the 'lazy' function will create the getter. + return null; + } + + void emitCompileTimeConstants(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + List<Constant> constants = handler.getConstantsForEmission(); + bool addedMakeConstantList = false; + for (Constant constant in constants) { + // No need to emit functions. We already did that. + if (constant.isFunction()) continue; + // Numbers, strings and booleans are currently always inlined. + if (constant.isPrimitive()) continue; + + String name = namer.constantName(constant); + // The name is null when the constant is already a JS constant. + // TODO(floitsch): every constant should be registered, so that we can + // share the ones that take up too much space (like some strings). + if (name == null) continue; + if (!addedMakeConstantList && constant.isList()) { + addedMakeConstantList = true; + emitMakeConstantList(buffer); + } + js.Expression init = + new js.Assignment( + new js.PropertyAccess.field( + new js.VariableUse(isolateProperties), + name), + constantInitializerExpression(constant)); + buffer.add(js.prettyPrint(init, compiler)); + buffer.add('$N'); + } + } + + void emitMakeConstantList(CodeBuffer buffer) { + buffer.add(namer.isolateName); + buffer.add(r'''.makeConstantList = function(list) { + list.immutable$list = true; + list.fixed$length = true; + return list; +}; +'''); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [member] must be a declaration element. + */ + void emitExtraAccessors(Element member, ClassBuilder builder) { + assert(invariant(member, member.isDeclaration)); + if (member.isGetter() || member.isField()) { + Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; + if (selectors != null && !selectors.isEmpty) { + emitCallStubForGetter(member, selectors, builder.addProperty); + } + } else if (member.isFunction()) { + if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) { + emitDynamicFunctionGetter(member, builder.addProperty); + } + } + } + + void emitNoSuchMethodHandlers(DefineStubFunction defineStub) { + // Do not generate no such method handlers if there is no class. + if (compiler.codegenWorld.instantiatedClasses.isEmpty) return; + + String noSuchMethodName = namer.publicInstanceMethodNameByArity( + Compiler.NO_SUCH_METHOD, Compiler.NO_SUCH_METHOD_ARG_COUNT); + + Element createInvocationMirrorElement = + compiler.findHelper(const SourceString("createInvocationMirror")); + String createInvocationMirrorName = + namer.getName(createInvocationMirrorElement); + + // Keep track of the JavaScript names we've already added so we + // do not introduce duplicates (bad for code size). + Set<String> addedJsNames = new Set<String>(); + + // Keep track of the noSuchMethod holders for each possible + // receiver type. + Map<ClassElement, Set<ClassElement>> noSuchMethodHolders = + new Map<ClassElement, Set<ClassElement>>(); + Set<ClassElement> noSuchMethodHoldersFor(DartType type) { + ClassElement element = type.element; + Set<ClassElement> result = noSuchMethodHolders[element]; + if (result == null) { + // For now, we check the entire world to see if an object of + // the given type may have a user-defined noSuchMethod + // implementation. We could do better by only looking at + // instantiated (or otherwise needed) classes. + result = compiler.world.findNoSuchMethodHolders(type); + noSuchMethodHolders[element] = result; + } + return result; + } + + js.Expression generateMethod(String jsName, Selector selector) { + // Values match JSInvocationMirror in js-helper library. + int type = selector.invocationMirrorKind; + String methodName = selector.invocationMirrorMemberName; + List<js.Parameter> parameters = <js.Parameter>[]; + CodeBuffer args = new CodeBuffer(); + for (int i = 0; i < selector.argumentCount; i++) { + parameters.add(new js.Parameter('\$$i')); + } + + List<js.Expression> argNames = + selector.getOrderedNamedArguments().map((SourceString name) => + js.string(name.slowToString())).toList(); + + String internalName = namer.invocationMirrorInternalName(selector); + + String createInvocationMirror = namer.getName( + compiler.createInvocationMirrorElement); + + js.Expression expression = + new js.This() + .dot(noSuchMethodName) + .callWith( + <js.Expression>[ + new js.VariableUse(namer.CURRENT_ISOLATE) + .dot(createInvocationMirror) + .callWith( + <js.Expression>[ + js.string(methodName), + js.string(internalName), + new js.LiteralNumber('$type'), + new js.ArrayInitializer.from( + parameters.map((param) => js.use(param.name)) + .toList()), + new js.ArrayInitializer.from(argNames)])]); + js.Expression function = + new js.Fun(parameters, + new js.Block(<js.Statement>[new js.Return(expression)])); + return function; + } + + void addNoSuchMethodHandlers(SourceString ignore, Set<Selector> selectors) { + // Cache the object class and type. + ClassElement objectClass = compiler.objectClass; + DartType objectType = objectClass.computeType(compiler); + + for (Selector selector in selectors) { + // Introduce a helper function that determines if the given + // class has a member that matches the current name and + // selector (grabbed from the scope). + bool hasMatchingMember(ClassElement holder) { + Element element = holder.lookupMember(selector.name); + if (element == null) return false; + + // TODO(kasperl): Consider folding this logic into the + // Selector.applies() method. + if (element is AbstractFieldElement) { + AbstractFieldElement field = element; + if (selector.isGetter()) { + return field.getter != null; + } else if (selector.isSetter()) { + return field.setter != null; + } else { + return false; + } + } else if (element is VariableElement) { + if (selector.isSetter() && element.modifiers.isFinalOrConst()) { + return false; + } + } + return selector.applies(element, compiler); + } + + // If the selector is typed, we check to see if that type may + // have a user-defined noSuchMethod implementation. If not, we + // skip the selector altogether. + DartType receiverType = objectType; + ClassElement receiverClass = objectClass; + if (selector is TypedSelector) { + TypedSelector typedSelector = selector; + receiverType = typedSelector.receiverType; + receiverClass = receiverType.element; + } + + // If the receiver class is guaranteed to have a member that + // matches what we're looking for, there's no need to + // introduce a noSuchMethod handler. It will never be called. + // + // As an example, consider this class hierarchy: + // + // A <-- noSuchMethod + // / \ + // C B <-- foo + // + // If we know we're calling foo on an object of type B we + // don't have to worry about the noSuchMethod method in A + // because objects of type B implement foo. On the other hand, + // if we end up calling foo on something of type C we have to + // add a handler for it. + if (hasMatchingMember(receiverClass)) continue; + + // If the holders of all user-defined noSuchMethod + // implementations that might be applicable to the receiver + // type have a matching member for the current name and + // selector, we avoid introducing a noSuchMethod handler. + // + // As an example, consider this class hierarchy: + // + // A <-- foo + // / \ + // noSuchMethod --> B C <-- bar + // | | + // C D <-- noSuchMethod + // + // When calling foo on an object of type A, we know that the + // implementations of noSuchMethod are in the classes B and D + // that also (indirectly) implement foo, so we do not need a + // handler for it. + // + // If we're calling bar on an object of type D, we don't need + // the handler either because all objects of type D implement + // bar through inheritance. + // + // If we're calling bar on an object of type A we do need the + // handler because we may have to call B.noSuchMethod since B + // does not implement bar. + Set<ClassElement> holders = noSuchMethodHoldersFor(receiverType); + if (holders.every(hasMatchingMember)) continue; + String jsName = namer.invocationMirrorInternalName(selector); + if (!addedJsNames.contains(jsName)) { + js.Expression method = generateMethod(jsName, selector); + defineStub(jsName, method); + addedJsNames.add(jsName); + } + } + } + + compiler.codegenWorld.invokedNames.forEach(addNoSuchMethodHandlers); + compiler.codegenWorld.invokedGetters.forEach(addNoSuchMethodHandlers); + compiler.codegenWorld.invokedSetters.forEach(addNoSuchMethodHandlers); + } + + String buildIsolateSetup(CodeBuffer buffer, + Element appMain, + Element isolateMain) { + String mainAccess = "${namer.isolateAccess(appMain)}"; + String currentIsolate = "${namer.CURRENT_ISOLATE}"; + // Since we pass the closurized version of the main method to + // the isolate method, we must make sure that it exists. + if (!compiler.codegenWorld.staticFunctionsNeedingGetter.contains(appMain)) { + Selector selector = new Selector.callClosure(0); + String invocationName = namer.invocationName(selector); + buffer.add("$mainAccess.$invocationName = $mainAccess$N"); + } + return "${namer.isolateAccess(isolateMain)}($mainAccess)"; + } + + emitMain(CodeBuffer buffer) { + if (compiler.isMockCompilation) return; + Element main = compiler.mainApp.find(Compiler.MAIN); + String mainCall = null; + if (compiler.hasIsolateSupport()) { + Element isolateMain = + compiler.isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE); + mainCall = buildIsolateSetup(buffer, main, isolateMain); + } else { + mainCall = '${namer.isolateAccess(main)}()'; + } + if (!compiler.enableMinification) { + buffer.add(""" + +// +// BEGIN invoke [main]. +// +"""); + } + buffer.add(""" +if (typeof document !== 'undefined' && document.readyState !== 'complete') { + document.addEventListener('readystatechange', function () { + if (document.readyState == 'complete') { + if (typeof dartMainRunner === 'function') { + dartMainRunner(function() { ${mainCall}; }); + } else { + ${mainCall}; + } + } + }, false); +} else { + if (typeof dartMainRunner === 'function') { + dartMainRunner(function() { ${mainCall}; }); + } else { + ${mainCall}; + } +} +"""); + if (!compiler.enableMinification) { + buffer.add(""" +// +// END invoke [main]. +// + +"""); + } + } + + void emitGetInterceptorMethod(CodeBuffer buffer, + String objectName, + String key, + Collection<ClassElement> classes) { + js.Statement buildReturnInterceptor(ClassElement cls) { + return js.return_(js.fieldAccess(js.use(namer.isolateAccess(cls)), + 'prototype')); + } + + js.VariableUse receiver = js.use('receiver'); + JavaScriptBackend backend = compiler.backend; + + /** + * Build a JavaScrit AST node for doing a type check on + * [cls]. [cls] must be an interceptor class. + */ + js.Statement buildInterceptorCheck(ClassElement cls) { + js.Expression condition; + assert(backend.isInterceptorClass(cls)); + if (cls == backend.jsBoolClass) { + condition = js.equals(js.typeOf(receiver), js.string('boolean')); + } else if (cls == backend.jsIntClass || + cls == backend.jsDoubleClass || + cls == backend.jsNumberClass) { + throw 'internal error'; + } else if (cls == backend.jsArrayClass) { + condition = js.equals(js.fieldAccess(receiver, 'constructor'), + js.use('Array')); + } else if (cls == backend.jsStringClass) { + condition = js.equals(js.typeOf(receiver), js.string('string')); + } else if (cls == backend.jsNullClass) { + condition = js.equals(receiver, new js.LiteralNull()); + } else if (cls == backend.jsFunctionClass) { + condition = js.equals(js.typeOf(receiver), js.string('function')); + } else { + throw 'internal error'; + } + return js.if_(condition, buildReturnInterceptor(cls)); + } + + bool hasArray = false; + bool hasBool = false; + bool hasDouble = false; + bool hasFunction = false; + bool hasInt = false; + bool hasNull = false; + bool hasNumber = false; + bool hasString = false; + for (ClassElement cls in classes) { + if (cls == backend.jsArrayClass) hasArray = true; + else if (cls == backend.jsBoolClass) hasBool = true; + else if (cls == backend.jsDoubleClass) hasDouble = true; + else if (cls == backend.jsFunctionClass) hasFunction = true; + else if (cls == backend.jsIntClass) hasInt = true; + else if (cls == backend.jsNullClass) hasNull = true; + else if (cls == backend.jsNumberClass) hasNumber = true; + else if (cls == backend.jsStringClass) hasString = true; + else throw 'Internal error: $cls'; + } + if (hasDouble) { + assert(!hasNumber); + hasNumber = true; + } + if (hasInt) hasNumber = true; + + js.Block block = new js.Block.empty(); + + if (hasNumber) { + js.Statement whenNumber; + + /// Note: there are two number classes in play: Dart's [num], + /// and JavaScript's Number (typeof receiver == 'number'). This + /// is the fallback used when we have determined that receiver + /// is a JavaScript Number. + js.Return returnNumberClass = buildReturnInterceptor( + hasDouble ? backend.jsDoubleClass : backend.jsNumberClass); + + if (hasInt) { + js.Expression isInt = + js.equals(js.call(js.fieldAccess(js.use('Math'), 'floor'), + [receiver]), + receiver); + (whenNumber = js.emptyBlock()).statements + ..add(js.if_(isInt, buildReturnInterceptor(backend.jsIntClass))) + ..add(returnNumberClass); + } else { + whenNumber = returnNumberClass; + } + block.statements.add( + js.if_(js.equals(js.typeOf(receiver), js.string('number')), + whenNumber)); + } + + if (hasString) { + block.statements.add(buildInterceptorCheck(backend.jsStringClass)); + } + if (hasNull) { + block.statements.add(buildInterceptorCheck(backend.jsNullClass)); + } else { + // Returning "undefined" here will provoke a JavaScript + // TypeError which is later identified as a null-error by + // [unwrapException] in js_helper.dart. + block.statements.add(js.if_(js.equals(receiver, new js.LiteralNull()), + js.return_(js.undefined()))); + } + if (hasFunction) { + block.statements.add(buildInterceptorCheck(backend.jsFunctionClass)); + } + if (hasBool) { + block.statements.add(buildInterceptorCheck(backend.jsBoolClass)); + } + // TODO(ahe): It might be faster to check for Array before + // function and bool. + if (hasArray) { + block.statements.add(buildInterceptorCheck(backend.jsArrayClass)); + } + block.statements.add(js.return_(js.fieldAccess(js.use(objectName), + 'prototype'))); + + js.PropertyAccess name = js.fieldAccess(js.use(isolateProperties), key); + buffer.add(js.prettyPrint(js.assign(name, js.fun(['receiver'], block)), + compiler)); + buffer.add(N); + } + + /** + * Emit all versions of the [:getInterceptor:] method. + */ + void emitGetInterceptorMethods(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + // If no class needs to be intercepted, just return. + if (backend.objectInterceptorClass == null) return; + String objectName = namer.isolateAccess(backend.objectInterceptorClass); + var specializedGetInterceptors = backend.specializedGetInterceptors; + for (String name in specializedGetInterceptors.keys.toList()..sort()) { + Collection<ClassElement> classes = specializedGetInterceptors[name]; + emitGetInterceptorMethod(buffer, objectName, name, classes); + } + } + + void computeNeededClasses() { + instantiatedClasses = + compiler.codegenWorld.instantiatedClasses.where(computeClassFilter()) + .toSet(); + neededClasses = new Set<ClassElement>.from(instantiatedClasses); + for (ClassElement element in instantiatedClasses) { + for (ClassElement superclass = element.superclass; + superclass != null; + superclass = superclass.superclass) { + if (neededClasses.contains(superclass)) break; + neededClasses.add(superclass); + } + } + } + + int _compareSelectors(Selector selector1, Selector selector2) { + int comparison = _compareSelectorNames(selector1, selector2); + if (comparison != 0) return comparison; + + JavaScriptBackend backend = compiler.backend; + Set<ClassElement> classes1 = backend.getInterceptedClassesOn(selector1); + Set<ClassElement> classes2 = backend.getInterceptedClassesOn(selector2); + if (classes1.length != classes2.length) { + return classes1.length - classes2.length; + } + String getInterceptor1 = + namer.getInterceptorName(backend.getInterceptorMethod, classes1); + String getInterceptor2 = + namer.getInterceptorName(backend.getInterceptorMethod, classes2); + return Comparable.compare(getInterceptor1, getInterceptor2); + } + + void emitOneShotInterceptors(CodeBuffer buffer) { + JavaScriptBackend backend = compiler.backend; + for (Selector selector in + backend.oneShotInterceptors.toList()..sort(_compareSelectors)) { + Set<ClassElement> classes = backend.getInterceptedClassesOn(selector); + String oneShotInterceptorName = namer.oneShotInterceptorName(selector); + String getInterceptorName = + namer.getInterceptorName(backend.getInterceptorMethod, classes); + + List<js.Parameter> parameters = <js.Parameter>[]; + List<js.Expression> arguments = <js.Expression>[]; + parameters.add(new js.Parameter('receiver')); + arguments.add(js.use('receiver')); + + if (selector.isSetter()) { + parameters.add(new js.Parameter('value')); + arguments.add(js.use('value')); + } else { + for (int i = 0; i < selector.argumentCount; i++) { + String argName = 'a$i'; + parameters.add(new js.Parameter(argName)); + arguments.add(js.use(argName)); + } + } + + String invocationName = backend.namer.invocationName(selector); + js.Fun function = + new js.Fun(parameters, + js.block1(js.return_( + js.use(isolateProperties) + .dot(getInterceptorName) + .callWith([js.use('receiver')]) + .dot(invocationName) + .callWith(arguments)))); + + js.PropertyAccess property = + js.fieldAccess(js.use(isolateProperties), oneShotInterceptorName); + + buffer.add(js.prettyPrint(js.assign(property, function), compiler)); + buffer.add(N); + } + } + + String assembleProgram() { + measure(() { + computeNeededClasses(); + + mainBuffer.add(GENERATED_BY); + if (!compiler.enableMinification) mainBuffer.add(HOOKS_API_USAGE); + mainBuffer.add('function ${namer.isolateName}()$_{}\n'); + mainBuffer.add('init()$N$n'); + // Shorten the code by using "$$" as temporary. + classesCollector = r"$$"; + mainBuffer.add('var $classesCollector$_=$_{}$N'); + // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary. + isolateProperties = namer.CURRENT_ISOLATE; + mainBuffer.add( + 'var $isolateProperties$_=$_$isolatePropertiesName$N'); + emitClasses(mainBuffer); + mainBuffer.add(boundClosureBuffer); + // Clear the buffer, so that we can reuse it for the native classes. + boundClosureBuffer.clear(); + emitStaticFunctions(mainBuffer); + emitStaticFunctionGetters(mainBuffer); + // We need to finish the classes before we construct compile time + // constants. + emitFinishClassesInvocationIfNecessary(mainBuffer); + emitRuntimeClassesAndTests(mainBuffer); + emitCompileTimeConstants(mainBuffer); + // Static field initializations require the classes and compile-time + // constants to be set up. + emitStaticNonFinalFieldInitializations(mainBuffer); + emitOneShotInterceptors(mainBuffer); + emitGetInterceptorMethods(mainBuffer); + emitLazilyInitializedStaticFields(mainBuffer); + + isolateProperties = isolatePropertiesName; + // The following code should not use the short-hand for the + // initialStatics. + mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=${_}null$N'); + mainBuffer.add(boundClosureBuffer); + emitFinishClassesInvocationIfNecessary(mainBuffer); + // After this assignment we will produce invalid JavaScript code if we use + // the classesCollector variable. + classesCollector = 'classesCollector should not be used from now on'; + + emitFinishIsolateConstructorInvocation(mainBuffer); + mainBuffer.add('var ${namer.CURRENT_ISOLATE}$_=' + '${_}new ${namer.isolateName}()$N'); + + nativeEmitter.assembleCode(mainBuffer); + emitMain(mainBuffer); + mainBuffer.add('function init()$_{\n'); + mainBuffer.add('$isolateProperties$_=$_{}$N'); + addDefineClassAndFinishClassFunctionsIfNecessary(mainBuffer); + addLazyInitializerFunctionIfNecessary(mainBuffer); + emitFinishIsolateConstructor(mainBuffer); + mainBuffer.add('}\n'); + compiler.assembledCode = mainBuffer.getText(); + + if (generateSourceMap) { + SourceFile compiledFile = new SourceFile(null, compiler.assembledCode); + String sourceMap = buildSourceMap(mainBuffer, compiledFile); + compiler.outputProvider('', 'js.map') + ..add(sourceMap) + ..close(); + } + }); + return compiler.assembledCode; + } + + String buildSourceMap(CodeBuffer buffer, SourceFile compiledFile) { + SourceMapBuilder sourceMapBuilder = new SourceMapBuilder(); + buffer.forEachSourceLocation(sourceMapBuilder.addMapping); + return sourceMapBuilder.build(compiledFile); + } +} + +const String GENERATED_BY = """ +// Generated by dart2js, the Dart to JavaScript compiler. +"""; +const String HOOKS_API_USAGE = """ +// The code supports the following hooks: +// dartPrint(message) - if this function is defined it is called +// instead of the Dart [print] method. +// dartMainRunner(main) - if this function is defined, the Dart [main] +// method will not be invoked directly. +// Instead, a closure that will invoke [main] is +// passed to [dartMainRunner]. +""";
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart new file mode 100644 index 0000000..06d5d01 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart
@@ -0,0 +1,138 @@ +// 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. + +part of js_backend; + +class CodeEmitterNoEvalTask extends CodeEmitterTask { + CodeEmitterNoEvalTask(Compiler compiler, + Namer namer, + bool generateSourceMap) + : super(compiler, namer, generateSourceMap); + + String get generateGetterSetterFunction { + return """ +function() { + throw 'Internal Error: no dynamic generation of getters and setters allowed'; +}"""; + } + + String get defineClassFunction { + return """ +function(cls, constructor, prototype) { + constructor.prototype = prototype; + constructor.builtin\$cls = cls; + return constructor; +}"""; + } + + String get protoSupportCheck { + // We don't modify the prototypes in CSP mode. Therefore we can have an + // easier prototype-check. + return 'var $supportsProtoName = !!{}.__proto__;\n'; + } + + String get finishIsolateConstructorFunction { + // We replace the old Isolate function with a new one that initializes + // all its field with the initial (and often final) value of all globals. + // + // We also copy over old values like the prototype, and the + // isolateProperties themselves. + return """ +function(oldIsolate) { + var isolateProperties = oldIsolate.${namer.isolatePropertiesName}; + function Isolate() { + for (var staticName in isolateProperties) { + if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) { + this[staticName] = isolateProperties[staticName]; + } + } + // Use the newly created object as prototype. In Chrome this creates a + // hidden class for the object and makes sure it is fast to access. + function ForceEfficientMap() {} + ForceEfficientMap.prototype = this; + new ForceEfficientMap; + } + Isolate.prototype = oldIsolate.prototype; + Isolate.prototype.constructor = Isolate; + Isolate.${namer.isolatePropertiesName} = isolateProperties; + return Isolate; +}"""; + } + + String get lazyInitializerFunction { + return """ +function(prototype, staticName, fieldName, getterName, lazyValue, getter) { +$lazyInitializerLogic +}"""; + } + + js.Expression buildLazyInitializedGetter(VariableElement element) { + String isolate = namer.CURRENT_ISOLATE; + return js.fun([], + js.block1( + js.return_( + js.fieldAccess(js.use(isolate), namer.getName(element))))); + } + + js.Expression buildConstructor(String mangledName, List<String> fieldNames) { + return new js.NamedFunction( + new js.VariableDeclaration(mangledName), + new js.Fun( + fieldNames + .map((fieldName) => new js.Parameter(fieldName)) + .toList(), + new js.Block( + fieldNames.map((fieldName) => + new js.ExpressionStatement( + new js.Assignment( + new js.This().dot(fieldName), + new js.VariableUse(fieldName)))) + .toList()))); + } + + void emitBoundClosureClassHeader(String mangledName, + String superName, + List<String> fieldNames, + ClassBuilder builder) { + builder.addProperty('', buildConstructor(mangledName, fieldNames)); + builder.addProperty('super', js.string(superName)); + } + + void emitClassConstructor(ClassElement classElement, ClassBuilder builder) { + // Say we have a class A with fields b, c and d, where c needs a getter and + // d needs both a getter and a setter. Then we produce: + // - a constructor (directly into the given [buffer]): + // function A(b, c, d) { this.b = b, this.c = c, this.d = d; } + // - getters and setters (stored in the [explicitGettersSetters] list): + // get$c : function() { return this.c; } + // get$d : function() { return this.d; } + // set$d : function(x) { this.d = x; } + List<String> fields = <String>[]; + visitClassFields(classElement, (Element member, + String name, + String accessorName, + bool needsGetter, + bool needsSetter, + bool needsCheckedSetter) { + fields.add(name); + }); + String constructorName = namer.safeName(classElement.name.slowToString()); + + builder.addProperty('', buildConstructor(constructorName, fields)); + } + + void emitSuper(String superName, ClassBuilder builder) { + if (superName != '') { + builder.addProperty('super', js.string(superName)); + } + } + + void emitClassFields(ClassElement classElement, + ClassBuilder builder, + { String superClass: "", + bool classIsNative: false}) { + } + + bool get getterAndSetterCanBeImplementedByFieldSpec => false; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart new file mode 100644 index 0000000..52e6ee0 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart
@@ -0,0 +1,33 @@ +// 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. + +library js_backend; + +import 'dart:collection' show LinkedHashMap; + +import '../closure.dart'; +import '../../compiler.dart' as api; +import '../elements/elements.dart'; +import '../elements/modelx.dart' show FunctionElementX; +import '../dart2jslib.dart' hide Selector; +import '../dart_types.dart'; +import '../js/js.dart' as js; +import '../native_handler.dart' as native; +import '../source_file.dart'; +import '../source_map_builder.dart'; +import '../ssa/ssa.dart'; +import '../tree/tree.dart'; +import '../universe/universe.dart'; +import '../util/characters.dart'; +import '../util/util.dart'; + +part 'backend.dart'; +part 'constant_emitter.dart'; +part 'constant_system_javascript.dart'; +part 'emitter.dart'; +part 'emitter_no_eval.dart'; +part 'minify_namer.dart'; +part 'namer.dart'; +part 'native_emitter.dart'; +part 'runtime_types.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart new file mode 100644 index 0000000..98344a1 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart
@@ -0,0 +1,200 @@ +// Copyright (c) 2011, 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. + +part of js_backend; + +/** + * Assigns JavaScript identifiers to Dart variables, class-names and members. + */ +class MinifyNamer extends Namer { + MinifyNamer(Compiler compiler) : super(compiler) { + reserveBackendNames(); + } + + String get isolateName => 'I'; + String get isolatePropertiesName => 'p'; + bool get shouldMinify => true; + + const ALPHABET_CHARACTERS = 52; // a-zA-Z. + const ALPHANUMERIC_CHARACTERS = 62; // a-zA-Z0-9. + + // You can pass an invalid identifier to this and unlike its non-minifying + // counterpart it will never return the proposedName as the new fresh name. + String getFreshName(String proposedName, + Set<String> usedNames, + Map<String, String> suggestedNames, + {bool ensureSafe: true}) { + var freshName; + var suggestion = suggestedNames[proposedName]; + if (suggestion != null && !usedNames.contains(suggestion)) { + freshName = suggestion; + } else { + freshName = _getUnusedName(proposedName, usedNames); + } + usedNames.add(freshName); + return freshName; + } + + SourceString getClosureVariableName(SourceString name, int id) { + if (id < ALPHABET_CHARACTERS) { + return new SourceString(new String.fromCharCodes([_letterNumber(id)])); + } + return new SourceString("${getMappedInstanceName('closure')}_$id"); + } + + void reserveBackendNames() { + // From issue 7554. These should not be used on objects (as instance + // variables) because they clash with names from the DOM. + const reservedNativeProperties = const <String>[ + 'Q', 'a', 'b', 'c', 'd', 'e', 'f', 'r', 'x', 'y', 'z', + // 2-letter: + 'ch', 'cx', 'cy', 'db', 'dx', 'dy', 'fr', 'fx', 'fy', 'go', 'id', 'k1', + 'k2', 'k3', 'k4', 'r1', 'r2', 'rx', 'ry', 'x1', 'x2', 'y1', 'y2', + // 3-letter: + 'add', 'all', 'alt', 'arc', 'CCW', 'cmp', 'dir', 'end', 'get', 'in1', + 'in2', 'INT', 'key', 'log', 'low', 'm11', 'm12', 'm13', 'm14', 'm21', + 'm22', 'm23', 'm24', 'm31', 'm32', 'm33', 'm34', 'm41', 'm42', 'm43', + 'm44', 'max', 'min', 'now', 'ONE', 'put', 'red', 'rel', 'rev', 'RGB', + 'sdp', 'set', 'src', 'tag', 'top', 'uid', 'uri', 'url', 'URL', + // 4-letter: + 'abbr', 'atob', 'Attr', 'axes', 'axis', 'back', 'BACK', 'beta', 'bias', + 'Blob', 'blue', 'blur', 'BLUR', 'body', 'BOOL', 'BOTH', 'btoa', 'BYTE', + 'cite', 'clip', 'code', 'cols', 'cues', 'data', 'DECR', 'DONE', 'face', + 'file', 'File', 'fill', 'find', 'font', 'form', 'gain', 'hash', 'head', + 'high', 'hint', 'host', 'href', 'HRTF', 'IDLE', 'INCR', 'info', 'INIT', + 'isId', 'item', 'KEEP', 'kind', 'knee', 'lang', 'left', 'LESS', 'line', + 'link', 'list', 'load', 'loop', 'mode', 'name', 'Node', 'None', 'NONE', + 'only', 'open', 'OPEN', 'ping', 'play', 'port', 'rect', 'Rect', 'refX', + 'refY', 'RGBA', 'root', 'rows', 'save', 'seed', 'seek', 'self', 'send', + 'show', 'SINE', 'size', 'span', 'stat', 'step', 'stop', 'tags', 'text', + 'Text', 'time', 'type', 'view', 'warn', 'wrap', 'ZERO']; + for (var name in reservedNativeProperties) { + if (name.length < 2) { + instanceNameMap[name] = name; + } + usedInstanceNames.add(name); + } + + // This list of popular instance variable names generated with: + // cat out.js | + // perl -ne '$_=~s/(?<![^a-z0-9_\$]\$)\.([a-z0-9_\$]+)/print("$1\n")/gei' | + // sort | uniq -c | sort -nr | head -40 + // Removed: html, call*, hasOwnProperty. + _populateSuggestedNames( + suggestedInstanceNames, + usedInstanceNames, + const <String>[ + r'$add', r'add$1', r'box_0', r'charCodeAt$1', r'constructor', + r'current', r'$defineNativeClass', r'$eq', r'$ne', + r'getPrototypeOf', r'hasOwnProperty', r'$index', r'$indexSet', + r'$isJavaScriptIndexingBehavior', r'$isolateProperties', + r'iterator', r'length', r'$lt', r'$gt', r'$le', r'$ge', + r'moveNext$0', r'node', r'on', r'prototype', r'push', r'self', + r'start', r'target', r'this_0', r'value', r'width', r'style']); + + _populateSuggestedNames( + suggestedGlobalNames, + usedGlobalNames, + const <String>[ + r'Object', r'$throw', r'$eq', r'S', r'ioore', r'UnsupportedError$', + r'length', r'$sub', r'getInterceptor$JSStringJSArray', r'$add', + r'$gt', r'$ge', r'$lt', r'$le', r'add', r'getInterceptor$JSNumber', + r'iterator', r'$index', r'iae', r'getInterceptor$JSArray', + r'ArgumentError$', r'BoundClosure', r'StateError$', + r'getInterceptor', r'max', r'$mul', r'List_List', r'Map_Map', + r'getInterceptor$JSString', r'$div', r'$indexSet', + r'List_List$from', r'Set_Set$from', r'toString', r'toInt', r'min', + r'StringBuffer_StringBuffer', r'contains1', r'WhereIterable$', + r'RangeError$value', r'JSString', r'JSNumber', + r'JSArray' + ]); + } + + void _populateSuggestedNames(Map<String, String> suggestionMap, + Set<String> used, + List<String> suggestions) { + int c = $a - 1; + String letter; + for (String name in suggestions) { + do { + assert(c != $Z); + c = (c == $z) ? $A : c + 1; + letter = new String.fromCharCodes([c]); + } while (used.contains(letter)); + assert(suggestionMap[name] == null); + suggestionMap[name] = letter; + } + } + + + // This gets a minified name based on a hash of the proposed name. This + // is slightly less efficient than just getting the next name in a series, + // but it means that small changes in the input program will give smallish + // changes in the output, which can be useful for diffing etc. + String _getUnusedName(String proposedName, Set<String> usedNames) { + int hash = _calculateHash(proposedName); + // Avoid very small hashes that won't try many names. + hash = hash < 1000 ? hash * 314159 : hash; // Yes, it's prime. + + // Try other n-character names based on the hash. We try one to three + // character identifiers. For each length we try around 10 different names + // in a predictable order determined by the proposed name. This is in order + // to make the renamer stable: small changes in the input should nornally + // result in relatively small changes in the output. + for (var n = 2; n <= 3; n++) { + int h = hash; + while (h > 10) { + var codes = <int>[_letterNumber(h)]; + int h2 = h ~/ ALPHABET_CHARACTERS; + for (var i = 1; i < n; i++) { + codes.add(_alphaNumericNumber(h2)); + h2 ~/= ALPHANUMERIC_CHARACTERS; + } + final candidate = new String.fromCharCodes(codes); + if (!usedNames.contains(candidate) && !jsReserved.contains(candidate)) { + return candidate; + } + // Try again with a slightly different hash. After around 10 turns + // around this loop h is zero and we try a longer name. + h ~/= 7; + } + } + + // If we can't find a hash based name in the three-letter space, then base + // the name on a letter and a counter. + var startLetter = new String.fromCharCodes([_letterNumber(hash)]); + var i = 0; + while (usedNames.contains("$startLetter$i")) { + i++; + } + return "$startLetter$i"; + } + + int _calculateHash(String name) { + int h = 0; + for (int i = 0; i < name.length; i++) { + h += name.charCodeAt(i); + h &= 0xffffffff; + h += h << 10; + h &= 0xffffffff; + h ^= h >> 6; + h &= 0xffffffff; + } + return h; + } + + int _letterNumber(int x) { + if (x >= ALPHABET_CHARACTERS) x %= ALPHABET_CHARACTERS; + if (x < 26) return $a + x; + return $A + x - 26; + } + + int _alphaNumericNumber(int x) { + if (x >= ALPHANUMERIC_CHARACTERS) x %= ALPHANUMERIC_CHARACTERS; + if (x < 26) return $a + x; + if (x < 52) return $A + x - 26; + return $0 + x - 52; + } + +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart new file mode 100644 index 0000000..e5bb967 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart
@@ -0,0 +1,754 @@ +// Copyright (c) 2011, 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. + +part of js_backend; + +/** + * Assigns JavaScript identifiers to Dart variables, class-names and members. + */ +class Namer implements ClosureNamer { + + static const javaScriptKeywords = const <String>[ + // These are current keywords. + "break", "delete", "function", "return", "typeof", "case", "do", "if", + "switch", "var", "catch", "else", "in", "this", "void", "continue", + "false", "instanceof", "throw", "while", "debugger", "finally", "new", + "true", "with", "default", "for", "null", "try", + + // These are future keywords. + "abstract", "double", "goto", "native", "static", "boolean", "enum", + "implements", "package", "super", "byte", "export", "import", "private", + "synchronized", "char", "extends", "int", "protected", "throws", + "class", "final", "interface", "public", "transient", "const", "float", + "long", "short", "volatile" + ]; + + static const reservedPropertySymbols = + const <String>["__proto__", "prototype", "constructor", "call"]; + + // Symbols that we might be using in our JS snippets. + static const reservedGlobalSymbols = const <String>[ + // Section references are from Ecma-262 + // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) + + // 15.1.1 Value Properties of the Global Object + "NaN", "Infinity", "undefined", + + // 15.1.2 Function Properties of the Global Object + "eval", "parseInt", "parseFloat", "isNaN", "isFinite", + + // 15.1.3 URI Handling Function Properties + "decodeURI", "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + + // 15.1.4 Constructor Properties of the Global Object + "Object", "Function", "Array", "String", "Boolean", "Number", "Date", + "RegExp", "Error", "EvalError", "RangeError", "ReferenceError", + "SyntaxError", "TypeError", "URIError", + + // 15.1.5 Other Properties of the Global Object + "Math", + + // 10.1.6 Activation Object + "arguments", + + // B.2 Additional Properties (non-normative) + "escape", "unescape", + + // Window props (https://developer.mozilla.org/en/DOM/window) + "applicationCache", "closed", "Components", "content", "controllers", + "crypto", "defaultStatus", "dialogArguments", "directories", + "document", "frameElement", "frames", "fullScreen", "globalStorage", + "history", "innerHeight", "innerWidth", "length", + "location", "locationbar", "localStorage", "menubar", + "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel", + "name", "navigator", "opener", "outerHeight", "outerWidth", + "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11", + "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY", + "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar", + "top", "window", + + // Window methods (https://developer.mozilla.org/en/DOM/window) + "alert", "addEventListener", "atob", "back", "blur", "btoa", + "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", + "disableExternalCapture", "dispatchEvent", "dump", + "enableExternalCapture", "escape", "find", "focus", "forward", + "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount", + "getComputedStyle", "getSelection", "home", "maximize", "minimize", + "moveBy", "moveTo", "open", "openDialog", "postMessage", "print", + "prompt", "QueryInterface", "releaseEvents", "removeEventListener", + "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy", + "scrollByLines", "scrollByPages", "scrollTo", "setInterval", + "setResizeable", "setTimeout", "showModalDialog", "sizeToContent", + "stop", "uuescape", "updateCommands", "XPCNativeWrapper", + "XPCSafeJSOjbectWrapper", + + // Mozilla Window event handlers, same cite + "onabort", "onbeforeunload", "onchange", "onclick", "onclose", + "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange", + "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown", + "onmousemove", "onmouseout", "onmouseover", "onmouseup", + "onmozorientation", "onpaint", "onreset", "onresize", "onscroll", + "onselect", "onsubmit", "onunload", + + // Safari Web Content Guide + // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf + // WebKit Window member data, from WebKit DOM Reference + // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html) + "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart", + "ongesturestart", "ongesturechange", "ongestureend", + + // extra window methods + "uneval", + + // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7, + // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1 + "getPrototypeOf", "let", "yield", + + // "future reserved words" + "abstract", "int", "short", "boolean", "interface", "static", "byte", + "long", "char", "final", "native", "synchronized", "float", "package", + "throws", "goto", "private", "transient", "implements", "protected", + "volatile", "double", "public", + + // IE methods + // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#) + "attachEvent", "clientInformation", "clipboardData", "createPopup", + "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth", + "onafterprint", "onbeforedeactivate", "onbeforeprint", + "oncontrolselect", "ondeactivate", "onhelp", "onresizeend", + + // Common browser-defined identifiers not defined in ECMAScript + "event", "external", "Debug", "Enumerator", "Global", "Image", + "ActiveXObject", "VBArray", "Components", + + // Functions commonly defined on Object + "toString", "getClass", "constructor", "prototype", "valueOf", + + // Client-side JavaScript identifiers + "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient", + "CanvasPattern", "CanvasRenderingContext2D", "CDATASection", + "CharacterData", "Comment", "CSS2Properties", "CSSRule", + "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType", + "DOMException", "DOMImplementation", "DOMParser", "Element", "Event", + "ExternalInterface", "FlashPlayer", "Form", "Frame", "History", + "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image", + "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType", + "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin", + "ProcessingInstruction", "Range", "RangeException", "Screen", "Select", + "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea", + "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer", + "XPathException", "XPathResult", "XSLTProcessor", + + // These keywords trigger the loading of the java-plugin. For the + // next-generation plugin, this results in starting a new Java process. + "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass", + "JavaArray", "JavaMember" + ]; + + Set<String> _jsReserved = null; + /// Names that cannot be used by members, top level and static + /// methods. + Set<String> get jsReserved { + if (_jsReserved == null) { + _jsReserved = new Set<String>(); + _jsReserved.addAll(javaScriptKeywords); + _jsReserved.addAll(reservedPropertySymbols); + } + return _jsReserved; + } + + Set<String> _jsVariableReserved = null; + /// Names that cannot be used by local variables and parameters. + Set<String> get jsVariableReserved { + if (_jsVariableReserved == null) { + _jsVariableReserved = new Set<String>(); + _jsVariableReserved.addAll(javaScriptKeywords); + _jsVariableReserved.addAll(reservedPropertySymbols); + _jsVariableReserved.addAll(reservedGlobalSymbols); + } + return _jsVariableReserved; + } + + final String CURRENT_ISOLATE = r'$'; + + /** + * Map from top-level or static elements to their unique identifiers provided + * by [getName]. + * + * Invariant: Keys must be declaration elements. + */ + final Compiler compiler; + final Map<Element, String> globals; + final Map<Selector, String> oneShotInterceptorNames; + final Map<String, LibraryElement> shortPrivateNameOwners; + + final Set<String> usedGlobalNames; + final Set<String> usedInstanceNames; + final Map<String, String> globalNameMap; + final Map<String, String> suggestedGlobalNames; + final Map<String, String> instanceNameMap; + final Map<String, String> suggestedInstanceNames; + + final Map<String, String> operatorNameMap; + final Map<String, int> popularNameCounters; + + final Map<Element, String> bailoutNames; + + final Map<Constant, String> constantNames; + + Namer(this.compiler) + : globals = new Map<Element, String>(), + oneShotInterceptorNames = new Map<Selector, String>(), + shortPrivateNameOwners = new Map<String, LibraryElement>(), + bailoutNames = new Map<Element, String>(), + usedGlobalNames = new Set<String>(), + usedInstanceNames = new Set<String>(), + instanceNameMap = new Map<String, String>(), + operatorNameMap = new Map<String, String>(), + globalNameMap = new Map<String, String>(), + suggestedGlobalNames = new Map<String, String>(), + suggestedInstanceNames = new Map<String, String>(), + constantNames = new Map<Constant, String>(), + popularNameCounters = new Map<String, int>(); + + String get isolateName => 'Isolate'; + String get isolatePropertiesName => r'$isolateProperties'; + /** + * Some closures must contain their name. The name is stored in + * [STATIC_CLOSURE_NAME_NAME]. + */ + String get STATIC_CLOSURE_NAME_NAME => r'$name'; + SourceString get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME; + bool get shouldMinify => false; + + bool isReserved(String name) => name == isolateName; + + String constantName(Constant constant) { + // In the current implementation it doesn't make sense to give names to + // function constants since the function-implementation itself serves as + // constant and can be accessed directly. + assert(!constant.isFunction()); + String result = constantNames[constant]; + if (result == null) { + String longName; + if (shouldMinify) { + if (constant.isString()) { + StringConstant stringConstant = constant; + // The minifier always constructs a new name, using the argument as + // input to its hashing algorithm. The given name does not need to be + // valid. + longName = stringConstant.value.slowToString(); + } else { + longName = "C"; + } + } else { + longName = "CONSTANT"; + } + result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames, + ensureSafe: true); + constantNames[constant] = result; + } + return result; + } + + String breakLabelName(LabelElement label) { + return '\$${label.labelName}\$${label.target.nestingLevel}'; + } + + String implicitBreakLabelName(TargetElement target) { + return '\$${target.nestingLevel}'; + } + + // We sometimes handle continue targets differently from break targets, + // so we have special continue-only labels. + String continueLabelName(LabelElement label) { + return 'c\$${label.labelName}\$${label.target.nestingLevel}'; + } + + String implicitContinueLabelName(TargetElement target) { + return 'c\$${target.nestingLevel}'; + } + + /** + * If the [name] is not private returns [:name.slowToString():]. Otherwise + * mangles the [name] so that each library has a unique name. + */ + String privateName(LibraryElement library, SourceString name) { + // Public names are easy. + String nameString = name.slowToString(); + if (!name.isPrivate()) return nameString; + + // The first library asking for a short private name wins. + LibraryElement owner = shouldMinify + ? library + : shortPrivateNameOwners.putIfAbsent(nameString, () => library); + + // If a private name could clash with a mangled private name we don't + // use the short name. For example a private name "_lib3_foo" would + // clash with "_foo" from "lib3". + if (owner == library && + !nameString.startsWith('_$LIBRARY_PREFIX') && + !shouldMinify) { + return nameString; + } + + // If a library name does not start with the [LIBRARY_PREFIX] then our + // assumptions about clashing with mangled private members do not hold. + String libraryName = getName(library); + assert(shouldMinify || libraryName.startsWith(LIBRARY_PREFIX)); + // TODO(erikcorry): Fix this with other manglings to avoid clashes. + return '_lib$libraryName\$$nameString'; + } + + String instanceMethodName(FunctionElement element) { + SourceString elementName = element.name; + SourceString name = operatorNameToIdentifier(elementName); + if (name != elementName) return getMappedOperatorName(name.slowToString()); + + LibraryElement library = element.getLibrary(); + if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY) { + ConstructorBodyElement bodyElement = element; + name = bodyElement.constructor.name; + } + FunctionSignature signature = element.computeSignature(compiler); + String methodName = + '${privateName(library, name)}\$${signature.parameterCount}'; + if (signature.optionalParametersAreNamed && + !signature.optionalParameters.isEmpty) { + StringBuffer buffer = new StringBuffer(); + signature.orderedOptionalParameters.forEach((Element element) { + buffer.add('\$${safeName(element.name.slowToString())}'); + }); + methodName = '$methodName$buffer'; + } + if (name == closureInvocationSelectorName) return methodName; + return getMappedInstanceName(methodName); + } + + String publicInstanceMethodNameByArity(SourceString name, int arity) { + SourceString newName = operatorNameToIdentifier(name); + if (newName != name) return getMappedOperatorName(newName.slowToString()); + assert(!name.isPrivate()); + var base = name.slowToString(); + // We don't mangle the closure invoking function name because it + // is generated by string concatenation in applyFunction from + // js_helper.dart. + var proposedName = '$base\$$arity'; + if (name == closureInvocationSelectorName) return proposedName; + return getMappedInstanceName(proposedName); + } + + String invocationName(Selector selector) { + if (selector.isGetter()) { + String proposedName = privateName(selector.library, selector.name); + return 'get\$${getMappedInstanceName(proposedName)}'; + } else if (selector.isSetter()) { + String proposedName = privateName(selector.library, selector.name); + return 'set\$${getMappedInstanceName(proposedName)}'; + } else { + SourceString name = selector.name; + if (selector.kind == SelectorKind.OPERATOR + || selector.kind == SelectorKind.INDEX) { + name = operatorNameToIdentifier(name); + assert(name != selector.name); + return getMappedOperatorName(name.slowToString()); + } + assert(name == operatorNameToIdentifier(name)); + StringBuffer buffer = new StringBuffer(); + for (SourceString argumentName in selector.getOrderedNamedArguments()) { + buffer.add(r'$'); + argumentName.printOn(buffer); + } + String suffix = '\$${selector.argumentCount}$buffer'; + // We don't mangle the closure invoking function name because it + // is generated by string concatenation in applyFunction from + // js_helper.dart. + if (selector.isClosureCall()) { + return "${name.slowToString()}$suffix"; + } else { + String proposedName = privateName(selector.library, name); + return getMappedInstanceName('$proposedName$suffix'); + } + } + } + + /** + * Returns the internal name used for an invocation mirror of this selector. + */ + String invocationMirrorInternalName(Selector selector) + => invocationName(selector); + + String instanceFieldName(Element element) { + String proposedName = privateName(element.getLibrary(), element.name); + return getMappedInstanceName(proposedName); + } + + // Construct a new name for the element based on the library and class it is + // in. The name here is not important, we just need to make sure it is + // unique. If we are minifying, we actually construct the name from the + // minified versions of the class and instance names, but the result is + // minified once again, so that is not visible in the end result. + String shadowedFieldName(Element fieldElement) { + // Check for following situation: Native field ${fieldElement.name} has + // fixed JSName ${fieldElement.nativeName()}, but a subclass shadows this + // name. We normally handle that by renaming the superclass field, but we + // can't do that because native fields have fixed JavaScript names. + // In practice this can't happen because we can't inherit from native + // classes. + assert (!fieldElement.hasFixedBackendName()); + + String libraryName = getName(fieldElement.getLibrary()); + String className = getName(fieldElement.getEnclosingClass()); + String instanceName = instanceFieldName(fieldElement); + return getMappedInstanceName('$libraryName\$$className\$$instanceName'); + } + + String setterName(Element element) { + // We dynamically create setters from the field-name. The setter name must + // therefore be derived from the instance field-name. + LibraryElement library = element.getLibrary(); + String name = getMappedInstanceName(privateName(library, element.name)); + return 'set\$$name'; + } + + String setterNameFromAccessorName(String name) { + // We dynamically create setters from the field-name. The setter name must + // therefore be derived from the instance field-name. + return 'set\$$name'; + } + + String publicGetterName(SourceString name) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + String fieldName = getMappedInstanceName(name.slowToString()); + return 'get\$$fieldName'; + } + + String getterNameFromAccessorName(String name) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + return 'get\$$name'; + } + + String getterName(Element element) { + // We dynamically create getters from the field-name. The getter name must + // therefore be derived from the instance field-name. + LibraryElement library = element.getLibrary(); + String name = getMappedInstanceName(privateName(library, element.name)); + return 'get\$$name'; + } + + String getMappedGlobalName(String proposedName) { + var newName = globalNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedGlobalNames, + suggestedGlobalNames, ensureSafe: true); + globalNameMap[proposedName] = newName; + } + return newName; + } + + String getMappedInstanceName(String proposedName) { + var newName = instanceNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedInstanceNames, + suggestedInstanceNames, ensureSafe: true); + instanceNameMap[proposedName] = newName; + } + return newName; + } + + String getMappedOperatorName(String proposedName) { + var newName = operatorNameMap[proposedName]; + if (newName == null) { + newName = getFreshName(proposedName, usedInstanceNames, + suggestedInstanceNames, ensureSafe: false); + operatorNameMap[proposedName] = newName; + } + return newName; + } + + String getFreshName(String proposedName, + Set<String> usedNames, + Map<String, String> suggestedNames, + {bool ensureSafe: true}) { + var candidate; + if (ensureSafe) { + proposedName = safeName(proposedName); + } + assert(!jsReserved.contains(proposedName)); + if (!usedNames.contains(proposedName)) { + candidate = proposedName; + } else { + var counter = popularNameCounters[proposedName]; + var i = counter == null ? 0 : counter; + while (usedNames.contains("$proposedName$i")) { + i++; + } + popularNameCounters[proposedName] = i + 1; + candidate = "$proposedName$i"; + } + usedNames.add(candidate); + return candidate; + } + + SourceString getClosureVariableName(SourceString name, int id) { + return new SourceString("${name.slowToString()}_$id"); + } + + static const String LIBRARY_PREFIX = "lib"; + + /** + * Returns a preferred JS-id for the given top-level or static element. + * The returned id is guaranteed to be a valid JS-id. + */ + String _computeGuess(Element element) { + assert(!element.isInstanceMember()); + String name; + if (element.isGenerativeConstructor()) { + if (element.name == element.getEnclosingClass().name) { + // Keep the class name for the class and not the factory. + name = "${element.name.slowToString()}\$"; + } else { + name = element.name.slowToString(); + } + } else if (Elements.isStaticOrTopLevel(element)) { + if (element.isMember()) { + ClassElement enclosingClass = element.getEnclosingClass(); + name = "${enclosingClass.name.slowToString()}_" + "${element.name.slowToString()}"; + } else { + name = element.name.slowToString(); + } + } else if (element.isLibrary()) { + name = LIBRARY_PREFIX; + } else { + name = element.name.slowToString(); + } + return name; + } + + String getInterceptorName(Element element, Collection<ClassElement> classes) { + if (classes.contains(compiler.objectClass)) { + // If the object class is in the set of intercepted classes, we + // need to go through the generic getInterceptorMethod. + return getName(element); + } + // Use the unminified names here to construct the interceptor names. This + // helps ensure that they don't all suddenly change names due to a name + // clash in the minifier, which would affect the diff size. + StringBuffer buffer = new StringBuffer('${element.name.slowToString()}\$'); + for (ClassElement cls in classes) { + buffer.add(cls.name.slowToString()); + } + return getMappedGlobalName(buffer.toString()); + } + + String getBailoutName(Element element) { + String name = bailoutNames[element]; + if (name != null) return name; + bool global = !element.isInstanceMember(); + // Despite the name of the variable, this gets the minified name when we + // are minifying, but it doesn't really make much difference. The + // important thing is that it is a unique name. We add $bailout and, if we + // are minifying, we minify the minified name and '$bailout'. + String unminifiedName = '${getName(element)}\$bailout'; + if (global) { + name = getMappedGlobalName(unminifiedName); + } else { + // Make sure two bailout methods on the same inheritance chain do not have + // the same name to prevent a subclass bailout method being accidentally + // called from the superclass main method. Use the count of the number of + // elements with the same name on the superclass chain to disambiguate + // based on 'level'. + int level = 0; + ClassElement classElement = element.getEnclosingClass().superclass; + while (classElement != null) { + if (classElement.localLookup(element.name) != null) level++; + classElement = classElement.superclass; + } + name = unminifiedName; + if (level != 0) { + name = '$unminifiedName$level'; + } + name = getMappedInstanceName(name); + } + bailoutNames[element] = name; + return name; + } + + /** + * Returns a preferred JS-id for the given element. The returned id is + * guaranteed to be a valid JS-id. Globals and static fields are furthermore + * guaranteed to be unique. + * + * For accessing statics consider calling + * [isolateAccess]/[isolateBailoutAccess] or [isolatePropertyAccess] instead. + */ + String getName(Element element) { + if (element.isInstanceMember()) { + if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY + || element.kind == ElementKind.FUNCTION) { + return instanceMethodName(element); + } else if (element.kind == ElementKind.GETTER) { + return getterName(element); + } else if (element.kind == ElementKind.SETTER) { + return setterName(element); + } else if (element.kind == ElementKind.FIELD) { + return instanceFieldName(element); + } else { + compiler.internalError('getName for bad kind: ${element.kind}', + node: element.parseNode(compiler)); + } + } else { + // Use declaration element to ensure invariant on [globals]. + element = element.declaration; + // Dealing with a top-level or static element. + String cached = globals[element]; + if (cached != null) return cached; + + String guess = _computeGuess(element); + ElementKind kind = element.kind; + if (kind == ElementKind.VARIABLE || + kind == ElementKind.PARAMETER) { + // The name is not guaranteed to be unique. + return safeName(guess); + } + if (kind == ElementKind.GENERATIVE_CONSTRUCTOR || + kind == ElementKind.FUNCTION || + kind == ElementKind.CLASS || + kind == ElementKind.FIELD || + kind == ElementKind.GETTER || + kind == ElementKind.SETTER || + kind == ElementKind.TYPEDEF || + kind == ElementKind.LIBRARY || + kind == ElementKind.MALFORMED_TYPE) { + bool fixedName = false; + if (kind == ElementKind.CLASS) { + ClassElement classElement = element; + } + if (Elements.isInstanceField(element)) { + fixedName = element.hasFixedBackendName(); + } + String result = fixedName + ? guess + : getFreshName(guess, usedGlobalNames, suggestedGlobalNames, + ensureSafe: true); + globals[element] = result; + return result; + } + compiler.internalError('getName for unknown kind: ${element.kind}', + node: element.parseNode(compiler)); + } + } + + String getLazyInitializerName(Element element) { + assert(Elements.isStaticOrTopLevelField(element)); + return getMappedGlobalName("get\$${getName(element)}"); + } + + String isolatePropertiesAccess(Element element) { + return "$isolateName.$isolatePropertiesName.${getName(element)}"; + } + + String isolateAccess(Element element) { + return "$CURRENT_ISOLATE.${getName(element)}"; + } + + String isolateBailoutAccess(Element element) { + String newName = getMappedGlobalName('${getName(element)}\$bailout'); + return '$CURRENT_ISOLATE.$newName'; + } + + String isolateLazyInitializerAccess(Element element) { + return "$CURRENT_ISOLATE.${getLazyInitializerName(element)}"; + } + + String operatorIsPrefix() => r'$is'; + + String operatorIs(Element element) { + // TODO(erikcorry): Reduce from $isx to ix when we are minifying. + return '${operatorIsPrefix()}${getName(element)}'; + } + + /* + * Returns a name that does not clash with reserved JS keywords, + * and also ensures it won't clash with other identifiers. + */ + String _safeName(String name, Set<String> reserved) { + if (reserved.contains(name) || name.startsWith(r'$')) { + name = '\$$name'; + } + assert(!reserved.contains(name)); + return name; + } + + String safeName(String name) => _safeName(name, jsReserved); + String safeVariableName(String name) => _safeName(name, jsVariableReserved); + + String oneShotInterceptorName(Selector selector) { + // TODO(ngeoffray): What to do about typed selectors? We could + // filter them out, or keep them and hope the generated one shot + // interceptor takes advantage of the type. + String cached = oneShotInterceptorNames[selector]; + if (cached != null) return cached; + SourceString name = operatorNameToIdentifier(selector.name); + String result = getFreshName(name.slowToString(), usedGlobalNames, + suggestedGlobalNames); + oneShotInterceptorNames[selector] = result; + return result; + } + + SourceString operatorNameToIdentifier(SourceString name) { + if (name == null) return null; + String value = name.stringValue; + if (value == null) { + return name; + } else if (value == '==') { + return const SourceString(r'$eq'); + } else if (value == '~') { + return const SourceString(r'$not'); + } else if (value == '[]') { + return const SourceString(r'$index'); + } else if (value == '[]=') { + return const SourceString(r'$indexSet'); + } else if (value == '*') { + return const SourceString(r'$mul'); + } else if (value == '/') { + return const SourceString(r'$div'); + } else if (value == '%') { + return const SourceString(r'$mod'); + } else if (value == '~/') { + return const SourceString(r'$tdiv'); + } else if (value == '+') { + return const SourceString(r'$add'); + } else if (value == '<<') { + return const SourceString(r'$shl'); + } else if (value == '>>') { + return const SourceString(r'$shr'); + } else if (value == '>=') { + return const SourceString(r'$ge'); + } else if (value == '>') { + return const SourceString(r'$gt'); + } else if (value == '<=') { + return const SourceString(r'$le'); + } else if (value == '<') { + return const SourceString(r'$lt'); + } else if (value == '&') { + return const SourceString(r'$and'); + } else if (value == '^') { + return const SourceString(r'$xor'); + } else if (value == '|') { + return const SourceString(r'$or'); + } else if (value == '-') { + return const SourceString(r'$sub'); + } else if (value == 'unary-') { + return const SourceString(r'$negate'); + } else { + return name; + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart new file mode 100644 index 0000000..9400f5f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart
@@ -0,0 +1,546 @@ +// 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. + +part of js_backend; + +class NativeEmitter { + + CodeEmitterTask emitter; + CodeBuffer nativeBuffer; + + // Classes that participate in dynamic dispatch. These are the + // classes that contain used members. + Set<ClassElement> classesWithDynamicDispatch; + + // Native classes found in the application. + Set<ClassElement> nativeClasses; + + // Caches the native subtypes of a native class. + Map<ClassElement, List<ClassElement>> subtypes; + + // Caches the direct native subtypes of a native class. + Map<ClassElement, List<ClassElement>> directSubtypes; + + // Caches the native methods that are overridden by a native class. + // Note that the method that overrides does not have to be native: + // it's the overridden method that must make sure it will dispatch + // to its subclass if it sees an instance whose class is a subclass. + Set<FunctionElement> overriddenMethods; + + // Caches the methods that have a native body. + Set<FunctionElement> nativeMethods; + + // Do we need the native emitter to take care of handling + // noSuchMethod for us? This flag is set to true in the emitter if + // it finds any native class that needs noSuchMethod handling. + bool handleNoSuchMethod = false; + + NativeEmitter(this.emitter) + : classesWithDynamicDispatch = new Set<ClassElement>(), + nativeClasses = new Set<ClassElement>(), + subtypes = new Map<ClassElement, List<ClassElement>>(), + directSubtypes = new Map<ClassElement, List<ClassElement>>(), + overriddenMethods = new Set<FunctionElement>(), + nativeMethods = new Set<FunctionElement>(), + nativeBuffer = new CodeBuffer(); + + Compiler get compiler => emitter.compiler; + JavaScriptBackend get backend => compiler.backend; + + String get _ => emitter._; + String get n => emitter.n; + String get N => emitter.N; + + String get dynamicName { + Element element = compiler.findHelper( + const SourceString('dynamicFunction')); + return backend.namer.isolateAccess(element); + } + + String get dynamicSetMetadataName { + Element element = compiler.findHelper( + const SourceString('dynamicSetMetadata')); + return backend.namer.isolateAccess(element); + } + + String get typeNameOfName { + Element element = compiler.findHelper( + const SourceString('getTypeNameOf')); + return backend.namer.isolateAccess(element); + } + + String get defPropName { + Element element = compiler.findHelper( + const SourceString('defineProperty')); + return backend.namer.isolateAccess(element); + } + + String get toStringHelperName { + Element element = compiler.findHelper( + const SourceString('toStringForNativeObject')); + return backend.namer.isolateAccess(element); + } + + String get hashCodeHelperName { + Element element = compiler.findHelper( + const SourceString('hashCodeForNativeObject')); + return backend.namer.isolateAccess(element); + } + + String get defineNativeClassName + => '${backend.namer.CURRENT_ISOLATE}.\$defineNativeClass'; + + String get defineNativeClassFunction { + return """ +function(cls, desc) { + var fields = desc['']; + var fields_array = fields ? fields.split(',') : []; + for (var i = 0; i < fields_array.length; i++) { + ${emitter.currentGenerateAccessorName}(fields_array[i], desc); + } + var hasOwnProperty = Object.prototype.hasOwnProperty; + for (var method in desc) { + if (method) { + if (hasOwnProperty.call(desc, method)) { + $dynamicName(method)[cls] = desc[method]; + } + } + } +}"""; + } + + bool isNativeGlobal(String quotedName) { + return identical(quotedName[1], '@'); + } + + String toNativeTag(ClassElement cls) { + String quotedName = cls.nativeTagInfo.slowToString(); + if (isNativeGlobal(quotedName)) { + // Global object, just be like the other types for now. + return quotedName.substring(3, quotedName.length - 1); + } else { + return quotedName.substring(2, quotedName.length - 1); + } + } + + void generateNativeClass(ClassElement classElement) { + assert(!classElement.hasBackendMembers); + nativeClasses.add(classElement); + + ClassBuilder builder = new ClassBuilder(); + emitter.emitClassFields(classElement, builder, classIsNative: true); + emitter.emitClassGettersSetters(classElement, builder); + emitter.emitInstanceMembers(classElement, builder); + + // An empty native class may be omitted since the superclass methods can be + // located via the dispatch metadata. + if (builder.properties.isEmpty) return; + + String nativeTag = toNativeTag(classElement); + js.Expression definition = + js.call(js.use(defineNativeClassName), + [js.string(nativeTag), builder.toObjectInitializer()]); + + nativeBuffer.add(js.prettyPrint(definition, compiler)); + nativeBuffer.add('$N$n'); + + classesWithDynamicDispatch.add(classElement); + } + + List<ClassElement> getDirectSubclasses(ClassElement cls) { + List<ClassElement> result = directSubtypes[cls]; + return result == null ? const<ClassElement>[] : result; + } + + void potentiallyConvertDartClosuresToJs(List<js.Statement> statements, + FunctionElement member, + List<js.Parameter> stubParameters) { + FunctionSignature parameters = member.computeSignature(compiler); + Element converter = + compiler.findHelper(const SourceString('convertDartClosureToJS')); + String closureConverter = backend.namer.isolateAccess(converter); + Set<String> stubParameterNames = new Set<String>.from( + stubParameters.map((param) => param.name)); + parameters.forEachParameter((Element parameter) { + String name = parameter.name.slowToString(); + // If [name] is not in [stubParameters], then the parameter is an optional + // parameter that was not provided for this stub. + for (js.Parameter stubParameter in stubParameters) { + if (stubParameter.name == name) { + DartType type = parameter.computeType(compiler).unalias(compiler); + if (type is FunctionType) { + // The parameter type is a function type either directly or through + // typedef(s). + int arity = type.computeArity(); + + statements.add( + new js.ExpressionStatement( + js.assign( + js.use(name), + js.use(closureConverter).callWith( + [js.use(name), new js.LiteralNumber('$arity')])))); + break; + } + } + } + }); + } + + List<js.Statement> generateParameterStubStatements( + Element member, + String invocationName, + List<js.Parameter> stubParameters, + List<js.Expression> argumentsBuffer, + int indexOfLastOptionalArgumentInParameters) { + // The target JS function may check arguments.length so we need to + // make sure not to pass any unspecified optional arguments to it. + // For example, for the following Dart method: + // foo([x, y, z]); + // The call: + // foo(y: 1) + // must be turned into a JS call to: + // foo(null, y). + + ClassElement classElement = member.enclosingElement; + String nativeTagInfo = classElement.nativeTagInfo.slowToString(); + + List<js.Statement> statements = <js.Statement>[]; + potentiallyConvertDartClosuresToJs(statements, member, stubParameters); + + String target; + List<js.Expression> arguments; + + if (!nativeMethods.contains(member)) { + // When calling a method that has a native body, we call it with our + // calling conventions. + target = backend.namer.getName(member); + arguments = argumentsBuffer; + } else { + // When calling a JS method, we call it with the native name, and only the + // arguments up until the last one provided. + target = member.fixedBackendName(); + arguments = argumentsBuffer.getRange( + 0, indexOfLastOptionalArgumentInParameters + 1); + } + statements.add( + new js.Return( + new js.VariableUse('this').dot(target).callWith(arguments))); + + if (!overriddenMethods.contains(member)) { + // Call the method directly. + return statements; + } else { + return <js.Statement>[ + generateMethodBodyWithPrototypeCheck( + invocationName, new js.Block(statements), stubParameters)]; + } + } + + // If a method is overridden, we must check if the prototype of 'this' has the + // method available. Otherwise, we may end up calling the method from the + // super class. If the method is not available, we make a direct call to + // Object.prototype.$methodName. This method will patch the prototype of + // 'this' to the real method. + js.Statement generateMethodBodyWithPrototypeCheck( + String methodName, + js.Statement body, + List<js.Parameter> parameters) { + return js.if_( + js.use('Object').dot('getPrototypeOf') + .callWith([js.use('this')]) + .dot('hasOwnProperty').callWith([js.string(methodName)]), + body, + js.return_( + js.use('Object').dot('prototype').dot(methodName).dot('call') + .callWith( + <js.Expression>[js.use('this')]..addAll( + parameters.map((param) => js.use(param.name)))))); + } + + js.Block generateMethodBodyWithPrototypeCheckForElement( + FunctionElement element, + js.Block body, + List<js.Parameter> parameters) { + ElementKind kind = element.kind; + if (kind != ElementKind.FUNCTION && + kind != ElementKind.GETTER && + kind != ElementKind.SETTER) { + compiler.internalError("unexpected kind: '$kind'", element: element); + } + + String methodName = backend.namer.getName(element); + return new js.Block( + [generateMethodBodyWithPrototypeCheck(methodName, body, parameters)]); + } + + + void emitDynamicDispatchMetadata() { + if (classesWithDynamicDispatch.isEmpty) return; + int length = classesWithDynamicDispatch.length; + if (!compiler.enableMinification) { + nativeBuffer.add('// $length dynamic classes.\n'); + } + + // Build a pre-order traversal over all the classes and their subclasses. + Set<ClassElement> seen = new Set<ClassElement>(); + List<ClassElement> classes = <ClassElement>[]; + void visit(ClassElement cls) { + if (seen.contains(cls)) return; + seen.add(cls); + getDirectSubclasses(cls).forEach(visit); + classes.add(cls); + } + classesWithDynamicDispatch.forEach(visit); + + List<ClassElement> preorderDispatchClasses = classes.where( + (cls) => !getDirectSubclasses(cls).isEmpty && + classesWithDynamicDispatch.contains(cls)).toList(); + + if (!compiler.enableMinification) { + nativeBuffer.add('// ${classes.length} classes\n'); + } + Iterable<ClassElement> classesThatHaveSubclasses = classes.where( + (ClassElement t) => !getDirectSubclasses(t).isEmpty); + if (!compiler.enableMinification) { + nativeBuffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); + } + + // Generate code that builds the map from cls tags used in dynamic dispatch + // to the set of cls tags of classes that extend (TODO: or implement) those + // classes. The set is represented as a string of tags joined with '|'. + // This is easily split into an array of tags, or converted into a regexp. + // + // To reduce the size of the sets, subsets are CSE-ed out into variables. + // The sets could be much smaller if we could make assumptions about the + // cls tags of other classes (which are constructor names or part of the + // result of Object.protocls.toString). For example, if objects that are + // Dart objects could be easily excluded, then we might be able to simplify + // the test, replacing dozens of HTMLxxxElement classes with the regexp + // /HTML.*Element/. + + // Temporary variables for common substrings. + List<String> varNames = <String>[]; + // Values of temporary variables. + Map<String, js.Expression> varDefns = new Map<String, js.Expression>(); + + // Expression to compute tags string for a class. The expression will + // initially be a string or expression building a string, but may be + // replaced with a variable reference to the common substring. + Map<ClassElement, js.Expression> tagDefns = + new Map<ClassElement, js.Expression>(); + + js.Expression makeExpression(ClassElement classElement) { + // Expression fragments for this set of cls keys. + List<js.Expression> expressions = <js.Expression>[]; + // TODO: Remove if cls is abstract. + List<String> subtags = [toNativeTag(classElement)]; + void walk(ClassElement cls) { + for (final ClassElement subclass in getDirectSubclasses(cls)) { + ClassElement tag = subclass; + js.Expression existing = tagDefns[tag]; + if (existing == null) { + // [subclass] is still within the subtree between dispatch classes. + subtags.add(toNativeTag(tag)); + walk(subclass); + } else { + // [subclass] is one of the preorderDispatchClasses, so CSE this + // reference with the previous reference. + js.VariableUse use = existing.asVariableUse(); + if (use != null && varDefns.containsKey(use.name)) { + // We end up here if the subclasses have a DAG structure. We + // don't have DAGs yet, but if the dispatch is used for mixins + // that will be a possibility. + // Re-use the previously created temporary variable. + expressions.add(new js.VariableUse(use.name)); + } else { + String varName = 'v${varNames.length}_${tag.name.slowToString()}'; + varNames.add(varName); + varDefns[varName] = existing; + tagDefns[tag] = new js.VariableUse(varName); + expressions.add(new js.VariableUse(varName)); + } + } + } + } + walk(classElement); + + if (!subtags.isEmpty) { + expressions.add(js.string(Strings.join(subtags, '|'))); + } + js.Expression expression; + if (expressions.length == 1) { + expression = expressions[0]; + } else { + js.Expression array = new js.ArrayInitializer.from(expressions); + expression = js.call(array.dot('join'), [js.string('|')]); + } + return expression; + } + + for (final ClassElement classElement in preorderDispatchClasses) { + tagDefns[classElement] = makeExpression(classElement); + } + + // Write out a thunk that builds the metadata. + if (!tagDefns.isEmpty) { + List<js.Statement> statements = <js.Statement>[]; + + List<js.VariableInitialization> initializations = + <js.VariableInitialization>[]; + for (final String varName in varNames) { + initializations.add( + new js.VariableInitialization( + new js.VariableDeclaration(varName), + varDefns[varName])); + } + if (!initializations.isEmpty) { + statements.add( + new js.ExpressionStatement( + new js.VariableDeclarationList(initializations))); + } + + // [table] is a list of lists, each inner list of the form: + // [dynamic-dispatch-tag, tags-of-classes-implementing-dispatch-tag] + // E.g. + // [['Node', 'Text|HTMLElement|HTMLDivElement|...'], ...] + js.Expression table = + new js.ArrayInitializer.from( + preorderDispatchClasses.map((cls) => + new js.ArrayInitializer.from([ + js.string(toNativeTag(cls)), + tagDefns[cls]]))); + + // $.dynamicSetMetadata(table); + statements.add( + new js.ExpressionStatement( + new js.Call( + new js.VariableUse(dynamicSetMetadataName), + [table]))); + + // (function(){statements})(); + if (emitter.compiler.enableMinification) nativeBuffer.add(';'); + nativeBuffer.add( + js.prettyPrint( + new js.ExpressionStatement( + new js.Call(new js.Fun([], new js.Block(statements)), [])), + compiler)); + } + } + + bool isSupertypeOfNativeClass(Element element) { + if (element.isTypeVariable()) { + compiler.cancel("Is check for type variable", element: element); + return false; + } + if (element.computeType(compiler).unalias(compiler) is FunctionType) { + // The element type is a function type either directly or through + // typedef(s). + return false; + } + + if (!element.isClass()) { + compiler.cancel("Is check does not handle element", element: element); + return false; + } + + return subtypes[element] != null; + } + + bool requiresNativeIsCheck(Element element) { + if (!element.isClass()) return false; + ClassElement cls = element; + if (cls.isNative()) return true; + return isSupertypeOfNativeClass(element); + } + + void assembleCode(CodeBuffer targetBuffer) { + if (nativeClasses.isEmpty) return; + emitDynamicDispatchMetadata(); + targetBuffer.add('$defineNativeClassName = ' + '$defineNativeClassFunction$N$n'); + + List<js.Property> objectProperties = <js.Property>[]; + + void addProperty(String name, js.Expression value) { + objectProperties.add(new js.Property(js.string(name), value)); + } + + // Because of native classes, we have to generate some is checks + // by calling a method, instead of accessing a property. So we + // attach to the JS Object prototype these methods that return + // false, and will be overridden by subclasses when they have to + // return true. + void emitIsChecks() { + for (ClassElement element in + Elements.sortedByPosition(emitter.checkedClasses)) { + if (!requiresNativeIsCheck(element)) continue; + if (element.isObject(compiler)) continue; + String name = backend.namer.operatorIs(element); + addProperty(name, + js.fun([], js.block1(js.return_(new js.LiteralBool(false))))); + } + } + emitIsChecks(); + + js.Expression makeCallOnThis(String functionName) => + js.fun([], + js.block1( + js.return_( + js.call(js.use(functionName), [js.use('this')])))); + + // In order to have the toString method on every native class, + // we must patch the JS Object prototype with a helper method. + String toStringName = backend.namer.publicInstanceMethodNameByArity( + const SourceString('toString'), 0); + addProperty(toStringName, makeCallOnThis(toStringHelperName)); + + // Same as above, but for hashCode. + String hashCodeName = + backend.namer.publicGetterName(const SourceString('hashCode')); + addProperty(hashCodeName, makeCallOnThis(hashCodeHelperName)); + + // Same as above, but for operator==. + String equalsName = backend.namer.publicInstanceMethodNameByArity( + const SourceString('=='), 1); + addProperty(equalsName, js.fun(['a'], js.block1( + js.return_(js.strictEquals(new js.This(), js.use('a')))))); + + // If the native emitter has been asked to take care of the + // noSuchMethod handlers, we do that now. + if (handleNoSuchMethod) { + emitter.emitNoSuchMethodHandlers(addProperty); + } + + // If we have any properties to add to Object.prototype, we run + // through them and add them using defineProperty. + if (!objectProperties.isEmpty) { + js.Expression init = + js.call( + js.fun(['table'], + js.block1( + new js.ForIn( + new js.VariableDeclarationList( + [new js.VariableInitialization( + new js.VariableDeclaration('key'), + null)]), + js.use('table'), + new js.ExpressionStatement( + js.call( + js.use(defPropName), + [js.use('Object').dot('prototype'), + js.use('key'), + new js.PropertyAccess(js.use('table'), + js.use('key'))]))))), + [new js.ObjectInitializer(objectProperties)]); + + if (emitter.compiler.enableMinification) targetBuffer.add(';'); + targetBuffer.add(js.prettyPrint( + new js.ExpressionStatement(init), compiler)); + targetBuffer.add('\n'); + } + + targetBuffer.add(nativeBuffer); + targetBuffer.add('\n'); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart new file mode 100644 index 0000000..a3a0339 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart
@@ -0,0 +1,173 @@ +// 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. + +part of js_backend; + +/// For each class, stores the possible class subtype tests that could succeed. +abstract class TypeChecks { + /// Get the set of checks required for class [element]. + Iterable<ClassElement> operator[](ClassElement element); + // Get the iterator for all classes that need type checks. + Iterator<ClassElement> get iterator; +} + +class RuntimeTypeInformation { + final Compiler compiler; + + RuntimeTypeInformation(this.compiler); + + /// Contains the classes of all arguments that have been used in + /// instantiations and checks. + Set<ClassElement> allArguments; + + bool isJsNative(Element element) { + return (element == compiler.intClass || + element == compiler.boolClass || + element == compiler.numClass || + element == compiler.doubleClass || + element == compiler.stringClass || + element == compiler.listClass || + element == compiler.objectClass || + element == compiler.dynamicClass); + } + + TypeChecks computeRequiredChecks() { + Set<ClassElement> instantiatedArguments = new Set<ClassElement>(); + for (DartType type in compiler.codegenWorld.instantiatedTypes) { + addAllInterfaceTypeArguments(type, instantiatedArguments); + } + + Set<ClassElement> checkedArguments = new Set<ClassElement>(); + for (DartType type in compiler.enqueuer.codegen.universe.isChecks) { + addAllInterfaceTypeArguments(type, checkedArguments); + } + + allArguments = new Set<ClassElement>.from(instantiatedArguments) + ..addAll(checkedArguments); + + TypeCheckMapping requiredChecks = new TypeCheckMapping(); + for (ClassElement element in instantiatedArguments) { + if (element == compiler.dynamicClass) continue; + if (checkedArguments.contains(element)) { + requiredChecks.add(element, element); + } + // Find all supertypes of [element] in [checkedArguments] and add checks. + for (DartType supertype in element.allSupertypes) { + ClassElement superelement = supertype.element; + if (checkedArguments.contains(superelement)) { + requiredChecks.add(element, superelement); + } + } + } + + return requiredChecks; + } + + void addAllInterfaceTypeArguments(DartType type, Set<ClassElement> classes) { + if (type is !InterfaceType) return; + for (DartType argument in type.typeArguments) { + forEachInterfaceType(argument, (InterfaceType t) { + ClassElement cls = t.element; + if (cls != compiler.dynamicClass && cls != compiler.objectClass) { + classes.add(cls); + } + }); + } + } + + void forEachInterfaceType(DartType type, f(InterfaceType type)) { + if (type.kind == TypeKind.INTERFACE) { + f(type); + InterfaceType interface = type; + for (DartType argument in interface.typeArguments) { + forEachInterfaceType(argument, f); + } + } + } + + /// Return the unique name for the element as an unquoted string. + String getNameAsString(Element element) { + JavaScriptBackend backend = compiler.backend; + return backend.namer.getName(element); + } + + /// Return the unique JS name for the element, which is a quoted string for + /// native classes and the isolate acccess to the constructor for classes. + String getJsName(Element element) { + JavaScriptBackend backend = compiler.backend; + Namer namer = backend.namer; + return namer.isolateAccess(element); + } + + String getRawTypeRepresentation(DartType type) { + String name = getNameAsString(type.element); + if (!type.element.isClass()) return name; + InterfaceType interface = type; + Link<DartType> variables = interface.element.typeVariables; + if (variables.isEmpty) return name; + List<String> arguments = []; + variables.forEach((_) => arguments.add('dynamic')); + return '$name<${Strings.join(arguments, ', ')}>'; + } + + String getTypeRepresentation(DartType type, void onVariable(variable)) { + StringBuffer builder = new StringBuffer(); + void build(DartType part) { + if (part is TypeVariableType) { + builder.add('#'); + onVariable(part); + } else { + bool hasArguments = part is InterfaceType && !part.isRaw; + Element element = part.element; + if (hasArguments) { + builder.add('['); + } + builder.add(getJsName(element)); + if (!hasArguments) return; + InterfaceType interface = part; + for (DartType argument in interface.typeArguments) { + builder.add(', '); + build(argument); + } + builder.add(']'); + } + } + build(type); + return builder.toString(); + } + + static bool hasTypeArguments(DartType type) { + if (type is InterfaceType) { + InterfaceType interfaceType = type; + return !interfaceType.isRaw; + } + return false; + } + + static int getTypeVariableIndex(TypeVariableType variable) { + ClassElement classElement = variable.element.getEnclosingClass(); + Link<DartType> variables = classElement.typeVariables; + for (int index = 0; !variables.isEmpty; + index++, variables = variables.tail) { + if (variables.head == variable) return index; + } + } +} + +class TypeCheckMapping implements TypeChecks { + final Map<ClassElement, Set<ClassElement>> map = + new Map<ClassElement, Set<ClassElement>>(); + + Iterable<ClassElement> operator[](ClassElement element) { + Set<ClassElement> result = map[element]; + return result != null ? result : const <ClassElement>[]; + } + + void add(ClassElement cls, ClassElement check) { + map.putIfAbsent(cls, () => new Set<ClassElement>()); + map[cls].add(check); + } + + Iterator<ClassElement> get iterator => map.keys.iterator; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart new file mode 100644 index 0000000..0fccce2 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart
@@ -0,0 +1,21 @@ +// 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. + +// Patch file for the dart:async library. + +import 'dart:_isolate_helper' show TimerImpl; + +patch class Timer { + patch factory Timer(int milliseconds, void callback(Timer timer)) { + return new TimerImpl(milliseconds, callback); + } + + /** + * Creates a new repeating timer. The [callback] is invoked every + * [milliseconds] millisecond until cancelled. + */ + patch factory Timer.repeating(int milliseconds, void callback(Timer timer)) { + return new TimerImpl.repeating(milliseconds, callback); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart new file mode 100644 index 0000000..75446c7 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart
@@ -0,0 +1,75 @@ +// 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. + +part of _js_helper; + +// This class has no constructor. This is on purpose since the instantiation +// is shortcut by the compiler. +class ConstantMap<V> implements Map<String, V> { + final int length; + // A constant map is backed by a JavaScript object. + final _jsObject; + final List<String> _keys; + + bool containsValue(V needle) { + return values.any((V value) => value == needle); + } + + bool containsKey(String key) { + if (key == '__proto__') return false; + return jsHasOwnProperty(_jsObject, key); + } + + V operator [](String key) { + if (!containsKey(key)) return null; + return jsPropertyAccess(_jsObject, key); + } + + void forEach(void f(String key, V value)) { + _keys.forEach((String key) => f(key, this[key])); + } + + Iterable<String> get keys { + return new _ConstantMapKeyIterable(this); + } + + Iterable<V> get values { + return _keys.map((String key) => this[key]); + } + + bool get isEmpty => length == 0; + + String toString() => Maps.mapToString(this); + + _throwUnmodifiable() { + throw new UnsupportedError("Cannot modify unmodifiable Map"); + } + void operator []=(String key, V val) => _throwUnmodifiable(); + V putIfAbsent(String key, V ifAbsent()) => _throwUnmodifiable(); + V remove(String key) => _throwUnmodifiable(); + void clear() => _throwUnmodifiable(); +} + +// This class has no constructor. This is on purpose since the instantiation +// is shortcut by the compiler. +class ConstantProtoMap<V> extends ConstantMap<V> { + final V _protoValue; + + bool containsKey(String key) { + if (key == '__proto__') return true; + return super.containsKey(key); + } + + V operator [](String key) { + if (key == '__proto__') return _protoValue; + return super[key]; + } +} + +class _ConstantMapKeyIterable extends Iterable<String> { + ConstantMap _map; + _ConstantMapKeyIterable(this._map); + + Iterator<String> get iterator => _map._keys.iterator; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart new file mode 100644 index 0000000..09e31da --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart
@@ -0,0 +1,250 @@ +// 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. + +// Patch file for dart:core classes. + +import 'dart:_interceptors'; +import 'dart:_js_helper' show checkNull, + getRuntimeTypeString, + isJsArray, + JSSyntaxRegExp, + Primitives, + TypeImpl, + stringJoinUnchecked, + JsStringBuffer; + +patch void print(var object) { + Primitives.printString(object.toString()); +} + +// Patch for Object implementation. +patch class Object { + patch int get hashCode => Primitives.objectHashCode(this); + + patch String toString() => Primitives.objectToString(this); + + patch dynamic noSuchMethod(InvocationMirror invocation) { + throw new NoSuchMethodError(this, + invocation.memberName, + invocation.positionalArguments, + invocation.namedArguments); + } + + patch Type get runtimeType { + String type = getRuntimeTypeString(this); + return new TypeImpl(type); + } +} + +// Patch for Function implementation. +patch class Function { + patch static apply(Function function, + List positionalArguments, + [Map<String,dynamic> namedArguments]) { + return Primitives.applyFunction( + function, positionalArguments, namedArguments); + } +} + +// Patch for Expando implementation. +patch class Expando<T> { + patch Expando([String name]) : this.name = name; + + patch T operator[](Object object) { + var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME); + return (values == null) ? null : Primitives.getProperty(values, _getKey()); + } + + patch void operator[]=(Object object, T value) { + var values = Primitives.getProperty(object, _EXPANDO_PROPERTY_NAME); + if (values == null) { + values = new Object(); + Primitives.setProperty(object, _EXPANDO_PROPERTY_NAME, values); + } + Primitives.setProperty(values, _getKey(), value); + } + + String _getKey() { + String key = Primitives.getProperty(this, _KEY_PROPERTY_NAME); + if (key == null) { + key = "expando\$key\$${_keyCount++}"; + Primitives.setProperty(this, _KEY_PROPERTY_NAME, key); + } + return key; + } + + static const String _KEY_PROPERTY_NAME = 'expando\$key'; + static const String _EXPANDO_PROPERTY_NAME = 'expando\$values'; + static int _keyCount = 0; +} + +patch class int { + patch static int parse(String source, + { int radix, + int onError(String source) }) { + return Primitives.parseInt(source, radix, onError); + } +} + +patch class double { + patch static double parse(String source, [int handleError(String source)]) { + return Primitives.parseDouble(source, handleError); + } +} + +patch class Error { + patch static String _objectToString(Object object) { + return Primitives.objectToString(object); + } +} + + +// Patch for DateTime implementation. +patch class DateTime { + patch DateTime._internal(int year, + int month, + int day, + int hour, + int minute, + int second, + int millisecond, + bool isUtc) + : this.isUtc = checkNull(isUtc), + millisecondsSinceEpoch = Primitives.valueFromDecomposedDate( + year, month, day, hour, minute, second, millisecond, isUtc) { + Primitives.lazyAsJsDate(this); + } + + patch DateTime._now() + : isUtc = false, + millisecondsSinceEpoch = Primitives.dateNow() { + Primitives.lazyAsJsDate(this); + } + + patch static int _brokenDownDateToMillisecondsSinceEpoch( + int year, int month, int day, int hour, int minute, int second, + int millisecond, bool isUtc) { + return Primitives.valueFromDecomposedDate( + year, month, day, hour, minute, second, millisecond, isUtc); + } + + patch String get timeZoneName { + if (isUtc) return "UTC"; + return Primitives.getTimeZoneName(this); + } + + patch Duration get timeZoneOffset { + if (isUtc) return new Duration(); + return new Duration(minutes: Primitives.getTimeZoneOffsetInMinutes(this)); + } + + patch int get year => Primitives.getYear(this); + + patch int get month => Primitives.getMonth(this); + + patch int get day => Primitives.getDay(this); + + patch int get hour => Primitives.getHours(this); + + patch int get minute => Primitives.getMinutes(this); + + patch int get second => Primitives.getSeconds(this); + + patch int get millisecond => Primitives.getMilliseconds(this); + + patch int get weekday => Primitives.getWeekday(this); +} + + +// Patch for Stopwatch implementation. +patch class Stopwatch { + patch static int _frequency() => 1000000; + patch static int _now() => Primitives.numMicroseconds(); +} + + +// Patch for List implementation. +patch class List<E> { + patch factory List([int length = 0]) { + // Explicit type test is necessary to protect Primitives.newGrowableList in + // unchecked mode. + if ((length is !int) || (length < 0)) { + throw new ArgumentError("Length must be a positive integer: $length."); + } + return Primitives.newGrowableList(length); + } + + patch factory List.fixedLength(int length, {E fill: null}) { + // Explicit type test is necessary to protect Primitives.newFixedList in + // unchecked mode. + if ((length is !int) || (length < 0)) { + throw new ArgumentError("Length must be a positive integer: $length."); + } + List result = Primitives.newFixedList(length); + if (length != 0 && fill != null) { + for (int i = 0; i < result.length; i++) { + result[i] = fill; + } + } + return result; + } +} + + +patch class String { + patch factory String.fromCharCodes(List<int> charCodes) { + if (!isJsArray(charCodes)) { + if (charCodes is !List) throw new ArgumentError(charCodes); + charCodes = new List.from(charCodes); + } + return Primitives.stringFromCharCodes(charCodes); + } +} + +// Patch for String implementation. +patch class Strings { + patch static String join(Iterable<String> strings, String separator) { + checkNull(strings); + if (separator is !String) throw new ArgumentError(separator); + return stringJoinUnchecked(_toJsStringArray(strings), separator); + } + + patch static String concatAll(Iterable<String> strings) { + return stringJoinUnchecked(_toJsStringArray(strings), ""); + } + + static List _toJsStringArray(Iterable<String> strings) { + checkNull(strings); + var array; + if (!isJsArray(strings)) { + strings = new List.from(strings); + } + final length = strings.length; + for (int i = 0; i < length; i++) { + final string = strings[i]; + if (string is !String) throw new ArgumentError(string); + } + return strings; + } +} + +patch class RegExp { + patch factory RegExp(String pattern, + {bool multiLine: false, + bool caseSensitive: true}) + => new JSSyntaxRegExp(pattern, + multiLine: multiLine, + caseSensitive: caseSensitive); +} + +// Patch for 'identical' function. +patch bool identical(Object a, Object b) { + return Primitives.identicalImplementation(a, b); +} + +patch class StringBuffer { + patch factory StringBuffer([Object content = ""]) { + return new JsStringBuffer(content); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart new file mode 100644 index 0000000..2ad4a1e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart
@@ -0,0 +1,138 @@ +// 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. + +library _foreign_helper; + +/** + * Emits a JavaScript code fragment parameterized by arguments. + * + * Hash characters `#` in the [codeTemplate] are replaced in left-to-right order + * with expressions that contain the values of, or evaluate to, the arguments. + * The number of hash marks must match the number or arguments. Although + * declared with arguments [arg0] through [arg2], the form actually has no limit + * on the number of arguments. + * + * The [typeDescription] argument is interpreted as a description of the + * behavior of the JavaScript code. Currently it describes the types that may + * be returned by the expression, with the additional behavior that the returned + * values may be fresh instances of the types. The type information must be + * correct as it is trusted by the compiler in optimizations, and it must be + * precise as possible since it is used for native live type analysis to + * tree-shake large parts of the DOM libraries. If poorly written, the + * [typeDescription] will cause unnecessarily bloated programs. (You can check + * for this by compiling with `--verbose`; there is an info message describing + * the number of native (DOM) types that can be removed, which usually should be + * greater than zero.) + * + * The [typeDescription] is a [String] which contains a union of types separated + * by vertical bar `|` symbols, e.g. `"num|String"` describes the union of + * numbers and Strings. There is no type in Dart that is this precise. The + * Dart alternative would be `Object` or `dynamic`, but these types imply that + * the JS-code might also be creating instances of all the DOM types. The + * [typeDescription] has several extensions to help describe the behavior more + * accurately. In addition to the union type already described: + * + * + `=List` is the JavaScript array type. This is more precise than `List`, + * which includes about fifty DOM types that also implement the List + * interface. + * + * + `=Object` is a plain JavaScript object. Some DOM methods return instances + * that have no corresponing Dart type (e.g. cross-frame documents), + * `=Object` can be used to describe these untyped' values. + * + * + `var`. If the entire [typeDescription] is `var` then the type is + * `dynamic` but the code is known to not create any instances. + * + * Examples: + * + * // Create a JavaScript Array. + * List a = JS('=List', 'new Array(#)', length); + * + * // Parent window might be an opaque cross-frame window. + * var thing = JS('=Object|Window', '#.parent', myWindow); + * + * Guidelines: + * + * + Do not use any parameter, local, method or field names in the + * [codeTemplate]. These names are all subject to arbitrary renaming by the + * compiler. Pass the values in via `#` substition, and test with the + * `--minify` dart2js command-line option. + * + * + The substituted expressions are values, not locations. + * + * JS('void', '# += "x"', this.field); + * + * `this.field` might not be a substituted as a reference to the field. The + * generated code might accidentally work as intended, but it also might be + * + * var t1 = this.field; + * t1 += "x"; + * + * or + * + * this.get$field() += "x"; + * + * The remedy in this case is to expand the `+=` operator, leaving all + * references to the Dart field as Dart code: + * + * this.field = JS('String', '# + "x"', this.field); + * + * + * Additional notes. + * + * In the future we may extend [typeDescription] to include other aspects of the + * behavior, for example, separating the returned types from the instantiated + * types, or including effects to allow the compiler to perform more + * optimizations around the code. This might be an extension of [JS] or a new + * function similar to [JS] with additional arguments for the new information. + */ +// Add additional optional arguments if needed. The method is treated internally +// as a variable argument method. +dynamic JS(String typeDescription, String codeTemplate, + [var arg0, var arg1, var arg2, var arg3, var arg4, var arg5, var arg6, + var arg7, var arg8, var arg9, var arg10, var arg11]) {} + +/** + * Returns the isolate in which this code is running. + */ +dynamic JS_CURRENT_ISOLATE() {} + +/** + * Invokes [function] in the context of [isolate]. + */ +dynamic JS_CALL_IN_ISOLATE(var isolate, Function function) {} + +/** + * Converts the Dart closure [function] into a JavaScript closure. + */ +dynamic DART_CLOSURE_TO_JS(Function function) {} + +/** + * Returns a raw reference to the JavaScript function which implements + * [function]. + * + * Warning: this is dangerous, you should probably use + * [DART_CLOSURE_TO_JS] instead. The returned object is not a valid + * Dart closure, does not store the isolate context or arity. + * + * A valid example of where this can be used is as the second argument + * to V8's Error.captureStackTrace. See + * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi. + */ +dynamic RAW_DART_FUNCTION_REF(Function function) {} + +/** + * Sets the current isolate to [isolate]. + */ +void JS_SET_CURRENT_ISOLATE(var isolate) {} + +/** + * Creates an isolate and returns it. + */ +dynamic JS_CREATE_ISOLATE() {} + +/** + * Returns the prefix used for generated is checks on classes. + */ +String JS_OPERATOR_IS_PREFIX() {}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart new file mode 100644 index 0000000..6a32f17 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart
@@ -0,0 +1,90 @@ +// 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. + +library _interceptors; + +import 'dart:collection'; +import 'dart:_collection-dev'; +import 'dart:_js_helper' show allMatchesInStringUnchecked, + Null, + JSSyntaxRegExp, + Primitives, + checkGrowable, + checkMutable, + checkNull, + checkNum, + checkString, + getRuntimeTypeString, + listInsertRange, + regExpGetNative, + stringContainsUnchecked, + stringLastIndexOfUnchecked, + stringReplaceAllFuncUnchecked, + stringReplaceAllUnchecked, + stringReplaceFirstUnchecked, + TypeImpl; +import 'dart:_foreign_helper' show JS; + +part 'js_array.dart'; +part 'js_number.dart'; +part 'js_string.dart'; + +/** + * The interceptor class for all non-primitive objects. All its + * members are synthethized by the compiler's emitter. + */ +class ObjectInterceptor { + const ObjectInterceptor(); +} + +/** + * Get the interceptor for [object]. Called by the compiler when it needs + * to emit a call to an intercepted method, that is a method that is + * defined in an interceptor class. + */ +getInterceptor(object) { + // This is a magic method: the compiler does specialization of it + // depending on the uses of intercepted methods and instantiated + // primitive types. +} + +/** + * The interceptor class for tear-off static methods. Unlike + * tear-off instance methods, tear-off static methods are just the JS + * function, and methods inherited from Object must therefore be + * intercepted. + */ +class JSFunction implements Function { + const JSFunction(); + String toString() => 'Closure'; +} + +/** + * The interceptor class for [bool]. + */ +class JSBool implements bool { + const JSBool(); + + // Note: if you change this, also change the function [S]. + String toString() => JS('String', r'String(#)', this); + + // The values here are SMIs, co-prime and differ about half of the bit + // positions, including the low bit, so they are different mod 2^k. + int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811); + + Type get runtimeType => bool; +} + +/** + * The interceptor class for [Null]. + */ +class JSNull implements Null { + const JSNull(); + + // Note: if you change this, also change the function [S]. + String toString() => 'null'; + + int get hashCode => 0; + Type get runtimeType => Null; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart new file mode 100644 index 0000000..0374de5 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart
@@ -0,0 +1,217 @@ +// 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. + +patch class _BufferUtils { + patch static bool _isBuiltinList(List buffer) { + throw new UnsupportedError("_isBuiltinList"); + } +} + +patch class _Directory { + patch static String _current() { + throw new UnsupportedError("Directory._current"); + } + patch static _createTemp(String template) { + throw new UnsupportedError("Directory._createTemp"); + } + patch static int _exists(String path) { + throw new UnsupportedError("Directory._exists"); + } + patch static _create(String path) { + throw new UnsupportedError("Directory._create"); + } + patch static _delete(String path, bool recursive) { + throw new UnsupportedError("Directory._delete"); + } + patch static _rename(String path, String newPath) { + throw new UnsupportedError("Directory._rename"); + } + patch static List _list(String path, bool recursive) { + throw new UnsupportedError("Directory._list"); + } + patch static SendPort _newServicePort() { + throw new UnsupportedError("Directory._newServicePort"); + } +} + +patch class _EventHandler { + patch static void _start() { + throw new UnsupportedError("EventHandler._start"); + } + + patch static _sendData(Object sender, + ReceivePort receivePort, + int data) { + throw new UnsupportedError("EventHandler._sendData"); + } +} + +patch class _FileUtils { + patch static SendPort _newServicePort() { + throw new UnsupportedError("FileUtils._newServicePort"); + } +} + +patch class _File { + patch static _exists(String name) { + throw new UnsupportedError("File._exists"); + } + patch static _create(String name) { + throw new UnsupportedError("File._create"); + } + patch static _delete(String name) { + throw new UnsupportedError("File._delete"); + } + patch static _directory(String name) { + throw new UnsupportedError("File._directory"); + } + patch static _lengthFromName(String name) { + throw new UnsupportedError("File._lengthFromName"); + } + patch static _lastModified(String name) { + throw new UnsupportedError("File._lastModified"); + } + patch static _open(String name, int mode) { + throw new UnsupportedError("File._open"); + } + patch static int _openStdio(int fd) { + throw new UnsupportedError("File._openStdio"); + } + patch static _fullPath(String name) { + throw new UnsupportedError("File._fullPath"); + } +} + +patch class _RandomAccessFile { + patch static int _close(int id) { + throw new UnsupportedError("RandomAccessFile._close"); + } + patch static _readByte(int id) { + throw new UnsupportedError("RandomAccessFile._readByte"); + } + patch static _read(int id, int bytes) { + throw new UnsupportedError("RandomAccessFile._read"); + } + patch static _readList(int id, List<int> buffer, int offset, int bytes) { + throw new UnsupportedError("RandomAccessFile._readList"); + } + patch static _writeByte(int id, int value) { + throw new UnsupportedError("RandomAccessFile._writeByte"); + } + patch static _writeList(int id, List<int> buffer, int offset, int bytes) { + throw new UnsupportedError("RandomAccessFile._writeList"); + } + patch static _position(int id) { + throw new UnsupportedError("RandomAccessFile._position"); + } + patch static _setPosition(int id, int position) { + throw new UnsupportedError("RandomAccessFile._setPosition"); + } + patch static _truncate(int id, int length) { + throw new UnsupportedError("RandomAccessFile._truncate"); + } + patch static _length(int id) { + throw new UnsupportedError("RandomAccessFile._length"); + } + patch static _flush(int id) { + throw new UnsupportedError("RandomAccessFile._flush"); + } +} + +patch class _HttpSessionManager { + patch static Uint8List _getRandomBytes(int count) { + throw new UnsupportedError("HttpSessionManager._getRandomBytes"); + } +} + +patch class _Platform { + patch static int _numberOfProcessors() { + throw new UnsupportedError("Platform._numberOfProcessors"); + } + patch static String _pathSeparator() { + throw new UnsupportedError("Platform._pathSeparator"); + } + patch static String _operatingSystem() { + throw new UnsupportedError("Platform._operatingSystem"); + } + patch static _localHostname() { + throw new UnsupportedError("Platform._localHostname"); + } + patch static _environment() { + throw new UnsupportedError("Platform._environment"); + } +} + +patch class _ProcessUtils { + patch static _exit(int status) { + throw new UnsupportedError("ProcessUtils._exit"); + } + patch static _setExitCode(int status) { + throw new UnsupportedError("ProcessUtils._setExitCode"); + } +} + +patch class Process { + patch static Future<Process> start(String executable, + List<String> arguments, + [ProcessOptions options]) { + throw new UnsupportedError("Process.start"); + } + + patch static Future<ProcessResult> run(String executable, + List<String> arguments, + [ProcessOptions options]) { + throw new UnsupportedError("Process.run"); + } +} + +patch class ServerSocket { + patch factory ServerSocket(String bindAddress, int port, int backlog) { + throw new UnsupportedError("ServerSocket constructor"); + } +} + +patch class Socket { + patch factory Socket(String host, int port) { + throw new UnsupportedError("Socket constructor"); + } +} + +patch class SecureSocket { + patch static void initialize({String database, + String password, + bool useBuiltinRoots: true}) { + throw new UnsupportedError("SecureSocket.setCertificateDatabase"); + } +} + +patch class _SecureFilter { + patch factory _SecureFilter() { + throw new UnsupportedError("_SecureFilter._SecureFilter"); + } +} + +patch class _StdIOUtils { + patch static InputStream _getStdioInputStream() { + throw new UnsupportedError("StdIOUtils._getStdioInputStream"); + } + patch static OutputStream _getStdioOutputStream(int fd) { + throw new UnsupportedError("StdIOUtils._getStdioOutputStream"); + } + patch static int _socketType(Socket socket) { + throw new UnsupportedError("StdIOUtils._socketType"); + } +} + +patch class _WindowsCodePageDecoder { + patch static String _decodeBytes(List<int> bytes) { + throw new UnsupportedError("_WindowsCodePageDecoder._decodeBytes"); + } +} + +patch class _WindowsCodePageEncoder { + patch static List<int> _encodeString(String string) { + throw new UnsupportedError("_WindowsCodePageEncoder._encodeString"); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart new file mode 100644 index 0000000..82a80fe --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart
@@ -0,0 +1,1329 @@ +// 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. + +library _isolate_helper; + +import 'dart:async'; +import 'dart:collection' show Queue, HashMap; +import 'dart:isolate'; +import 'dart:_js_helper' show convertDartClosureToJS, + Null; +import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, + JS, + JS_CREATE_ISOLATE, + JS_SET_CURRENT_ISOLATE; + +ReceivePort lazyPort; + +/** + * Called by the compiler to support switching + * between isolates when we get a callback from the DOM. + */ +void _callInIsolate(_IsolateContext isolate, Function function) { + isolate.eval(function); + _globalState.topEventLoop.run(); +} + +/** + * Called by the compiler to fetch the current isolate context. + */ +_IsolateContext _currentIsolate() => _globalState.currentContext; + +/** + * Wrapper that takes the dart entry point and runs it within an isolate. The + * dart2js compiler will inject a call of the form + * [: startRootIsolate(main); :] when it determines that this wrapping + * is needed. For single-isolate applications (e.g. hello world), this + * call is not emitted. + */ +void startRootIsolate(entry) { + _globalState = new _Manager(); + + // Don't start the main loop again, if we are in a worker. + if (_globalState.isWorker) return; + final rootContext = new _IsolateContext(); + _globalState.rootContext = rootContext; + + // BUG(5151491): Setting currentContext should not be necessary, but + // because closures passed to the DOM as event handlers do not bind their + // isolate automatically we try to give them a reasonable context to live in + // by having a "default" isolate (the first one created). + _globalState.currentContext = rootContext; + + rootContext.eval(entry); + _globalState.topEventLoop.run(); +} + +/******************************************************** + Inserted from lib/isolate/dart2js/isolateimpl.dart + ********************************************************/ + +/** + * Concepts used here: + * + * "manager" - A manager contains one or more isolates, schedules their + * execution, and performs other plumbing on their behalf. The isolate + * present at the creation of the manager is designated as its "root isolate". + * A manager may, for example, be implemented on a web Worker. + * + * [_Manager] - State present within a manager (exactly once, as a global). + * + * [_ManagerStub] - A handle held within one manager that allows interaction + * with another manager. A target manager may be addressed by zero or more + * [_ManagerStub]s. + * + */ + +/** + * A native object that is shared across isolates. This object is visible to all + * isolates running under the same manager (either UI or background web worker). + * + * This is code that is intended to 'escape' the isolate boundaries in order to + * implement the semantics of isolates in JavaScript. Without this we would have + * been forced to implement more code (including the top-level event loop) in + * JavaScript itself. + */ +// TODO(eub, sigmund): move the "manager" to be entirely in JS. +// Running any Dart code outside the context of an isolate gives it +// the chance to break the isolate abstraction. +_Manager get _globalState => JS("_Manager", r"$globalState"); + +set _globalState(_Manager val) { + JS("void", r"$globalState = #", val); +} + +/** State associated with the current manager. See [globalState]. */ +// TODO(sigmund): split in multiple classes: global, thread, main-worker states? +class _Manager { + + /** Next available isolate id within this [_Manager]. */ + int nextIsolateId = 0; + + /** id assigned to this [_Manager]. */ + int currentManagerId = 0; + + /** + * Next available manager id. Only used by the main manager to assign a unique + * id to each manager created by it. + */ + int nextManagerId = 1; + + /** Context for the currently running [Isolate]. */ + _IsolateContext currentContext = null; + + /** Context for the root [Isolate] that first run in this [_Manager]. */ + _IsolateContext rootContext = null; + + /** The top-level event loop. */ + _EventLoop topEventLoop; + + /** Whether this program is running from the command line. */ + bool fromCommandLine; + + /** Whether this [_Manager] is running as a web worker. */ + bool isWorker; + + /** Whether we support spawning web workers. */ + bool supportsWorkers; + + /** + * Whether to use web workers when implementing isolates. Set to false for + * debugging/testing. + */ + bool get useWorkers => supportsWorkers; + + /** + * Whether to use the web-worker JSON-based message serialization protocol. By + * default this is only used with web workers. For debugging, you can force + * using this protocol by changing this field value to [true]. + */ + bool get needSerialization => useWorkers; + + /** + * Registry of isolates. Isolates must be registered if, and only if, receive + * ports are alive. Normally no open receive-ports means that the isolate is + * dead, but DOM callbacks could resurrect it. + */ + Map<int, _IsolateContext> isolates; + + /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */ + _ManagerStub mainManager; + + /** Registry of active [_ManagerStub]s. Only used in the main [_Manager]. */ + Map<int, _ManagerStub> managers; + + _Manager() { + _nativeDetectEnvironment(); + topEventLoop = new _EventLoop(); + isolates = new Map<int, _IsolateContext>(); + managers = new Map<int, _ManagerStub>(); + if (isWorker) { // "if we are not the main manager ourself" is the intent. + mainManager = new _MainManagerStub(); + _nativeInitWorkerMessageHandler(); + } + } + + void _nativeDetectEnvironment() { + bool isWindowDefined = globalWindow != null; + bool isWorkerDefined = globalWorker != null; + + isWorker = !isWindowDefined && globalPostMessageDefined; + supportsWorkers = isWorker + || (isWorkerDefined && IsolateNatives.thisScript != null); + fromCommandLine = !isWindowDefined && !isWorker; + } + + void _nativeInitWorkerMessageHandler() { + var function = JS('', + "function (e) { #(#, e); }", + DART_CLOSURE_TO_JS(IsolateNatives._processWorkerMessage), + mainManager); + JS("void", r"#.onmessage = #", globalThis, function); + // We define dartPrint so that the implementation of the Dart + // print method knows what to call. + // TODO(ngeoffray): Should we forward to the main isolate? What if + // it exited? + JS('void', r'#.dartPrint = function (object) {}', globalThis); + } + + + /** + * Close the worker running this code if all isolates are done and + * there is no active timer. + */ + void maybeCloseWorker() { + if (isWorker + && isolates.isEmpty + && topEventLoop.activeTimerCount == 0) { + mainManager.postMessage(_serializeMessage({'command': 'close'})); + } + } +} + +/** Context information tracked for each isolate. */ +class _IsolateContext { + /** Current isolate id. */ + int id; + + /** Registry of receive ports currently active on this isolate. */ + Map<int, ReceivePort> ports; + + /** Holds isolate globals (statics and top-level properties). */ + var isolateStatics; // native object containing all globals of an isolate. + + _IsolateContext() { + id = _globalState.nextIsolateId++; + ports = new Map<int, ReceivePort>(); + isolateStatics = JS_CREATE_ISOLATE(); + } + + /** + * Run [code] in the context of the isolate represented by [this]. + */ + dynamic eval(Function code) { + var old = _globalState.currentContext; + _globalState.currentContext = this; + this._setGlobals(); + var result = null; + try { + result = code(); + } finally { + _globalState.currentContext = old; + if (old != null) old._setGlobals(); + } + return result; + } + + void _setGlobals() { + JS_SET_CURRENT_ISOLATE(isolateStatics); + } + + /** Lookup a port registered for this isolate. */ + ReceivePort lookup(int portId) => ports[portId]; + + /** Register a port on this isolate. */ + void register(int portId, ReceivePort port) { + if (ports.containsKey(portId)) { + throw new Exception("Registry: ports must be registered only once."); + } + ports[portId] = port; + _globalState.isolates[id] = this; // indicate this isolate is active + } + + /** Unregister a port on this isolate. */ + void unregister(int portId) { + ports.remove(portId); + if (ports.isEmpty) { + _globalState.isolates.remove(id); // indicate this isolate is not active + } + } +} + +/** Represent the event loop on a javascript thread (DOM or worker). */ +class _EventLoop { + final Queue<_IsolateEvent> events = new Queue<_IsolateEvent>(); + int activeTimerCount = 0; + + _EventLoop(); + + void enqueue(isolate, fn, msg) { + events.addLast(new _IsolateEvent(isolate, fn, msg)); + } + + _IsolateEvent dequeue() { + if (events.isEmpty) return null; + return events.removeFirst(); + } + + void checkOpenReceivePortsFromCommandLine() { + if (_globalState.rootContext != null + && _globalState.isolates.containsKey(_globalState.rootContext.id) + && _globalState.fromCommandLine + && _globalState.rootContext.ports.isEmpty) { + // We want to reach here only on the main [_Manager] and only + // on the command-line. In the browser the isolate might + // still be alive due to DOM callbacks, but the presumption is + // that on the command-line, no future events can be injected + // into the event queue once it's empty. Node has setTimeout + // so this presumption is incorrect there. We think(?) that + // in d8 this assumption is valid. + throw new Exception("Program exited with open ReceivePorts."); + } + } + + /** Process a single event, if any. */ + bool runIteration() { + final event = dequeue(); + if (event == null) { + checkOpenReceivePortsFromCommandLine(); + _globalState.maybeCloseWorker(); + return false; + } + event.process(); + return true; + } + + /** + * Runs multiple iterations of the run-loop. If possible, each iteration is + * run asynchronously. + */ + void _runHelper() { + if (globalWindow != null) { + // Run each iteration from the browser's top event loop. + void next() { + if (!runIteration()) return; + new Timer(0, (_) => next()); + } + next(); + } else { + // Run synchronously until no more iterations are available. + while (runIteration()) {} + } + } + + /** + * Call [_runHelper] but ensure that worker exceptions are propragated. + */ + void run() { + if (!_globalState.isWorker) { + _runHelper(); + } else { + try { + _runHelper(); + } catch (e, trace) { + _globalState.mainManager.postMessage(_serializeMessage( + {'command': 'error', 'msg': '$e\n$trace' })); + } + } + } +} + +/** An event in the top-level event queue. */ +class _IsolateEvent { + _IsolateContext isolate; + Function fn; + String message; + + _IsolateEvent(this.isolate, this.fn, this.message); + + void process() { + isolate.eval(fn); + } +} + +/** An interface for a stub used to interact with a manager. */ +abstract class _ManagerStub { + get id; + void set id(int i); + void set onmessage(Function f); + void postMessage(msg); + void terminate(); +} + +/** A stub for interacting with the main manager. */ +class _MainManagerStub implements _ManagerStub { + get id => 0; + void set id(int i) { throw new UnimplementedError(); } + void set onmessage(f) { + throw new Exception("onmessage should not be set on MainManagerStub"); + } + void postMessage(msg) { + JS("void", r"#.postMessage(#)", globalThis, msg); + } + void terminate() {} // Nothing useful to do here. +} + +/** + * A stub for interacting with a manager built on a web worker. This + * definition uses a 'hidden' type (* prefix on the native name) to + * enforce that the type is defined dynamically only when web workers + * are actually available. + */ +// @Native("*Worker"); +class _WorkerStub implements _ManagerStub { + get id => JS("", "#.id", this); + void set id(i) { JS("void", "#.id = #", this, i); } + void set onmessage(f) { JS("void", "#.onmessage = #", this, f); } + void postMessage(msg) { JS("void", "#.postMessage(#)", this, msg); } + void terminate() { JS("void", "#.terminate()", this); } +} + +const String _SPAWNED_SIGNAL = "spawned"; + +var globalThis = IsolateNatives.computeGlobalThis(); +var globalWindow = JS('', "#.window", globalThis); +var globalWorker = JS('', "#.Worker", globalThis); +bool globalPostMessageDefined = + JS('', "#.postMessage !== (void 0)", globalThis); + +class IsolateNatives { + + static String thisScript = computeThisScript(); + + /** + * The src url for the script tag that loaded this code. Used to create + * JavaScript workers. + */ + static String computeThisScript() { + // TODO(7369): Find a cross-platform non-brittle way of getting the + // currently running script. + var scripts = JS('', r"document.getElementsByTagName('script')"); + // The scripts variable only contains the scripts that have already been + // executed. The last one is the currently running script. + for (int i = 0, len = JS('int', '#.length', scripts); i < len; i++) { + var script = JS('', '#[#]', scripts, i); + var src = JS('String|Null', '# && #.src', script, script); + // Filter out the test controller script, and the Dart + // bootstrap script. + if (src != null + && !src.endsWith('test_controller.js') + && !src.endsWith('dart.js')) { + return src; + } + } + return null; + } + + static computeGlobalThis() => JS('', 'function() { return this; }()'); + + /** Starts a new worker with the given URL. */ + static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url); + + /** + * Assume that [e] is a browser message event and extract its message data. + * We don't import the dom explicitly so, when workers are disabled, this + * library can also run on top of nodejs. + */ + static _getEventData(e) => JS("", "#.data", e); + + /** + * Process messages on a worker, either to control the worker instance or to + * pass messages along to the isolate running in the worker. + */ + static void _processWorkerMessage(sender, e) { + var msg = _deserializeMessage(_getEventData(e)); + switch (msg['command']) { + case 'start': + _globalState.currentManagerId = msg['id']; + Function entryPoint = _getJSFunctionFromName(msg['functionName']); + var replyTo = _deserializeMessage(msg['replyTo']); + var context = new _IsolateContext(); + _globalState.topEventLoop.enqueue(context, function() { + _startIsolate(entryPoint, replyTo); + }, 'worker-start'); + // Make sure we always have a current context in this worker. + // TODO(7907): This is currently needed because we're using + // Timers to implement Futures, and this isolate library + // implementation uses Futures. We should either stop using + // Futures in this library, or re-adapt if Futures get a + // different implementation. + _globalState.currentContext = context; + _globalState.topEventLoop.run(); + break; + case 'spawn-worker': + _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']); + break; + case 'message': + SendPort port = msg['port']; + // If the port has been closed, we ignore the message. + if (port != null) { + msg['port'].send(msg['msg'], msg['replyTo']); + } + _globalState.topEventLoop.run(); + break; + case 'close': + _log("Closing Worker"); + _globalState.managers.remove(sender.id); + sender.terminate(); + _globalState.topEventLoop.run(); + break; + case 'log': + _log(msg['msg']); + break; + case 'print': + if (_globalState.isWorker) { + _globalState.mainManager.postMessage( + _serializeMessage({'command': 'print', 'msg': msg})); + } else { + print(msg['msg']); + } + break; + case 'error': + throw msg['msg']; + } + } + + /** Log a message, forwarding to the main [_Manager] if appropriate. */ + static _log(msg) { + if (_globalState.isWorker) { + _globalState.mainManager.postMessage( + _serializeMessage({'command': 'log', 'msg': msg })); + } else { + try { + _consoleLog(msg); + } catch (e, trace) { + throw new Exception(trace); + } + } + } + + static void _consoleLog(msg) { + JS("void", r"#.console.log(#)", globalThis, msg); + } + + /** Find a constructor given its name. */ + static dynamic _getJSConstructorFromName(String factoryName) { + return JS("", r"$[#]", factoryName); + } + + static dynamic _getJSFunctionFromName(String functionName) { + return JS("", r"$[#]", functionName); + } + + /** + * Get a string name for the function, if possible. The result for + * anonymous functions is browser-dependent -- it may be "" or "anonymous" + * but you should probably not count on this. + */ + static String _getJSFunctionName(Function f) { + return JS("String|Null", r"(#.$name || #)", f, null); + } + + /** Create a new JavaScript object instance given its constructor. */ + static dynamic _allocate(var ctor) { + return JS("", "new #()", ctor); + } + + static SendPort spawnFunction(void topLevelFunction()) { + final name = _getJSFunctionName(topLevelFunction); + if (name == null) { + throw new UnsupportedError( + "only top-level functions can be spawned."); + } + return spawn(name, null, false); + } + + static SendPort spawnDomFunction(void topLevelFunction()) { + final name = _getJSFunctionName(topLevelFunction); + if (name == null) { + throw new UnsupportedError( + "only top-level functions can be spawned."); + } + return spawn(name, null, true); + } + + // TODO(sigmund): clean up above, after we make the new API the default: + + static spawn(String functionName, String uri, bool isLight) { + Completer<SendPort> completer = new Completer<SendPort>(); + ReceivePort port = new ReceivePort(); + port.receive((msg, SendPort replyPort) { + port.close(); + assert(msg == _SPAWNED_SIGNAL); + completer.complete(replyPort); + }); + + SendPort signalReply = port.toSendPort(); + + if (_globalState.useWorkers && !isLight) { + _startWorker(functionName, uri, signalReply); + } else { + _startNonWorker(functionName, uri, signalReply); + } + return new _BufferingSendPort( + _globalState.currentContext.id, completer.future); + } + + static SendPort _startWorker( + String functionName, String uri, SendPort replyPort) { + if (_globalState.isWorker) { + _globalState.mainManager.postMessage(_serializeMessage({ + 'command': 'spawn-worker', + 'functionName': functionName, + 'uri': uri, + 'replyPort': replyPort})); + } else { + _spawnWorker(functionName, uri, replyPort); + } + } + + static SendPort _startNonWorker( + String functionName, String uri, SendPort replyPort) { + // TODO(eub): support IE9 using an iframe -- Dart issue 1702. + if (uri != null) throw new UnsupportedError( + "Currently spawnUri is not supported without web workers."); + _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { + final func = _getJSFunctionFromName(functionName); + _startIsolate(func, replyPort); + }, 'nonworker start'); + } + + static void _startIsolate(Function topLevel, SendPort replyTo) { + lazyPort = new ReceivePort(); + replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); + topLevel(); + } + + /** + * Spawns an isolate in a worker. [factoryName] is the Javascript constructor + * name for the isolate entry point class. + */ + static void _spawnWorker(functionName, uri, replyPort) { + if (functionName == null) functionName = 'main'; + if (uri == null) uri = thisScript; + final worker = _newWorker(uri); + worker.onmessage = JS('', + 'function(e) { #(#, e); }', + DART_CLOSURE_TO_JS(_processWorkerMessage), + worker); + var workerId = _globalState.nextManagerId++; + // We also store the id on the worker itself so that we can unregister it. + worker.id = workerId; + _globalState.managers[workerId] = worker; + worker.postMessage(_serializeMessage({ + 'command': 'start', + 'id': workerId, + // Note: we serialize replyPort twice because the child worker needs to + // first deserialize the worker id, before it can correctly deserialize + // the port (port deserialization is sensitive to what is the current + // workerId). + 'replyTo': _serializeMessage(replyPort), + 'functionName': functionName })); + } +} + +/******************************************************** + Inserted from lib/isolate/dart2js/ports.dart + ********************************************************/ + +/** Common functionality to all send ports. */ +class _BaseSendPort implements SendPort { + /** Id for the destination isolate. */ + final int _isolateId; + + const _BaseSendPort(this._isolateId); + + void _checkReplyTo(SendPort replyTo) { + if (replyTo != null + && replyTo is! _NativeJsSendPort + && replyTo is! _WorkerSendPort + && replyTo is! _BufferingSendPort) { + throw new Exception("SendPort.send: Illegal replyTo port type"); + } + } + + Future call(var message) { + final completer = new Completer(); + final port = new ReceivePortImpl(); + send(message, port.toSendPort()); + port.receive((value, ignoreReplyTo) { + port.close(); + if (value is Exception) { + completer.completeError(value); + } else { + completer.complete(value); + } + }); + return completer.future; + } + + void send(var message, [SendPort replyTo]); + bool operator ==(var other); + int get hashCode; +} + +/** A send port that delivers messages in-memory via native JavaScript calls. */ +class _NativeJsSendPort extends _BaseSendPort implements SendPort { + final ReceivePortImpl _receivePort; + + const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); + + void send(var message, [SendPort replyTo = null]) { + _waitForPendingPorts([message, replyTo], () { + _checkReplyTo(replyTo); + // Check that the isolate still runs and the port is still open + final isolate = _globalState.isolates[_isolateId]; + if (isolate == null) return; + if (_receivePort._callback == null) return; + + // We force serialization/deserialization as a simple way to ensure + // isolate communication restrictions are respected between isolates that + // live in the same worker. [_NativeJsSendPort] delivers both messages + // from the same worker and messages from other workers. In particular, + // messages sent from a worker via a [_WorkerSendPort] are received at + // [_processWorkerMessage] and forwarded to a native port. In such cases, + // here we'll see [_globalState.currentContext == null]. + final shouldSerialize = _globalState.currentContext != null + && _globalState.currentContext.id != _isolateId; + var msg = message; + var reply = replyTo; + if (shouldSerialize) { + msg = _serializeMessage(msg); + reply = _serializeMessage(reply); + } + _globalState.topEventLoop.enqueue(isolate, () { + if (_receivePort._callback != null) { + if (shouldSerialize) { + msg = _deserializeMessage(msg); + reply = _deserializeMessage(reply); + } + _receivePort._callback(msg, reply); + } + }, 'receive $message'); + }); + } + + bool operator ==(var other) => (other is _NativeJsSendPort) && + (_receivePort == other._receivePort); + + int get hashCode => _receivePort._id; +} + +/** A send port that delivers messages via worker.postMessage. */ +// TODO(eub): abstract this for iframes. +class _WorkerSendPort extends _BaseSendPort implements SendPort { + final int _workerId; + final int _receivePortId; + + const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId) + : super(isolateId); + + void send(var message, [SendPort replyTo = null]) { + _waitForPendingPorts([message, replyTo], () { + _checkReplyTo(replyTo); + final workerMessage = _serializeMessage({ + 'command': 'message', + 'port': this, + 'msg': message, + 'replyTo': replyTo}); + + if (_globalState.isWorker) { + // Communication from one worker to another go through the + // main worker. + _globalState.mainManager.postMessage(workerMessage); + } else { + // Deliver the message only if the worker is still alive. + _ManagerStub manager = _globalState.managers[_workerId]; + if (manager != null) { + manager.postMessage(workerMessage); + } + } + }); + } + + bool operator ==(var other) { + return (other is _WorkerSendPort) && + (_workerId == other._workerId) && + (_isolateId == other._isolateId) && + (_receivePortId == other._receivePortId); + } + + int get hashCode { + // TODO(sigmund): use a standard hash when we get one available in corelib. + return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; + } +} + +/** A port that buffers messages until an underlying port gets resolved. */ +class _BufferingSendPort extends _BaseSendPort implements SendPort { + /** Internal counter to assign unique ids to each port. */ + static int _idCount = 0; + + /** For implementing equals and hashcode. */ + final int _id; + + /** Underlying port, when resolved. */ + SendPort _port; + + /** + * Future of the underlying port, so that we can detect when this port can be + * sent on messages. + */ + Future<SendPort> _futurePort; + + /** Pending messages (and reply ports). */ + List pending; + + _BufferingSendPort(isolateId, this._futurePort) + : super(isolateId), _id = _idCount, pending = [] { + _idCount++; + _futurePort.then((p) { + _port = p; + for (final item in pending) { + p.send(item['message'], item['replyTo']); + } + pending = null; + }); + } + + _BufferingSendPort.fromPort(isolateId, this._port) + : super(isolateId), _id = _idCount { + _idCount++; + } + + void send(var message, [SendPort replyTo]) { + if (_port != null) { + _port.send(message, replyTo); + } else { + pending.add({'message': message, 'replyTo': replyTo}); + } + } + + bool operator ==(var other) => + other is _BufferingSendPort && _id == other._id; + int get hashCode => _id; +} + +/** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ +class ReceivePortImpl implements ReceivePort { + int _id; + Function _callback; + static int _nextFreeId = 1; + + ReceivePortImpl() + : _id = _nextFreeId++ { + _globalState.currentContext.register(_id, this); + } + + void receive(void onMessage(var message, SendPort replyTo)) { + _callback = onMessage; + } + + void close() { + _callback = null; + _globalState.currentContext.unregister(_id); + } + + SendPort toSendPort() { + return new _NativeJsSendPort(this, _globalState.currentContext.id); + } +} + +/** Wait until all ports in a message are resolved. */ +_waitForPendingPorts(var message, void callback()) { + final finder = new _PendingSendPortFinder(); + finder.traverse(message); + Future.wait(finder.ports).then((_) => callback()); +} + + +/** Visitor that finds all unresolved [SendPort]s in a message. */ +class _PendingSendPortFinder extends _MessageTraverser { + List<Future<SendPort>> ports; + _PendingSendPortFinder() : super(), ports = [] { + _visited = new _JsVisitedMap(); + } + + visitPrimitive(x) {} + + visitList(List list) { + final seen = _visited[list]; + if (seen != null) return; + _visited[list] = true; + // TODO(sigmund): replace with the following: (bug #1660) + // list.forEach(_dispatch); + list.forEach((e) => _dispatch(e)); + } + + visitMap(Map map) { + final seen = _visited[map]; + if (seen != null) return; + + _visited[map] = true; + // TODO(sigmund): replace with the following: (bug #1660) + // map.values.forEach(_dispatch); + map.values.forEach((e) => _dispatch(e)); + } + + visitSendPort(SendPort port) { + if (port is _BufferingSendPort && port._port == null) { + ports.add(port._futurePort); + } + } +} + +/******************************************************** + Inserted from lib/isolate/dart2js/messages.dart + ********************************************************/ + +// Defines message visitors, serialization, and deserialization. + +/** Serialize [message] (or simulate serialization). */ +_serializeMessage(message) { + if (_globalState.needSerialization) { + return new _JsSerializer().traverse(message); + } else { + return new _JsCopier().traverse(message); + } +} + +/** Deserialize [message] (or simulate deserialization). */ +_deserializeMessage(message) { + if (_globalState.needSerialization) { + return new _JsDeserializer().deserialize(message); + } else { + // Nothing more to do. + return message; + } +} + +class _JsSerializer extends _Serializer { + + _JsSerializer() : super() { _visited = new _JsVisitedMap(); } + + visitSendPort(SendPort x) { + if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); + if (x is _WorkerSendPort) return visitWorkerSendPort(x); + if (x is _BufferingSendPort) return visitBufferingSendPort(x); + throw "Illegal underlying port $x"; + } + + visitNativeJsSendPort(_NativeJsSendPort port) { + return ['sendport', _globalState.currentManagerId, + port._isolateId, port._receivePort._id]; + } + + visitWorkerSendPort(_WorkerSendPort port) { + return ['sendport', port._workerId, port._isolateId, port._receivePortId]; + } + + visitBufferingSendPort(_BufferingSendPort port) { + if (port._port != null) { + return visitSendPort(port._port); + } else { + // TODO(floitsch): Use real exception (which one?). + throw + "internal error: must call _waitForPendingPorts to ensure all" + " ports are resolved at this point."; + } + } + +} + + +class _JsCopier extends _Copier { + + _JsCopier() : super() { _visited = new _JsVisitedMap(); } + + visitSendPort(SendPort x) { + if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); + if (x is _WorkerSendPort) return visitWorkerSendPort(x); + if (x is _BufferingSendPort) return visitBufferingSendPort(x); + throw "Illegal underlying port $p"; + } + + SendPort visitNativeJsSendPort(_NativeJsSendPort port) { + return new _NativeJsSendPort(port._receivePort, port._isolateId); + } + + SendPort visitWorkerSendPort(_WorkerSendPort port) { + return new _WorkerSendPort( + port._workerId, port._isolateId, port._receivePortId); + } + + SendPort visitBufferingSendPort(_BufferingSendPort port) { + if (port._port != null) { + return visitSendPort(port._port); + } else { + // TODO(floitsch): Use real exception (which one?). + throw + "internal error: must call _waitForPendingPorts to ensure all" + " ports are resolved at this point."; + } + } + +} + +class _JsDeserializer extends _Deserializer { + + SendPort deserializeSendPort(List x) { + int managerId = x[1]; + int isolateId = x[2]; + int receivePortId = x[3]; + // If two isolates are in the same manager, we use NativeJsSendPorts to + // deliver messages directly without using postMessage. + if (managerId == _globalState.currentManagerId) { + var isolate = _globalState.isolates[isolateId]; + if (isolate == null) return null; // Isolate has been closed. + var receivePort = isolate.lookup(receivePortId); + if (receivePort == null) return null; // Port has been closed. + return new _NativeJsSendPort(receivePort, isolateId); + } else { + return new _WorkerSendPort(managerId, isolateId, receivePortId); + } + } + +} + +class _JsVisitedMap implements _MessageTraverserVisitedMap { + List tagged; + + /** Retrieves any information stored in the native object [object]. */ + operator[](var object) { + return _getAttachedInfo(object); + } + + /** Injects some information into the native [object]. */ + void operator[]=(var object, var info) { + tagged.add(object); + _setAttachedInfo(object, info); + } + + /** Get ready to rumble. */ + void reset() { + assert(tagged == null); + tagged = new List(); + } + + /** Remove all information injected in the native objects. */ + void cleanup() { + for (int i = 0, length = tagged.length; i < length; i++) { + _clearAttachedInfo(tagged[i]); + } + tagged = null; + } + + void _clearAttachedInfo(var o) { + JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null); + } + + void _setAttachedInfo(var o, var info) { + JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info); + } + + _getAttachedInfo(var o) { + return JS("", "#['__MessageTraverser__attached_info__']", o); + } +} + +// only visible for testing purposes +// TODO(sigmund): remove once we can disable privacy for testing (bug #1882) +class TestingOnly { + static copy(x) { + return new _JsCopier().traverse(x); + } + + // only visible for testing purposes + static serialize(x) { + _Serializer serializer = new _JsSerializer(); + _Deserializer deserializer = new _JsDeserializer(); + return deserializer.deserialize(serializer.traverse(x)); + } +} + +/******************************************************** + Inserted from lib/isolate/serialization.dart + ********************************************************/ + +class _MessageTraverserVisitedMap { + + operator[](var object) => null; + void operator[]=(var object, var info) { } + + void reset() { } + void cleanup() { } + +} + +/** Abstract visitor for dart objects that can be sent as isolate messages. */ +class _MessageTraverser { + + _MessageTraverserVisitedMap _visited; + _MessageTraverser() : _visited = new _MessageTraverserVisitedMap(); + + /** Visitor's entry point. */ + traverse(var x) { + if (isPrimitive(x)) return visitPrimitive(x); + _visited.reset(); + var result; + try { + result = _dispatch(x); + } finally { + _visited.cleanup(); + } + return result; + } + + _dispatch(var x) { + if (isPrimitive(x)) return visitPrimitive(x); + if (x is List) return visitList(x); + if (x is Map) return visitMap(x); + if (x is SendPort) return visitSendPort(x); + if (x is SendPortSync) return visitSendPortSync(x); + + // Overridable fallback. + return visitObject(x); + } + + visitPrimitive(x); + visitList(List x); + visitMap(Map x); + visitSendPort(SendPort x); + visitSendPortSync(SendPortSync x); + + visitObject(Object x) { + // TODO(floitsch): make this a real exception. (which one)? + throw "Message serialization: Illegal value $x passed"; + } + + static bool isPrimitive(x) { + return (x == null) || (x is String) || (x is num) || (x is bool); + } +} + + +/** A visitor that recursively copies a message. */ +class _Copier extends _MessageTraverser { + + visitPrimitive(x) => x; + + List visitList(List list) { + List copy = _visited[list]; + if (copy != null) return copy; + + int len = list.length; + + // TODO(floitsch): we loose the generic type of the List. + copy = new List(len); + _visited[list] = copy; + for (int i = 0; i < len; i++) { + copy[i] = _dispatch(list[i]); + } + return copy; + } + + Map visitMap(Map map) { + Map copy = _visited[map]; + if (copy != null) return copy; + + // TODO(floitsch): we loose the generic type of the map. + copy = new Map(); + _visited[map] = copy; + map.forEach((key, val) { + copy[_dispatch(key)] = _dispatch(val); + }); + return copy; + } + +} + +/** Visitor that serializes a message as a JSON array. */ +class _Serializer extends _MessageTraverser { + int _nextFreeRefId = 0; + + visitPrimitive(x) => x; + + visitList(List list) { + int copyId = _visited[list]; + if (copyId != null) return ['ref', copyId]; + + int id = _nextFreeRefId++; + _visited[list] = id; + var jsArray = _serializeList(list); + // TODO(floitsch): we are losing the generic type. + return ['list', id, jsArray]; + } + + visitMap(Map map) { + int copyId = _visited[map]; + if (copyId != null) return ['ref', copyId]; + + int id = _nextFreeRefId++; + _visited[map] = id; + var keys = _serializeList(map.keys.toList()); + var values = _serializeList(map.values.toList()); + // TODO(floitsch): we are losing the generic type. + return ['map', id, keys, values]; + } + + _serializeList(List list) { + int len = list.length; + var result = new List(len); + for (int i = 0; i < len; i++) { + result[i] = _dispatch(list[i]); + } + return result; + } +} + +/** Deserializes arrays created with [_Serializer]. */ +class _Deserializer { + Map<int, dynamic> _deserialized; + + _Deserializer(); + + static bool isPrimitive(x) { + return (x == null) || (x is String) || (x is num) || (x is bool); + } + + deserialize(x) { + if (isPrimitive(x)) return x; + // TODO(floitsch): this should be new HashMap<int, var|Dynamic>() + _deserialized = new HashMap(); + return _deserializeHelper(x); + } + + _deserializeHelper(x) { + if (isPrimitive(x)) return x; + assert(x is List); + switch (x[0]) { + case 'ref': return _deserializeRef(x); + case 'list': return _deserializeList(x); + case 'map': return _deserializeMap(x); + case 'sendport': return deserializeSendPort(x); + default: return deserializeObject(x); + } + } + + _deserializeRef(List x) { + int id = x[1]; + var result = _deserialized[id]; + assert(result != null); + return result; + } + + List _deserializeList(List x) { + int id = x[1]; + // We rely on the fact that Dart-lists are directly mapped to Js-arrays. + List dartList = x[2]; + _deserialized[id] = dartList; + int len = dartList.length; + for (int i = 0; i < len; i++) { + dartList[i] = _deserializeHelper(dartList[i]); + } + return dartList; + } + + Map _deserializeMap(List x) { + Map result = new Map(); + int id = x[1]; + _deserialized[id] = result; + List keys = x[2]; + List values = x[3]; + int len = keys.length; + assert(len == values.length); + for (int i = 0; i < len; i++) { + var key = _deserializeHelper(keys[i]); + var value = _deserializeHelper(values[i]); + result[key] = value; + } + return result; + } + + deserializeSendPort(List x); + + deserializeObject(List x) { + // TODO(floitsch): Use real exception (which one?). + throw "Unexpected serialized object"; + } +} + +class TimerImpl implements Timer { + final bool _once; + bool _inEventLoop = false; + int _handle; + + TimerImpl(int milliseconds, void callback(Timer timer)) + : _once = true { + if (milliseconds == 0 && (!hasTimer() || _globalState.isWorker)) { + // This makes a dependency between the async library and the + // event loop of the isolate library. The compiler makes sure + // that the event loop is compiled if [Timer] is used. + // TODO(7907): In case of web workers, we need to use the event + // loop instead of setTimeout, to make sure the futures get executed in + // order. + _globalState.topEventLoop.enqueue(_globalState.currentContext, () { + callback(this); + }, 'timer'); + _inEventLoop = true; + } else if (hasTimer()) { + _globalState.topEventLoop.activeTimerCount++; + void internalCallback() { + callback(this); + _handle = null; + _globalState.topEventLoop.activeTimerCount--; + } + _handle = JS('int', '#.setTimeout(#, #)', + globalThis, + convertDartClosureToJS(internalCallback, 0), + milliseconds); + } else { + assert(milliseconds > 0); + throw new UnsupportedError("Timer greater than 0."); + } + } + + TimerImpl.repeating(int milliseconds, void callback(Timer timer)) + : _once = false { + if (hasTimer()) { + _globalState.topEventLoop.activeTimerCount++; + _handle = JS('int', '#.setInterval(#, #)', + globalThis, + convertDartClosureToJS(() { callback(this); }, 0), + milliseconds); + } else { + throw new UnsupportedError("Repeating timer."); + } + } + + void cancel() { + if (hasTimer()) { + if (_inEventLoop) { + throw new UnsupportedError("Timer in event loop cannot be canceled."); + } + if (_handle == null) return; + _globalState.topEventLoop.activeTimerCount--; + if (_once) { + JS('void', '#.clearTimeout(#)', globalThis, _handle); + } else { + JS('void', '#.clearInterval(#)', globalThis, _handle); + } + _handle = null; + } else { + throw new UnsupportedError("Canceling a timer."); + } + } +} + +bool hasTimer() => JS('', '#.setTimeout', globalThis) != null;
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart new file mode 100644 index 0000000..aab847a --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart
@@ -0,0 +1,34 @@ +// 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. + +// Patch file for the dart:isolate library. + +import 'dart:_isolate_helper' show IsolateNatives, + lazyPort, + ReceivePortImpl; + +patch class _Isolate { + patch static ReceivePort get port { + if (lazyPort == null) { + lazyPort = new ReceivePort(); + } + return lazyPort; + } + + patch static SendPort spawnFunction(void topLevelFunction(), + [bool UnhandledExceptionCallback(IsolateUnhandledException e)]) { + return IsolateNatives.spawnFunction(topLevelFunction); + } + + patch static SendPort spawnUri(String uri) { + return IsolateNatives.spawn(null, uri, false); + } +} + +/** Default factory for receive ports. */ +patch class ReceivePort { + patch factory ReceivePort() { + return new ReceivePortImpl(); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart new file mode 100644 index 0000000..e33ae3f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart
@@ -0,0 +1,295 @@ +// 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. + +part of _interceptors; + +/** + * The interceptor class for [List]. The compiler recognizes this + * class as an interceptor, and changes references to [:this:] to + * actually use the receiver of the method, which is generated as an extra + * argument added to each member. + */ +class JSArray<E> implements List<E> { + const JSArray(); + + void add(E value) { + checkGrowable(this, 'add'); + JS('void', r'#.push(#)', this, value); + } + + E removeAt(int index) { + if (index is !int) throw new ArgumentError(index); + if (index < 0 || index >= length) { + throw new RangeError.value(index); + } + checkGrowable(this, 'removeAt'); + return JS('var', r'#.splice(#, 1)[0]', this, index); + } + + E removeLast() { + checkGrowable(this, 'removeLast'); + if (length == 0) throw new RangeError.value(-1); + return JS('var', r'#.pop()', this); + } + + void remove(Object element) { + checkGrowable(this, 'remove'); + for (int i = 0; i < this.length; i++) { + if (this[i] == element) { + JS('var', r'#.splice(#, 1)', this, i); + return; + } + } + } + + void removeAll(Iterable elements) { + IterableMixinWorkaround.removeAllList(this, elements); + } + + void retainAll(Iterable elements) { + IterableMixinWorkaround.retainAll(this, elements); + } + + void removeMatching(bool test(E element)) { + // This could, and should, be optimized. + IterableMixinWorkaround.removeMatchingList(this, test); + } + + void retainMatching(bool test(E element)) { + IterableMixinWorkaround.removeMatchingList(this, + (E element) => !test(element)); + } + + Iterable<E> where(bool f(E element)) { + return IterableMixinWorkaround.where(this, f); + } + + Iterable expand(Iterable f(E element)) { + return IterableMixinWorkaround.expand(this, f); + } + + void addAll(Collection<E> collection) { + for (E e in collection) { + this.add(e); + } + } + + void addLast(E value) { + checkGrowable(this, 'addLast'); + JS('void', r'#.push(#)', this, value); + } + + void clear() { + length = 0; + } + + void forEach(void f(E element)) { + return IterableMixinWorkaround.forEach(this, f); + } + + Iterable map(f(E element)) { + return IterableMixinWorkaround.mapList(this, f); + } + + List mappedBy(f(E element)) { + return IterableMixinWorkaround.mappedByList(this, f); + } + + String join([String separator]) { + if (separator == null) separator = ""; + var list = new List(this.length); + for (int i = 0; i < this.length; i++) { + list[i] = "${this[i]}"; + } + return JS('String', "#.join(#)", list, separator); + } + + Iterable<E> take(int n) { + return IterableMixinWorkaround.takeList(this, n); + } + + Iterable<E> takeWhile(bool test(E value)) { + return IterableMixinWorkaround.takeWhile(this, test); + } + + Iterable<E> skip(int n) { + return IterableMixinWorkaround.skipList(this, n); + } + + Iterable<E> skipWhile(bool test(E value)) { + return IterableMixinWorkaround.skipWhile(this, test); + } + + reduce(initialValue, combine(previousValue, E element)) { + return IterableMixinWorkaround.reduce(this, initialValue, combine); + } + + E firstMatching(bool test(E value), {E orElse()}) { + return IterableMixinWorkaround.firstMatching(this, test, orElse); + } + + E lastMatching(bool test(E value), {E orElse()}) { + return IterableMixinWorkaround.lastMatchingInList(this, test, orElse); + } + + E singleMatching(bool test(E value)) { + return IterableMixinWorkaround.singleMatching(this, test); + } + + E elementAt(int index) { + return this[index]; + } + + List<E> getRange(int start, int length) { + // TODO(ngeoffray): Parameterize the return value. + if (0 == length) return []; + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + if (start < 0) throw new RangeError.value(start); + int end = start + length; + if (end > this.length) { + throw new RangeError.value(length); + } + if (length < 0) throw new ArgumentError(length); + return JS('=List', r'#.slice(#, #)', this, start, end); + } + + void insertRange(int start, int length, [E initialValue]) { + return listInsertRange(this, start, length, initialValue); + } + + E get first { + if (length > 0) return this[0]; + throw new StateError("No elements"); + } + + E get last { + if (length > 0) return this[length - 1]; + throw new StateError("No elements"); + } + + E get single { + if (length == 1) return this[0]; + if (length == 0) throw new StateError("No elements"); + throw new StateError("More than one element"); + } + + E min([int compare(E a, E b)]) => IterableMixinWorkaround.min(this, compare); + + E max([int compare(E a, E b)]) => IterableMixinWorkaround.max(this, compare); + + void removeRange(int start, int length) { + checkGrowable(this, 'removeRange'); + if (length == 0) { + return; + } + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + var receiverLength = this.length; + if (start < 0 || start >= receiverLength) { + throw new RangeError.value(start); + } + if (start + length > receiverLength) { + throw new RangeError.value(start + length); + } + Arrays.copy(this, + start + length, + this, + start, + receiverLength - length - start); + this.length = receiverLength - length; + } + + void setRange(int start, int length, List<E> from, [int startFrom = 0]) { + checkMutable(this, 'set range'); + if (length == 0) return; + checkNull(start); // TODO(ahe): This is not specified but co19 tests it. + checkNull(length); // TODO(ahe): This is not specified but co19 tests it. + checkNull(from); // TODO(ahe): This is not specified but co19 tests it. + checkNull(startFrom); // TODO(ahe): This is not specified but co19 tests it. + if (start is !int) throw new ArgumentError(start); + if (length is !int) throw new ArgumentError(length); + if (startFrom is !int) throw new ArgumentError(startFrom); + if (length < 0) throw new ArgumentError(length); + if (start < 0) throw new RangeError.value(start); + if (start + length > this.length) { + throw new RangeError.value(start + length); + } + + Arrays.copy(from, startFrom, this, start, length); + } + + bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); + + bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); + + List<E> get reversed => IterableMixinWorkaround.reversedList(this); + + void sort([int compare(E a, E b)]) { + checkMutable(this, 'sort'); + IterableMixinWorkaround.sortList(this, compare); + } + + int indexOf(E element, [int start = 0]) { + if (start is !int) throw new ArgumentError(start); + return Arrays.indexOf(this, element, start, length); + } + + int lastIndexOf(E element, [int start]) { + if (start == null) start = this.length - 1; + return Arrays.lastIndexOf(this, element, start); + } + + bool contains(E other) { + for (int i = 0; i < length; i++) { + if (other == this[i]) return true; + } + return false; + } + + bool get isEmpty => length == 0; + + String toString() => Collections.collectionToString(this); + + List<E> toList() => new List<E>.from(this); + + Set<E> toSet() => new Set<E>.from(this); + + Iterator<E> get iterator => new ListIterator<E>(this); + + int get hashCode => Primitives.objectHashCode(this); + + Type get runtimeType { + // Call getRuntimeTypeString to get the name including type arguments. + return new TypeImpl(getRuntimeTypeString(this)); + } + + int get length => JS('int', r'#.length', this); + + void set length(int newLength) { + if (newLength is !int) throw new ArgumentError(newLength); + if (newLength < 0) throw new RangeError.value(newLength); + checkGrowable(this, 'set length'); + JS('void', r'#.length = #', this, newLength); + } + + E operator [](int index) { + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + return JS('var', '#[#]', this, index); + } + + void operator []=(int index, E value) { + checkMutable(this, 'indexed set'); + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + JS('void', r'#[#] = #', this, index, value); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart new file mode 100644 index 0000000..0fbff9c --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart
@@ -0,0 +1,1513 @@ +// 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. + +library _js_helper; + +import 'dart:collection'; +import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, + JS, + JS_CALL_IN_ISOLATE, + JS_CURRENT_ISOLATE, + JS_OPERATOR_IS_PREFIX, + JS_HAS_EQUALS, + RAW_DART_FUNCTION_REF, + UNINTERCEPTED; +import 'dart:_interceptors' show getInterceptor; + +part 'constant_map.dart'; +part 'native_helper.dart'; +part 'regexp_helper.dart'; +part 'string_helper.dart'; + +bool isJsArray(var value) { + return value != null && JS('bool', r'#.constructor === Array', value); +} + +checkMutable(list, reason) { + if (JS('bool', r'!!(#.immutable$list)', list)) { + throw new UnsupportedError(reason); + } +} + +checkGrowable(list, reason) { + if (JS('bool', r'!!(#.fixed$length)', list)) { + throw new UnsupportedError(reason); + } +} + +String S(value) { + if (value is String) return value; + if ((value is num && value != 0) || value is bool) { + return JS('String', r'String(#)', value); + } + if (value == null) return 'null'; + var res = value.toString(); + if (res is !String) throw new ArgumentError(value); + return res; +} + +createInvocationMirror(name, internalName, type, arguments, argumentNames) => + new JSInvocationMirror(name, internalName, type, arguments, argumentNames); + +class JSInvocationMirror implements InvocationMirror { + static const METHOD = 0; + static const GETTER = 1; + static const SETTER = 2; + + final String memberName; + final String _internalName; + final int _kind; + final List _arguments; + final List _namedArgumentNames; + /** Map from argument name to index in _arguments. */ + Map<String,dynamic> _namedIndices = null; + + JSInvocationMirror(this.memberName, + this._internalName, + this._kind, + this._arguments, + this._namedArgumentNames); + + bool get isMethod => _kind == METHOD; + bool get isGetter => _kind == GETTER; + bool get isSetter => _kind == SETTER; + bool get isAccessor => _kind != METHOD; + + List get positionalArguments { + if (isGetter) return null; + var list = []; + var argumentCount = + _arguments.length - _namedArgumentNames.length; + for (var index = 0 ; index < argumentCount ; index++) { + list.add(_arguments[index]); + } + return list; + } + + Map<String,dynamic> get namedArguments { + if (isAccessor) return null; + var map = <String,dynamic>{}; + int namedArgumentCount = _namedArgumentNames.length; + int namedArgumentsStartIndex = _arguments.length - namedArgumentCount; + for (int i = 0; i < namedArgumentCount; i++) { + map[_namedArgumentNames[i]] = _arguments[namedArgumentsStartIndex + i]; + } + return map; + } + + static final _objectInterceptor = getInterceptor(new Object()); + invokeOn(Object object) { + var interceptor = getInterceptor(object); + var receiver = object; + var name = _internalName; + var arguments = _arguments; + if (identical(interceptor, _objectInterceptor)) { + if (!isJsArray(arguments)) arguments = new List.from(arguments); + } else { + arguments = [object]..addAll(arguments); + receiver = interceptor; + } + return JS("var", "#[#].apply(#, #)", receiver, name, receiver, arguments); + } +} + +class Primitives { + static int hashCodeSeed = 0; + + static int objectHashCode(object) { + int hash = JS('var', r'#.$identityHash', object); + if (hash == null) { + // TOOD(ahe): We should probably randomize this somehow. + hash = ++hashCodeSeed; + JS('void', r'#.$identityHash = #', object, hash); + } + return hash; + } + + /** + * This is the low-level method that is used to implement + * [print]. It is possible to override this function from JavaScript + * by defining a function in JavaScript called "dartPrint". + */ + static void printString(String string) { + if (JS('bool', r'typeof dartPrint == "function"')) { + // Support overriding print from JavaScript. + JS('void', r'dartPrint(#)', string); + return; + } + + // Inside browser. + if (JS('bool', r'typeof window == "object"')) { + // On IE, the console is only defined if dev tools is open. + if (JS('bool', r'typeof console == "object"')) { + JS('void', r'console.log(#)', string); + } + return; + } + + // Running in d8, the V8 developer shell, or in Firefox' js-shell. + if (JS('bool', r'typeof print == "function"')) { + JS('void', r'print(#)', string); + return; + } + + // This is somewhat nasty, but we don't want to drag in a bunch of + // dependencies to handle a situation that cannot happen. So we + // avoid using Dart [:throw:] and Dart [toString]. + JS('void', "throw 'Unable to print message: ' + String(#)", string); + } + + static void _throwFormatException(String string) { + throw new FormatException(string); + } + + static int parseInt(String source, + int radix, + int handleError(String source)) { + if (handleError == null) handleError = _throwFormatException; + + checkString(source); + var match = JS('=List|Null', + r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(#)', + source); + int digitsIndex = 1; + int hexIndex = 2; + int decimalIndex = 3; + int nonDecimalHexIndex = 4; + if (radix == null) { + radix = 10; + if (match != null) { + if (match[hexIndex] != null) { + // Cannot fail because we know that the digits are all hex. + return JS('num', r'parseInt(#, 16)', source); + } + if (match[decimalIndex] != null) { + // Cannot fail because we know that the digits are all decimal. + return JS('num', r'parseInt(#, 10)', source); + } + return handleError(source); + } + } else { + if (radix is! int) throw new ArgumentError("Radix is not an integer"); + if (radix < 2 || radix > 36) { + throw new RangeError("Radix $radix not in range 2..36"); + } + if (match != null) { + if (radix == 10 && match[decimalIndex] != null) { + // Cannot fail because we know that the digits are all decimal. + return JS('num', r'parseInt(#, 10)', source); + } + if (radix < 10 || match[decimalIndex] == null) { + // We know that the characters must be ASCII as otherwise the + // regexp wouldn't have matched. Calling toLowerCase is thus + // guaranteed to be a safe operation. If it wasn't ASCII, then + // "İ" would become "i", and we would accept it for radices greater + // than 18. + int maxCharCode; + if (radix <= 10) { + // Allow all digits less than the radix. For example 0, 1, 2 for + // radix 3. + // "0".charCodeAt(0) + radix - 1; + maxCharCode = 0x30 + radix - 1; + } else { + // Characters are located after the digits in ASCII. Therefore we + // only check for the character code. The regexp above made already + // sure that the string does not contain anything but digits or + // characters. + // "0".charCodeAt(0) + radix - 1; + maxCharCode = 0x61 + radix - 10 - 1; + } + String digitsPart = match[digitsIndex].toLowerCase(); + for (int i = 0; i < digitsPart.length; i++) { + if (digitsPart.charCodeAt(i) > maxCharCode) { + return handleError(source); + } + } + } + } + } + if (match == null) return handleError(source); + return JS('num', r'parseInt(#, #)', source, radix); + } + + static double parseDouble(String source, int handleError(String source)) { + checkString(source); + if (handleError == null) handleError = _throwFormatException; + // Notice that JS parseFloat accepts garbage at the end of the string. + // Accept only: + // - NaN + // - [+/-]Infinity + // - a Dart double literal + // We do not allow leading or trailing whitespace. + if (!JS('bool', + r'/^\s*(?:NaN|[+-]?(?:Infinity|' + r'(?:\.\d+|\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*$/.test(#)', + source)) { + return handleError(source); + } + var result = JS('num', r'parseFloat(#)', source); + if (result.isNaN && source != 'NaN') { + return handleError(source); + } + return result; + } + + /** [: r"$".charCodeAt(0) :] */ + static const int DOLLAR_CHAR_VALUE = 36; + + static String objectTypeName(Object object) { + String name = constructorNameFallback(object); + if (name == 'Object') { + // Try to decompile the constructor by turning it into a string + // and get the name out of that. If the decompiled name is a + // string, we use that instead of the very generic 'Object'. + var decompiled = JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]', + JS('var', r'String(#.constructor)', object)); + if (decompiled is String) name = decompiled; + } + // TODO(kasperl): If the namer gave us a fresh global name, we may + // want to remove the numeric suffix that makes it unique too. + if (identical(name.charCodeAt(0), DOLLAR_CHAR_VALUE)) name = name.substring(1); + return name; + } + + static String objectToString(Object object) { + String name = objectTypeName(object); + return "Instance of '$name'"; + } + + static List newGrowableList(length) { + return JS('=List', r'new Array(#)', length); + } + + static List newFixedList(length) { + var result = JS('=List', r'new Array(#)', length); + JS('void', r'#.fixed$length = #', result, true); + return result; + } + + static num dateNow() => JS('num', r'Date.now()'); + + static num numMicroseconds() { + if (JS('bool', 'typeof window != "undefined" && window !== null')) { + var performance = JS('var', 'window.performance'); + if (performance != null && + JS('bool', 'typeof #.webkitNow == "function"', performance)) { + return (1000 * JS('num', '#.webkitNow()', performance)).floor(); + } + } + return 1000 * dateNow(); + } + + // This is to avoid stack overflows due to very large argument arrays in + // apply(). It fixes http://dartbug.com/6919 + static String _fromCharCodeApply(List<int> array) { + String result = ""; + const kMaxApply = 500; + int end = array.length; + for (var i = 0; i < end; i += kMaxApply) { + var subarray; + if (end <= kMaxApply) { + subarray = array; + } else { + subarray = JS('=List', r'#.slice(#, #)', array, + i, i + kMaxApply < end ? i + kMaxApply : end); + } + result = JS('String', '# + String.fromCharCode.apply(#, #)', + result, null, subarray); + } + return result; + } + + static String stringFromCodePoints(codePoints) { + List<int> a = <int>[]; + for (var i in codePoints) { + if (i is !int) throw new ArgumentError(i); + if (i <= 0xffff) { + a.add(i); + } else if (i <= 0x10ffff) { + a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff))); + a.add(0xdc00 + (i & 0x3ff)); + } else { + throw new ArgumentError(i); + } + } + return _fromCharCodeApply(a); + } + + static String stringFromCharCodes(charCodes) { + for (var i in charCodes) { + if (i is !int) throw new ArgumentError(i); + if (i < 0) throw new ArgumentError(i); + if (i > 0xffff) return stringFromCodePoints(charCodes); + } + return _fromCharCodeApply(charCodes); + } + + static String getTimeZoneName(receiver) { + // When calling toString on a Date it will emit the timezone in parenthesis. + // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)". + // We extract this name using a regexp. + var d = lazyAsJsDate(receiver); + return JS('String', r'/\((.*)\)/.exec(#.toString())[1]', d); + } + + static int getTimeZoneOffsetInMinutes(receiver) { + // Note that JS and Dart disagree on the sign of the offset. + return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver)); + } + + static valueFromDecomposedDate(years, month, day, hours, minutes, seconds, + milliseconds, isUtc) { + final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000; + checkInt(years); + checkInt(month); + checkInt(day); + checkInt(hours); + checkInt(minutes); + checkInt(seconds); + checkInt(milliseconds); + checkBool(isUtc); + var jsMonth = month - 1; + var value; + if (isUtc) { + value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)', + years, jsMonth, day, hours, minutes, seconds, milliseconds); + } else { + value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()', + years, jsMonth, day, hours, minutes, seconds, milliseconds); + } + if (value.isNaN || + value < -MAX_MILLISECONDS_SINCE_EPOCH || + value > MAX_MILLISECONDS_SINCE_EPOCH) { + throw new ArgumentError(); + } + if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc); + return value; + } + + static patchUpY2K(value, years, isUtc) { + var date = JS('', r'new Date(#)', value); + if (isUtc) { + JS('num', r'#.setUTCFullYear(#)', date, years); + } else { + JS('num', r'#.setFullYear(#)', date, years); + } + return JS('num', r'#.valueOf()', date); + } + + // Lazily keep a JS Date stored in the JS object. + static lazyAsJsDate(receiver) { + if (JS('bool', r'#.date === (void 0)', receiver)) { + JS('void', r'#.date = new Date(#)', receiver, + receiver.millisecondsSinceEpoch); + } + return JS('var', r'#.date', receiver); + } + + // The getters for date and time parts below add a positive integer to ensure + // that the result is really an integer, because the JavaScript implementation + // may return -0.0 instead of 0. + + static getYear(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver)); + } + + static getMonth(receiver) { + return (receiver.isUtc) + ? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver)) + : JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver)); + } + + static getDay(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver)); + } + + static getHours(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver)); + } + + static getMinutes(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver)); + } + + static getSeconds(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver)); + } + + static getMilliseconds(receiver) { + return (receiver.isUtc) + ? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver)) + : JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver)); + } + + static getWeekday(receiver) { + int weekday = (receiver.isUtc) + ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver)) + : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver)); + // Adjust by one because JS weeks start on Sunday. + return (weekday + 6) % 7 + 1; + } + + static valueFromDateString(str) { + if (str is !String) throw new ArgumentError(str); + var value = JS('num', r'Date.parse(#)', str); + if (value.isNaN) throw new ArgumentError(str); + return value; + } + + static getProperty(object, key) { + if (object == null || object is bool || object is num || object is String) { + throw new ArgumentError(object); + } + return JS('var', '#[#]', object, key); + } + + static void setProperty(object, key, value) { + if (object == null || object is bool || object is num || object is String) { + throw new ArgumentError(object); + } + JS('void', '#[#] = #', object, key, value); + } + + static applyFunction(Function function, + List positionalArguments, + Map<String, dynamic> namedArguments) { + int argumentCount = 0; + StringBuffer buffer = new StringBuffer(); + List arguments = []; + + if (positionalArguments != null) { + argumentCount += positionalArguments.length; + arguments.addAll(positionalArguments); + } + + // Sort the named arguments to get the right selector name and + // arguments order. + if (namedArguments != null && !namedArguments.isEmpty) { + // Call new List.from to make sure we get a JavaScript array. + List<String> listOfNamedArguments = + new List<String>.from(namedArguments.keys); + argumentCount += namedArguments.length; + // We're sorting on strings, and the behavior is the same between + // Dart string sort and JS string sort. To avoid needing the Dart + // sort implementation, we use the JavaScript one instead. + JS('void', '#.sort()', listOfNamedArguments); + listOfNamedArguments.forEach((String name) { + buffer.add('\$$name'); + arguments.add(namedArguments[name]); + }); + } + + String selectorName = 'call\$$argumentCount$buffer'; + var jsFunction = JS('var', '#[#]', function, selectorName); + if (jsFunction == null) { + throw new NoSuchMethodError(function, selectorName, arguments, {}); + } + // We bound 'this' to [function] because of how we compile + // closures: escaped local variables are stored and accessed through + // [function]. + return JS('var', '#.apply(#, #)', jsFunction, function, arguments); + } + + static getConstructor(String className) { + // TODO(ahe): How to safely access $? + return JS('var', r'$[#]', className); + } + + static bool identicalImplementation(a, b) { + return JS('bool', '# == null', a) + ? JS('bool', '# == null', b) + : JS('bool', '# === #', a, b); + } +} + +/** + * Called by generated code to throw an illegal-argument exception, + * for example, if a non-integer index is given to an optimized + * indexed access. + */ +iae(argument) { + throw new ArgumentError(argument); +} + +/** + * Called by generated code to throw an index-out-of-range exception, + * for example, if a bounds check fails in an optimized indexed + * access. + */ +ioore(index) { + throw new RangeError.value(index); +} + +listInsertRange(receiver, start, length, initialValue) { + if (length == 0) { + return; + } + if (length is !int) throw new ArgumentError(length); + if (length < 0) throw new ArgumentError(length); + if (start is !int) throw new ArgumentError(start); + + var receiverLength = JS('num', r'#.length', receiver); + if (start < 0 || start > receiverLength) { + throw new RangeError.value(start); + } + receiver.length = receiverLength + length; + Arrays.copy(receiver, + start, + receiver, + start + length, + receiverLength - start); + if (initialValue != null) { + for (int i = start; i < start + length; i++) { + receiver[i] = initialValue; + } + } + receiver.length = receiverLength + length; +} + +stringLastIndexOfUnchecked(receiver, element, start) + => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start); + + +checkNull(object) { + if (object == null) throw new ArgumentError(null); + return object; +} + +checkNum(value) { + if (value is !num) { + throw new ArgumentError(value); + } + return value; +} + +checkInt(value) { + if (value is !int) { + throw new ArgumentError(value); + } + return value; +} + +checkBool(value) { + if (value is !bool) { + throw new ArgumentError(value); + } + return value; +} + +checkString(value) { + if (value is !String) { + throw new ArgumentError(value); + } + return value; +} + +class MathNatives { + static double sqrt(num value) + => JS('double', r'Math.sqrt(#)', checkNum(value)); + + static double sin(num value) + => JS('double', r'Math.sin(#)', checkNum(value)); + + static double cos(num value) + => JS('double', r'Math.cos(#)', checkNum(value)); + + static double tan(num value) + => JS('double', r'Math.tan(#)', checkNum(value)); + + static double acos(num value) + => JS('double', r'Math.acos(#)', checkNum(value)); + + static double asin(num value) + => JS('double', r'Math.asin(#)', checkNum(value)); + + static double atan(num value) + => JS('double', r'Math.atan(#)', checkNum(value)); + + static double atan2(num a, num b) + => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b)); + + static double exp(num value) + => JS('double', r'Math.exp(#)', checkNum(value)); + + static double log(num value) + => JS('double', r'Math.log(#)', checkNum(value)); + + static num pow(num value, num exponent) { + checkNum(value); + checkNum(exponent); + return JS('num', r'Math.pow(#, #)', value, exponent); + } + + static double random() => JS('double', r'Math.random()'); +} + +/** + * Wrap the given Dart object and record a stack trace. + * + * The code in [unwrapException] deals with getting the original Dart + * object out of the wrapper again. + */ +$throw(ex) { + if (ex == null) ex = const NullThrownError(); + var wrapper = new DartError(ex); + + if (JS('bool', '!!Error.captureStackTrace')) { + // Use V8 API for recording a "fast" stack trace (this installs a + // "stack" property getter on [wrapper]). + JS('void', r'Error.captureStackTrace(#, #)', + wrapper, RAW_DART_FUNCTION_REF($throw)); + } else { + // Otherwise, produce a stack trace and record it in the wrapper. + // This is a slower way to create a stack trace which works on + // some browsers, but may simply evaluate to null. + String stackTrace = JS('', 'new Error().stack'); + JS('void', '#.stack = #', wrapper, stackTrace); + } + return wrapper; +} + +/** + * Wrapper class for throwing exceptions. + */ +class DartError { + /// The Dart object (or primitive JavaScript value) which was thrown is + /// attached to this object as a field named 'dartException'. We do this + /// only in raw JS so that we can use the 'in' operator and so that the + /// minifier does not rename the field. Therefore it is not declared as a + /// real field. + + DartError(var dartException) { + JS('void', '#.dartException = #', this, dartException); + // Install a toString method that the JavaScript system will call + // to format uncaught exceptions. + JS('void', '#.toString = #', this, DART_CLOSURE_TO_JS(toStringWrapper)); + } + + /** + * V8/Chrome installs a property getter, "stack", when calling + * Error.captureStackTrace (see [$throw]). In [$throw], we make sure + * that this property is always set. + */ + String get stack => JS('', '#.stack', this); + + /** + * This method can be invoked by calling toString from + * JavaScript. See the constructor of this class. + * + * We only expect this method to be called (indirectly) by the + * browser when an uncaught exception occurs. Instance of this class + * should never escape into Dart code (except for [$throw] above). + */ + String toString() { + // If Error.captureStackTrace is available, accessing stack from + // this method would cause recursion because the stack property + // (on this object) is actually a getter which calls toString on + // this object (via the wrapper installed in this class' + // constructor). Fortunately, both Chrome and d8 prints the stack + // trace and Chrome even applies source maps to the stack + // trace. Remeber, this method is only ever invoked by the browser + // when an uncaught exception occurs. + var dartException = JS('var', r'#.dartException', this); + if (JS('bool', '!!Error.captureStackTrace') || (stack == null)) { + return dartException.toString(); + } else { + return '$dartException\n$stack'; + } + } + + /** + * This method is installed as JavaScript toString method on + * [DartError]. So JavaScript 'this' binds to an instance of + * DartError. + */ + static toStringWrapper() => JS('', r'this').toString(); +} + +makeLiteralListConst(list) { + JS('bool', r'#.immutable$list = #', list, true); + JS('bool', r'#.fixed$length = #', list, true); + return list; +} + +throwRuntimeError(message) { + throw new RuntimeError(message); +} + +/** + * The SSA builder generates a call to this method when a malformed type is used + * in a subtype test. + */ +throwMalformedSubtypeError(value, type, reasons) { + throw new TypeErrorImplementation.malformedSubtype(value, type, reasons); +} + +throwAbstractClassInstantiationError(className) { + throw new AbstractClassInstantiationError(className); +} + +/** + * Called from catch blocks in generated code to extract the Dart + * exception from the thrown value. The thrown value may have been + * created by [$throw] or it may be a 'native' JS exception. + * + * Some native exceptions are mapped to new Dart instances, others are + * returned unmodified. + */ +unwrapException(ex) { + // Note that we are checking if the object has the property. If it + // has, it could be set to null if the thrown value is null. + if (JS('bool', r'"dartException" in #', ex)) { + return JS('', r'#.dartException', ex); + } + + // Grab hold of the exception message. This field is available on + // all supported browsers. + var message = JS('var', r'#.message', ex); + + if (JS('bool', r'# instanceof TypeError', ex)) { + // The type and arguments fields are Chrome specific but they + // allow us to get very detailed information about what kind of + // exception occurred. + var type = JS('var', r'#.type', ex); + var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex); + if (contains(message, 'JSNull') || + type == 'property_not_function' || + type == 'called_non_callable' || + type == 'non_object_property_call' || + type == 'non_object_property_load') { + return new NoSuchMethodError(null, name, [], {}); + } else if (type == 'undefined_method') { + return new NoSuchMethodError('', name, [], {}); + } + + var ieErrorCode = JS('int', '#.number & 0xffff', ex); + var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex); + // If we cannot use [type] to determine what kind of exception + // we're dealing with we fall back on looking at the exception + // message if it is available and a string. + if (message is String) { + if (message.endsWith('is null') || + message.endsWith('is undefined') || + message.endsWith('is null or undefined') || + message.endsWith('of undefined') || + message.endsWith('of null')) { + return new NoSuchMethodError(null, '<unknown>', [], {}); + } else if (contains(message, ' has no method ') || + contains(message, ' is not a function') || + (ieErrorCode == 438 && ieFacilityNumber == 10)) { + // Examples: + // x.foo is not a function + // 'undefined' is not a function (evaluating 'x.foo(1,2,3)') + // Object doesn't support property or method 'foo' which sets the error + // code 438 in IE. + // TODO(kasperl): Compute the right name if possible. + return new NoSuchMethodError('', '<unknown>', [], {}); + } + } + + // If we cannot determine what kind of error this is, we fall back + // to reporting this as a generic exception. It's probably better + // than nothing. + return new Exception(message is String ? message : ''); + } + + if (JS('bool', r'# instanceof RangeError', ex)) { + if (message is String && contains(message, 'call stack')) { + return new StackOverflowError(); + } + + // In general, a RangeError is thrown when trying to pass a number + // as an argument to a function that does not allow a range that + // includes that number. + return new ArgumentError(); + } + + // Check for the Firefox specific stack overflow signal. + if (JS('bool', + r"typeof InternalError == 'function' && # instanceof InternalError", + ex)) { + if (message is String && message == 'too much recursion') { + return new StackOverflowError(); + } + } + + // Just return the exception. We should not wrap it because in case + // the exception comes from the DOM, it is a JavaScript + // object backed by a native Dart class. + return ex; +} + +/** + * Called by generated code to fetch the stack trace from an + * exception. + */ +StackTrace getTraceFromException(exception) { + return new StackTrace(JS("var", r"#.stack", exception)); +} + +class StackTrace { + var stack; + StackTrace(this.stack); + String toString() => stack != null ? stack : ''; +} + + +/** + * Called by generated code to build a map literal. [keyValuePairs] is + * a list of key, value, key, value, ..., etc. + */ +makeLiteralMap(List keyValuePairs) { + Iterator iterator = keyValuePairs.iterator; + Map result = new LinkedHashMap(); + while (iterator.moveNext()) { + String key = iterator.current; + iterator.moveNext(); + var value = iterator.current; + result[key] = value; + } + return result; +} + +invokeClosure(Function closure, + var isolate, + int numberOfArguments, + var arg1, + var arg2) { + if (numberOfArguments == 0) { + return JS_CALL_IN_ISOLATE(isolate, () => closure()); + } else if (numberOfArguments == 1) { + return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1)); + } else if (numberOfArguments == 2) { + return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2)); + } else { + throw new Exception( + 'Unsupported number of arguments for wrapped closure'); + } +} + +/** + * Called by generated code to convert a Dart closure to a JS + * closure when the Dart closure is passed to the DOM. + */ +convertDartClosureToJS(closure, int arity) { + if (closure == null) return null; + var function = JS('var', r'#.$identity', closure); + if (JS('bool', r'!!#', function)) return function; + // By fetching the current isolate before creating the JavaScript + // function, we prevent the compiler from inlining its use in + // the JavaScript function below (the compiler generates code for + // fetching the isolate before creating the JavaScript function). + // If it was inlined, the JavaScript function would not get the + // current isolate, but the one that is active when the callback + // executes. + var currentIsolate = JS_CURRENT_ISOLATE(); + + // We use $0 and $1 to not clash with variable names used by the + // compiler and/or minifier. + function = JS("var", + r"""function($0, $1) { return #(#, #, #, $0, $1); }""", + DART_CLOSURE_TO_JS(invokeClosure), + closure, + JS_CURRENT_ISOLATE(), + arity); + + JS('void', r'#.$identity = #', closure, function); + return function; +} + +/** + * Super class for Dart closures. + */ +class Closure implements Function { + String toString() => "Closure"; +} + +bool jsHasOwnProperty(var jsObject, String property) { + return JS('bool', r'#.hasOwnProperty(#)', jsObject, property); +} + +jsPropertyAccess(var jsObject, String property) { + return JS('var', r'#[#]', jsObject, property); +} + +/** + * Called at the end of unaborted switch cases to get the singleton + * FallThroughError exception that will be thrown. + */ +getFallThroughError() => const FallThroughErrorImplementation(); + +/** + * Represents the type Dynamic. The compiler treats this specially. + */ +abstract class Dynamic_ { +} + +/** + * A metadata annotation describing the types instantiated by a native element. + * + * The annotation is valid on a native method and a field of a native class. + * + * By default, a field of a native class is seen as an instantiation point for + * all native classes that are a subtype of the field's type, and a native + * method is seen as an instantiation point fo all native classes that are a + * subtype of the method's return type, or the argument types of the declared + * type of the method's callback parameter. + * + * An @[Creates] annotation overrides the default set of instantiated types. If + * one or more @[Creates] annotations are present, the type of the native + * element is ignored, and the union of @[Creates] annotations is used instead. + * The names in the strings are resolved and the program will fail to compile + * with dart2js if they do not name types. + * + * The argument to [Creates] is a string. The string is parsed as the names of + * one or more types, separated by vertical bars `|`. There are some special + * names: + * + * * `=List`. This means 'exactly List', which is the JavaScript Array + * implementation of [List] and no other implementation. + * + * * `=Object`. This means 'exactly Object', which is a plain JavaScript object + * with properties and none of the subtypes of Object. + * + * Example: we may know that a method always returns a specific implementation: + * + * @Creates('_NodeList') + * List<Node> getElementsByTagName(String tag) native; + * + * Useful trick: A method can be marked as not instantiating any native classes + * with the annotation `@Creates('Null')`. This is useful for fields on native + * classes that are used only in Dart code. + * + * @Creates('Null') + * var _cachedFoo; + */ +class Creates { + final String types; + const Creates(this.types); +} + +/** + * A metadata annotation describing the types returned or yielded by a native + * element. + * + * The annotation is valid on a native method and a field of a native class. + * + * By default, a native method or field is seen as returning or yielding all + * subtypes if the method return type or field type. This annotation allows a + * more precise set of types to be specified. + * + * See [Creates] for the syntax of the argument. + * + * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys. + * + * @Returns('String|num|=List') + * dynamic key; + * + * // Equivalent: + * @Returns('String') @Returns('num') @Returns('=List') + * dynamic key; + */ +class Returns { + final String types; + const Returns(this.types); +} + +/** + * A metadata annotation placed on native methods and fields of native classes + * to specify the JavaScript name. + * + * This example declares a Dart field + getter + setter called `$dom_title` that + * corresponds to the JavaScript property `title`. + * + * class Docmument native "*Foo" { + * @JSName('title') + * String $dom_title; + * } + */ +class JSName { + final String name; + const JSName(this.name); +} + +/** + * Represents the type of Null. The compiler treats this specially. + * TODO(lrn): Null should be defined in core. It's a class, like int. + * It just happens to act differently in assignability tests and, + * like int, can't be extended or implemented. + */ +class Null { + factory Null() { + throw new UnsupportedError('new Null()'); + } +} + +setRuntimeTypeInfo(target, typeInfo) { + assert(typeInfo == null || isJsArray(typeInfo)); + // We have to check for null because factories may return null. + if (target != null) JS('var', r'#.$builtinTypeInfo = #', target, typeInfo); +} + +getRuntimeTypeInfo(target) { + if (target == null) return null; + var res = JS('var', r'#.$builtinTypeInfo', target); + // If the object does not have runtime type information, return an + // empty literal, to avoid null checks. + // TODO(ngeoffray): Make the object a top-level field to avoid + // allocating a new object every single time. + return (res == null) ? JS('var', '{}') : res; +} + +/** + * The following methods are called by the runtime to implement + * checked mode and casts. We specialize each primitive type (eg int, bool), and + * use the compiler's convention to do is-checks on regular objects. + */ +boolConversionCheck(value) { + boolTypeCheck(value); + assert(value != null); + return value; +} + +stringTypeCheck(value) { + if (value == null) return value; + if (value is String) return value; + throw new TypeErrorImplementation(value, 'String'); +} + +stringTypeCast(value) { + if (value is String || value == null) return value; + // TODO(lrn): When reified types are available, pass value.class and String. + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'String'); +} + +doubleTypeCheck(value) { + if (value == null) return value; + if (value is double) return value; + throw new TypeErrorImplementation(value, 'double'); +} + +doubleTypeCast(value) { + if (value is double || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'double'); +} + +numTypeCheck(value) { + if (value == null) return value; + if (value is num) return value; + throw new TypeErrorImplementation(value, 'num'); +} + +numTypeCast(value) { + if (value is num || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'num'); +} + +boolTypeCheck(value) { + if (value == null) return value; + if (value is bool) return value; + throw new TypeErrorImplementation(value, 'bool'); +} + +boolTypeCast(value) { + if (value is bool || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'bool'); +} + +functionTypeCheck(value) { + if (value == null) return value; + if (value is Function) return value; + throw new TypeErrorImplementation(value, 'Function'); +} + +functionTypeCast(value) { + if (value is Function || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'Function'); +} + +intTypeCheck(value) { + if (value == null) return value; + if (value is int) return value; + throw new TypeErrorImplementation(value, 'int'); +} + +intTypeCast(value) { + if (value is int || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'int'); +} + +void propertyTypeError(value, property) { + // Cuts the property name to the class name. + String name = property.substring(3, property.length); + throw new TypeErrorImplementation(value, name); +} + +void propertyTypeCastError(value, property) { + // Cuts the property name to the class name. + String actualType = Primitives.objectTypeName(value); + String expectedType = property.substring(3, property.length); + throw new CastErrorImplementation(actualType, expectedType); +} + +/** + * For types that are not supertypes of native (eg DOM) types, + * we emit a simple property check to check that an object implements + * that type. + */ +propertyTypeCheck(value, property) { + if (value == null) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +/** + * For types that are not supertypes of native (eg DOM) types, + * we emit a simple property check to check that an object implements + * that type. + */ +propertyTypeCast(value, property) { + if (value == null || JS('bool', '!!#[#]', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * For types that are supertypes of native (eg DOM) types, we emit a + * call because we cannot add a JS property to their prototype at load + * time. + */ +callTypeCheck(value, property) { + if (value == null) return value; + if ((identical(JS('String', 'typeof #', value), 'object')) + && JS('bool', '#[#]()', value, property)) { + return value; + } + propertyTypeError(value, property); +} + +/** + * For types that are supertypes of native (eg DOM) types, we emit a + * call because we cannot add a JS property to their prototype at load + * time. + */ +callTypeCast(value, property) { + if (value == null + || ((JS('bool', 'typeof # === "object"', value)) + && JS('bool', '#[#]()', value, property))) { + return value; + } + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for num and String and their + * supertype since [value] can be a JS primitive. + */ +numberOrStringSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +numberOrStringSuperTypeCast(value, property) { + if (value is String) return value; + if (value is num) return value; + return propertyTypeCast(value, property); +} + +numberOrStringSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +numberOrStringSuperNativeTypeCast(value, property) { + if (value == null) return value; + if (value is String) return value; + if (value is num) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for String and its supertype + * since [value] can be a JS primitive. + */ +stringSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +stringSuperTypeCast(value, property) { + if (value is String) return value; + return propertyTypeCast(value, property); +} + +stringSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is String) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +stringSuperNativeTypeCast(value, property) { + if (value is String || value == null) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +/** + * Specialization of the type check for List and its supertypes, + * since [value] can be a JS array. + */ +listTypeCheck(value) { + if (value == null) return value; + if (value is List) return value; + throw new TypeErrorImplementation(value, 'List'); +} + +listTypeCast(value) { + if (value is List || value == null) return value; + throw new CastErrorImplementation( + Primitives.objectTypeName(value), 'List'); +} + +listSuperTypeCheck(value, property) { + if (value == null) return value; + if (value is List) return value; + if (JS('bool', '!!#[#]', value, property)) return value; + propertyTypeError(value, property); +} + +listSuperTypeCast(value, property) { + if (value is List) return value; + return propertyTypeCast(value, property); +} + +listSuperNativeTypeCheck(value, property) { + if (value == null) return value; + if (value is List) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeError(value, property); +} + +listSuperNativeTypeCast(value, property) { + if (value is List || value == null) return value; + if (JS('bool', '#[#]()', value, property)) return value; + propertyTypeCastError(value, property); +} + +voidTypeCheck(value) { + if (value == null) return value; + throw new TypeErrorImplementation(value, 'void'); +} + +malformedTypeCheck(value, type, reasons) { + if (value == null) return value; + throwMalformedSubtypeError(value, type, reasons); +} + +/** + * Special interface recognized by the compiler and implemented by DOM + * objects that support integer indexing. This interface is not + * visible to anyone, and is only injected into special libraries. + */ +abstract class JavaScriptIndexingBehavior { +} + +// TODO(lrn): These exceptions should be implemented in core. +// When they are, remove the 'Implementation' here. + +/** Thrown by type assertions that fail. */ +class TypeErrorImplementation implements TypeError { + final String message; + + /** + * Normal type error caused by a failed subtype test. + */ + TypeErrorImplementation(Object value, String type) + : message = "type '${Primitives.objectTypeName(value)}' is not a subtype " + "of type '$type'"; + + /** + * Type error caused by a subtype test on a malformed type. + */ + TypeErrorImplementation.malformedSubtype(Object value, + String type, String reasons) + : message = "type '${Primitives.objectTypeName(value)}' is not a subtype " + "of type '$type' because '$type' is malformed: $reasons."; + + String toString() => message; +} + +/** Thrown by the 'as' operator if the cast isn't valid. */ +class CastErrorImplementation implements CastError { + // TODO(lrn): Rename to CastError (and move implementation into core). + // TODO(lrn): Change actualType and expectedType to "Type" when reified + // types are available. + final Object actualType; + final Object expectedType; + + CastErrorImplementation(this.actualType, this.expectedType); + + String toString() { + return "CastError: Casting value of type $actualType to" + " incompatible type $expectedType"; + } +} + +class FallThroughErrorImplementation implements FallThroughError { + const FallThroughErrorImplementation(); + String toString() => "Switch case fall-through."; +} + +/** + * Helper function for implementing asserts. The compiler treats this specially. + */ +void assertHelper(condition) { + if (condition is Function) condition = condition(); + if (condition is !bool) { + throw new TypeErrorImplementation(condition, 'bool'); + } + // Compare to true to avoid boolean conversion check in checked + // mode. + if (!identical(condition, true)) throw new AssertionError(); +} + +/** + * Called by generated code when a method that must be statically + * resolved cannot be found. + */ +void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) { + throw new NoSuchMethodError(obj, name, arguments, const {}, + expectedArgumentNames); +} + +/** + * Called by generated code when a static field's initializer references the + * field that is currently being initialized. + */ +void throwCyclicInit(String staticName) { + throw new RuntimeError("Cyclic initialization for static $staticName"); +} + +class TypeImpl implements Type { + final String typeName; + TypeImpl(this.typeName); + toString() => typeName; + int get hashCode => typeName.hashCode; + bool operator ==(other) { + if (other is !TypeImpl) return false; + return typeName == other.typeName; + } +} + +String getClassName(var object) { + return JS('String', r'#.constructor.builtin$cls', object); +} + +String getTypeArgumentAsString(List runtimeType) { + String className = getConstructorName(runtimeType[0]); + if (runtimeType.length == 1) return className; + return '$className<${joinArguments(runtimeType, 1)}>'; +} + +String getConstructorName(type) => JS('String', r'#.builtin$cls', type); + +String runtimeTypeToString(type) { + if (type == null) { + return 'dynamic'; + } else if (isJsArray(type)) { + // A list representing a type with arguments. + return getTypeArgumentAsString(type); + } else { + // A reference to the constructor. + return getConstructorName(type); + } +} + +String joinArguments(var types, int startIndex) { + bool firstArgument = true; + StringBuffer buffer = new StringBuffer(); + for (int index = startIndex; index < types.length; index++) { + if (firstArgument) { + firstArgument = false; + } else { + buffer. add(', '); + } + var argument = types[index]; + buffer.add(runtimeTypeToString(argument)); + } + return buffer.toString(); +} + +String getRuntimeTypeString(var object) { + String className = isJsArray(object) ? 'List' : getClassName(object); + var typeInfo = JS('var', r'#.$builtinTypeInfo', object); + if (typeInfo == null) return className; + return "$className<${joinArguments(typeInfo, 0)}>"; +} + +/** + * Check whether the type represented by [s] is a subtype of the type + * represented by [t]. + * + * Type representations can be: + * 1) a JavaScript constructor for a class C: the represented type is the raw + * type C. + * 2) a JavaScript object: this represents a class for which there is no + * JavaScript constructor, because it is only used in type arguments or it + * is native. The represented type is the raw type of this class. + * 3) a JavaScript array: the first entry is of type 1 or 2 and identifies the + * class of the type and the rest of the array are the type arguments. + * 4) [:null:]: the dynamic type. + */ +bool isSubtype(var s, var t) { + // If either type is dynamic, [s] is a subtype of [t]. + if (JS('bool', '# == null', s) || JS('bool', '# == null', t)) return true; + // Subtyping is reflexive. + if (JS('bool', '# === #', s, t)) return true; + // Get the object describing the class and check for the subtyping flag + // constructed from the type of [t]. + var typeOfS = isJsArray(s) ? s[0] : s; + var typeOfT = isJsArray(t) ? t[0] : t; + var test = '${JS_OPERATOR_IS_PREFIX()}${runtimeTypeToString(typeOfT)}'; + if (JS('var', r'#[#]', typeOfS, test) == null) return false; + // The class of [s] is a subclass of the class of [t]. If either of the types + // is raw, [s] is a subtype of [t]. + if (!isJsArray(s) || !isJsArray(t)) return true; + // Recursively check the type arguments. + int len = s.length; + if (len != t.length) return false; + for (int i = 1; i < len; i++) { + if (!isSubtype(s[i], t[i])) { + return false; + } + } + return true; +} + +createRuntimeType(String name) => new TypeImpl(name);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart new file mode 100644 index 0000000..fa5677f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart
@@ -0,0 +1,272 @@ +// 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. + +part of _interceptors; + +/** + * The super interceptor class for [JSInt] and [JSDouble]. The compiler + * recognizes this class as an interceptor, and changes references to + * [:this:] to actually use the receiver of the method, which is + * generated as an extra argument added to each member. + */ +class JSNumber implements num { + const JSNumber(); + + int compareTo(num b) { + if (b is! num) throw new ArgumentError(b); + if (this < b) { + return -1; + } else if (this > b) { + return 1; + } else if (this == b) { + if (this == 0) { + bool bIsNegative = b.isNegative; + if (isNegative == bIsNegative) return 0; + if (isNegative) return -1; + return 1; + } + return 0; + } else if (isNaN) { + if (b.isNaN) { + return 0; + } + return 1; + } else { + return -1; + } + } + + bool get isNegative => (this == 0) ? (1 / this) < 0 : this < 0; + + bool get isNaN => JS('bool', r'isNaN(#)', this); + + num remainder(num b) { + checkNull(b); // TODO(ngeoffray): This is not specified but co19 tests it. + if (b is! num) throw new ArgumentError(b); + return JS('num', r'# % #', this, b); + } + + num abs() => JS('num', r'Math.abs(#)', this); + + int toInt() { + if (isNaN) throw new UnsupportedError('NaN'); + if (isInfinite) throw new UnsupportedError('Infinity'); + num truncated = truncate(); + return JS('bool', r'# == -0.0', truncated) ? 0 : truncated; + } + + num ceil() => JS('num', r'Math.ceil(#)', this); + + num floor() => JS('num', r'Math.floor(#)', this); + + bool get isInfinite { + return JS('bool', r'# == Infinity', this) + || JS('bool', r'# == -Infinity', this); + } + + num round() { + if (this < 0) { + return JS('num', r'-Math.round(-#)', this); + } else { + return JS('num', r'Math.round(#)', this); + } + } + + num clamp(lowerLimit, upperLimit) { + if (lowerLimit is! num) throw new ArgumentError(lowerLimit); + if (upperLimit is! num) throw new ArgumentError(upperLimit); + if (lowerLimit.compareTo(upperLimit) > 0) { + throw new ArgumentError(lowerLimit); + } + if (this.compareTo(lowerLimit) < 0) return lowerLimit; + if (this.compareTo(upperLimit) > 0) return upperLimit; + return this; + } + + double toDouble() => this; + + num truncate() => this < 0 ? ceil() : floor(); + + String toStringAsFixed(int fractionDigits) { + checkNum(fractionDigits); + // TODO(floitsch): fractionDigits must be an integer. + if (fractionDigits < 0 || fractionDigits > 20) { + throw new RangeError(fractionDigits); + } + String result = JS('String', r'#.toFixed(#)', this, fractionDigits); + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toStringAsExponential([int fractionDigits]) { + String result; + if (fractionDigits != null) { + // TODO(floitsch): fractionDigits must be an integer. + checkNum(fractionDigits); + if (fractionDigits < 0 || fractionDigits > 20) { + throw new RangeError(fractionDigits); + } + result = JS('String', r'#.toExponential(#)', this, fractionDigits); + } else { + result = JS('String', r'#.toExponential()', this); + } + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toStringAsPrecision(int precision) { + // TODO(floitsch): precision must be an integer. + checkNum(precision); + if (precision < 1 || precision > 21) { + throw new RangeError(precision); + } + String result = JS('String', r'#.toPrecision(#)', + this, precision); + if (this == 0 && isNegative) return "-$result"; + return result; + } + + String toRadixString(int radix) { + checkNum(radix); + if (radix < 2 || radix > 36) throw new RangeError(radix); + return JS('String', r'#.toString(#)', this, radix); + } + + // Note: if you change this, also change the function [S]. + String toString() { + if (this == 0 && JS('bool', '(1 / #) < 0', this)) { + return '-0.0'; + } else { + return JS('String', r'String(#)', this); + } + } + + int get hashCode => JS('int', '# & 0x1FFFFFFF', this); + + num operator -() => JS('num', r'-#', this); + + num operator +(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# + #', this, other); + } + + num operator -(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# - #', this, other); + } + + num operator /(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# / #', this, other); + } + + num operator *(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# * #', this, other); + } + + num operator %(num other) { + if (other is !num) throw new ArgumentError(other); + // Euclidean Modulo. + num result = JS('num', r'# % #', this, other); + if (result == 0) return 0; // Make sure we don't return -0.0. + if (result > 0) return result; + if (JS('num', '#', other) < 0) { + return result - JS('num', '#', other); + } else { + return result + JS('num', '#', other); + } + } + + num operator ~/(num other) { + if (other is !num) throw new ArgumentError(other); + return (JS('num', r'# / #', this, other)).truncate(); + } + + // TODO(ngeoffray): Move the bit operations below to [JSInt] and + // make them take an int. Because this will make operations slower, + // we define these methods on number for now but we need to decide + // the grain at which we do the type checks. + + num operator <<(num other) { + if (other is !num) throw new ArgumentError(other); + if (JS('num', '#', other) < 0) throw new ArgumentError(other); + // JavaScript only looks at the last 5 bits of the shift-amount. Shifting + // by 33 is hence equivalent to a shift by 1. + if (JS('bool', r'# > 31', other)) return 0; + return JS('num', r'(# << #) >>> 0', this, other); + } + + num operator >>(num other) { + if (other is !num) throw new ArgumentError(other); + if (JS('num', '#', other) < 0) throw new ArgumentError(other); + if (JS('num', '#', this) > 0) { + // JavaScript only looks at the last 5 bits of the shift-amount. In JS + // shifting by 33 is hence equivalent to a shift by 1. Shortcut the + // computation when that happens. + if (JS('bool', r'# > 31', other)) return 0; + // Given that 'a' is positive we must not use '>>'. Otherwise a number + // that has the 31st bit set would be treated as negative and shift in + // ones. + return JS('num', r'# >>> #', this, other); + } + // For negative numbers we just clamp the shift-by amount. 'a' could be + // negative but not have its 31st bit set. The ">>" would then shift in + // 0s instead of 1s. Therefore we cannot simply return 0xFFFFFFFF. + if (JS('num', '#', other) > 31) other = 31; + return JS('num', r'(# >> #) >>> 0', this, other); + } + + num operator &(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# & #) >>> 0', this, other); + } + + num operator |(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# | #) >>> 0', this, other); + } + + num operator ^(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', r'(# ^ #) >>> 0', this, other); + } + + bool operator <(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# < #', this, other); + } + + bool operator >(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# > #', this, other); + } + + bool operator <=(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# <= #', this, other); + } + + bool operator >=(num other) { + if (other is !num) throw new ArgumentError(other); + return JS('num', '# >= #', this, other); + } +} + +class JSInt extends JSNumber implements int { + const JSInt(); + + bool get isEven => (this & 1) == 0; + + bool get isOdd => (this & 1) == 1; + + Type get runtimeType => int; + + int operator ~() => JS('int', r'(~#) >>> 0', this); +} + +class JSDouble extends JSNumber implements double { + const JSDouble(); + Type get runtimeType => double; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart new file mode 100644 index 0000000..381a690 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart
@@ -0,0 +1,229 @@ +// 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. + +part of _interceptors; + +/** + * The interceptor class for [String]. The compiler recognizes this + * class as an interceptor, and changes references to [:this:] to + * actually use the receiver of the method, which is generated as an extra + * argument added to each member. + */ +class JSString implements String { + const JSString(); + + int charCodeAt(index) => codeUnitAt(index); + + int codeUnitAt(int index) { + if (index is !num) throw new ArgumentError(index); + if (index < 0) throw new RangeError.value(index); + if (index >= length) throw new RangeError.value(index); + return JS('int', r'#.charCodeAt(#)', this, index); + } + + Iterable<Match> allMatches(String str) { + checkString(str); + return allMatchesInStringUnchecked(this, str); + } + + String concat(String other) { + if (other is !String) throw new ArgumentError(other); + return JS('String', r'# + #', this, other); + } + + bool endsWith(String other) { + checkString(other); + int otherLength = other.length; + if (otherLength > length) return false; + return other == substring(length - otherLength); + } + + String replaceAll(Pattern from, String to) { + checkString(to); + return stringReplaceAllUnchecked(this, from, to); + } + + String replaceAllMapped(Pattern from, String convert(Match match)) { + return this.splitMapJoin(from, onMatch: convert); + } + + String splitMapJoin(Pattern from, + {String onMatch(Match match), + String onNonMatch(String nonMatch)}) { + return stringReplaceAllFuncUnchecked(this, from, onMatch, onNonMatch); + } + + String replaceFirst(Pattern from, String to) { + checkString(to); + return stringReplaceFirstUnchecked(this, from, to); + } + + List<String> split(Pattern pattern) { + checkNull(pattern); + if (pattern is String) { + return JS('=List', r'#.split(#)', this, pattern); + } else if (pattern is JSSyntaxRegExp) { + var re = regExpGetNative(pattern); + return JS('=List', r'#.split(#)', this, re); + } else { + throw "String.split(Pattern) UNIMPLEMENTED"; + } + } + + List<String> splitChars() { + return JS('=List', r'#.split("")', this); + } + + bool startsWith(String other) { + checkString(other); + int otherLength = other.length; + if (otherLength > length) return false; + return JS('bool', r'# == #', other, + JS('String', r'#.substring(0, #)', this, otherLength)); + } + + String substring(int startIndex, [int endIndex]) { + checkNum(startIndex); + if (endIndex == null) endIndex = length; + checkNum(endIndex); + if (startIndex < 0 ) throw new RangeError.value(startIndex); + if (startIndex > endIndex) throw new RangeError.value(startIndex); + if (endIndex > length) throw new RangeError.value(endIndex); + return JS('String', r'#.substring(#, #)', this, startIndex, endIndex); + } + + String slice([int startIndex, int endIndex]) { + int start, end; + if (startIndex == null) { + start = 0; + } else if (startIndex is! int) { + throw new ArgumentError("startIndex is not int"); + } else if (startIndex >= 0) { + start = startIndex; + } else { + start = this.length + startIndex; + } + if (start < 0 || start > this.length) { + throw new RangeError( + "startIndex out of range: $startIndex (length: $length)"); + } + if (endIndex == null) { + end = this.length; + } else if (endIndex is! int) { + throw new ArgumentError("endIndex is not int"); + } else if (endIndex >= 0) { + end = endIndex; + } else { + end = this.length + endIndex; + } + if (end < 0 || end > this.length) { + throw new RangeError( + "endIndex out of range: $endIndex (length: $length)"); + } + if (end < start) { + throw new ArgumentError( + "End before start: $endIndex < $startIndex (length: $length)"); + } + return JS('String', '#.substring(#, #)', this, start, end); + } + + + String toLowerCase() { + return JS('String', r'#.toLowerCase()', this); + } + + String toUpperCase() { + return JS('String', r'#.toUpperCase()', this); + } + + String trim() { + return JS('String', r'#.trim()', this); + } + + List<int> get charCodes { + List<int> result = new List<int>.fixedLength(length); + for (int i = 0; i < length; i++) { + result[i] = JS('int', '#.charCodeAt(#)', this, i); + } + return result; + } + + Iterable<int> get codeUnits { + throw new UnimplementedError("String.codeUnits"); + } + + Iterable<int> get runes { + throw new UnimplementedError("String.runes"); + } + + int indexOf(String other, [int start = 0]) { + checkNull(other); + if (start is !int) throw new ArgumentError(start); + if (other is !String) throw new ArgumentError(other); + if (start < 0) return -1; + return JS('int', r'#.indexOf(#, #)', this, other, start); + } + + int lastIndexOf(String other, [int start]) { + checkNull(other); + if (other is !String) throw new ArgumentError(other); + if (start != null) { + if (start is !num) throw new ArgumentError(start); + if (start < 0) return -1; + if (start >= length) { + if (other == "") return length; + start = length - 1; + } + } else { + start = length - 1; + } + return stringLastIndexOfUnchecked(this, other, start); + } + + bool contains(String other, [int startIndex = 0]) { + checkNull(other); + return stringContainsUnchecked(this, other, startIndex); + } + + bool get isEmpty => length == 0; + + int compareTo(String other) { + if (other is !String) throw new ArgumentError(other); + return this == other ? 0 + : JS('bool', r'# < #', this, other) ? -1 : 1; + } + + // Note: if you change this, also change the function [S]. + String toString() => this; + + /** + * This is the [Jenkins hash function][1] but using masking to keep + * values in SMI range. + * + * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function + */ + int get hashCode { + // TODO(ahe): This method shouldn't have to use JS. Update when our + // optimizations are smarter. + int hash = 0; + for (int i = 0; i < length; i++) { + hash = 0x1fffffff & (hash + JS('int', r'#.charCodeAt(#)', this, i)); + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + hash = JS('int', '# ^ (# >> 6)', hash, hash); + } + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = JS('int', '# ^ (# >> 11)', hash, hash); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } + + Type get runtimeType => String; + + int get length => JS('int', r'#.length', this); + + String operator [](int index) { + if (index is !int) throw new ArgumentError(index); + if (index >= length || index < 0) throw new RangeError.value(index); + return JS('String', '#[#]', this, index); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart new file mode 100644 index 0000000..3bccb2c --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart
@@ -0,0 +1,68 @@ +// 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. + +// Patch file for dart:math library. +import 'dart:_foreign_helper' show JS; + +patch double sqrt(num x) + => JS('double', r'Math.sqrt(#)', checkNum(x)); + +patch double sin(num x) + => JS('double', r'Math.sin(#)', checkNum(x)); + +patch double cos(num x) + => JS('double', r'Math.cos(#)', checkNum(x)); + +patch double tan(num x) + => JS('double', r'Math.tan(#)', checkNum(x)); + +patch double acos(num x) + => JS('double', r'Math.acos(#)', checkNum(x)); + +patch double asin(num x) + => JS('double', r'Math.asin(#)', checkNum(x)); + +patch double atan(num x) + => JS('double', r'Math.atan(#)', checkNum(x)); + +patch double atan2(num a, num b) + => JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b)); + +patch double exp(num x) + => JS('double', r'Math.exp(#)', checkNum(x)); + +patch double log(num x) + => JS('double', r'Math.log(#)', checkNum(x)); + +patch num pow(num x, num exponent) { + checkNum(x); + checkNum(exponent); + return JS('num', r'Math.pow(#, #)', x, exponent); +} + +patch class Random { + patch factory Random([int seed]) => const _Random(); +} + +class _Random implements Random { + // The Dart2JS implementation of Random doesn't use a seed. + const _Random(); + + int nextInt(int max) { + if (max < 0) throw new ArgumentError("negative max: $max"); + if (max > 0xFFFFFFFF) max = 0xFFFFFFFF; + return JS("int", "(Math.random() * #) >>> 0", max); + } + + /** + * Generates a positive random floating point value uniformly distributed on + * the range from 0.0, inclusive, to 1.0, exclusive. + */ + double nextDouble() => JS("double", "Math.random()"); + + /** + * Generates a random boolean value. + */ + bool nextBool() => JS("bool", "Math.random() < 0.5"); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart new file mode 100644 index 0000000..1236507 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart
@@ -0,0 +1,114 @@ +// 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. + +// Patch library for dart:mirrors. + +import 'dart:_foreign_helper' show JS; + +// Yeah, seriously: mirrors in dart2js are experimental... +const String _MIRROR_OPT_IN_MESSAGE = """ + +This program is using an experimental feature called \"mirrors\". As +currently implemented, mirrors do not work with minification, and will +cause spurious errors depending on how code was optimized. + +The authors of this program are aware of these problems and have +decided the thrill of using an experimental feature is outweighing the +risks. Furthermore, the authors of this program understand that +long-term, to fix the problems mentioned above, mirrors may have +negative impact on size and performance of Dart programs compiled to +JavaScript. +"""; + +bool _mirrorsEnabled = false; + +/** + * Stub class for the mirror system. + */ +patch MirrorSystem currentMirrorSystem() { + _ensureEnabled(); + throw new UnsupportedError("MirrorSystem not implemented"); +} + +patch Future<MirrorSystem> mirrorSystemOf(SendPort port) { + _ensureEnabled(); + throw new UnsupportedError("MirrorSystem not implemented"); +} + +patch InstanceMirror reflect(Object reflectee) { + if (!_mirrorsEnabled && (_MIRROR_OPT_IN_MESSAGE == reflectee)) { + // Turn on mirrors and warn that it is an experimental feature. + _mirrorsEnabled = true; + print(reflectee); + } + _ensureEnabled(); + return new _InstanceMirror(reflectee); +} + +class _InstanceMirror extends InstanceMirror { + static final Expando<ClassMirror> classMirrors = new Expando<ClassMirror>(); + + final reflectee; + + _InstanceMirror(this.reflectee) { + _ensureEnabled(); + } + + bool get hasReflectee => true; + + ClassMirror get type { + String className = Primitives.objectTypeName(reflectee); + var constructor = Primitives.getConstructor(className); + var mirror = classMirrors[constructor]; + if (mirror == null) { + mirror = new _ClassMirror(className, constructor); + classMirrors[constructor] = mirror; + } + return mirror; + } + + Future<InstanceMirror> invoke(String memberName, + List<Object> positionalArguments, + [Map<String,Object> namedArguments]) { + if (namedArguments != null && !namedArguments.isEmpty) { + throw new UnsupportedError('Named arguments are not implemented'); + } + // Copy the list to ensure that it can safely be passed to + // JavaScript. + var jsList = new List.from(positionalArguments); + var mangledName = '${memberName}\$${positionalArguments.length}'; + var method = JS('var', '#[#]', reflectee, mangledName); + var completer = new Completer<InstanceMirror>(); + // TODO(ahe): [Completer] or [Future] should have API to create a + // delayed action. Simulating with a [Timer]. + new Timer(0, (timer) { + if (JS('String', 'typeof #', method) == 'function') { + var result = + JS('var', '#.apply(#, #)', method, reflectee, jsList); + completer.complete(new _InstanceMirror(result)); + } else { + completer.completeError('not a method $memberName'); + } + }); + return completer.future; + } + + String toString() => 'InstanceMirror($reflectee)'; +} + +class _ClassMirror extends ClassMirror { + final String _name; + final _jsConstructor; + + _ClassMirror(this._name, this._jsConstructor) { + _ensureEnabled(); + } + + String toString() => 'ClassMirror($_name)'; +} + +_ensureEnabled() { + if (_mirrorsEnabled) return; + throw new UnsupportedError('dart:mirrors is an experimental feature'); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart new file mode 100644 index 0000000..c75e554 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart
@@ -0,0 +1,431 @@ +// 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. + +part of _js_helper; + +String typeNameInChrome(obj) { + String name = JS('String', "#.constructor.name", obj); + return typeNameInWebKitCommon(name); +} + +String typeNameInSafari(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + // Safari is very similar to Chrome. + return typeNameInWebKitCommon(name); +} + +String typeNameInWebKitCommon(tag) { + String name = JS('String', '#', tag); + if (name == 'Window') return 'DOMWindow'; + if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; + if (name == 'WebKitMutationObserver') return 'MutationObserver'; + if (name == 'AudioChannelMerger') return 'ChannelMergerNode'; + if (name == 'AudioChannelSplitter') return 'ChannelSplitterNode'; + if (name == 'AudioGainNode') return 'GainNode'; + if (name == 'AudioPannerNode') return 'PannerNode'; + if (name == 'JavaScriptAudioNode') return 'ScriptProcessorNode'; + if (name == 'Oscillator') return 'OscillatorNode'; + if (name == 'RealtimeAnalyserNode') return 'AnalyserNode'; + if (name == 'IDBVersionChangeRequest') return 'IDBOpenDBRequest'; + return name; +} + +String typeNameInOpera(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'ApplicationCache') return 'DOMApplicationCache'; + return name; +} + +String typeNameInFirefox(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'CSS2Properties') return 'CSSStyleDeclaration'; + if (name == 'DataTransfer') return 'Clipboard'; + if (name == 'DragEvent') return 'MouseEvent'; + if (name == 'GeoGeolocation') return 'Geolocation'; + if (name == 'MouseScrollEvent') return 'WheelEvent'; + if (name == 'OfflineResourceList') return 'DOMApplicationCache'; + if (name == 'WorkerMessageEvent') return 'MessageEvent'; + if (name == 'XMLDocument') return 'Document'; + return name; +} + +String typeNameInIE(obj) { + String name = JS('String', '#', constructorNameFallback(obj)); + if (name == 'Window') return 'DOMWindow'; + if (name == 'Document') { + // IE calls both HTML and XML documents 'Document', so we check for the + // xmlVersion property, which is the empty string on HTML documents. + if (JS('bool', '!!#.xmlVersion', obj)) return 'Document'; + return 'HTMLDocument'; + } + if (name == 'ApplicationCache') return 'DOMApplicationCache'; + if (name == 'CanvasPixelArray') return 'Uint8ClampedArray'; + if (name == 'DataTransfer') return 'Clipboard'; + if (name == 'DragEvent') return 'MouseEvent'; + if (name == 'HTMLDDElement') return 'HTMLElement'; + if (name == 'HTMLDTElement') return 'HTMLElement'; + if (name == 'HTMLTableDataCellElement') return 'HTMLTableCellElement'; + if (name == 'HTMLTableHeaderCellElement') return 'HTMLTableCellElement'; + if (name == 'HTMLPhraseElement') return 'HTMLElement'; + if (name == 'MSStyleCSSProperties') return 'CSSStyleDeclaration'; + if (name == 'MouseWheelEvent') return 'WheelEvent'; + if (name == 'Position') return 'Geoposition'; + + // Patches for types which report themselves as Objects. + if (name == 'Object') { + if (JS('bool', 'window.DataView && (# instanceof window.DataView)', obj)) { + return 'DataView'; + } + } + return name; +} + +String constructorNameFallback(object) { + if (object == null) return 'Null'; + var constructor = JS('var', "#.constructor", object); + if (identical(JS('String', "typeof(#)", constructor), 'function')) { + // The constructor isn't null or undefined at this point. Try + // to grab hold of its name. + var name = JS('var', '#.name', constructor); + // If the name is a non-empty string, we use that as the type + // name of this object. On Firefox, we often get 'Object' as + // the constructor name even for more specialized objects so + // we have to fall through to the toString() based implementation + // below in that case. + if (name is String + && !identical(name, '') + && !identical(name, 'Object') + && !identical(name, 'Function.prototype')) { // Can happen in Opera. + return name; + } + } + String string = JS('String', 'Object.prototype.toString.call(#)', object); + return JS('String', '#.substring(8, # - 1)', string, string.length); +} + +/** + * If a lookup on an object [object] that has [tag] fails, this function is + * called to provide an alternate tag. This allows us to fail gracefully if we + * can make a good guess, for example, when browsers add novel kinds of + * HTMLElement that we have never heard of. + */ +String alternateTag(object, String tag) { + // Does it smell like some kind of HTML element? + if (JS('bool', r'!!/^HTML[A-Z].*Element$/.test(#)', tag)) { + // Check that it is not a simple JavaScript object. + String string = JS('String', 'Object.prototype.toString.call(#)', object); + if (string == '[object Object]') return null; + return 'HTMLElement'; + } + return null; +} + +// TODO(ngeoffray): stop using this method once our optimizers can +// change str1.contains(str2) into str1.indexOf(str2) != -1. +bool contains(String userAgent, String name) { + return JS('int', '#.indexOf(#)', userAgent, name) != -1; +} + +int arrayLength(List array) { + return JS('int', '#.length', array); +} + +arrayGet(List array, int index) { + return JS('var', '#[#]', array, index); +} + +void arraySet(List array, int index, var value) { + JS('var', '#[#] = #', array, index, value); +} + +propertyGet(var object, String property) { + return JS('var', '#[#]', object, property); +} + +bool callHasOwnProperty(var function, var object, String property) { + return JS('bool', '#.call(#, #)', function, object, property); +} + +void propertySet(var object, String property, var value) { + JS('var', '#[#] = #', object, property, value); +} + +getPropertyFromPrototype(var object, String name) { + return JS('var', 'Object.getPrototypeOf(#)[#]', object, name); +} + +newJsObject() { + return JS('var', '{}'); +} + +/** + * Returns the function to use to get the type name of an object. + */ +Function getFunctionForTypeNameOf() { + // If we're not in the browser, we're almost certainly running on v8. + if (!identical(JS('String', 'typeof(navigator)'), 'object')) return typeNameInChrome; + + String userAgent = JS('String', "navigator.userAgent"); + if (contains(userAgent, 'Chrome') || contains(userAgent, 'DumpRenderTree')) { + return typeNameInChrome; + } else if (contains(userAgent, 'Firefox')) { + return typeNameInFirefox; + } else if (contains(userAgent, 'MSIE')) { + return typeNameInIE; + } else if (contains(userAgent, 'Opera')) { + return typeNameInOpera; + } else if (contains(userAgent, 'AppleWebKit')) { + // Chrome matches 'AppleWebKit' too, but we test for Chrome first, so this + // is not a problem. + // Note: Just testing for "Safari" doesn't work when the page is embedded + // in a UIWebView on iOS 6. + return typeNameInSafari; + } else { + return constructorNameFallback; + } +} + + +/** + * Cached value for the function to use to get the type name of an + * object. + */ +Function _getTypeNameOf; + +/** + * Returns the type name of [obj]. + */ +String getTypeNameOf(var obj) { + if (_getTypeNameOf == null) _getTypeNameOf = getFunctionForTypeNameOf(); + return _getTypeNameOf(obj); +} + +String toStringForNativeObject(var obj) { + String name = JS('String', '#', getTypeNameOf(obj)); + return 'Instance of $name'; +} + +int hashCodeForNativeObject(object) => Primitives.objectHashCode(object); + +/** + * Sets a JavaScript property on an object. + */ +void defineProperty(var obj, String property, var value) { + JS('void', + 'Object.defineProperty(#, #, ' + '{value: #, enumerable: false, writable: true, configurable: true})', + obj, + property, + value); +} + +/** + * This method looks up the type name of [obj] in [methods]. [methods] + * is a Javascript object. If it cannot find it, it looks into the + * [_dynamicMetadata] array. If the method can still not be found, it + * creates a method that will throw a [NoSuchMethodError]. + * + * Once it has a method, the prototype of [obj] is patched with that + * method, on the property [name]. The method is then invoked. + * + * This method returns the result of invoking the found method. + */ +dynamicBind(var obj, + String name, + var methods, + List arguments) { + // The tag is related to the class name. E.g. the dart:html class + // '_ButtonElement' has the tag 'HTMLButtonElement'. TODO(erikcorry): rename + // getTypeNameOf to getTypeTag. + String tag = getTypeNameOf(obj); + var hasOwnPropertyFunction = JS('var', 'Object.prototype.hasOwnProperty'); + + var method = dynamicBindLookup(hasOwnPropertyFunction, tag, methods); + if (method == null) { + String secondTag = alternateTag(obj, tag); + if (secondTag != null) { + method = dynamicBindLookup(hasOwnPropertyFunction, secondTag, methods); + } + } + + // If we didn't find the method then look up in the Dart Object class, using + // getTypeNameOf in case the minifier has renamed Object. + if (method == null) { + String nameOfObjectClass = getTypeNameOf(const Object()); + method = + lookupDynamicClass(hasOwnPropertyFunction, methods, nameOfObjectClass); + } + + var proto = JS('var', 'Object.getPrototypeOf(#)', obj); + if (method == null) { + // If the method cannot be found, we use a trampoline method that + // will throw a [NoSuchMethodError] if the object is of the + // exact prototype, or will call [dynamicBind] again if the object + // is a subclass. + method = JS('var', + 'function () {' + 'if (Object.getPrototypeOf(this) === #) {' + 'throw new TypeError(# + " is not a function");' + '} else {' + 'return Object.prototype[#].apply(this, arguments);' + '}' + '}', + proto, name, name); + } + + if (!callHasOwnProperty(hasOwnPropertyFunction, proto, name)) { + defineProperty(proto, name, method); + } + + return JS('var', '#.apply(#, #)', method, obj, arguments); +} + +dynamicBindLookup(var hasOwnPropertyFunction, String tag, var methods) { + var method = lookupDynamicClass(hasOwnPropertyFunction, methods, tag); + // Look at the inheritance data, getting the class tags and using them + // to check the methods table for this method name. + if (method == null && _dynamicMetadata != null) { + for (int i = 0; i < arrayLength(_dynamicMetadata); i++) { + MetaInfo entry = arrayGet(_dynamicMetadata, i); + if (callHasOwnProperty(hasOwnPropertyFunction, entry._set, tag)) { + method = + lookupDynamicClass(hasOwnPropertyFunction, methods, entry._tag); + // Stop if we found it in the methods array. + if (method != null) break; + } + } + } + return method; +} + +// For each method name and class inheritance subtree, we use an ordinary JS +// object as a hash map to store the method for each class. Entries are added +// in native_emitter.dart (see dynamicName). In order to avoid the class names +// clashing with the method names on Object.prototype (needed for native +// objects) we must always use hasOwnProperty. +var lookupDynamicClass(var hasOwnPropertyFunction, + var methods, + String className) { + return callHasOwnProperty(hasOwnPropertyFunction, methods, className) ? + propertyGet(methods, className) : + null; +} + +/** + * Code for doing the dynamic dispatch on JavaScript prototypes that are not + * available at compile-time. Each property of a native Dart class + * is registered through this function, which is called with the + * following pattern: + * + * dynamicFunction('propertyName').prototypeName = // JS code + * + * What this function does is: + * - Creates a map of { prototypeName: JS code }. + * - Attaches 'propertyName' to the JS Object prototype that will + * intercept at runtime all calls to propertyName. + * - Sets the value of 'propertyName' to the returned method from + * [dynamicBind]. + * + */ +dynamicFunction(name) { + var f = JS('var', 'Object.prototype[#]', name); + if (f != null && JS('bool', '!!#.methods', f)) { + return JS('var', '#.methods', f); + } + + // TODO(ngeoffray): We could make this a map if the code we + // generate plays well with a Dart map. + var methods = JS('var', '{}'); + // If there is a method attached to the Dart Object class, use it as + // the method to call in case no method is registered for that type. + var dartMethod = getPropertyFromPrototype(const Object(), name); + // Take the method from the Dart Object class if we didn't find it yet and it + // is there. + if (dartMethod != null) propertySet(methods, 'Object', dartMethod); + + var bind = JS('var', + 'function() {' + 'return #(this, #, #, Array.prototype.slice.call(arguments));' + '}', + DART_CLOSURE_TO_JS(dynamicBind), name, methods); + + JS('void', '#.methods = #', bind, methods); + defineProperty(JS('var', 'Object.prototype'), name, bind); + return methods; +} + +/** + * This class encodes the class hierarchy when we need it for dynamic + * dispatch. + */ +class MetaInfo { + /** + * The type name this [MetaInfo] relates to. + */ + String _tag; + + /** + * A string containing the names of subtypes of [tag], separated by + * '|'. + */ + String _tags; + + /** + * A list of names of subtypes of [tag]. + */ + Object _set; + + MetaInfo(this._tag, this._tags, this._set); +} + +List<MetaInfo> get _dynamicMetadata { + // Because [dynamicMetadata] has to be shared with multiple isolates + // that access native classes (eg multiple DOM isolates), + // [_dynamicMetadata] cannot be a field, otherwise all non-main + // isolates would not have any value for it. + if (identical(JS('var', 'typeof(\$dynamicMetadata)'), 'undefined')) { + _dynamicMetadata = <MetaInfo>[]; + } + return JS('var', '\$dynamicMetadata'); +} + +void set _dynamicMetadata(List<MetaInfo> table) { + JS('void', '\$dynamicMetadata = #', table); +} + +/** + * Builds the metadata used for encoding the class hierarchy of native + * classes. The following example: + * + * class A native "*A" {} + * class B extends A native "*B" {} + * + * Will generate: + * ['A', 'A|B'] + * + * This method returns a list of [MetaInfo] objects. + */ +List <MetaInfo> buildDynamicMetadata(List<List<String>> inputTable) { + List<MetaInfo> result = <MetaInfo>[]; + for (int i = 0; i < arrayLength(inputTable); i++) { + String tag = JS('String', '#', arrayGet(arrayGet(inputTable, i), 0)); + String tags = JS('String', '#', arrayGet(arrayGet(inputTable, i), 1)); + var set = newJsObject(); + List<String> tagNames = tags.split('|'); + for (int j = 0; j < arrayLength(tagNames); j++) { + propertySet(set, arrayGet(tagNames, j), true); + } + result.add(new MetaInfo(tag, tags, set)); + } + return result; +} + +/** + * Called by the compiler to setup [_dynamicMetadata]. + */ +void dynamicSetMetadata(List<List<String>> inputTable) { + _dynamicMetadata = buildDynamicMetadata(inputTable); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart new file mode 100644 index 0000000..b62b0b5 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart
@@ -0,0 +1,151 @@ +// 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. + +part of _js_helper; + +List regExpExec(JSSyntaxRegExp regExp, String str) { + var nativeRegExp = regExpGetNative(regExp); + var result = JS('=List', r'#.exec(#)', nativeRegExp, str); + if (JS('bool', r'# == null', result)) return null; + return result; +} + +bool regExpTest(JSSyntaxRegExp regExp, String str) { + var nativeRegExp = regExpGetNative(regExp); + return JS('bool', r'#.test(#)', nativeRegExp, str); +} + +regExpGetNative(JSSyntaxRegExp regExp) { + var r = JS('var', r'#._re', regExp); + if (r == null) { + r = JS('var', r'#._re = #', regExp, regExpMakeNative(regExp)); + } + return r; +} + +regExpAttachGlobalNative(JSSyntaxRegExp regExp) { + JS('void', r'#._re = #', regExp, regExpMakeNative(regExp, global: true)); +} + +regExpMakeNative(JSSyntaxRegExp regExp, {bool global: false}) { + String pattern = regExp.pattern; + bool isMultiLine = regExp.isMultiLine; + bool isCaseSensitive = regExp.isCaseSensitive; + checkString(pattern); + StringBuffer sb = new StringBuffer(); + if (isMultiLine) sb.add('m'); + if (!isCaseSensitive) sb.add('i'); + if (global) sb.add('g'); + try { + return JS('var', r'new RegExp(#, #)', pattern, sb.toString()); + } catch (e) { + throw new IllegalJSRegExpException(pattern, + JS('String', r'String(#)', e)); + } +} + +int regExpMatchStart(m) => JS('int', r'#.index', m); + +class JSSyntaxRegExp implements RegExp { + final String _pattern; + final bool _isMultiLine; + final bool _isCaseSensitive; + + const JSSyntaxRegExp(String pattern, + {bool multiLine: false, + bool caseSensitive: true}) + : _pattern = pattern, + _isMultiLine = multiLine, + _isCaseSensitive = caseSensitive; + + Match firstMatch(String str) { + List<String> m = regExpExec(this, checkString(str)); + if (m == null) return null; + var matchStart = regExpMatchStart(m); + // m.lastIndex only works with flag 'g'. + var matchEnd = matchStart + m[0].length; + return new _MatchImplementation(pattern, str, matchStart, matchEnd, m); + } + + bool hasMatch(String str) => regExpTest(this, checkString(str)); + + String stringMatch(String str) { + var match = firstMatch(str); + return match == null ? null : match.group(0); + } + + Iterable<Match> allMatches(String str) { + checkString(str); + return new _AllMatchesIterable(this, str); + } + + String get pattern => _pattern; + bool get isMultiLine => _isMultiLine; + bool get isCaseSensitive => _isCaseSensitive; + + static JSSyntaxRegExp _globalVersionOf(JSSyntaxRegExp other) { + JSSyntaxRegExp re = + new JSSyntaxRegExp(other.pattern, + multiLine: other.isMultiLine, + caseSensitive: other.isCaseSensitive); + regExpAttachGlobalNative(re); + return re; + } + + _getNative() => regExpGetNative(this); +} + +class _MatchImplementation implements Match { + final String pattern; + final String str; + final int start; + final int end; + final List<String> _groups; + + const _MatchImplementation( + String this.pattern, + String this.str, + int this.start, + int this.end, + List<String> this._groups); + + String group(int index) => _groups[index]; + String operator [](int index) => group(index); + int get groupCount => _groups.length - 1; + + List<String> groups(List<int> groups) { + List<String> out = []; + for (int i in groups) { + out.add(group(i)); + } + return out; + } +} + +class _AllMatchesIterable extends Iterable<Match> { + final JSSyntaxRegExp _re; + final String _str; + + const _AllMatchesIterable(this._re, this._str); + + Iterator<Match> get iterator => new _AllMatchesIterator(_re, _str); +} + +class _AllMatchesIterator implements Iterator<Match> { + final RegExp _re; + final String _str; + Match _current; + + _AllMatchesIterator(JSSyntaxRegExp re, String this._str) + : _re = JSSyntaxRegExp._globalVersionOf(re); + + Match get current => _current; + + bool moveNext() { + // firstMatch actually acts as nextMatch because of + // hidden global flag. + _current = _re.firstMatch(_str); + return _current != null; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart new file mode 100644 index 0000000..326b892 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart
@@ -0,0 +1,130 @@ +// 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. + +// This is an empty dummy patch file for the VM dart:scalarlist library. +// This is needed in order to be able to generate documentation for the +// scalarlist library. + +patch class Int8List { + patch factory Int8List(int length) { + throw new UnsupportedError('Int8List'); + } + + patch factory Int8List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int8List.view'); + } +} + + +patch class Uint8List { + patch factory Uint8List(int length) { + throw new UnsupportedError('Uint8List'); + } + + patch factory Uint8List.view(ByteArray array, + [int start = 0, int length]) { + throw new UnsupportedError('Uint8List.view'); + } +} + + +patch class Uint8ClampedList { + patch factory Uint8ClampedList(int length) { + throw new UnsupportedError('Uint8ClampedList'); + } + + patch factory Uint8ClampedList.view(ByteArray array, + [int start = 0, int length]) { + throw new UnsupportedError('Uint8ClampedList.view'); + } +} + + +patch class Int16List { + patch factory Int16List(int length) { + throw new UnsupportedError('Int16List'); + + } + + patch factory Int16List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int16List.view'); + } +} + + +patch class Uint16List { + patch factory Uint16List(int length) { + throw new UnsupportedError('Uint16List'); + } + + patch factory Uint16List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint16List.view'); + } +} + + +patch class Int32List { + patch factory Int32List(int length) { + throw new UnsupportedError('Int32List'); + } + + patch factory Int32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int32List.view'); + } +} + + +patch class Uint32List { + patch factory Uint32List(int length) { + throw new UnsupportedError('Uint32List'); + } + + patch factory Uint32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint32List.view'); + } +} + + +patch class Int64List { + patch factory Int64List(int length) { + throw new UnsupportedError('Int64List'); + } + + patch factory Int64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Int64List.view'); + } +} + + +patch class Uint64List { + patch factory Uint64List(int length) { + throw new UnsupportedError('Uint64List'); + } + + patch factory Uint64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Uint64List.view'); + } +} + + +patch class Float32List { + patch factory Float32List(int length) { + throw new UnsupportedError('Float32List'); + } + + patch factory Float32List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Float32List.view'); + } +} + + +patch class Float64List { + patch factory Float64List(int length) { + throw new UnsupportedError('Float64List'); + } + + patch factory Float64List.view(ByteArray array, [int start = 0, int length]) { + throw new UnsupportedError('Float64List.view'); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart b/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart new file mode 100644 index 0000000..8ac1fa4 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart
@@ -0,0 +1,234 @@ +// 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. + +part of _js_helper; + +class StringMatch implements Match { + const StringMatch(int this.start, + String this.str, + String this.pattern); + + int get end => start + pattern.length; + String operator[](int g) => group(g); + int get groupCount => 0; + + String group(int group_) { + if (group_ != 0) { + throw new RangeError.value(group_); + } + return pattern; + } + + List<String> groups(List<int> groups_) { + List<String> result = new List<String>(); + for (int g in groups_) { + result.add(group(g)); + } + return result; + } + + final int start; + final String str; + final String pattern; +} + +List<Match> allMatchesInStringUnchecked(String needle, String haystack) { + // Copied from StringBase.allMatches in + // /runtime/lib/string_base.dart + List<Match> result = new List<Match>(); + int length = haystack.length; + int patternLength = needle.length; + int startIndex = 0; + while (true) { + int position = haystack.indexOf(needle, startIndex); + if (position == -1) { + break; + } + result.add(new StringMatch(position, haystack, needle)); + int endIndex = position + patternLength; + if (endIndex == length) { + break; + } else if (position == endIndex) { + ++startIndex; // empty match, advance and restart + } else { + startIndex = endIndex; + } + } + return result; +} + +stringContainsUnchecked(receiver, other, startIndex) { + if (other is String) { + return receiver.indexOf(other, startIndex) != -1; + } else if (other is JSSyntaxRegExp) { + return other.hasMatch(receiver.substring(startIndex)); + } else { + var substr = receiver.substring(startIndex); + return other.allMatches(substr).iterator.moveNext(); + } +} + +stringReplaceJS(receiver, replacer, to) { + // The JavaScript String.replace method recognizes replacement + // patterns in the replacement string. Dart does not have that + // behavior. + to = JS('String', r"#.replace('$', '$$$$')", to); + return JS('String', r'#.replace(#, #)', receiver, replacer, to); +} + +final RegExp quoteRegExp = new JSSyntaxRegExp(r'[-[\]{}()*+?.,\\^$|#\s]'); + +stringReplaceAllUnchecked(receiver, from, to) { + checkString(to); + if (from is String) { + if (from == "") { + if (receiver == "") { + return to; + } else { + StringBuffer result = new StringBuffer(); + int length = receiver.length; + result.add(to); + for (int i = 0; i < length; i++) { + result.add(receiver[i]); + result.add(to); + } + return result.toString(); + } + } else { + var quoter = regExpMakeNative(quoteRegExp, global: true); + var quoted = JS('String', r'#.replace(#, "\\$&")', from, quoter); + RegExp replaceRegExp = new JSSyntaxRegExp(quoted); + var replacer = regExpMakeNative(replaceRegExp, global: true); + return stringReplaceJS(receiver, replacer, to); + } + } else if (from is JSSyntaxRegExp) { + var re = regExpMakeNative(from, global: true); + return stringReplaceJS(receiver, re, to); + } else { + checkNull(from); + // TODO(floitsch): implement generic String.replace (with patterns). + throw "String.replaceAll(Pattern) UNIMPLEMENTED"; + } +} + +String _matchString(Match match) => match[0]; +String _stringIdentity(String string) => string; + +stringReplaceAllFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { + if (pattern is! Pattern) { + throw new ArgumentError("${pattern} is not a Pattern"); + } + if (onMatch == null) onMatch = _matchString; + if (onNonMatch == null) onNonMatch = _stringIdentity; + if (pattern is String) { + return stringReplaceAllStringFuncUnchecked(receiver, pattern, + onMatch, onNonMatch); + } + StringBuffer buffer = new StringBuffer(); + int startIndex = 0; + for (Match match in pattern.allMatches(receiver)) { + buffer.add(onNonMatch(receiver.substring(startIndex, match.start))); + buffer.add(onMatch(match)); + startIndex = match.end; + } + buffer.add(onNonMatch(receiver.substring(startIndex))); + return buffer.toString(); +} + +stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch) { + // Pattern is the empty string. + StringBuffer buffer = new StringBuffer(); + int length = receiver.length; + int i = 0; + buffer.add(onNonMatch("")); + while (i < length) { + buffer.add(onMatch(new StringMatch(i, receiver, ""))); + // Special case to avoid splitting a surrogate pair. + int code = receiver.charCodeAt(i); + if ((code & ~0x3FF) == 0xD800 && length > i + 1) { + // Leading surrogate; + code = receiver.charCodeAt(i + 1); + if ((code & ~0x3FF) == 0xDC00) { + // Matching trailing surrogate. + buffer.add(onNonMatch(receiver.substring(i, i + 2))); + i += 2; + continue; + } + } + buffer.add(onNonMatch(receiver[i])); + i++; + } + buffer.add(onMatch(new StringMatch(i, receiver, ""))); + buffer.add(onNonMatch("")); + return buffer.toString(); +} + +stringReplaceAllStringFuncUnchecked(receiver, pattern, onMatch, onNonMatch) { + int patternLength = pattern.length; + if (patternLength == 0) { + return stringReplaceAllEmptyFuncUnchecked(receiver, onMatch, onNonMatch); + } + int length = receiver.length; + StringBuffer buffer = new StringBuffer(); + int startIndex = 0; + while (startIndex < length) { + int position = receiver.indexOf(pattern, startIndex); + if (position == -1) { + break; + } + buffer.add(onNonMatch(receiver.substring(startIndex, position))); + buffer.add(onMatch(new StringMatch(position, receiver, pattern))); + startIndex = position + patternLength; + } + buffer.add(onNonMatch(receiver.substring(startIndex))); + return buffer.toString(); +} + + +stringReplaceFirstUnchecked(receiver, from, to) { + if (from is String) { + return stringReplaceJS(receiver, from, to); + } else if (from is JSSyntaxRegExp) { + var re = regExpGetNative(from); + return stringReplaceJS(receiver, re, to); + } else { + checkNull(from); + // TODO(floitsch): implement generic String.replace (with patterns). + throw "String.replace(Pattern) UNIMPLEMENTED"; + } +} + +stringJoinUnchecked(array, separator) { + return JS('String', r'#.join(#)', array, separator); +} + +class JsStringBuffer implements StringBuffer { + String _contents; + + JsStringBuffer(content) + : _contents = (content is String) ? content : '$content'; + + int get length => _contents.length; + + bool get isEmpty => length == 0; + + void add(Object obj) { + _contents = JS('String', '# + #', _contents, + (obj is String) ? obj : '$obj'); + } + + void addAll(Iterable objects) { + for (Object obj in objects) add(obj); + } + + void addCharCode(int charCode) { + add(new String.fromCharCodes([charCode])); + } + + void clear() { + _contents = ""; + } + + String toString() => _contents; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart b/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart new file mode 100644 index 0000000..074cece --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart
@@ -0,0 +1,837 @@ +// 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. + +part of dart2js; + +/** + * [CompilerTask] for loading libraries and setting up the import/export scopes. + * + * The library loader uses four different kinds of URIs in different parts of + * the loading process. + * + * ## User URI ## + * + * A 'user URI' is a URI provided by the user in code and as the main entry URI + * at the command line. These generally come in 3 versions: + * + * * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'. + * + * * A dart URI such as 'dart:core' and 'dart:_js_helper'. + * + * * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'. + * + * A user URI can also be absolute, like 'file:///foo.dart' or + * 'http://example.com/bar.dart', but such URIs cannot necessarily be used for + * locating source files, since the scheme must be supported by the input + * provider. The standard input provider for dart2js only supports the 'file' + * scheme. + * + * ## Resolved URI ## + * + * A 'resolved URI' is a (user) URI that has been resolved to an absolute URI + * based on the readable URI (see below) from which it was loaded. A URI with an + * explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved. + * A relative URI like for instance '../foo/bar.dart' is translated into an + * resolved URI in one of three ways: + * + * * If provided as the main entry URI at the command line, the URI is resolved + * relative to the current working directory, say + * 'file:///current/working/dir/', and the resolved URI is therefore + * 'file:///current/working/foo/bar.dart'. + * + * * If the relative URI is provided in an import, export or part tag, and the + * readable URI of the enclosing compilation unit is a file URI, + * 'file://some/path/baz.dart', then the resolved URI is + * 'file://some/foo/bar.dart'. + * + * * If the relative URI is provided in an import, export or part tag, and the + * readable URI of the enclosing compilation unit is a package URI, + * 'package:some/path/baz.dart', then the resolved URI is + * 'package:some/foo/bar.dart'. + * + * The resolved URI thus preserves the scheme through resolution: A readable + * file URI results in an resolved file URI and a readable package URI results + * in an resolved package URI. Note that since a dart URI is not a readable URI, + * import, export or part tags within platform libraries are not interpreted as + * dart URIs but instead relative to the library source file location. + * + * The resolved URI of a library is also used as the canonical URI + * ([LibraryElement.canonicalUri]) by which we identify which libraries are + * identical. This means that libraries loaded through the 'package' scheme will + * resolve to the same library when loaded from within using relative URIs (see + * for instance the test 'standalone/package/package1_test.dart'). But loading a + * platform library using a relative URI will _not_ result in the same library + * as when loaded through the dart URI. + * + * ## Readable URI ## + * + * A 'readable URI' is an absolute URI whose scheme is either 'package' or + * something supported by the input provider, normally 'file'. Dart URIs such as + * 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead + * resolved into a readable URI using the library root URI provided from the + * command line and the list of platform libraries found in + * 'sdk/lib/_internal/libraries.dart'. This is done through the + * [Compiler.translateResolvedUri] method which checks whether a library by that + * name exists and in case of internal libraries whether access is granted. + * + * ## Resource URI ## + * + * A 'resource URI' is an absolute URI with a scheme supported by the input + * provider. For the standard implementation this means a URI with the 'file' + * scheme. Readable URIs are converted into resource URIs as part of the + * [Compiler.readScript] method. In the standard implementation the package URIs + * are converted to file URIs using the package root URI provided on the + * command line as base. If the package root URI is + * 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart' + * will be resolved to the resource URI + * 'file:///current/working/dir/foo/bar.dart'. + * + * The distinction between readable URI and resource URI is necessary to ensure + * that these imports + * + * import 'package:foo.dart' as a; + * import 'packages/foo.dart' as b; + * + * do _not_ resolve to the same library when the package root URI happens to + * point to the 'packages' folder. + * + */ +abstract class LibraryLoader extends CompilerTask { + LibraryLoader(Compiler compiler) : super(compiler); + + /** + * Loads the library specified by the [resolvedUri] and returns its + * [LibraryElement]. + * + * If the library is not already loaded, the method creates the + * [LibraryElement] for the library and computes the import/export scope, + * loading and computing the import/export scopes of all required libraries in + * the process. The method handles cyclic dependency between libraries. + * + * This is the main entry point for [LibraryLoader]. + */ + // TODO(johnniwinther): Remove [canonicalUri] together with + // [Compiler.scanBuiltinLibrary]. + LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri); + + // TODO(johnniwinther): Remove this when patches don't need special parsing. + void registerLibraryFromTag(LibraryDependencyHandler handler, + LibraryElement library, + LibraryDependency tag); + + /** + * Adds the elements in the export scope of [importedLibrary] to the import + * scope of [importingLibrary]. + */ + // TODO(johnniwinther): Move handling of 'js_helper' to the library loader + // to remove this method from the [LibraryLoader] interface. + void importLibrary(LibraryElement importingLibrary, + LibraryElement importedLibrary, + Import tag); +} + +/** + * [CombinatorFilter] is a succinct representation of a list of combinators from + * a library dependency tag. + */ +class CombinatorFilter { + const CombinatorFilter(); + + /** + * Returns [:true:] if [element] is excluded by this filter. + */ + bool exclude(Element element) => false; + + /** + * Creates a filter based on the combinators of [tag]. + */ + factory CombinatorFilter.fromTag(LibraryDependency tag) { + if (tag == null || tag.combinators == null) { + return const CombinatorFilter(); + } + + // If the list of combinators contain at least one [:show:] we can create + // a positive list of elements to include, otherwise we create a negative + // list of elements to exclude. + bool show = false; + Set<SourceString> nameSet; + for (Combinator combinator in tag.combinators) { + if (combinator.isShow) { + show = true; + var set = new Set<SourceString>(); + for (Identifier identifier in combinator.identifiers) { + set.add(identifier.source); + } + if (nameSet == null) { + nameSet = set; + } else { + nameSet = nameSet.intersection(set); + } + } + } + if (nameSet == null) { + nameSet = new Set<SourceString>(); + } + for (Combinator combinator in tag.combinators) { + if (combinator.isHide) { + for (Identifier identifier in combinator.identifiers) { + if (show) { + // We have a positive list => Remove hidden elements. + nameSet.remove(identifier.source); + } else { + // We have no positive list => Accumulate hidden elements. + nameSet.add(identifier.source); + } + } + } + } + return show ? new ShowFilter(nameSet) : new HideFilter(nameSet); + } +} + +/** + * A list of combinators represented as a list of element names to include. + */ +class ShowFilter extends CombinatorFilter { + final Set<SourceString> includedNames; + + ShowFilter(this.includedNames); + + bool exclude(Element element) => !includedNames.contains(element.name); +} + +/** + * A list of combinators represented as a list of element names to exclude. + */ +class HideFilter extends CombinatorFilter { + final Set<SourceString> excludedNames; + + HideFilter(this.excludedNames); + + bool exclude(Element element) => excludedNames.contains(element.name); +} + +/** + * Implementation class for [LibraryLoader]. The distinction between + * [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from + * the [LibraryLoader] interface. + */ +class LibraryLoaderTask extends LibraryLoader { + LibraryLoaderTask(Compiler compiler) : super(compiler); + String get name => 'LibraryLoader'; + + final Map<String, LibraryElement> libraryNames = + new LinkedHashMap<String, LibraryElement>(); + + LibraryDependencyHandler currentHandler; + + LibraryElement loadLibrary(Uri resolvedUri, Node node, Uri canonicalUri) { + return measure(() { + assert(currentHandler == null); + currentHandler = new LibraryDependencyHandler(compiler); + LibraryElement library = + createLibrary(currentHandler, null, resolvedUri, node, canonicalUri); + currentHandler.computeExports(); + currentHandler = null; + return library; + }); + } + + /** + * Processes the library tags in [library]. + * + * The imported/exported libraries are loaded and processed recursively but + * the import/export scopes are not set up. + */ + void processLibraryTags(LibraryDependencyHandler handler, + LibraryElement library) { + int tagState = TagState.NO_TAG_SEEN; + + /** + * If [value] is less than [tagState] complain and return + * [tagState]. Otherwise return the new value for [tagState] + * (transition function for state machine). + */ + int checkTag(int value, LibraryTag tag) { + if (tagState > value) { + compiler.reportError(tag, 'out of order'); + return tagState; + } + return TagState.NEXT[value]; + } + + bool importsDartCore = false; + var libraryDependencies = new LinkBuilder<LibraryDependency>(); + Uri base = library.entryCompilationUnit.script.uri; + for (LibraryTag tag in library.tags.reverse()) { + if (tag.isImport) { + Import import = tag; + tagState = checkTag(TagState.IMPORT_OR_EXPORT, import); + if (import.uri.dartString.slowToString() == 'dart:core') { + importsDartCore = true; + } + libraryDependencies.addLast(import); + } else if (tag.isExport) { + tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag); + libraryDependencies.addLast(tag); + } else if (tag.isLibraryName) { + tagState = checkTag(TagState.LIBRARY, tag); + if (library.libraryTag != null) { + compiler.cancel("duplicated library declaration", node: tag); + } else { + library.libraryTag = tag; + } + checkDuplicatedLibraryName(library); + } else if (tag.isPart) { + Part part = tag; + StringNode uri = part.uri; + Uri resolvedUri = base.resolve(uri.dartString.slowToString()); + tagState = checkTag(TagState.SOURCE, part); + scanPart(part, resolvedUri, library); + } else { + compiler.internalError("Unhandled library tag.", node: tag); + } + } + + // Apply patch, if any. + if (library.isPlatformLibrary) { + patchDartLibrary(handler, library, library.canonicalUri.path); + } + + // Import dart:core if not already imported. + if (!importsDartCore && !isDartCore(library.canonicalUri)) { + handler.registerDependency(library, null, loadCoreLibrary(handler)); + } + + for (LibraryDependency tag in libraryDependencies.toLink()) { + registerLibraryFromTag(handler, library, tag); + } + } + + void checkDuplicatedLibraryName(LibraryElement library) { + LibraryName tag = library.libraryTag; + if (tag != null) { + String name = library.getLibraryOrScriptName(); + LibraryElement existing = + libraryNames.putIfAbsent(name, () => library); + if (!identical(existing, library)) { + Uri uri = library.entryCompilationUnit.script.uri; + compiler.reportMessage( + compiler.spanFromSpannable(tag.name, uri), + MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}), + api.Diagnostic.WARNING); + Uri existingUri = existing.entryCompilationUnit.script.uri; + compiler.reportMessage( + compiler.spanFromSpannable(existing.libraryTag.name, existingUri), + MessageKind.DUPLICATED_LIBRARY_NAME.error({'libraryName': name}), + api.Diagnostic.WARNING); + } + } + } + + bool isDartCore(Uri uri) => uri.scheme == "dart" && uri.path == "core"; + + /** + * Lazily loads and returns the [LibraryElement] for the dart:core library. + */ + LibraryElement loadCoreLibrary(LibraryDependencyHandler handler) { + if (compiler.coreLibrary == null) { + Uri coreUri = new Uri.fromComponents(scheme: 'dart', path: 'core'); + compiler.coreLibrary + = createLibrary(handler, null, coreUri, null, coreUri); + } + return compiler.coreLibrary; + } + + void patchDartLibrary(LibraryDependencyHandler handler, + LibraryElement library, String dartLibraryPath) { + if (library.isPatched) return; + Uri patchUri = compiler.resolvePatchUri(dartLibraryPath); + if (patchUri != null) { + compiler.patchParser.patchLibrary(handler, patchUri, library); + } + } + + /** + * Handle a part tag in the scope of [library]. The [resolvedUri] given is + * used as is, any URI resolution should be done beforehand. + */ + void scanPart(Part part, Uri resolvedUri, LibraryElement library) { + if (!resolvedUri.isAbsolute()) throw new ArgumentError(resolvedUri); + Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part); + Script sourceScript = compiler.readScript(readableUri, part); + CompilationUnitElement unit = + new CompilationUnitElementX(sourceScript, library); + compiler.withCurrentElement(unit, () { + compiler.scanner.scan(unit); + if (unit.partTag == null) { + bool wasDiagnosticEmitted = false; + compiler.withCurrentElement(library, () { + wasDiagnosticEmitted = + compiler.onDeprecatedFeature(part, 'missing part-of tag'); + }); + if (wasDiagnosticEmitted) { + compiler.reportMessage( + compiler.spanFromElement(unit), + MessageKind.MISSING_PART_OF_TAG.error(), + api.Diagnostic.INFO); + } + } + }); + } + + /** + * Handle an import/export tag by loading the referenced library and + * registering its dependency in [handler] for the computation of the import/ + * export scope. + */ + void registerLibraryFromTag(LibraryDependencyHandler handler, + LibraryElement library, + LibraryDependency tag) { + Uri base = library.entryCompilationUnit.script.uri; + Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString()); + LibraryElement loadedLibrary = + createLibrary(handler, library, resolvedUri, tag.uri, resolvedUri); + handler.registerDependency(library, tag, loadedLibrary); + + if (!loadedLibrary.hasLibraryName()) { + compiler.withCurrentElement(library, () { + compiler.reportError(tag == null ? null : tag.uri, + 'no library name found in ${loadedLibrary.canonicalUri}'); + }); + } + } + + /** + * Create (or reuse) a library element for the library specified by the + * [resolvedUri]. + * + * If a new library is created, the [handler] is notified. + */ + // TODO(johnniwinther): Remove [canonicalUri] and make [resolvedUri] the + // canonical uri when [Compiler.scanBuiltinLibrary] is removed. + LibraryElement createLibrary(LibraryDependencyHandler handler, + LibraryElement importingLibrary, + Uri resolvedUri, Node node, Uri canonicalUri) { + bool newLibrary = false; + Uri readableUri = + compiler.translateResolvedUri(importingLibrary, resolvedUri, node); + if (readableUri == null) return null; + LibraryElement createLibrary() { + newLibrary = true; + Script script = compiler.readScript(readableUri, node); + LibraryElement element = new LibraryElementX(script, canonicalUri); + handler.registerNewLibrary(element); + native.maybeEnableNative(compiler, element); + return element; + } + LibraryElement library; + if (canonicalUri == null) { + library = createLibrary(); + } else { + library = compiler.libraries.putIfAbsent(canonicalUri.toString(), + createLibrary); + } + if (newLibrary) { + compiler.withCurrentElement(library, () { + compiler.scanner.scanLibrary(library); + processLibraryTags(handler, library); + handler.registerLibraryExports(library); + compiler.onLibraryScanned(library, resolvedUri); + }); + } + return library; + } + + // TODO(johnniwinther): Remove this method when 'js_helper' is handled by + // [LibraryLoaderTask]. + void importLibrary(LibraryElement importingLibrary, + LibraryElement importedLibrary, + Import tag) { + new ImportLink(tag, importedLibrary).importLibrary(compiler, + importingLibrary); + } +} + + +/** + * The fields of this class models a state machine for checking script + * tags come in the correct order. + */ +class TagState { + static const int NO_TAG_SEEN = 0; + static const int LIBRARY = 1; + static const int IMPORT_OR_EXPORT = 2; + static const int SOURCE = 3; + static const int RESOURCE = 4; + + /** Next state. */ + static const List<int> NEXT = + const <int>[NO_TAG_SEEN, + IMPORT_OR_EXPORT, // Only one library tag is allowed. + IMPORT_OR_EXPORT, + SOURCE, + RESOURCE]; +} + +/** + * An [import] tag and the [importedLibrary] imported through [import]. + */ +class ImportLink { + final Import import; + final LibraryElement importedLibrary; + + ImportLink(this.import, this.importedLibrary); + + /** + * Imports the library into the [importingLibrary]. + */ + void importLibrary(Compiler compiler, LibraryElement importingLibrary) { + assert(invariant(importingLibrary, + importedLibrary.exportsHandled, + message: 'Exports not handled on $importedLibrary')); + var combinatorFilter = new CombinatorFilter.fromTag(import); + if (import != null && import.prefix != null) { + SourceString prefix = import.prefix.source; + Element e = importingLibrary.find(prefix); + if (e == null) { + e = new PrefixElementX(prefix, importingLibrary.entryCompilationUnit, + import.getBeginToken()); + importingLibrary.addToScope(e, compiler); + } + if (!identical(e.kind, ElementKind.PREFIX)) { + compiler.withCurrentElement(e, () { + compiler.reportWarning(new Identifier(e.position()), + 'duplicated definition'); + }); + compiler.reportError(import.prefix, 'duplicate definition'); + } + PrefixElement prefixElement = e; + importedLibrary.forEachExport((Element element) { + if (combinatorFilter.exclude(element)) return; + // TODO(johnniwinther): Clean-up like [checkDuplicateLibraryName]. + Element existing = + prefixElement.imported.putIfAbsent(element.name, () => element); + if (!identical(existing, element)) { + compiler.withCurrentElement(existing, () { + compiler.reportWarning(new Identifier(existing.position()), + 'duplicated import'); + }); + compiler.withCurrentElement(element, () { + compiler.reportError(new Identifier(element.position()), + 'duplicated import'); + }); + } + }); + } else { + importedLibrary.forEachExport((Element element) { + compiler.withCurrentElement(element, () { + if (combinatorFilter.exclude(element)) return; + importingLibrary.addImport(element, compiler); + }); + }); + } + } +} + +/** + * The combinator filter computed from an export tag and the library dependency + * node for the library that declared the export tag. This represents an edge in + * the library dependency graph. + */ +class ExportLink { + final CombinatorFilter combinatorFilter; + final LibraryDependencyNode exportNode; + + ExportLink(Export export, LibraryDependencyNode this.exportNode) + : this.combinatorFilter = new CombinatorFilter.fromTag(export); + + /** + * Exports [element] to the dependent library unless [element] is filtered by + * the export combinators. Returns [:true:] if the set pending exports of the + * dependent library was modified. + */ + bool exportElement(Element element) { + if (combinatorFilter.exclude(element)) return false; + return exportNode.addElementToPendingExports(element); + } +} + +/** + * A node in the library dependency graph. + * + * This class is used to collect the library dependencies expressed through + * import and export tags, and as the work-list entry in computations of library + * exports performed in [LibraryDependencyHandler.computeExports]. + */ +class LibraryDependencyNode { + final LibraryElement library; + + // TODO(ahe): Remove [hashCodeCounter] and [hashCode] when + // VM implementation of Object.hashCode is not slow. + final int hashCode = ++hashCodeCounter; + static int hashCodeCounter = 0; + + + /** + * A linked list of the import tags that import [library] mapped to the + * corresponding libraries. This is used to propagate exports into imports + * after the export scopes have been computed. + */ + Link<ImportLink> imports = const Link<ImportLink>(); + + /** + * A linked list of the export tags the dependent upon this node library. + * This is used to propagate exports during the computation of export scopes. + */ + Link<ExportLink> dependencies = const Link<ExportLink>(); + + /** + * The export scope for [library] which is gradually computed by the work-list + * computation in [LibraryDependencyHandler.computeExports]. + */ + Map<SourceString, Element> exportScope = + new LinkedHashMap<SourceString, Element>(); + + /** + * The set of exported elements that need to be propageted to dependent + * libraries as part of the work-list computation performed in + * [LibraryDependencyHandler.computeExports]. + */ + Set<Element> pendingExportSet = new Set<Element>(); + + LibraryDependencyNode(LibraryElement this.library); + + /** + * Registers that the library of this node imports [importLibrary] through the + * [import] tag. + */ + void registerImportDependency(Import import, + LibraryElement importedLibrary) { + imports = imports.prepend(new ImportLink(import, importedLibrary)); + } + + /** + * Registers that the library of this node is exported by + * [exportingLibraryNode] through the [export] tag. + */ + void registerExportDependency(Export export, + LibraryDependencyNode exportingLibraryNode) { + dependencies = + dependencies.prepend(new ExportLink(export, exportingLibraryNode)); + } + + /** + * Registers all non-private locally declared members of the library of this + * node to be exported. This forms the basis for the work-list computation of + * the export scopes performed in [LibraryDependencyHandler.computeExports]. + */ + void registerInitialExports() { + pendingExportSet.addAll(library.getNonPrivateElementsInScope()); + } + + void registerHandledExports(LibraryElement exportedLibraryElement, + CombinatorFilter filter) { + assert(invariant(library, exportedLibraryElement.exportsHandled)); + for (Element exportedElement in exportedLibraryElement.exports) { + if (!filter.exclude(exportedElement)) { + pendingExportSet.add(exportedElement); + } + } + } + + /** + * Registers the compute export scope with the node library. + */ + void registerExports() { + library.setExports(exportScope.values.toList()); + } + + /** + * Registers the imports of the node library. + */ + void registerImports(Compiler compiler) { + for (ImportLink link in imports) { + link.importLibrary(compiler, library); + } + } + + /** + * Copies and clears pending export set for this node. + */ + List<Element> pullPendingExports() { + List<Element> pendingExports = new List.from(pendingExportSet); + pendingExportSet.clear(); + return pendingExports; + } + + /** + * Adds [element] to the export scope for this node. If the [element] name + * is a duplicate, an error element is inserted into the export scope. + */ + Element addElementToExportScope(Compiler compiler, Element element) { + SourceString name = element.name; + Element existingElement = exportScope[name]; + if (existingElement != null) { + if (existingElement.isErroneous()) { + compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + element = existingElement; + } else if (existingElement.getLibrary() != library) { + // Declared elements hide exported elements. + compiler.reportErrorCode(existingElement, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + compiler.reportErrorCode(element, MessageKind.DUPLICATE_EXPORT, + {'name': name}); + element = exportScope[name] = new ErroneousElementX( + MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library); + } + } else { + exportScope[name] = element; + } + return element; + } + + /** + * Propagates the exported [element] to all library nodes that depend upon + * this node. If the propagation updated any pending exports, [:true:] is + * returned. + */ + bool propagateElement(Element element) { + bool change = false; + for (ExportLink link in dependencies) { + if (link.exportElement(element)) { + change = true; + } + } + return change; + } + + /** + * Adds [element] to the pending exports of this node and returns [:true:] if + * the pending export set was modified. The combinators of [export] are used + * to filter the element. + */ + bool addElementToPendingExports(Element element) { + if (!identical(exportScope[element.name], element)) { + if (!pendingExportSet.contains(element)) { + pendingExportSet.add(element); + return true; + } + } + return false; + } +} + +/** + * Helper class used for computing the possibly cyclic import/export scopes of + * a set of libraries. + * + * This class is used by [ScannerTask.loadLibrary] to collect all newly loaded + * libraries and to compute their import/export scopes through a fixed-point + * algorithm. + */ +class LibraryDependencyHandler { + final Compiler compiler; + + /** + * Newly loaded libraries and their corresponding node in the library + * dependency graph. Libraries that have already been fully loaded are not + * part of the dependency graph of this handler since their export scopes have + * already been computed. + */ + Map<LibraryElement,LibraryDependencyNode> nodeMap = + new LinkedHashMap<LibraryElement,LibraryDependencyNode>(); + + LibraryDependencyHandler(Compiler this.compiler); + + /** + * Performs a fixed-point computation on the export scopes of all registered + * libraries and creates the import/export of the libraries based on the + * fixed-point. + */ + void computeExports() { + bool changed = true; + while (changed) { + changed = false; + Map<LibraryDependencyNode, List<Element>> tasks = + new LinkedHashMap<LibraryDependencyNode, List<Element>>(); + + // Locally defined elements take precedence over exported + // elements. So we must propagate local elements first. We + // ensure this by pulling the pending exports before + // propagating. This enforces that we handle exports + // breadth-first, with locally defined elements being level 0. + nodeMap.forEach((_, LibraryDependencyNode node) { + List<Element> pendingExports = node.pullPendingExports(); + tasks[node] = pendingExports; + }); + tasks.forEach((LibraryDependencyNode node, List<Element> pendingExports) { + pendingExports.forEach((Element element) { + element = node.addElementToExportScope(compiler, element); + if (node.propagateElement(element)) { + changed = true; + } + }); + }); + } + + // Setup export scopes. These have to be set before computing the import + // scopes to avoid accessing uncomputed export scopes during handling of + // imports. + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { + node.registerExports(); + }); + + // Setup import scopes. + nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { + node.registerImports(compiler); + }); + } + + /** + * Registers that [library] depends on [loadedLibrary] through [tag]. + */ + void registerDependency(LibraryElement library, + LibraryDependency tag, + LibraryElement loadedLibrary) { + if (tag is Export) { + // [loadedLibrary] is exported by [library]. + LibraryDependencyNode exportingNode = nodeMap[library]; + if (loadedLibrary.exportsHandled) { + // Export scope already computed on [loadedLibrary]. + var combinatorFilter = new CombinatorFilter.fromTag(tag); + exportingNode.registerHandledExports(loadedLibrary, combinatorFilter); + return; + } + LibraryDependencyNode exportedNode = nodeMap[loadedLibrary]; + assert(invariant(loadedLibrary, exportedNode != null, + message: "$loadedLibrary has not been registered")); + assert(invariant(library, exportingNode != null, + message: "$library has not been registered")); + exportedNode.registerExportDependency(tag, exportingNode); + } else if (tag == null || tag is Import) { + // [loadedLibrary] is imported by [library]. + LibraryDependencyNode importingNode = nodeMap[library]; + assert(invariant(library, importingNode != null, + message: "$library has not been registered")); + importingNode.registerImportDependency(tag, loadedLibrary); + } + } + + /** + * Registers [library] for the processing of its import/export scope. + */ + void registerNewLibrary(LibraryElement library) { + nodeMap[library] = new LibraryDependencyNode(library); + } + + /** + * Registers all top-level entities of [library] as starting point for the + * fixed-point computation of the import/export scopes. + */ + void registerLibraryExports(LibraryElement library) { + nodeMap[library].registerInitialExports(); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart new file mode 100644 index 0000000..13a15f8 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart
@@ -0,0 +1,1742 @@ +// 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. + +library mirrors_dart2js; + +import 'dart:async'; +import 'dart:collection' show LinkedHashMap; +import 'dart:io'; +import 'dart:uri'; + +import '../../compiler.dart' as diagnostics; +import '../elements/elements.dart'; +import '../resolution/resolution.dart' show ResolverTask, ResolverVisitor; +import '../apiimpl.dart' show Compiler; +import '../scanner/scannerlib.dart' hide SourceString; +import '../ssa/ssa.dart'; +import '../dart2jslib.dart' hide Compiler; +import '../dart_types.dart'; +import '../filenames.dart'; +import '../source_file.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../util/uri_extras.dart'; +import '../dart2js.dart'; +import '../util/characters.dart'; +import '../source_file_provider.dart'; + +import 'mirrors.dart'; +import 'mirrors_util.dart'; +import 'util.dart'; + +//------------------------------------------------------------------------------ +// Utility types and functions for the dart2js mirror system +//------------------------------------------------------------------------------ + +bool _isPrivate(String name) { + return name.startsWith('_'); +} + +List<ParameterMirror> _parametersFromFunctionSignature( + Dart2JsMirrorSystem system, + Dart2JsMethodMirror method, + FunctionSignature signature) { + var parameters = <ParameterMirror>[]; + Link<Element> link = signature.requiredParameters; + while (!link.isEmpty) { + parameters.add(new Dart2JsParameterMirror( + system, method, link.head, false, false)); + link = link.tail; + } + link = signature.optionalParameters; + bool isNamed = signature.optionalParametersAreNamed; + while (!link.isEmpty) { + parameters.add(new Dart2JsParameterMirror( + system, method, link.head, true, isNamed)); + link = link.tail; + } + return parameters; +} + +Dart2JsTypeMirror _convertTypeToTypeMirror( + Dart2JsMirrorSystem system, + DartType type, + InterfaceType defaultType, + [FunctionSignature functionSignature]) { + if (type == null) { + return new Dart2JsInterfaceTypeMirror(system, defaultType); + } else if (type is InterfaceType) { + if (type == system.compiler.types.dynamicType) { + return new Dart2JsDynamicMirror(system, type); + } else { + return new Dart2JsInterfaceTypeMirror(system, type); + } + } else if (type is TypeVariableType) { + return new Dart2JsTypeVariableMirror(system, type); + } else if (type is FunctionType) { + return new Dart2JsFunctionTypeMirror(system, type, functionSignature); + } else if (type is VoidType) { + return new Dart2JsVoidMirror(system, type); + } else if (type is TypedefType) { + return new Dart2JsTypedefMirror(system, type); + } else if (type is MalformedType) { + // TODO(johnniwinther): We need a mirror on malformed types. + return system.dynamicType; + } + _diagnosticListener.internalError( + "Unexpected type $type of kind ${type.kind}"); + system.compiler.internalError("Unexpected type $type of kind ${type.kind}"); +} + +Collection<Dart2JsMemberMirror> _convertElementMemberToMemberMirrors( + Dart2JsContainerMirror library, Element element) { + if (element.isSynthesized) { + return const <Dart2JsMemberMirror>[]; + } else if (element is VariableElement) { + return <Dart2JsMemberMirror>[new Dart2JsFieldMirror(library, element)]; + } else if (element is FunctionElement) { + return <Dart2JsMemberMirror>[new Dart2JsMethodMirror(library, element)]; + } else if (element is AbstractFieldElement) { + var members = <Dart2JsMemberMirror>[]; + if (element.getter != null) { + members.add(new Dart2JsMethodMirror(library, element.getter)); + } + if (element.setter != null) { + members.add(new Dart2JsMethodMirror(library, element.setter)); + } + return members; + } + library.mirrors.compiler.internalError( + "Unexpected member type $element ${element.kind}"); +} + +MethodMirror _convertElementMethodToMethodMirror(Dart2JsContainerMirror library, + Element element) { + if (element is FunctionElement) { + return new Dart2JsMethodMirror(library, element); + } else { + return null; + } +} + +InstanceMirror _convertConstantToInstanceMirror(Dart2JsMirrorSystem mirrors, + Constant constant) { + if (constant is BoolConstant) { + return new Dart2JsBoolConstantMirror(mirrors, constant); + } else if (constant is NumConstant) { + return new Dart2JsNumConstantMirror(mirrors, constant); + } else if (constant is StringConstant) { + return new Dart2JsStringConstantMirror(mirrors, constant); + } else if (constant is ListConstant) { + return new Dart2JsListConstantMirror(mirrors, constant); + } else if (constant is MapConstant) { + return new Dart2JsMapConstantMirror(mirrors, constant); + } else if (constant is TypeConstant) { + return new Dart2JsTypeConstantMirror(mirrors, constant); + } else if (constant is FunctionConstant) { + return new Dart2JsConstantMirror(mirrors, constant); + } else if (constant is NullConstant) { + return new Dart2JsNullConstantMirror(mirrors, constant); + } else if (constant is ConstructedConstant) { + return new Dart2JsConstructedConstantMirror(mirrors, constant); + } + mirrors.compiler.internalError("Unexpected constant $constant"); +} + +class Dart2JsMethodKind { + static const Dart2JsMethodKind REGULAR = const Dart2JsMethodKind("regular"); + static const Dart2JsMethodKind GENERATIVE = + const Dart2JsMethodKind("generative"); + static const Dart2JsMethodKind REDIRECTING = + const Dart2JsMethodKind("redirecting"); + static const Dart2JsMethodKind CONST = const Dart2JsMethodKind("const"); + static const Dart2JsMethodKind FACTORY = const Dart2JsMethodKind("factory"); + static const Dart2JsMethodKind GETTER = const Dart2JsMethodKind("getter"); + static const Dart2JsMethodKind SETTER = const Dart2JsMethodKind("setter"); + static const Dart2JsMethodKind OPERATOR = const Dart2JsMethodKind("operator"); + + final String text; + + const Dart2JsMethodKind(this.text); + + String toString() => text; +} + + +String _getOperatorFromOperatorName(String name) { + Map<String, String> mapping = const { + 'eq': '==', + 'not': '~', + 'index': '[]', + 'indexSet': '[]=', + 'mul': '*', + 'div': '/', + 'mod': '%', + 'tdiv': '~/', + 'add': '+', + 'sub': '-', + 'shl': '<<', + 'shr': '>>', + 'ge': '>=', + 'gt': '>', + 'le': '<=', + 'lt': '<', + 'and': '&', + 'xor': '^', + 'or': '|', + }; + String newName = mapping[name]; + if (newName == null) { + throw new Exception('Unhandled operator name: $name'); + } + return newName; +} + +DiagnosticListener get _diagnosticListener { + return const Dart2JsDiagnosticListener(); +} + +class Dart2JsDiagnosticListener implements DiagnosticListener { + const Dart2JsDiagnosticListener(); + + void cancel(String reason, {node, token, instruction, element}) { + print(reason); + } + + 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); + } + + SourceSpan spanFromSpannable(Node node, [Uri uri]) { + // TODO(johnniwinther): implement this. + throw 'unimplemented'; + } + + void reportMessage(SourceSpan span, Diagnostic message, + diagnostics.Diagnostic kind) { + // TODO(johnniwinther): implement this. + throw 'unimplemented'; + } + + bool onDeprecatedFeature(Spannable span, String feature) { + // TODO(johnniwinther): implement this? + throw 'unimplemented'; + } +} + +//------------------------------------------------------------------------------ +// Compilation implementation +//------------------------------------------------------------------------------ + +// TODO(johnniwinther): Support client configurable handlers/providers. +class Dart2JsCompilation implements Compilation { + Compiler _compiler; + final Uri cwd; + final SourceFileProvider provider; + + Dart2JsCompilation(Path script, Path libraryRoot, + [Path packageRoot, List<String> opts = const <String>[]]) + : cwd = getCurrentDirectory(), + provider = new SourceFileProvider() { + var handler = new FormattingDiagnosticHandler(provider); + var libraryUri = cwd.resolve(libraryRoot.toString()); + var packageUri; + if (packageRoot != null) { + packageUri = cwd.resolve(packageRoot.toString()); + } else { + packageUri = libraryUri; + } + _compiler = new Compiler(provider.readStringFromUri, + null, + handler.diagnosticHandler, + libraryUri, packageUri, opts); + var scriptUri = cwd.resolve(script.toString()); + // TODO(johnniwinther): Detect file not found + _compiler.run(scriptUri); + } + + Dart2JsCompilation.library(List<Path> libraries, Path libraryRoot, + [Path packageRoot, List<String> opts = const <String>[]]) + : cwd = getCurrentDirectory(), + provider = new SourceFileProvider() { + var libraryUri = cwd.resolve(libraryRoot.toString()); + var packageUri; + if (packageRoot != null) { + packageUri = cwd.resolve(packageRoot.toString()); + } else { + packageUri = libraryUri; + } + opts = new List<String>.from(opts); + opts.add('--analyze-only'); + opts.add('--analyze-all'); + _compiler = new Compiler(provider.readStringFromUri, + null, + silentDiagnosticHandler, + libraryUri, packageUri, opts); + var librariesUri = <Uri>[]; + for (Path library in libraries) { + librariesUri.add(cwd.resolve(library.toString())); + // TODO(johnniwinther): Detect file not found + } + _compiler.librariesToAnalyzeWhenRun = librariesUri; + _compiler.run(null); + } + + MirrorSystem get mirrors => new Dart2JsMirrorSystem(_compiler); + + Future<String> compileToJavaScript() => + new Future<String>.immediate(_compiler.assembledCode); +} + + +//------------------------------------------------------------------------------ +// Dart2Js specific extensions of mirror interfaces +//------------------------------------------------------------------------------ + +abstract class Dart2JsMirror implements Mirror { + Dart2JsMirrorSystem get mirrors; +} + +abstract class Dart2JsDeclarationMirror extends Dart2JsMirror + implements DeclarationMirror { + + bool get isTopLevel => owner != null && owner is LibraryMirror; + + bool get isPrivate => _isPrivate(simpleName); + + /** + * Returns the first token for the source of this declaration, not including + * metadata annotations. + */ + Token getBeginToken(); + + /** + * Returns the last token for the source of this declaration. + */ + Token getEndToken(); + + /** + * Returns the script for the source of this declaration. + */ + Script getScript(); +} + +abstract class Dart2JsTypeMirror extends Dart2JsDeclarationMirror + implements TypeMirror { +} + +abstract class Dart2JsElementMirror extends Dart2JsDeclarationMirror { + final Dart2JsMirrorSystem mirrors; + final Element _element; + List<InstanceMirror> _metadata; + + Dart2JsElementMirror(this.mirrors, this._element) { + assert (mirrors != null); + assert (_element != null); + } + + String get simpleName => _element.name.slowToString(); + + String get displayName => simpleName; + + /** + * Computes the first token for this declaration using the begin token of the + * element node or element position as indicator. + */ + Token getBeginToken() { + // TODO(johnniwinther): Avoid calling [parseNode]. + Node node = _element.parseNode(mirrors.compiler); + if (node == null) { + return _element.position(); + } + return node.getBeginToken(); + } + + /** + * Computes the last token for this declaration using the end token of the + * element node or element position as indicator. + */ + Token getEndToken() { + // TODO(johnniwinther): Avoid calling [parseNode]. + Node node = _element.parseNode(mirrors.compiler); + if (node == null) { + return _element.position(); + } + return node.getEndToken(); + } + + /** + * Returns the first token for the source of this declaration, including + * metadata annotations. + */ + Token getFirstToken() { + if (!_element.metadata.isEmpty) { + for (MetadataAnnotation metadata in _element.metadata) { + if (metadata.beginToken != null) { + return metadata.beginToken; + } + } + } + return getBeginToken(); + } + + Script getScript() => _element.getCompilationUnit().script; + + SourceLocation get location { + Token beginToken = getFirstToken(); + Script script = getScript(); + SourceSpan span; + if (beginToken == null) { + span = new SourceSpan(script.uri, 0, 0); + } else { + Token endToken = getEndToken(); + span = mirrors.compiler.spanFromTokens(beginToken, endToken, script.uri); + } + return new Dart2JsSourceLocation(script, span); + } + + String toString() => _element.toString(); + + int get hashCode => qualifiedName.hashCode; + + void _appendCommentTokens(Token commentToken) { + while (commentToken != null && commentToken.kind == COMMENT_TOKEN) { + _metadata.add(new Dart2JsCommentInstanceMirror( + mirrors, commentToken.slowToString())); + commentToken = commentToken.next; + } + } + + List<InstanceMirror> get metadata { + if (_metadata == null) { + _metadata = <InstanceMirror>[]; + for (MetadataAnnotation metadata in _element.metadata) { + _appendCommentTokens(mirrors.compiler.commentMap[metadata.beginToken]); + metadata.ensureResolved(mirrors.compiler); + _metadata.add( + _convertConstantToInstanceMirror(mirrors, metadata.value)); + } + _appendCommentTokens(mirrors.compiler.commentMap[getBeginToken()]); + } + // TODO(johnniwinther): Return an unmodifiable list instead. + return new List<InstanceMirror>.from(_metadata); + } +} + +abstract class Dart2JsMemberMirror extends Dart2JsElementMirror + implements MemberMirror { + + Dart2JsMemberMirror(Dart2JsMirrorSystem system, Element element) + : super(system, element); + + bool get isConstructor => false; + + bool get isVariable => false; + + bool get isMethod => false; + + bool get isStatic => false; + + bool get isParameter => false; +} + +//------------------------------------------------------------------------------ +// Mirror system implementation. +//------------------------------------------------------------------------------ + +class Dart2JsMirrorSystem implements MirrorSystem { + final Compiler compiler; + Map<String, Dart2JsLibraryMirror> _libraries; + Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap; + + Dart2JsMirrorSystem(this.compiler) + : _libraryMap = new Map<LibraryElement, Dart2JsLibraryMirror>(); + + void _ensureLibraries() { + if (_libraries == null) { + _libraries = <String, Dart2JsLibraryMirror>{}; + compiler.libraries.forEach((_, LibraryElement v) { + var mirror = new Dart2JsLibraryMirror(mirrors, v); + _libraries[mirror.simpleName] = mirror; + _libraryMap[v] = mirror; + }); + } + } + + Map<String, LibraryMirror> get libraries { + _ensureLibraries(); + return new ImmutableMapWrapper<String, LibraryMirror>(_libraries); + } + + Dart2JsLibraryMirror _getLibrary(LibraryElement element) => + _libraryMap[element]; + + Dart2JsMirrorSystem get mirrors => this; + + TypeMirror get dynamicType => + _convertTypeToTypeMirror(this, compiler.types.dynamicType, null); + + TypeMirror get voidType => + _convertTypeToTypeMirror(this, compiler.types.voidType, null); +} + +abstract class Dart2JsContainerMirror extends Dart2JsElementMirror + implements ContainerMirror { + Map<String, MemberMirror> _members; + + Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element) + : super(system, element); + + void _ensureMembers(); + + Map<String, MemberMirror> get members { + _ensureMembers(); + return new ImmutableMapWrapper<String, MemberMirror>(_members); + } + + Map<String, MethodMirror> get functions { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => member is MethodMirror ? member : null); + } + + Map<String, MethodMirror> get getters { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isGetter ? + member : null); + } + + Map<String, MethodMirror> get setters { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isSetter ? + member : null); + } + + Map<String, VariableMirror> get variables { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, VariableMirror>( + _members, + (MemberMirror member) => member is VariableMirror ? member : null); + } +} + +class Dart2JsLibraryMirror extends Dart2JsContainerMirror + implements LibraryMirror { + Map<String, ClassMirror> _classes; + + Dart2JsLibraryMirror(Dart2JsMirrorSystem system, LibraryElement library) + : super(system, library); + + LibraryElement get _library => _element; + + Uri get uri => _library.canonicalUri; + + DeclarationMirror get owner => null; + + bool get isPrivate => false; + + LibraryMirror library() => this; + + /** + * Returns the library name (for libraries with a #library tag) or the script + * file name (for scripts without a #library tag). The latter case is used to + * provide a 'library name' for scripts, to use for instance in dartdoc. + */ + String get simpleName { + if (_library.libraryTag != null) { + // TODO(ahe): Remove StringNode check when old syntax is removed. + StringNode name = _library.libraryTag.name.asStringNode(); + if (name != null) { + return name.dartString.slowToString(); + } else { + return _library.libraryTag.name.toString(); + } + } else { + // Use the file name as script name. + String path = _library.canonicalUri.path; + return path.substring(path.lastIndexOf('/') + 1); + } + } + + String get qualifiedName => simpleName; + + void _ensureClasses() { + if (_classes == null) { + _classes = <String, ClassMirror>{}; + _library.forEachLocalMember((Element e) { + if (e.isClass()) { + ClassElement classElement = e; + classElement.ensureResolved(mirrors.compiler); + var type = new Dart2JsClassMirror.fromLibrary(this, classElement); + assert(invariant(_library, !_classes.containsKey(type.simpleName), + message: "Type name '${type.simpleName}' " + "is not unique in $_library.")); + _classes[type.simpleName] = type; + } else if (e.isTypedef()) { + var type = new Dart2JsTypedefMirror.fromLibrary(this, + e.computeType(mirrors.compiler)); + assert(invariant(_library, !_classes.containsKey(type.simpleName), + message: "Type name '${type.simpleName}' " + "is not unique in $_library.")); + _classes[type.simpleName] = type; + } + }); + } + } + + void _ensureMembers() { + if (_members == null) { + _members = <String, MemberMirror>{}; + _library.forEachLocalMember((Element e) { + if (!e.isClass() && !e.isTypedef()) { + for (var member in _convertElementMemberToMemberMirrors(this, e)) { + assert(!_members.containsKey(member.simpleName)); + _members[member.simpleName] = member; + } + } + }); + } + } + + Map<String, ClassMirror> get classes { + _ensureClasses(); + return new ImmutableMapWrapper<String, ClassMirror>(_classes); + } + + /** + * Computes the first token of this library using the first library tag as + * indicator. + */ + Token getBeginToken() { + if (_library.libraryTag != null) { + return _library.libraryTag.getBeginToken(); + } else if (!_library.tags.isEmpty) { + return _library.tags.reverse().head.getBeginToken(); + } + return null; + } + + /** + * Computes the first token of this library using the last library tag as + * indicator. + */ + Token getEndToken() { + if (!_library.tags.isEmpty) { + return _library.tags.head.getEndToken(); + } + return null; + } +} + +class Dart2JsSourceLocation implements SourceLocation { + final Script _script; + final SourceSpan _span; + int _line; + int _column; + + Dart2JsSourceLocation(this._script, this._span); + + int _computeLine() { + var sourceFile = _script.file as SourceFile; + if (sourceFile != null) { + return sourceFile.getLine(offset) + 1; + } + var index = 0; + var lineNumber = 0; + while (index <= offset && index < sourceText.length) { + index = sourceText.indexOf('\n', index) + 1; + if (index <= 0) break; + lineNumber++; + } + return lineNumber; + } + + int get line { + if (_line == null) { + _line = _computeLine(); + } + return _line; + } + + int _computeColumn() { + if (length == 0) return 0; + + var sourceFile = _script.file as SourceFile; + if (sourceFile != null) { + return sourceFile.getColumn(sourceFile.getLine(offset), offset) + 1; + } + int index = offset - 1; + var columnNumber = 0; + while (0 <= index && index < sourceText.length) { + columnNumber++; + var charCode = sourceText.charCodeAt(index); + if (charCode == $CR || charCode == $LF) { + break; + } + index--; + } + return columnNumber; + } + + int get column { + if (_column == null) { + _column = _computeColumn(); + } + return _column; + } + + int get offset => _span.begin; + + int get length => _span.end - _span.begin; + + String get text => _script.text.substring(_span.begin, _span.end); + + Uri get sourceUri => _script.uri; + + String get sourceText => _script.text; +} + +class Dart2JsParameterMirror extends Dart2JsMemberMirror + implements ParameterMirror { + final MethodMirror _method; + final bool isOptional; + final bool isNamed; + + factory Dart2JsParameterMirror(Dart2JsMirrorSystem system, + MethodMirror method, + VariableElement element, + bool isOptional, + bool isNamed) { + if (element is FieldParameterElement) { + return new Dart2JsFieldParameterMirror(system, + method, element, isOptional, isNamed); + } + return new Dart2JsParameterMirror._normal(system, + method, element, isOptional, isNamed); + } + + Dart2JsParameterMirror._normal(Dart2JsMirrorSystem system, + this._method, + VariableElement element, + this.isOptional, + this.isNamed) + : super(system, element); + + DeclarationMirror get owner => _method; + + VariableElement get _variableElement => _element; + + String get qualifiedName => '${_method.qualifiedName}#${simpleName}'; + + TypeMirror get type => _convertTypeToTypeMirror(mirrors, + _variableElement.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType, + _variableElement.variables.functionSignature); + + + bool get isFinal => false; + + bool get isConst => false; + + String get defaultValue { + if (hasDefaultValue) { + SendSet expression = _variableElement.cachedNode.asSendSet(); + return unparse(expression.arguments.head); + } + return null; + } + + bool get hasDefaultValue { + return _variableElement.cachedNode != null && + _variableElement.cachedNode is SendSet; + } + + bool get isInitializingFormal => false; + + VariableMirror get initializedField => null; +} + +class Dart2JsFieldParameterMirror extends Dart2JsParameterMirror { + + Dart2JsFieldParameterMirror(Dart2JsMirrorSystem system, + MethodMirror method, + FieldParameterElement element, + bool isOptional, + bool isNamed) + : super._normal(system, method, element, isOptional, isNamed); + + FieldParameterElement get _fieldParameterElement => _element; + + TypeMirror get type { + if (_fieldParameterElement.variables.cachedNode.type != null) { + return super.type; + } + return _convertTypeToTypeMirror(mirrors, + _fieldParameterElement.fieldElement.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType, + _variableElement.variables.functionSignature); + } + + bool get isInitializingFormal => true; + + VariableMirror get initializedField => new Dart2JsFieldMirror( + _method.owner, _fieldParameterElement.fieldElement); +} + +//------------------------------------------------------------------------------ +// Declarations +//------------------------------------------------------------------------------ +class Dart2JsClassMirror extends Dart2JsContainerMirror + implements Dart2JsTypeMirror, ClassMirror { + final Dart2JsLibraryMirror library; + List<TypeVariableMirror> _typeVariables; + + Dart2JsClassMirror(Dart2JsMirrorSystem system, ClassElement _class) + : this.library = system._getLibrary(_class.getLibrary()), + super(system, _class); + + ClassElement get _class => _element; + + Dart2JsClassMirror.fromLibrary(Dart2JsLibraryMirror library, + ClassElement _class) + : this.library = library, + super(library.mirrors, _class); + + DeclarationMirror get owner => library; + + String get qualifiedName => '${library.qualifiedName}.${simpleName}'; + + void _ensureMembers() { + if (_members == null) { + _members = <String, Dart2JsMemberMirror>{}; + _class.forEachMember((_, e) { + for (var member in _convertElementMemberToMemberMirrors(this, e)) { + assert(!_members.containsKey(member.simpleName)); + _members[member.simpleName] = member; + } + }); + } + } + + Map<String, MethodMirror> get methods => functions; + + Map<String, MethodMirror> get constructors { + _ensureMembers(); + return new AsFilteredImmutableMap<String, MemberMirror, MethodMirror>( + _members, (m) => m.isConstructor ? m : null); + } + + bool get isObject => _class == mirrors.compiler.objectClass; + + bool get isDynamic => false; + + bool get isVoid => false; + + bool get isTypeVariable => false; + + bool get isTypedef => false; + + bool get isFunction => false; + + ClassMirror get originalDeclaration => this; + + ClassMirror get superclass { + if (_class.supertype != null) { + return new Dart2JsInterfaceTypeMirror(mirrors, _class.supertype); + } + return null; + } + + List<ClassMirror> get superinterfaces { + var list = <ClassMirror>[]; + Link<DartType> link = _class.interfaces; + while (!link.isEmpty) { + var type = _convertTypeToTypeMirror(mirrors, link.head, + mirrors.compiler.types.dynamicType); + list.add(type); + link = link.tail; + } + return list; + } + + bool get isClass => !_class.isInterface(); + + bool get isInterface => _class.isInterface(); + + bool get isAbstract => _class.modifiers.isAbstract(); + + bool get isOriginalDeclaration => true; + + List<TypeMirror> get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List<TypeVariableMirror> get typeVariables { + if (_typeVariables == null) { + _typeVariables = <TypeVariableMirror>[]; + _class.ensureResolved(mirrors.compiler); + for (TypeVariableType typeVariable in _class.typeVariables) { + _typeVariables.add( + new Dart2JsTypeVariableMirror(mirrors, typeVariable)); + } + } + return _typeVariables; + } + + /** + * Returns the default type for this interface. + */ + ClassMirror get defaultFactory { + if (_class.defaultClass != null) { + return new Dart2JsInterfaceTypeMirror(mirrors, _class.defaultClass); + } + return null; + } + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! ClassMirror) { + return false; + } + if (library != other.library) { + return false; + } + if (!identical(isOriginalDeclaration, other.isOriginalDeclaration)) { + return false; + } + return qualifiedName == other.qualifiedName; + } +} + +class Dart2JsTypedefMirror extends Dart2JsTypeElementMirror + implements Dart2JsTypeMirror, TypedefMirror { + final Dart2JsLibraryMirror _library; + List<TypeVariableMirror> _typeVariables; + TypeMirror _definition; + + Dart2JsTypedefMirror(Dart2JsMirrorSystem system, TypedefType _typedef) + : this._library = system._getLibrary(_typedef.element.getLibrary()), + super(system, _typedef); + + Dart2JsTypedefMirror.fromLibrary(Dart2JsLibraryMirror library, + TypedefType _typedef) + : this._library = library, + super(library.mirrors, _typedef); + + TypedefType get _typedef => _type; + + String get qualifiedName => '${library.qualifiedName}.${simpleName}'; + + LibraryMirror get library => _library; + + bool get isTypedef => true; + + List<TypeMirror> get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List<TypeVariableMirror> get typeVariables { + if (_typeVariables == null) { + _typeVariables = <TypeVariableMirror>[]; + for (TypeVariableType typeVariable in _typedef.typeArguments) { + _typeVariables.add( + new Dart2JsTypeVariableMirror(mirrors, typeVariable)); + } + } + return _typeVariables; + } + + TypeMirror get value { + if (_definition == null) { + // TODO(johnniwinther): Should be [ensureResolved]. + mirrors.compiler.resolveTypedef(_typedef.element); + _definition = _convertTypeToTypeMirror( + mirrors, + _typedef.element.alias, + mirrors.compiler.types.dynamicType, + _typedef.element.functionSignature); + } + return _definition; + } + + ClassMirror get originalDeclaration => this; + + // TODO(johnniwinther): How should a typedef respond to these? + ClassMirror get superclass => null; + + List<ClassMirror> get superinterfaces => const <ClassMirror>[]; + + bool get isClass => false; + + bool get isInterface => false; + + bool get isOriginalDeclaration => true; + + bool get isAbstract => false; +} + +class Dart2JsTypeVariableMirror extends Dart2JsTypeElementMirror + implements TypeVariableMirror { + final TypeVariableType _typeVariableType; + ClassMirror _declarer; + + Dart2JsTypeVariableMirror(Dart2JsMirrorSystem system, + TypeVariableType typeVariableType) + : this._typeVariableType = typeVariableType, + super(system, typeVariableType) { + assert(_typeVariableType != null); + } + + + String get qualifiedName => '${declarer.qualifiedName}.${simpleName}'; + + ClassMirror get declarer { + if (_declarer == null) { + if (_typeVariableType.element.enclosingElement.isClass()) { + _declarer = new Dart2JsClassMirror(mirrors, + _typeVariableType.element.enclosingElement); + } else if (_typeVariableType.element.enclosingElement.isTypedef()) { + _declarer = new Dart2JsTypedefMirror(mirrors, + _typeVariableType.element.enclosingElement.computeType( + mirrors.compiler)); + } + } + return _declarer; + } + + LibraryMirror get library => declarer.library; + + DeclarationMirror get owner => declarer; + + bool get isTypeVariable => true; + + TypeMirror get upperBound => _convertTypeToTypeMirror( + mirrors, + _typeVariableType.element.bound, + mirrors.compiler.objectClass.computeType(mirrors.compiler)); + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeVariableMirror) { + return false; + } + if (declarer != other.declarer) { + return false; + } + return qualifiedName == other.qualifiedName; + } +} + + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +abstract class Dart2JsTypeElementMirror extends Dart2JsElementMirror + implements Dart2JsTypeMirror { + final DartType _type; + + Dart2JsTypeElementMirror(Dart2JsMirrorSystem system, DartType type) + : super(system, type.element), + this._type = type; + + String get simpleName => _type.name.slowToString(); + + DeclarationMirror get owner => library; + + LibraryMirror get library { + return mirrors._getLibrary(_type.element.getLibrary()); + } + + bool get isObject => false; + + bool get isVoid => false; + + bool get isDynamic => false; + + bool get isTypeVariable => false; + + bool get isTypedef => false; + + bool get isFunction => false; + + String toString() => _type.toString(); + + Map<String, MemberMirror> get members => const <String, MemberMirror>{}; + + Map<String, MethodMirror> get constructors => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get methods => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get getters => const <String, MethodMirror>{}; + + Map<String, MethodMirror> get setters => const <String, MethodMirror>{}; + + Map<String, VariableMirror> get variables => const <String, VariableMirror>{}; + + ClassMirror get defaultFactory => null; +} + +class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror + implements ClassMirror { + List<TypeMirror> _typeArguments; + + Dart2JsInterfaceTypeMirror(Dart2JsMirrorSystem system, + InterfaceType interfaceType) + : super(system, interfaceType); + + InterfaceType get _interfaceType => _type; + + String get qualifiedName => originalDeclaration.qualifiedName; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MemberMirror> get members => originalDeclaration.members; + + bool get isObject => mirrors.compiler.objectClass == _type.element; + + bool get isDynamic => mirrors.compiler.dynamicClass == _type.element; + + ClassMirror get originalDeclaration + => new Dart2JsClassMirror(mirrors, _type.element); + + // TODO(johnniwinther): Substitute type arguments for type variables. + ClassMirror get superclass => originalDeclaration.superclass; + + // TODO(johnniwinther): Substitute type arguments for type variables. + List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces; + + bool get isClass => originalDeclaration.isClass; + + bool get isInterface => originalDeclaration.isInterface; + + bool get isAbstract => originalDeclaration.isAbstract; + + bool get isPrivate => originalDeclaration.isPrivate; + + bool get isOriginalDeclaration => false; + + List<TypeMirror> get typeArguments { + if (_typeArguments == null) { + _typeArguments = <TypeMirror>[]; + if (!_interfaceType.isRaw) { + Link<DartType> type = _interfaceType.typeArguments; + while (type != null && type.head != null) { + _typeArguments.add(_convertTypeToTypeMirror(mirrors, type.head, + mirrors.compiler.types.dynamicType)); + type = type.tail; + } + } + } + return _typeArguments; + } + + List<TypeVariableMirror> get typeVariables => + originalDeclaration.typeVariables; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get constructors => + originalDeclaration.constructors; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get methods => originalDeclaration.methods; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get setters => originalDeclaration.setters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MethodMirror> get getters => originalDeclaration.getters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, VariableMirror> get variables => originalDeclaration.variables; + + // TODO(johnniwinther): Substitute type arguments for type variables? + ClassMirror get defaultFactory => originalDeclaration.defaultFactory; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! ClassMirror) { + return false; + } + if (other.isOriginalDeclaration) { + return false; + } + if (originalDeclaration != other.originalDeclaration) { + return false; + } + var thisTypeArguments = typeArguments.iterator; + var otherTypeArguments = other.typeArguments.iterator; + while (thisTypeArguments.moveNext()) { + if (!otherTypeArguments.moveNext()) return false; + if (thisTypeArguments.current != otherTypeArguments.current) { + return false; + } + } + return !otherTypeArguments.moveNext(); + } +} + + +class Dart2JsFunctionTypeMirror extends Dart2JsTypeElementMirror + implements FunctionTypeMirror { + final FunctionSignature _functionSignature; + List<ParameterMirror> _parameters; + + Dart2JsFunctionTypeMirror(Dart2JsMirrorSystem system, + FunctionType functionType, this._functionSignature) + : super(system, functionType) { + assert (_functionSignature != null); + } + + FunctionType get _functionType => _type; + + // TODO(johnniwinther): Is this the qualified name of a function type? + String get qualifiedName => originalDeclaration.qualifiedName; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map<String, MemberMirror> get members { + var method = callMethod; + if (method != null) { + var map = new Map<String, MemberMirror>.from( + originalDeclaration.members); + var name = method.qualifiedName; + assert(!map.containsKey(name)); + map[name] = method; + return new ImmutableMapWrapper<String, MemberMirror>(map); + } + return originalDeclaration.members; + } + + bool get isFunction => true; + + MethodMirror get callMethod => _convertElementMethodToMethodMirror( + mirrors._getLibrary(_functionType.element.getLibrary()), + _functionType.element); + + ClassMirror get originalDeclaration + => new Dart2JsClassMirror(mirrors, mirrors.compiler.functionClass); + + // TODO(johnniwinther): Substitute type arguments for type variables. + ClassMirror get superclass => originalDeclaration.superclass; + + // TODO(johnniwinther): Substitute type arguments for type variables. + List<ClassMirror> get superinterfaces => originalDeclaration.superinterfaces; + + bool get isClass => originalDeclaration.isClass; + + bool get isInterface => originalDeclaration.isInterface; + + bool get isPrivate => originalDeclaration.isPrivate; + + bool get isOriginalDeclaration => false; + + bool get isAbstract => false; + + List<TypeMirror> get typeArguments => const <TypeMirror>[]; + + List<TypeVariableMirror> get typeVariables => + originalDeclaration.typeVariables; + + TypeMirror get returnType { + return _convertTypeToTypeMirror(mirrors, _functionType.returnType, + mirrors.compiler.types.dynamicType); + } + + List<ParameterMirror> get parameters { + if (_parameters == null) { + _parameters = _parametersFromFunctionSignature(mirrors, callMethod, + _functionSignature); + } + return _parameters; + } +} + +class Dart2JsVoidMirror extends Dart2JsTypeElementMirror { + + Dart2JsVoidMirror(Dart2JsMirrorSystem system, VoidType voidType) + : super(system, voidType); + + VoidType get _voidType => _type; + + String get qualifiedName => simpleName; + + /** + * The void type has no location. + */ + SourceLocation get location => null; + + /** + * The void type has no library. + */ + LibraryMirror get library => null; + + bool get isVoid => true; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeMirror) { + return false; + } + return other.isVoid; + } +} + + +class Dart2JsDynamicMirror extends Dart2JsTypeElementMirror { + Dart2JsDynamicMirror(Dart2JsMirrorSystem system, InterfaceType voidType) + : super(system, voidType); + + InterfaceType get _dynamicType => _type; + + String get qualifiedName => simpleName; + + /** + * The dynamic type has no location. + */ + SourceLocation get location => null; + + /** + * The dynamic type has no library. + */ + LibraryMirror get library => null; + + bool get isDynamic => true; + + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is! TypeMirror) { + return false; + } + return other.isDynamic; + } +} + +//------------------------------------------------------------------------------ +// Member mirrors implementation. +//------------------------------------------------------------------------------ + +class Dart2JsMethodMirror extends Dart2JsMemberMirror + implements MethodMirror { + final Dart2JsContainerMirror _objectMirror; + final String simpleName; + final String displayName; + final String constructorName; + final String operatorName; + final Dart2JsMethodKind _kind; + + Dart2JsMethodMirror._internal(Dart2JsContainerMirror objectMirror, + FunctionElement function, + String this.simpleName, + String this.displayName, + String this.constructorName, + String this.operatorName, + Dart2JsMethodKind this._kind) + : this._objectMirror = objectMirror, + super(objectMirror.mirrors, function); + + factory Dart2JsMethodMirror(Dart2JsContainerMirror objectMirror, + FunctionElement function) { + String realName = function.name.slowToString(); + // TODO(ahe): This method should not be calling + // Elements.operatorNameToIdentifier. + String simpleName = + Elements.operatorNameToIdentifier(function.name).slowToString(); + String displayName; + String constructorName = null; + String operatorName = null; + Dart2JsMethodKind kind; + if (function.kind == ElementKind.GETTER) { + kind = Dart2JsMethodKind.GETTER; + displayName = simpleName; + } else if (function.kind == ElementKind.SETTER) { + kind = Dart2JsMethodKind.SETTER; + displayName = simpleName; + simpleName = '$simpleName='; + } else if (function.kind == ElementKind.GENERATIVE_CONSTRUCTOR) { + // TODO(johnniwinther): Support detection of redirecting constructors. + constructorName = ''; + int dollarPos = simpleName.indexOf('\$'); + if (dollarPos != -1) { + constructorName = simpleName.substring(dollarPos + 1); + simpleName = simpleName.substring(0, dollarPos); + // Simple name is TypeName.constructorName. + simpleName = '$simpleName.$constructorName'; + } else { + // Simple name is TypeName. + } + if (function.modifiers.isConst()) { + kind = Dart2JsMethodKind.CONST; + } else { + kind = Dart2JsMethodKind.GENERATIVE; + } + displayName = simpleName; + } else if (function.modifiers.isFactory()) { + kind = Dart2JsMethodKind.FACTORY; + constructorName = ''; + int dollarPos = simpleName.indexOf('\$'); + if (dollarPos != -1) { + constructorName = simpleName.substring(dollarPos+1); + simpleName = simpleName.substring(0, dollarPos); + simpleName = '$simpleName.$constructorName'; + } + // Simple name is TypeName.constructorName. + displayName = simpleName; + } else if (realName == 'unary-') { + kind = Dart2JsMethodKind.OPERATOR; + operatorName = '-'; + // Simple name is 'unary-'. + simpleName = Mirror.UNARY_MINUS; + // Display name is 'operator operatorName'. + displayName = 'operator -'; + } else if (simpleName.startsWith('operator\$')) { + String str = simpleName.substring(9); + simpleName = 'operator'; + kind = Dart2JsMethodKind.OPERATOR; + operatorName = _getOperatorFromOperatorName(str); + // Simple name is 'operator operatorName'. + simpleName = operatorName; + // Display name is 'operator operatorName'. + displayName = 'operator $operatorName'; + } else { + kind = Dart2JsMethodKind.REGULAR; + displayName = simpleName; + } + return new Dart2JsMethodMirror._internal(objectMirror, function, + simpleName, displayName, constructorName, operatorName, kind); + } + + FunctionElement get _function => _element; + + String get qualifiedName + => '${owner.qualifiedName}.$simpleName'; + + DeclarationMirror get owner => _objectMirror; + + bool get isTopLevel => _objectMirror is LibraryMirror; + + bool get isConstructor + => isGenerativeConstructor || isConstConstructor || + isFactoryConstructor || isRedirectingConstructor; + + bool get isMethod => !isConstructor; + + bool get isPrivate => + isConstructor ? _isPrivate(constructorName) : _isPrivate(simpleName); + + bool get isStatic => _function.modifiers.isStatic(); + + List<ParameterMirror> get parameters { + return _parametersFromFunctionSignature(mirrors, this, + _function.computeSignature(mirrors.compiler)); + } + + TypeMirror get returnType => _convertTypeToTypeMirror( + mirrors, _function.computeSignature(mirrors.compiler).returnType, + mirrors.compiler.types.dynamicType); + + bool get isAbstract => _function.isAbstract(mirrors.compiler); + + bool get isRegularMethod => !(isGetter || isSetter || isConstructor); + + bool get isConstConstructor => _kind == Dart2JsMethodKind.CONST; + + bool get isGenerativeConstructor => _kind == Dart2JsMethodKind.GENERATIVE; + + bool get isRedirectingConstructor => _kind == Dart2JsMethodKind.REDIRECTING; + + bool get isFactoryConstructor => _kind == Dart2JsMethodKind.FACTORY; + + bool get isGetter => _kind == Dart2JsMethodKind.GETTER; + + bool get isSetter => _kind == Dart2JsMethodKind.SETTER; + + bool get isOperator => _kind == Dart2JsMethodKind.OPERATOR; +} + +class Dart2JsFieldMirror extends Dart2JsMemberMirror implements VariableMirror { + Dart2JsContainerMirror _objectMirror; + VariableElement _variable; + + Dart2JsFieldMirror(Dart2JsContainerMirror objectMirror, + VariableElement variable) + : this._objectMirror = objectMirror, + this._variable = variable, + super(objectMirror.mirrors, variable); + + String get qualifiedName + => '${owner.qualifiedName}.$simpleName'; + + DeclarationMirror get owner => _objectMirror; + + bool get isTopLevel => _objectMirror is LibraryMirror; + + bool get isVariable => true; + + bool get isStatic => _variable.modifiers.isStatic(); + + bool get isFinal => _variable.modifiers.isFinal(); + + bool get isConst => _variable.modifiers.isConst(); + + TypeMirror get type => _convertTypeToTypeMirror(mirrors, + _variable.computeType(mirrors.compiler), + mirrors.compiler.types.dynamicType); +} + +//////////////////////////////////////////////////////////////////////////////// +// Mirrors on constant values used for metadata. +//////////////////////////////////////////////////////////////////////////////// + +class Dart2JsConstantMirror extends InstanceMirror { + final Dart2JsMirrorSystem mirrors; + final Constant _constant; + + Dart2JsConstantMirror(this.mirrors, this._constant); + + ClassMirror get type { + return new Dart2JsClassMirror(mirrors, + _constant.computeType(mirrors.compiler).element); + } + + bool get hasReflectee => false; + + get reflectee { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } + + Future<InstanceMirror> getField(String fieldName) { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } +} + +class Dart2JsNullConstantMirror extends Dart2JsConstantMirror { + Dart2JsNullConstantMirror(Dart2JsMirrorSystem mirrors, NullConstant constant) + : super(mirrors, constant); + + NullConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => null; +} + +class Dart2JsBoolConstantMirror extends Dart2JsConstantMirror { + Dart2JsBoolConstantMirror(Dart2JsMirrorSystem mirrors, BoolConstant constant) + : super(mirrors, constant); + + Dart2JsBoolConstantMirror.fromBool(Dart2JsMirrorSystem mirrors, bool value) + : super(mirrors, value ? new TrueConstant() : new FalseConstant()); + + BoolConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant is TrueConstant; +} + +class Dart2JsStringConstantMirror extends Dart2JsConstantMirror { + Dart2JsStringConstantMirror(Dart2JsMirrorSystem mirrors, + StringConstant constant) + : super(mirrors, constant); + + Dart2JsStringConstantMirror.fromString(Dart2JsMirrorSystem mirrors, + String text) + : super(mirrors, + new StringConstant(new DartString.literal(text), null)); + + StringConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant.value.slowToString(); +} + +class Dart2JsNumConstantMirror extends Dart2JsConstantMirror { + Dart2JsNumConstantMirror(Dart2JsMirrorSystem mirrors, + NumConstant constant) + : super(mirrors, constant); + + NumConstant get _constant => super._constant; + + bool get hasReflectee => true; + + get reflectee => _constant.value; +} + +class Dart2JsListConstantMirror extends Dart2JsConstantMirror + implements ListInstanceMirror { + Dart2JsListConstantMirror(Dart2JsMirrorSystem mirrors, + ListConstant constant) + : super(mirrors, constant); + + ListConstant get _constant => super._constant; + + int get length => _constant.length; + + Future<InstanceMirror> operator[](int index) { + if (index < 0) throw new RangeError('Negative index'); + if (index >= _constant.length) throw new RangeError('Index out of bounds'); + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, _constant.entries[index])); + } +} + +class Dart2JsMapConstantMirror extends Dart2JsConstantMirror + implements MapInstanceMirror { + List<String> _listCache; + + Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors, + MapConstant constant) + : super(mirrors, constant); + + MapConstant get _constant => super._constant; + + List<String> get _list { + if (_listCache == null) { + _listCache = new List<String>(_constant.keys.entries.length); + int index = 0; + for (StringConstant keyConstant in _constant.keys.entries) { + _listCache[index] = keyConstant.value.slowToString(); + index++; + } + } + return _listCache; + } + + int get length => _constant.length; + + Collection<String> get keys { + // TODO(johnniwinther): Return an unmodifiable list instead. + return new List<String>.from(_list); + } + + Future<InstanceMirror> operator[](String key) { + int index = _list.indexOf(key); + if (index == -1) return null; + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, _constant.values[index])); + } +} + +class Dart2JsTypeConstantMirror extends Dart2JsConstantMirror + implements TypeInstanceMirror { + + Dart2JsTypeConstantMirror(Dart2JsMirrorSystem mirrors, + TypeConstant constant) + : super(mirrors, constant); + + TypeConstant get _constant => super._constant; + + TypeMirror get representedType => _convertTypeToTypeMirror( + mirrors, _constant.representedType, mirrors.compiler.types.dynamicType); +} + +class Dart2JsConstructedConstantMirror extends Dart2JsConstantMirror { + Map<String,Constant> _fieldMapCache; + + Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors, + ConstructedConstant constant) + : super(mirrors, constant); + + ConstructedConstant get _constant => super._constant; + + Map<String,Constant> get _fieldMap { + if (_fieldMapCache == null) { + _fieldMapCache = new LinkedHashMap<String,Constant>(); + if (identical(_constant.type.element.kind, ElementKind.CLASS)) { + var index = 0; + ClassElement element = _constant.type.element; + element.forEachInstanceField((_, Element field) { + String fieldName = field.name.slowToString(); + _fieldMapCache.putIfAbsent(fieldName, () => _constant.fields[index]); + index++; + }, includeBackendMembers: true, includeSuperMembers: true); + } + } + return _fieldMapCache; + } + + Future<InstanceMirror> getField(String fieldName) { + Constant fieldConstant = _fieldMap[fieldName]; + if (fieldConstant != null) { + return new Future<InstanceMirror>.immediate( + _convertConstantToInstanceMirror(mirrors, fieldConstant)); + } + return super.getField(fieldName); + } +} + +class Dart2JsCommentInstanceMirror implements CommentInstanceMirror { + final Dart2JsMirrorSystem mirrors; + final String text; + String _trimmedText; + + Dart2JsCommentInstanceMirror(this.mirrors, this.text); + + ClassMirror get type { + return new Dart2JsClassMirror(mirrors, mirrors.compiler.documentClass); + } + + bool get isDocComment => text.startsWith('/**') || text.startsWith('///'); + + String get trimmedText { + if (_trimmedText == null) { + _trimmedText = stripComment(text); + } + return _trimmedText; + } + + bool get hasReflectee => false; + + get reflectee { + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } + + Future<InstanceMirror> getField(String fieldName) { + if (fieldName == 'isDocComment') { + return new Future.immediate( + new Dart2JsBoolConstantMirror.fromBool(mirrors, isDocComment)); + } else if (fieldName == 'text') { + return new Future.immediate( + new Dart2JsStringConstantMirror.fromString(mirrors, text)); + } else if (fieldName == 'trimmedText') { + return new Future.immediate( + new Dart2JsStringConstantMirror.fromString(mirrors, trimmedText)); + } + // TODO(johnniwinther): Which exception/error should be thrown here? + throw new UnsupportedError('InstanceMirror does not have a reflectee'); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart new file mode 100644 index 0000000..6f5189e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart
@@ -0,0 +1,738 @@ +// 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. + +library mirrors; + +import 'dart:async'; +import 'dart:io'; +import 'dart:uri'; + +// TODO(rnystrom): Use "package:" URL (#4968). +import 'dart2js_mirror.dart'; + +/** + * [Compilation] encapsulates the compilation of a program. + */ +abstract class Compilation { + /** + * Creates a new compilation which has [script] as its entry point. + */ + factory Compilation(Path script, + Path libraryRoot, + [Path packageRoot, + List<String> opts = const <String>[]]) { + return new Dart2JsCompilation(script, libraryRoot, packageRoot, opts); + } + + /** + * Creates a new compilation which consists of a set of libraries, but which + * has no entry point. This compilation cannot generate output but can only + * be used for static inspection of the source code. + */ + factory Compilation.library(List<Path> libraries, + Path libraryRoot, + [Path packageRoot, + List<String> opts = const []]) { + return new Dart2JsCompilation.library(libraries, libraryRoot, + packageRoot, opts); + } + + /** + * Returns the mirror system for this compilation. + */ + final MirrorSystem mirrors; + + /** + * Returns a future for the compiled JavaScript code. + */ + Future<String> compileToJavaScript(); +} + +/** + * The main interface for the whole mirror system. + */ +abstract class MirrorSystem { + /** + * Returns an unmodifiable map of all libraries in this mirror system. + */ + Map<String, LibraryMirror> get libraries; + + /** + * A mirror on the [:dynamic:] type. + */ + TypeMirror get dynamicType; + + /** + * A mirror on the [:void:] type. + */ + TypeMirror get voidType; +} + + +/** + * An entity in the mirror system. + */ +abstract class Mirror { + static const String UNARY_MINUS = 'unary-'; + + // TODO(johnniwinther): Do we need this on all mirrors? + /** + * Returns the mirror system which contains this mirror. + */ + MirrorSystem get mirrors; +} + +abstract class DeclarationMirror implements Mirror { + /** + * The simple name of the entity. The simple name is unique within the + * scope of the entity declaration. + * + * The simple name is in most cases the declared single identifier name of + * the entity, such as 'method' for a method [:void method() {...}:]. For an + * unnamed constructor for [:class Foo:] the simple name is 'Foo'. For a + * constructor for [:class Foo:] named 'named' the simple name is 'Foo.named'. + * For a property [:foo:] the simple name of the getter method is 'foo' and + * the simple name of the setter is 'foo='. For operators the simple name is + * the operator itself, for example '+' for [:operator +:]. + * + * The simple name for the unary minus operator is [UNARY_MINUS]. + */ + String get simpleName; + + /** + * The display name is the normal representation of the entity name. In most + * cases the display name is the simple name, but for a setter 'foo=' the + * display name is simply 'foo' and for the unary minus operator the display + * name is 'operator -'. The display name is not unique. + */ + String get displayName; + + /** + * Returns the name of this entity qualified by is enclosing context. For + * instance, the qualified name of a method 'method' in class 'Class' in + * library 'library' is 'library.Class.method'. + */ + String get qualifiedName; + + /** + * The source location of this Dart language entity. + */ + SourceLocation get location; + + /** + * A mirror on the owner of this function. This is the declaration immediately + * surrounding the reflectee. + * + * Note that for libraries, the owner will be [:null:]. + */ + DeclarationMirror get owner; + + /** + * Is this declaration private? + * + * Note that for libraries, this will be [:false:]. + */ + bool get isPrivate; + + /** + * Is this declaration top-level? + * + * This is defined to be equivalent to: + * [:mirror.owner != null && mirror.owner is LibraryMirror:] + */ + bool get isTopLevel; + + /** + * A list of the metadata associated with this declaration. + */ + List<InstanceMirror> get metadata; +} + +abstract class ObjectMirror implements Mirror { + /** + * Invokes a getter and returns a mirror on the result. The getter + * can be the implicit getter for a field or a user-defined getter + * method. + */ + Future<InstanceMirror> getField(String fieldName); +} + +/** + * An [InstanceMirror] reflects an instance of a Dart language object. + */ +abstract class InstanceMirror implements ObjectMirror { + /** + * A mirror on the type of the reflectee. + */ + ClassMirror get type; + + /** + * Does [reflectee] contain the instance reflected by this mirror? + * This will always be true in the local case (reflecting instances + * in the same isolate), but only true in the remote case if this + * mirror reflects a simple value. + * + * A value is simple if one of the following holds: + * - the value is null + * - the value is of type [num] + * - the value is of type [bool] + * - the value is of type [String] + */ + bool get hasReflectee; + + /** + * If the [InstanceMirror] reflects an instance it is meaningful to + * have a local reference to, we provide access to the actual + * instance here. + * + * If you access [reflectee] when [hasReflectee] is false, an + * exception is thrown. + */ + get reflectee; +} + +/** + * Specialized [InstanceMirror] used for reflection on constant lists. + */ +abstract class ListInstanceMirror implements InstanceMirror { + Future<InstanceMirror> operator[](int index); + int get length; +} + +/** + * Specialized [InstanceMirror] used for reflection on constant maps. + */ +abstract class MapInstanceMirror implements InstanceMirror { + /** + * Returns a collection containing all the keys in the map. + */ + Collection<String> get keys; + + /** + * Returns a future on the instance mirror of the value for the given key or + * null if key is not in the map. + */ + Future<InstanceMirror> operator[](String key); + + /** + * The number of {key, value} pairs in the map. + */ + int get length; +} + +/** + * Specialized [InstanceMirror] used for reflection on type constants. + */ +abstract class TypeInstanceMirror implements InstanceMirror { + /** + * Returns the type mirror for the type represented by the reflected type + * constant. + */ + TypeMirror get representedType; +} + +/** + * Specialized [InstanceMirror] used for reflection on comments as metadata. + */ +abstract class CommentInstanceMirror implements InstanceMirror { + /** + * The comment text as written in the source text. + */ + String get text; + + /** + * The comment text without the start, end, and padding text. + * + * For example, if [text] is [: /** Comment text. */ :] then the [trimmedText] + * is [: Comment text. :]. + */ + String get trimmedText; + + /** + * Is [:true:] if this comment is a documentation comment. + * + * That is, that the comment is either enclosed in [: /** ... */ :] or starts + * with [: /// :]. + */ + bool get isDocComment; +} + +/** + * Common interface for classes and libraries. + */ +abstract class ContainerMirror implements Mirror { + + /** + * An immutable map from from names to mirrors for all members in this + * container. + */ + Map<String, MemberMirror> get members; +} + +/** + * A library. + */ +abstract class LibraryMirror implements ContainerMirror, DeclarationMirror { + /** + * An immutable map from from names to mirrors for all members in this + * library. + * + * The members of a library are its top-level classes, functions, variables, + * getters, and setters. + */ + Map<String, MemberMirror> get members; + + /** + * An immutable map from names to mirrors for all class + * declarations in this library. + */ + Map<String, ClassMirror> get classes; + + /** + * An immutable map from names to mirrors for all function, getter, + * and setter declarations in this library. + */ + Map<String, MethodMirror> get functions; + + /** + * An immutable map from names to mirrors for all getter + * declarations in this library. + */ + Map<String, MethodMirror> get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations in this library. + */ + Map<String, MethodMirror> get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations in this library. + */ + Map<String, VariableMirror> get variables; + + /** + * Returns the canonical URI for this library. + */ + Uri get uri; +} + +/** + * Common interface for classes, interfaces, typedefs and type variables. + */ +abstract class TypeMirror implements DeclarationMirror { + /** + * Returns the library in which this member resides. + */ + LibraryMirror get library; + + /** + * Is [:true:] iff this type is the [:Object:] type. + */ + bool get isObject; + + /** + * Is [:true:] iff this type is the [:dynamic:] type. + */ + bool get isDynamic; + + /** + * Is [:true:] iff this type is the void type. + */ + bool get isVoid; + + /** + * Is [:true:] iff this type is a type variable. + */ + bool get isTypeVariable; + + /** + * Is [:true:] iff this type is a typedef. + */ + bool get isTypedef; + + /** + * Is [:true:] iff this type is a function type. + */ + bool get isFunction; +} + +/** + * A class or interface type. + */ +abstract class ClassMirror implements TypeMirror, ContainerMirror { + /** + * A mirror on the original declaration of this type. + * + * For most classes, they are their own original declaration. For + * generic classes, however, there is a distinction between the + * original class declaration, which has unbound type variables, and + * the instantiations of generic classes, which have bound type + * variables. + */ + ClassMirror get originalDeclaration; + + /** + * Returns the super class of this type, or null if this type is [Object] or a + * typedef. + */ + ClassMirror get superclass; + + /** + * Returns a list of the interfaces directly implemented by this type. + */ + List<ClassMirror> get superinterfaces; + + /** + * Is [:true:] iff this type is a class. + */ + bool get isClass; + + /** + * Is [:true:] iff this type is an interface. + */ + bool get isInterface; + + /** + * Is this the original declaration of this type? + * + * For most classes, they are their own original declaration. For + * generic classes, however, there is a distinction between the + * original class declaration, which has unbound type variables, and + * the instantiations of generic classes, which have bound type + * variables. + */ + bool get isOriginalDeclaration; + + /** + * Is [:true:] if this class is declared abstract. + */ + bool get isAbstract; + + /** + * Returns a list of the type arguments for this type. + */ + List<TypeMirror> get typeArguments; + + /** + * Returns the list of type variables for this type. + */ + List<TypeVariableMirror> get typeVariables; + + /** + * An immutable map from from names to mirrors for all members of + * this type. + * + * The members of a type are its methods, fields, getters, and + * setters. Note that constructors and type variables are not + * considered to be members of a type. + * + * This does not include inherited members. + */ + Map<String, MemberMirror> get members; + + /** + * An immutable map from names to mirrors for all method, + * declarations for this type. This does not include getters and + * setters. + */ + Map<String, MethodMirror> get methods; + + /** + * An immutable map from names to mirrors for all getter + * declarations for this type. + */ + Map<String, MethodMirror> get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations for this type. + */ + Map<String, MethodMirror> get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations for this type. + */ + Map<String, VariableMirror> get variables; + + /** + * An immutable map from names to mirrors for all constructor + * declarations for this type. + */ + Map<String, MethodMirror> get constructors; + + /** + * Returns the default type for this interface. + */ + ClassMirror get defaultFactory; +} + +/** + * A type parameter as declared on a generic type. + */ +abstract class TypeVariableMirror implements TypeMirror { + /** + * Returns the bound of the type parameter. + */ + TypeMirror get upperBound; +} + +/** + * A function type. + */ +abstract class FunctionTypeMirror implements ClassMirror { + /** + * Returns the return type of this function type. + */ + TypeMirror get returnType; + + /** + * Returns the parameters for this function type. + */ + List<ParameterMirror> get parameters; + + /** + * Returns the call method for this function type. + */ + MethodMirror get callMethod; +} + +/** + * A typedef. + */ +abstract class TypedefMirror implements ClassMirror { + /** + * The defining type for this typedef. + * + * For instance [:void f(int):] for a [:typedef void f(int):]. + */ + TypeMirror get value; +} + +/** + * A member of a type, i.e. a field, method or constructor. + */ +abstract class MemberMirror implements DeclarationMirror { + /** + * Is this member a constructor? + */ + bool get isConstructor; + + /** + * Is this member a variable? + * + * This is [:false:] for locals. + */ + bool get isVariable; + + /** + * Is this member a method?. + * + * This is [:false:] for constructors. + */ + bool get isMethod; + + /** + * Is this member declared static? + */ + bool get isStatic; + + /** + * Is this member a parameter? + */ + bool get isParameter; +} + +/** + * A field. + */ +abstract class VariableMirror implements MemberMirror { + + /** + * Returns true if this field is final. + */ + bool get isFinal; + + /** + * Returns true if this field is const. + */ + bool get isConst; + + /** + * Returns the type of this field. + */ + TypeMirror get type; +} + +/** + * Common interface constructors and methods, including factories, getters and + * setters. + */ +abstract class MethodMirror implements MemberMirror { + /** + * Returns the list of parameters for this method. + */ + List<ParameterMirror> get parameters; + + /** + * Returns the return type of this method. + */ + TypeMirror get returnType; + + /** + * Is the reflectee abstract? + */ + bool get isAbstract; + + /** + * Is the reflectee a regular function or method? + * + * A function or method is regular if it is not a getter, setter, or + * constructor. Note that operators, by this definition, are + * regular methods. + */ + bool get isRegularMethod; + + /** + * Is the reflectee a const constructor? + */ + bool get isConstConstructor; + + /** + * Is the reflectee a generative constructor? + */ + bool get isGenerativeConstructor; + + /** + * Is the reflectee a redirecting constructor? + */ + bool get isRedirectingConstructor; + + /** + * Is the reflectee a factory constructor? + */ + bool get isFactoryConstructor; + + /** + * Returns the constructor name for named constructors and factory methods, + * e.g. [:'bar':] for constructor [:Foo.bar:] of type [:Foo:]. + */ + String get constructorName; + + /** + * Is [:true:] if this method is a getter method. + */ + bool get isGetter; + + /** + * Is [:true:] if this method is a setter method. + */ + bool get isSetter; + + /** + * Is [:true:] if this method is an operator method. + */ + bool get isOperator; + + /** + * Returns the operator name for operator methods, e.g. [:'<':] for + * [:operator <:] + */ + String get operatorName; +} + +/** + * A formal parameter. + */ +abstract class ParameterMirror implements VariableMirror { + /** + * Returns the type of this parameter. + */ + TypeMirror get type; + + /** + * Returns the default value for this parameter. + */ + String get defaultValue; + + /** + * Does this parameter have a default value? + */ + bool get hasDefaultValue; + + /** + * Is this parameter optional? + */ + bool get isOptional; + + /** + * Is this parameter named? + */ + bool get isNamed; + + /** + * Returns [:true:] iff this parameter is an initializing formal of a + * constructor. That is, if it is of the form [:this.x:] where [:x:] is a + * field. + */ + bool get isInitializingFormal; + + /** + * Returns the initialized field, if this parameter is an initializing formal. + */ + VariableMirror get initializedField; +} + +/** + * A [SourceLocation] describes the span of an entity in Dart source code. + * A [SourceLocation] with a non-zero [length] should be the minimum span that + * encloses the declaration of the mirrored entity. + */ +abstract class SourceLocation { + /** + * The 1-based line number for this source location. + * + * A value of 0 means that the line number is unknown. + */ + int get line; + + /** + * The 1-based column number for this source location. + * + * A value of 0 means that the column number is unknown. + */ + int get column; + + /** + * The 0-based character offset into the [sourceText] where this source + * location begins. + * + * A value of -1 means that the offset is unknown. + */ + int get offset; + + /** + * The number of characters in this source location. + * + * A value of 0 means that the [offset] is approximate. + */ + int get length; + + /** + * The text of the location span. + */ + String get text; + + /** + * Returns the URI where the source originated. + */ + Uri get sourceUri; + + /** + * Returns the text of this source. + */ + String get sourceText; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart new file mode 100644 index 0000000..d904089 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart
@@ -0,0 +1,161 @@ +// 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. + +library mirrors_util; + +import 'dart:collection' show Queue; + +// TODO(rnystrom): Use "package:" URL (#4968). +import 'mirrors.dart'; + +//------------------------------------------------------------------------------ +// Utility functions for using the Mirror API +//------------------------------------------------------------------------------ + +/** + * Returns an iterable over the type declarations directly inheriting from + * the declaration of this type. + */ +Iterable<ClassMirror> computeSubdeclarations(ClassMirror type) { + type = type.originalDeclaration; + var subtypes = <ClassMirror>[]; + type.mirrors.libraries.forEach((_, library) { + for (ClassMirror otherType in library.classes.values) { + var superClass = otherType.superclass; + if (superClass != null) { + superClass = superClass.originalDeclaration; + if (type.library == superClass.library) { + if (superClass == type) { + subtypes.add(otherType); + } + } + } + final superInterfaces = otherType.superinterfaces; + for (ClassMirror superInterface in superInterfaces) { + superInterface = superInterface.originalDeclaration; + if (type.library == superInterface.library) { + if (superInterface == type) { + subtypes.add(otherType); + } + } + } + } + }); + return subtypes; +} + +LibraryMirror findLibrary(MemberMirror member) { + DeclarationMirror owner = member.owner; + if (owner is LibraryMirror) { + return owner; + } else if (owner is TypeMirror) { + return owner.library; + } + throw new Exception('Unexpected owner: ${owner}'); +} + +class HierarchyIterable extends Iterable<ClassMirror> { + final bool includeType; + final ClassMirror type; + + HierarchyIterable(this.type, {bool includeType}) + : this.includeType = includeType; + + Iterator<ClassMirror> get iterator => + new HierarchyIterator(type, includeType: includeType); +} + +/** + * [HierarchyIterator] iterates through the class hierarchy of the provided + * type. + * + * First the superclass relation is traversed, skipping [Object], next the + * superinterface relation and finally is [Object] visited. The supertypes are + * visited in breadth first order and a superinterface is visited more than once + * if implemented through multiple supertypes. + */ +class HierarchyIterator implements Iterator<ClassMirror> { + final Queue<ClassMirror> queue = new Queue<ClassMirror>(); + ClassMirror object; + ClassMirror _current; + + HierarchyIterator(ClassMirror type, {bool includeType}) { + if (includeType) { + queue.add(type); + } else { + push(type); + } + } + + ClassMirror push(ClassMirror type) { + if (type.superclass != null) { + if (type.superclass.isObject) { + object = type.superclass; + } else { + queue.addFirst(type.superclass); + } + } + queue.addAll(type.superinterfaces); + return type; + } + + ClassMirror get current => _current; + + bool moveNext() { + _current = null; + if (queue.isEmpty) { + if (object == null) return false; + _current = object; + object = null; + return true; + } else { + _current = push(queue.removeFirst()); + return true; + } + } +} + +final RegExp _singleLineCommentStart = new RegExp(r'^///? ?(.*)'); +final RegExp _multiLineCommentStartEnd = + new RegExp(r'^/\*\*? ?([\s\S]*)\*/$', multiLine: true); +final RegExp _multiLineCommentLineStart = new RegExp(r'^[ \t]*\* ?(.*)'); + +/** + * Pulls the raw text out of a comment (i.e. removes the comment + * characters). + */ +String stripComment(String comment) { + Match match = _singleLineCommentStart.firstMatch(comment); + if (match != null) { + return match[1]; + } + match = _multiLineCommentStartEnd.firstMatch(comment); + if (match != null) { + comment = match[1]; + var sb = new StringBuffer(); + List<String> lines = comment.split('\n'); + for (int index = 0 ; index < lines.length ; index++) { + String line = lines[index]; + if (index == 0) { + sb.add(line); // Add the first line unprocessed. + continue; + } + sb.add('\n'); + match = _multiLineCommentLineStart.firstMatch(line); + if (match != null) { + sb.add(match[1]); + } else if (index < lines.length-1 || !line.trim().isEmpty) { + // Do not add the last line if it only contains white space. + // This interprets cases like + // /* + // * Foo + // */ + // as "\nFoo\n" and not as "\nFoo\n ". + sb.add(line); + } + } + return sb.toString(); + } + throw new ArgumentError('Invalid comment $comment'); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart new file mode 100644 index 0000000..3470211 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart
@@ -0,0 +1,173 @@ +// 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. + +library util; + +/** + * An abstract map implementation. This class can be used as a superclass for + * implementing maps, requiring only the further implementation of the + * [:operator []:], [:forEach:] and [:length:] methods to provide a fully + * implemented immutable map. + */ +abstract class AbstractMap<K,V> implements Map<K,V> { + AbstractMap(); + + AbstractMap.from(Map<K,V> other) { + other.forEach((k,v) => this[k] = v); + } + + void operator []=(K key, value) { + throw new UnsupportedError('[]= is not supported'); + } + + void clear() { + throw new UnsupportedError('clear() is not supported'); + } + + bool containsKey(K key) { + var found = false; + forEach((k,_) { + if (k == key) { + found = true; + } + }); + return found; + } + + bool containsValue(V value) { + var found = false; + forEach((_,v) { + if (v == value) { + found = true; + } + }); + return found; + } + + Collection<K> get keys { + var keys = <K>[]; + forEach((k,_) => keys.add(k)); + return keys; + } + + Collection<V> get values { + var values = <V>[]; + forEach((_,v) => values.add(v)); + return values; + } + + bool get isEmpty => length == 0; + V putIfAbsent(K key, V ifAbsent()) { + if (!containsKey(key)) { + V value = this[key]; + this[key] = ifAbsent(); + return value; + } + return null; + } + + V remove(K key) { + throw new UnsupportedError('V remove(K key) is not supported'); + } +} + +/** + * [ImmutableMapWrapper] wraps a (mutable) map as an immutable map where all + * mutating operations throw [UnsupportedError] upon invocation. + */ +class ImmutableMapWrapper<K,V> extends AbstractMap<K,V> { + final Map<K,V> _map; + + ImmutableMapWrapper(this._map); + + int get length => _map.length; + + V operator [](K key) { + if (key is K) { + return _map[key]; + } + return null; + } + + void forEach(void f(K key, V value)) { + _map.forEach(f); + } +} + +/** + * A [Filter] function returns [:true:] iff [value] should be included. + */ +typedef bool Filter<V>(V value); + +/** + * An immutable map wrapper capable of filtering the input map. + */ +class FilteredImmutableMap<K,V> extends ImmutableMapWrapper<K,V> { + final Filter<V> _filter; + + FilteredImmutableMap(Map<K,V> map, this._filter) : super(map); + + int get length { + var count = 0; + forEach((k,v) { + count++; + }); + return count; + } + + void forEach(void f(K key, V value)) { + _map.forEach((K k, V v) { + if (_filter(v)) { + f(k, v); + } + }); + } +} + +/** + * An [AsFilter] takes a [value] of type [V1] and returns [value] iff it is of + * type [V2] or [:null:] otherwise. An [AsFilter] therefore behaves like the + * [:as:] expression. + */ +typedef V2 AsFilter<V1, V2>(V1 value); + +/** + * An immutable map wrapper capable of filtering the input map based on types. + * It takes an [AsFilter] function which converts the original values of type + * [Vin] into values of type [Vout], or returns [:null:] if the value should + * not be included in the filtered map. + */ +class AsFilteredImmutableMap<K, Vin, Vout> extends AbstractMap<K, Vout> { + final Map<K, Vin> _map; + final AsFilter<Vin, Vout> _filter; + + AsFilteredImmutableMap(this._map, this._filter); + + int get length { + var count = 0; + forEach((k,v) { + count++; + }); + return count; + } + + Vout operator [](K key) { + if (key is K) { + Vin value = _map[key]; + if (value != null) { + return _filter(value); + } + } + return null; + } + + void forEach(void f(K key, Vout value)) { + _map.forEach((K k, Vin v) { + var value = _filter(v); + if (value != null) { + f(k, value); + } + }); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart b/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart new file mode 100644 index 0000000..373f16e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart
@@ -0,0 +1,902 @@ +// 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. + +library native; + +import 'dart:collection' show Queue; +import 'dart:uri'; +import 'dart2jslib.dart' hide SourceString; +import 'dart_types.dart'; +import 'elements/elements.dart'; +import 'js_backend/js_backend.dart'; +import 'resolution/resolution.dart' show ResolverVisitor; +import 'scanner/scannerlib.dart'; +import 'ssa/ssa.dart'; +import 'tree/tree.dart'; +import 'util/util.dart'; + + +/// This class is a temporary work-around until we get a more powerful DartType. +class SpecialType { + final String name; + const SpecialType._(this.name); + + /// The type Object, but no subtypes: + static const JsObject = const SpecialType._('=Object'); + + /// The specific implementation of List that is JavaScript Array: + static const JsArray = const SpecialType._('=List'); +} + + +/** + * This could be an abstract class but we use it as a stub for the dart_backend. + */ +class NativeEnqueuer { + /// Initial entry point to native enqueuer. + void processNativeClasses(Iterable<LibraryElement> libraries) {} + + /// Notification of a main Enqueuer worklist element. For methods, adds + /// information from metadata attributes, and computes types instantiated due + /// to calling the method. + void registerElement(Element element) {} + + /// Notification of native field. Adds information from metadata attributes. + void handleFieldAnnotations(Element field) {} + + /// Computes types instantiated due to getting a native field. + void registerFieldLoad(Element field) {} + + /// Computes types instantiated due to setting a native field. + void registerFieldStore(Element field) {} + + NativeBehavior getNativeBehaviorOf(Send node) => null; + + /** + * Handles JS-calls, which can be an instantiation point for types. + * + * For example, the following code instantiates and returns native classes + * that are `_DOMWindowImpl` or a subtype. + * + * JS('_DOMWindowImpl', 'window') + * + */ + // TODO(sra): The entry from codegen will not have a resolver. + void registerJsCall(Send node, ResolverVisitor resolver) {} + + /// Emits a summary information using the [log] function. + void logSummary(log(message)) {} +} + + +abstract class NativeEnqueuerBase implements NativeEnqueuer { + + /** + * The set of all native classes. Each native class is in [nativeClasses] and + * exactly one of [unusedClasses], [pendingClasses] and [registeredClasses]. + */ + final Set<ClassElement> nativeClasses = new Set<ClassElement>(); + + final Set<ClassElement> registeredClasses = new Set<ClassElement>(); + final Set<ClassElement> pendingClasses = new Set<ClassElement>(); + final Set<ClassElement> unusedClasses = new Set<ClassElement>(); + + /** + * Records matched constraints ([SpecialType] or [DartType]). Once a type + * constraint has been matched, there is no need to match it again. + */ + final Set matchedTypeConstraints = new Set(); + + /// Pending actions. Classes in [pendingClasses] have action thunks in + /// [queue] to register the class. + final queue = new Queue(); + bool flushing = false; + + /// Maps JS foreign calls to their computed native behavior. + final Map<Node, NativeBehavior> nativeBehaviors = + new Map<Node, NativeBehavior>(); + + final Enqueuer world; + final Compiler compiler; + final bool enableLiveTypeAnalysis; + + ClassElement _annotationCreatesClass; + ClassElement _annotationReturnsClass; + ClassElement _annotationJsNameClass; + + /// Subclasses of [NativeEnqueuerBase] are constructed by the backend. + NativeEnqueuerBase(this.world, this.compiler, this.enableLiveTypeAnalysis); + + void processNativeClasses(Iterable<LibraryElement> libraries) { + libraries.forEach(processNativeClassesInLibrary); + processNativeClassesInLibrary(compiler.isolateHelperLibrary); + if (!enableLiveTypeAnalysis) { + nativeClasses.forEach((c) => enqueueClass(c, 'forced')); + flushQueue(); + } + } + + void processNativeClassesInLibrary(LibraryElement library) { + // Use implementation to ensure the inclusion of injected members. + library.implementation.forEachLocalMember((Element element) { + if (element.isClass() && element.isNative()) { + processNativeClass(element); + } + }); + } + + void processNativeClass(ClassElement classElement) { + nativeClasses.add(classElement); + unusedClasses.add(classElement); + // Resolve class to ensure the class has valid inheritance info. + classElement.ensureResolved(compiler); + } + + ClassElement get annotationCreatesClass { + findAnnotationClasses(); + return _annotationCreatesClass; + } + + ClassElement get annotationReturnsClass { + findAnnotationClasses(); + return _annotationReturnsClass; + } + + ClassElement get annotationJsNameClass { + findAnnotationClasses(); + return _annotationJsNameClass; + } + + void findAnnotationClasses() { + if (_annotationCreatesClass != null) return; + ClassElement find(name) { + Element e = compiler.findHelper(name); + if (e == null || e is! ClassElement) { + compiler.cancel("Could not find implementation class '${name}'"); + } + return e; + } + _annotationCreatesClass = find(const SourceString('Creates')); + _annotationReturnsClass = find(const SourceString('Returns')); + _annotationJsNameClass = find(const SourceString('JSName')); + } + + /// Returns the JSName annotation string or `null` if no JSName annotation is + /// present. + String findJsNameFromAnnotation(Element element) { + String name = null; + ClassElement annotationClass = annotationJsNameClass; + for (Link<MetadataAnnotation> link = element.metadata; + !link.isEmpty; + link = link.tail) { + MetadataAnnotation annotation = link.head.ensureResolved(compiler); + var value = annotation.value; + if (value is! ConstructedConstant) continue; + if (value.type is! InterfaceType) continue; + if (!identical(value.type.element, annotationClass)) continue; + + var fields = value.fields; + // TODO(sra): Better validation of the constant. + if (fields.length != 1 || fields[0] is! StringConstant) { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Annotations needs one string: ${partial.parseNode(compiler)}'); + } + String specString = fields[0].toDartString().slowToString(); + if (name == null) { + name = specString; + } else { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Too many JSName annotations: ${partial.parseNode(compiler)}'); + } + } + return name; + } + + enqueueClass(ClassElement classElement, cause) { + assert(unusedClasses.contains(classElement)); + unusedClasses.remove(classElement); + pendingClasses.add(classElement); + queue.add(() { processClass(classElement, cause); }); + } + + void flushQueue() { + if (flushing) return; + flushing = true; + while (!queue.isEmpty) { + (queue.removeFirst())(); + } + flushing = false; + } + + processClass(ClassElement classElement, cause) { + assert(!registeredClasses.contains(classElement)); + + bool firstTime = registeredClasses.isEmpty; + pendingClasses.remove(classElement); + registeredClasses.add(classElement); + + world.registerInstantiatedClass(classElement); + + // Also parse the node to know all its methods because otherwise it will + // only be parsed if there is a call to one of its constructors. + classElement.parseNode(compiler); + + if (firstTime) { + queue.add(onFirstNativeClass); + } + } + + registerElement(Element element) { + compiler.withCurrentElement(element, () { + if (element.isFunction() || element.isGetter() || element.isSetter()) { + handleMethodAnnotations(element); + if (element.isNative()) { + registerMethodUsed(element); + } + } else if (element.isField()) { + handleFieldAnnotations(element); + if (element.isNative()) { + registerFieldLoad(element); + registerFieldStore(element); + } + } + }); + } + + handleFieldAnnotations(Element element) { + if (element.enclosingElement.isNative()) { + // Exclude non-instance (static) fields - they not really native and are + // compiled as isolate globals. Access of a property of a constructor + // function or a non-method property in the prototype chain, must be coded + // using a JS-call. + if (element.isInstanceMember()) { + setNativeName(element); + } + } + } + + handleMethodAnnotations(Element method) { + if (isNativeMethod(method)) { + setNativeName(method); + } + } + + /// Sets the native name of [element], either from an annotation, or + /// defaulting to the Dart name. + void setNativeName(Element element) { + String name = findJsNameFromAnnotation(element); + if (name == null) name = element.name.slowToString(); + element.setNative(name); + } + + bool isNativeMethod(Element element) { + if (!element.getLibrary().canUseNative) return false; + // Native method? + return compiler.withCurrentElement(element, () { + Node node = element.parseNode(compiler); + if (node is! FunctionExpression) return false; + node = node.body; + Token token = node.getBeginToken(); + if (identical(token.stringValue, 'native')) return true; + return false; + }); + } + + void registerMethodUsed(Element method) { + processNativeBehavior( + NativeBehavior.ofMethod(method, compiler), + method); + flushQueue(); + } + + void registerFieldLoad(Element field) { + processNativeBehavior( + NativeBehavior.ofFieldLoad(field, compiler), + field); + flushQueue(); + } + + void registerFieldStore(Element field) { + processNativeBehavior( + NativeBehavior.ofFieldStore(field, compiler), + field); + flushQueue(); + } + + void registerJsCall(Send node, ResolverVisitor resolver) { + NativeBehavior behavior = NativeBehavior.ofJsCall(node, compiler, resolver); + processNativeBehavior(behavior, node); + nativeBehaviors[node] = behavior; + flushQueue(); + } + + NativeBehavior getNativeBehaviorOf(Send node) => nativeBehaviors[node]; + + processNativeBehavior(NativeBehavior behavior, cause) { + bool allUsedBefore = unusedClasses.isEmpty; + for (var type in behavior.typesInstantiated) { + if (matchedTypeConstraints.contains(type)) continue; + matchedTypeConstraints.add(type); + if (type is SpecialType) { + if (type == SpecialType.JsArray) { + world.registerInstantiatedClass(compiler.listClass); + } else if (type == SpecialType.JsObject) { + world.registerInstantiatedClass(compiler.objectClass); + } + continue; + } + if (type is InterfaceType) { + if (type.element == compiler.intClass) { + world.registerInstantiatedClass(compiler.intClass); + } else if (type.element == compiler.doubleClass) { + world.registerInstantiatedClass(compiler.doubleClass); + } else if (type.element == compiler.numClass) { + world.registerInstantiatedClass(compiler.doubleClass); + world.registerInstantiatedClass(compiler.intClass); + } else if (type.element == compiler.stringClass) { + world.registerInstantiatedClass(compiler.stringClass); + } else if (type.element == compiler.nullClass) { + world.registerInstantiatedClass(compiler.nullClass); + } else if (type.element == compiler.boolClass) { + world.registerInstantiatedClass(compiler.boolClass); + } + } + assert(type is DartType); + enqueueUnusedClassesMatching( + (nativeClass) => compiler.types.isSubtype(nativeClass.thisType, type), + cause, + 'subtypeof($type)'); + } + + // Give an info so that library developers can compile with -v to find why + // all the native classes are included. + if (unusedClasses.isEmpty && !allUsedBefore) { + compiler.log('All native types marked as used due to $cause.'); + } + } + + enqueueUnusedClassesMatching(bool predicate(classElement), + cause, + [String reason]) { + Iterable matches = unusedClasses.where(predicate); + matches.forEach((c) => enqueueClass(c, cause)); + } + + onFirstNativeClass() { + staticUse(name) => world.registerStaticUse(compiler.findHelper(name)); + + staticUse(const SourceString('dynamicFunction')); + staticUse(const SourceString('dynamicSetMetadata')); + staticUse(const SourceString('defineProperty')); + staticUse(const SourceString('toStringForNativeObject')); + staticUse(const SourceString('hashCodeForNativeObject')); + + addNativeExceptions(); + } + + addNativeExceptions() { + enqueueUnusedClassesMatching((classElement) { + // TODO(sra): Annotate exception classes in dart:html. + String name = classElement.name.slowToString(); + if (name.contains('Exception')) return true; + if (name.contains('Error')) return true; + return false; + }, + 'native exception'); + } +} + + +class NativeResolutionEnqueuer extends NativeEnqueuerBase { + + NativeResolutionEnqueuer(Enqueuer world, Compiler compiler) + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); + + void logSummary(log(message)) { + log('Resolved ${registeredClasses.length} native elements used, ' + '${unusedClasses.length} native elements dead.'); + } +} + + +class NativeCodegenEnqueuer extends NativeEnqueuerBase { + + final CodeEmitterTask emitter; + + final Set<ClassElement> doneAddSubtypes = new Set<ClassElement>(); + + NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter) + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); + + void processNativeClasses(Iterable<LibraryElement> libraries) { + super.processNativeClasses(libraries); + + // HACK HACK - add all the resolved classes. + NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; + for (final classElement in enqueuer.registeredClasses) { + if (unusedClasses.contains(classElement)) { + enqueueClass(classElement, 'was resolved'); + } + } + flushQueue(); + } + + processClass(ClassElement classElement, cause) { + super.processClass(classElement, cause); + // Add the information that this class is a subtype of its supertypes. The + // code emitter and the ssa builder use that information. + addSubtypes(classElement, emitter.nativeEmitter); + } + + void addSubtypes(ClassElement cls, NativeEmitter emitter) { + if (!cls.isNative()) return; + if (doneAddSubtypes.contains(cls)) return; + doneAddSubtypes.add(cls); + + // Walk the superclass chain since classes on the superclass chain might not + // be instantiated (abstract or simply unused). + addSubtypes(cls.superclass, emitter); + + for (DartType type in cls.allSupertypes) { + List<Element> subtypes = emitter.subtypes.putIfAbsent( + type.element, + () => <ClassElement>[]); + subtypes.add(cls); + } + + // Skip through all the mixin applications in the super class + // chain. That way, the direct subtypes set only contain the + // natives classes. + ClassElement superclass = cls.superclass; + while (superclass != null && superclass.isMixinApplication) { + assert(!superclass.isNative()); + superclass = superclass.superclass; + } + + List<Element> directSubtypes = emitter.directSubtypes.putIfAbsent( + superclass, + () => <ClassElement>[]); + directSubtypes.add(cls); + } + + void logSummary(log(message)) { + log('Compiled ${registeredClasses.length} native classes, ' + '${unusedClasses.length} native classes omitted.'); + } +} + +void maybeEnableNative(Compiler compiler, + LibraryElement library) { + String libraryName = library.canonicalUri.toString(); + if (library.entryCompilationUnit.script.name.contains( + 'dart/tests/compiler/dart2js_native') + || libraryName == 'dart:async' + || libraryName == 'dart:html' + || libraryName == 'dart:html_common' + || libraryName == 'dart:indexed_db' + || libraryName == 'dart:svg' + || libraryName == 'dart:web_audio') { + library.canUseNative = true; + } +} + +/** + * A summary of the behavior of a native element. + * + * Native code can return values of one type and cause native subtypes of + * another type to be instantiated. By default, we compute both from the + * declared type. + * + * A field might yield any native type that 'is' the field type. + * + * A method might create and return instances of native subclasses of its + * declared return type, and a callback argument may be called with instances of + * the callback parameter type (e.g. Event). + * + * If there is one or more @Creates annotations, the union of the named types + * replaces the inferred instantiated type, and the return type is ignored for + * the purpose of inferring instantiated types. + * + * @Creates(IDBCursor) // Created asynchronously. + * @Creates(IDBRequest) // Created synchronously (for return value). + * IDBRequest request = objectStore.openCursor(); + * + * If there is one or more @Returns annotations, the union of the named types + * replaces the declared return type. + * + * @Returns(IDBRequest) + * IDBRequest request = objectStore.openCursor(); + */ +class NativeBehavior { + + /// [DartType]s or [SpecialType]s returned or yielded by the native element. + final List typesReturned = []; + + /// [DartType]s or [SpecialType]s instantiated by the native element. + final List typesInstantiated = []; + + static final NativeBehavior NONE = new NativeBehavior(); + + //NativeBehavior(); + + static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { + // The first argument of a JS-call is a string encoding various attributes + // of the code. + // + // 'Type1|Type2'. A union type. + // '=Object'. A JavaScript Object, no subtype. + // '=List'. A JavaScript Array, no subtype. + + var argNodes = jsCall.arguments; + if (argNodes.isEmpty) { + compiler.cancel("JS expression has no type", node: jsCall); + } + + var firstArg = argNodes.head; + LiteralString specLiteral = firstArg.asLiteralString(); + if (specLiteral != null) { + String specString = specLiteral.dartString.slowToString(); + // Various things that are not in fact types. + if (specString == 'void') return NativeBehavior.NONE; + if (specString == '' || specString == 'var') { + var behavior = new NativeBehavior(); + behavior.typesReturned.add(compiler.objectClass.computeType(compiler)); + return behavior; + } + var behavior = new NativeBehavior(); + for (final typeString in specString.split('|')) { + var type = _parseType(typeString, compiler, + (name) => resolver.resolveTypeFromString(name), + jsCall); + behavior.typesInstantiated.add(type); + behavior.typesReturned.add(type); + } + return behavior; + } + + // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It + // is not very satisfactory because it does not work for void, dynamic. + + compiler.cancel("Unexpected JS first argument", node: firstArg); + } + + static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) { + FunctionType type = method.computeType(compiler); + var behavior = new NativeBehavior(); + behavior.typesReturned.add(type.returnType); + behavior._capture(type, compiler); + + // TODO(sra): Optional arguments are currently missing from the + // DartType. This should be fixed so the following work-around can be + // removed. + method.computeSignature(compiler).forEachOptionalParameter( + (Element parameter) { + behavior._escape(parameter.computeType(compiler), compiler); + }); + + behavior._overrideWithAnnotations(method, compiler); + return behavior; + } + + static NativeBehavior ofFieldLoad(Element field, Compiler compiler) { + DartType type = field.computeType(compiler); + var behavior = new NativeBehavior(); + behavior.typesReturned.add(type); + behavior._capture(type, compiler); + behavior._overrideWithAnnotations(field, compiler); + return behavior; + } + + static NativeBehavior ofFieldStore(Element field, Compiler compiler) { + DartType type = field.computeType(compiler); + var behavior = new NativeBehavior(); + behavior._escape(type, compiler); + // We don't override the default behaviour - the annotations apply to + // loading the field. + return behavior; + } + + void _overrideWithAnnotations(Element element, Compiler compiler) { + if (element.metadata.isEmpty) return; + + DartType lookup(String name) { + Element e = element.buildScope().lookup(new SourceString(name)); + if (e == null) return null; + if (e is! ClassElement) return null; + e.ensureResolved(compiler); + return e.computeType(compiler); + } + + NativeEnqueuerBase enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; + var creates = _collect(element, compiler, enqueuer.annotationCreatesClass, + lookup); + var returns = _collect(element, compiler, enqueuer.annotationReturnsClass, + lookup); + + if (creates != null) { + typesInstantiated..clear()..addAll(creates); + } + if (returns != null) { + typesReturned..clear()..addAll(returns); + } + } + + /** + * Returns a list of type constraints from the annotations of + * [annotationClass]. + * Returns `null` if no constraints. + */ + static _collect(Element element, Compiler compiler, Element annotationClass, + lookup(str)) { + var types = null; + for (Link<MetadataAnnotation> link = element.metadata; + !link.isEmpty; + link = link.tail) { + MetadataAnnotation annotation = link.head.ensureResolved(compiler); + var value = annotation.value; + if (value is! ConstructedConstant) continue; + if (value.type is! InterfaceType) continue; + if (!identical(value.type.element, annotationClass)) continue; + + var fields = value.fields; + // TODO(sra): Better validation of the constant. + if (fields.length != 1 || fields[0] is! StringConstant) { + PartialMetadataAnnotation partial = annotation; + compiler.cancel( + 'Annotations needs one string: ${partial.parseNode(compiler)}'); + } + String specString = fields[0].toDartString().slowToString(); + for (final typeString in specString.split('|')) { + var type = _parseType(typeString, compiler, lookup, annotation); + if (types == null) types = []; + types.add(type); + } + } + return types; + } + + /// Models the behavior of having intances of [type] escape from Dart code + /// into native code. + void _escape(DartType type, Compiler compiler) { + type = type.unalias(compiler); + if (type is FunctionType) { + // A function might be called from native code, passing us novel + // parameters. + _escape(type.returnType, compiler); + for (Link<DartType> parameters = type.parameterTypes; + !parameters.isEmpty; + parameters = parameters.tail) { + _capture(parameters.head, compiler); + } + } + } + + /// Models the behavior of Dart code receiving instances and methods of [type] + /// from native code. We usually start the analysis by capturing a native + /// method that has been used. + void _capture(DartType type, Compiler compiler) { + type = type.unalias(compiler); + if (type is FunctionType) { + _capture(type.returnType, compiler); + for (Link<DartType> parameters = type.parameterTypes; + !parameters.isEmpty; + parameters = parameters.tail) { + _escape(parameters.head, compiler); + } + } else { + typesInstantiated.add(type); + } + } + + static _parseType(String typeString, Compiler compiler, + lookup(name), locationNodeOrElement) { + if (typeString == '=Object') return SpecialType.JsObject; + if (typeString == '=List') return SpecialType.JsArray; + if (typeString == 'dynamic') { + return compiler.dynamicClass.computeType(compiler); + } + DartType type = lookup(typeString); + if (type != null) return type; + + int index = typeString.indexOf('<'); + if (index < 1) { + compiler.cancel("Type '$typeString' not found", + node: _errorNode(locationNodeOrElement, compiler)); + } + type = lookup(typeString.substring(0, index)); + if (type != null) { + // TODO(sra): Parse type parameters. + return type; + } + compiler.cancel("Type '$typeString' not found", + node: _errorNode(locationNodeOrElement, compiler)); + } + + static _errorNode(locationNodeOrElement, compiler) { + if (locationNodeOrElement is Node) return locationNodeOrElement; + return locationNodeOrElement.parseNode(compiler); + } +} + +void checkAllowedLibrary(ElementListener listener, Token token) { + LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); + if (!currentLibrary.canUseNative) { + listener.recoverableError("Unexpected token", token: token); + } +} + +Token handleNativeBlockToSkip(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + token = token.next; + if (identical(token.kind, STRING_TOKEN)) { + token = token.next; + } + if (identical(token.stringValue, '{')) { + BeginGroupToken beginGroupToken = token; + token = beginGroupToken.endGroup; + } + return token; +} + +Token handleNativeClassBodyToSkip(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + listener.handleIdentifier(token); + token = token.next; + if (!identical(token.kind, STRING_TOKEN)) { + return listener.unexpected(token); + } + token = token.next; + if (!identical(token.stringValue, '{')) { + return listener.unexpected(token); + } + BeginGroupToken beginGroupToken = token; + token = beginGroupToken.endGroup; + return token; +} + +Token handleNativeClassBody(Listener listener, Token token) { + checkAllowedLibrary(listener, token); + token = token.next; + if (!identical(token.kind, STRING_TOKEN)) { + listener.unexpected(token); + } else { + token = token.next; + } + return token; +} + +Token handleNativeFunctionBody(ElementListener listener, Token token) { + checkAllowedLibrary(listener, token); + Token begin = token; + listener.beginReturnStatement(token); + token = token.next; + bool hasExpression = false; + if (identical(token.kind, STRING_TOKEN)) { + hasExpression = true; + listener.beginLiteralString(token); + listener.endLiteralString(0); + token = token.next; + } + listener.endReturnStatement(hasExpression, begin, token); + // TODO(ngeoffray): expect a ';'. + // Currently there are method with both native marker and Dart body. + return token.next; +} + +SourceString checkForNativeClass(ElementListener listener) { + SourceString nativeTagInfo; + Node node = listener.nodes.head; + if (node != null + && node.asIdentifier() != null + && node.asIdentifier().source.stringValue == 'native') { + nativeTagInfo = node.asIdentifier().token.next.value; + listener.popNode(); + } + return nativeTagInfo; +} + +bool isOverriddenMethod(FunctionElement element, + ClassElement cls, + NativeEmitter nativeEmitter) { + List<ClassElement> subtypes = nativeEmitter.subtypes[cls]; + if (subtypes == null) return false; + for (ClassElement subtype in subtypes) { + if (subtype.lookupLocalMember(element.name) != null) return true; + } + return false; +} + +final RegExp nativeRedirectionRegExp = new RegExp(r'^[a-zA-Z][a-zA-Z_$0-9]*$'); + +void handleSsaNative(SsaBuilder builder, Expression nativeBody) { + Compiler compiler = builder.compiler; + FunctionElement element = builder.work.element; + NativeEmitter nativeEmitter = builder.emitter.nativeEmitter; + + HInstruction convertDartClosure(Element parameter, FunctionType type) { + HInstruction local = builder.localsHandler.readLocal(parameter); + Constant arityConstant = + builder.constantSystem.createInt(type.computeArity()); + HInstruction arity = builder.graph.addConstant(arityConstant); + // TODO(ngeoffray): For static methods, we could pass a method with a + // defined arity. + Element helper = builder.backend.getClosureConverter(); + builder.pushInvokeHelper2(helper, local, arity, HType.UNKNOWN); + HInstruction closure = builder.pop(); + return closure; + } + + // Check which pattern this native method follows: + // 1) foo() native; + // hasBody = false + // 2) foo() native "bar"; + // No longer supported, this is now done with @JSName('foo') and case 1. + // 3) foo() native "return 42"; + // hasBody = true + bool hasBody = false; + assert(element.isNative()); + String nativeMethodName = element.fixedBackendName(); + if (nativeBody != null) { + LiteralString jsCode = nativeBody.asLiteralString(); + String str = jsCode.dartString.slowToString(); + if (nativeRedirectionRegExp.hasMatch(str)) { + compiler.cancel("Deprecated syntax, use @JSName('name') instead.", + node: nativeBody); + } + hasBody = true; + } + + if (!hasBody) { + nativeEmitter.nativeMethods.add(element); + } + + FunctionSignature parameters = element.computeSignature(builder.compiler); + if (!hasBody) { + List<String> arguments = <String>[]; + List<HInstruction> inputs = <HInstruction>[]; + String receiver = ''; + if (element.isInstanceMember()) { + receiver = '#.'; + inputs.add(builder.localsHandler.readThis()); + } + parameters.forEachParameter((Element parameter) { + DartType type = parameter.computeType(compiler).unalias(compiler); + HInstruction input = builder.localsHandler.readLocal(parameter); + if (type is FunctionType) { + // The parameter type is a function type either directly or through + // typedef(s). + input = convertDartClosure(parameter, type); + } + inputs.add(input); + arguments.add('#'); + }); + + String foreignParameters = Strings.join(arguments, ','); + String nativeMethodCall; + if (element.kind == ElementKind.FUNCTION) { + nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)'; + } else if (element.kind == ElementKind.GETTER) { + nativeMethodCall = '$receiver$nativeMethodName'; + } else if (element.kind == ElementKind.SETTER) { + nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters'; + } else { + builder.compiler.internalError('unexpected kind: "${element.kind}"', + element: element); + } + + DartString jsCode = new DartString.literal(nativeMethodCall); + builder.push(new HForeign(jsCode, HType.UNKNOWN, inputs)); + builder.close(new HReturn(builder.pop())).addSuccessor(builder.graph.exit); + } else { + if (parameters.parameterCount != 0) { + compiler.cancel( + 'native "..." syntax is restricted to functions with zero parameters', + node: nativeBody); + } + LiteralString jsCode = nativeBody.asLiteralString(); + builder.push(new HForeign.statement(jsCode.dartString, <HInstruction>[])); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart new file mode 100644 index 0000000..5a0da01 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart
@@ -0,0 +1,596 @@ +// 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. + +/** + * This library contains the infrastructure to parse and integrate patch files. + * + * Three types of elements can be patched: [LibraryElement], [ClassElement], + * [FunctionElement]. Patches are introduced in patch libraries which are loaded + * together with the corresponding origin library. Which libraries that are + * patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found + * in [:lib/_internal/libraries.dart:]. + * + * Patch libraries are parsed like regular library and thus provided with their + * own elements. These elements which are distinct from the elements from the + * patched library and the relation between patched and patch elements is + * established through the [:patch:] and [:origin:] fields found on + * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields + * are set on the patched elements to point to their corresponding patch + * element, and the [:origin:] elements are set on the patch elements to point + * their corresponding patched elements. + * + * The fields [Element.isPatched] and [Element.isPatch] can be used to determine + * whether the [:patch:] or [:origin:] field, respectively, has been set on an + * element, regardless of whether the element is one of the three patchable + * element types or not. + * + * ## Variants of Classes and Functions ## + * + * With patches there are four variants of classes and function: + * + * Regular: A class or function which is not declared in a patch library and + * which has no corresponding patch. + * Origin: A class or function which is not declared in a patch library and + * which has a corresponding patch. Origin functions must use the [:external:] + * modifier and can have no body. Origin classes and functions are also + * called 'patched'. + * Patch: A class or function which is declared in a patch library and which + * has a corresponding origin. Both patch classes and patch functions must use + * the [:patch:] modifier. + * Injected: A class or function (or even field) which is declared in a + * patch library and which has no corresponding origin. An injected element + * cannot use the [:patch:] modifier. Injected elements are never visible from + * outside the patch library in which they have been declared. For this + * reason, injected elements are often declared private and therefore called + * also called 'patch private'. + * + * Examples of the variants is shown in the code below: + * + * // In the origin library: + * class RegularClass { // A regular class. + * void regularMethod() {} // A regular method. + * } + * class PatchedClass { // An origin class. + * int regularField; // A regular field. + * void regularMethod() {} // A regular method. + * external void patchedMethod(); // An origin method. + * } + * + * // In the patch library: + * class _InjectedClass { // An injected class. + * void _injectedMethod() {} // An injected method. + * } + * patch class PatchedClass { // A patch class. + * int _injectedField; { // An injected field. + * patch void patchedMethod() {} // A patch method. + * } + * + * + * ## Declaration and Implementation ## + * + * With patches we have two views on elements: as the 'declaration' which + * introduces the entity and defines its interface, and as the 'implementation' + * which defines the actual implementation of the entity. + * + * Every element has a 'declaration' and an 'implementation' element. For + * regular and injected elements these are the same. For origin elements the + * declaration is the element itself and the implementation is the patch element + * found through its [:patch:] field. For patch elements the implementation is + * the element itself and the declaration is the origin element found through + * its [:origin:] field. The declaration and implementation of any element is + * conveniently available through the [Element.declaration] and + * [Element.implementation] getters. + * + * Most patch-related invariants enforced through-out the compiler are defined + * in terms of 'declaration' and 'implementation', and tested through the + * predicate getters [Element.isDeclaration] and [Element.isImplementation]. + * Patch invariants are stated both in comments and as assertions. + * + * + * ## General invariant guidelines ## + * + * For [LibraryElement] we always use declarations. This means the + * [Element.getLibrary] method will only return library declarations. Patch + * library implementations are only accessed through calls to + * [Element.getImplementationLibrary] which is used to setup the correct + * [Element.enclosingElement] relation between patch/injected elements and the + * patch library. + * + * For [ClassElement] and [FunctionElement] we use declarations for determining + * identity and implementations for work based on the AST nodes, such as + * resolution, type-checking, type inference, building SSA graphs, etc. + * - Worklist only contain declaration elements. + * - Most maps and sets use declarations exclusively, and their individual + * invariants are stated in the field comments. + * - [TreeElements] only map to patch elements from inside a patch library. + * TODO(johnniwinther): Simplify this invariant to use only declarations in + * [TreeElements]. + * - Builders shift between declaration and implementation depending on usages. + * - Compile-time constants use constructor implementation exclusively. + * - Work on function parameters is performed on the declaration of the function + * element. + */ + +library patchparser; + +import "dart:uri"; +import "tree/tree.dart" as tree; +import "dart2jslib.dart" as leg; // CompilerTask, Compiler. +import "apiimpl.dart"; +import "../compiler.dart" as api; +import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners +import "elements/elements.dart"; +import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX; +import 'util/util.dart'; + +class PatchParserTask extends leg.CompilerTask { + PatchParserTask(leg.Compiler compiler): super(compiler); + final String name = "Patching Parser"; + + /** + * Scans a library patch file, applies the method patches and + * injections to the library, and returns a list of class + * patches. + */ + void patchLibrary(leg.LibraryDependencyHandler handler, + Uri patchUri, LibraryElement originLibrary) { + + leg.Script script = compiler.readScript(patchUri, null); + var patchLibrary = new LibraryElementX(script, null, originLibrary); + compiler.withCurrentElement(patchLibrary, () { + handler.registerNewLibrary(patchLibrary); + LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>(); + compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () { + // This patches the elements of the patch library into [library]. + // Injected elements are added directly under the compilation unit. + // Patch elements are stored on the patched functions or classes. + scanLibraryElements(patchLibrary.entryCompilationUnit, imports); + }); + // After scanning declarations, we handle the import tags in the patch. + // TODO(lrn): These imports end up in the original library and are in + // scope for the original methods too. This should be fixed. + compiler.importHelperLibrary(originLibrary); + for (tree.LibraryTag tag in imports.toLink()) { + compiler.libraryLoader.registerLibraryFromTag( + handler, patchLibrary, tag); + } + }); + } + + void scanLibraryElements( + CompilationUnitElement compilationUnit, + LinkBuilder<tree.LibraryTag> imports) { + measure(() { + // TODO(lrn): Possibly recursively handle #source directives in patch. + leg.Script script = compilationUnit.script; + Token tokens = new StringScanner(script.text).tokenize(); + Function idGenerator = compiler.getNextFreeClassId; + PatchListener patchListener = + new PatchElementListener(compiler, + compilationUnit, + idGenerator, + imports); + new PatchParser(patchListener).parseUnit(tokens); + }); + } + + void parsePatchClassNode(PartialClassElement element) { + // Parse [PartialClassElement] using a "patch"-aware parser instead + // of calling its [parseNode] method. + if (element.cachedNode != null) return; + + return measure(() => compiler.withCurrentElement(element, () { + PatchMemberListener listener = new PatchMemberListener(compiler, element); + Parser parser = new PatchClassElementParser(listener); + Token token = parser.parseTopLevelDeclaration(element.beginToken); + assert(identical(token, element.endToken.next)); + element.cachedNode = listener.popNode(); + assert(listener.nodes.isEmpty); + + Link<Element> patches = element.localMembers; + applyContainerPatch(element.origin, patches); + })); + } + + void applyContainerPatch(ClassElement originClass, + Link<Element> patches) { + for (Element patch in patches) { + if (!isPatchElement(patch)) continue; + + Element origin = originClass.localLookup(patch.name); + patchElement(compiler, origin, patch); + } + } +} + +/** + * Extension of the [Listener] interface to handle the extra "patch" pseudo- + * keyword in patch files. + * Patch files shouldn't have a type named "patch". + */ +abstract class PatchListener extends Listener { + void beginPatch(Token patch); + void endPatch(Token patch); +} + +/** + * Partial parser that extends the top-level and class grammars to allow the + * word "patch" in front of some declarations. + */ +class PatchParser extends PartialParser { + PatchParser(PatchListener listener) : super(listener); + + PatchListener get patchListener => listener; + + bool isPatch(Token token) { + return token.stringValue == null && + token.slowToString() == "patch"; + } + + /** + * Parse top-level declarations, and allow "patch" in front of functions + * and classes. + */ + Token parseTopLevelDeclaration(Token token) { + if (!isPatch(token)) { + return super.parseTopLevelDeclaration(token); + } + Token patch = token; + token = token.next; + String value = token.stringValue; + if (identical(value, 'interface') + || identical(value, 'typedef') + || identical(value, '#') + || identical(value, 'abstract')) { + // At the top level, you can only patch functions and classes. + // Patch classes and functions can't be marked abstract. + return listener.unexpected(patch); + } + patchListener.beginPatch(patch); + token = super.parseTopLevelDeclaration(token); + patchListener.endPatch(patch); + return token; + } + + /** + * Parse a class member. + * If the member starts with "patch", it's a member override. + * Only methods can be overridden, including constructors, getters and + * setters, but not fields. If "patch" occurs in front of a field, the error + * is caught elsewhere. + */ + Token parseMember(Token token) { + if (!isPatch(token)) { + return super.parseMember(token); + } + Token patch = token; + patchListener.beginPatch(patch); + token = super.parseMember(token.next); + patchListener.endPatch(patch); + return token; + } +} + +/** + * Partial parser for patch files that also handles the members of class + * declarations. + */ +class PatchClassElementParser extends PatchParser { + PatchClassElementParser(PatchListener listener) : super(listener); + + Token parseClassBody(Token token) => fullParseClassBody(token); +} + +/** + * Extension of [ElementListener] for parsing patch files. + */ +class PatchElementListener extends ElementListener implements PatchListener { + final LinkBuilder<tree.LibraryTag> imports; + bool isMemberPatch = false; + bool isClassPatch = false; + + PatchElementListener(leg.DiagnosticListener listener, + CompilationUnitElement patchElement, + int idGenerator(), + this.imports) + : super(listener, patchElement, idGenerator); + + MetadataAnnotation popMetadataHack() { + // TODO(ahe): Remove this method. + popNode(); // Discard null. + return new PatchMetadataAnnotation(); + } + + void beginPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = true; + } else { + isMemberPatch = true; + } + handleIdentifier(token); + } + + void endPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = false; + } else { + isMemberPatch = false; + } + } + + /** + * Allow script tags (import only, the parser rejects the rest for now) in + * patch files. The import tags will be added to the library. + */ + bool allowLibraryTags() => true; + + void addLibraryTag(tree.LibraryTag tag) { + super.addLibraryTag(tag); + imports.addLast(tag); + } + + void pushElement(Element patch) { + if (isMemberPatch || (isClassPatch && patch is ClassElement)) { + // Apply patch. + patch.addMetadata(popMetadataHack()); + LibraryElement originLibrary = compilationUnitElement.getLibrary(); + assert(originLibrary.isPatched); + Element origin = originLibrary.localLookup(patch.name); + patchElement(listener, origin, patch); + } + super.pushElement(patch); + } +} + +/** + * Extension of [MemberListener] for parsing patch class bodies. + */ +class PatchMemberListener extends MemberListener implements PatchListener { + bool isMemberPatch = false; + bool isClassPatch = false; + PatchMemberListener(leg.DiagnosticListener listener, + Element enclosingElement) + : super(listener, enclosingElement); + + MetadataAnnotation popMetadataHack() { + // TODO(ahe): Remove this method. + popNode(); // Discard null. + return new PatchMetadataAnnotation(); + } + + void beginPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = true; + } else { + isMemberPatch = true; + } + handleIdentifier(token); + } + + void endPatch(Token token) { + if (identical(token.next.stringValue, "class")) { + isClassPatch = false; + } else { + isMemberPatch = false; + } + } + + void addMember(Element element) { + if (isMemberPatch || (isClassPatch && element is ClassElement)) { + element.addMetadata(popMetadataHack()); + } + super.addMember(element); + } +} + +// TODO(ahe): Get rid of this class. +class PatchMetadataAnnotation extends MetadataAnnotationX { + final leg.Constant value = null; + + PatchMetadataAnnotation() : super(STATE_DONE); + + Token get beginToken => null; + Token get endToken => null; +} + +void patchElement(leg.DiagnosticListener listener, + Element origin, + Element patch) { + if (origin == null) { + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_NON_EXISTING.error({'name': patch.name}), + api.Diagnostic.ERROR); + return; + } + if (!(origin.isClass() || + origin.isConstructor() || + origin.isFunction() || + origin.isAbstractField())) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NONPATCHABLE.error(), + api.Diagnostic.ERROR); + return; + } + if (patch.isClass()) { + tryPatchClass(listener, origin, patch); + } else if (patch.isGetter()) { + tryPatchGetter(listener, origin, patch); + } else if (patch.isSetter()) { + tryPatchSetter(listener, origin, patch); + } else if (patch.isConstructor()) { + tryPatchConstructor(listener, origin, patch); + } else if(patch.isFunction()) { + tryPatchFunction(listener, origin, patch); + } else { + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_NONPATCHABLE.error(), + api.Diagnostic.ERROR); + } +} + +void tryPatchClass(leg.DiagnosticListener listener, + Element origin, + ClassElement patch) { + if (!origin.isClass()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_CLASS.error({'className': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_CLASS.error({'className': patch.name}), + api.Diagnostic.INFO); + return; + } + patchClass(listener, origin, patch); +} + +void patchClass(leg.DiagnosticListener listener, + ClassElement origin, + ClassElement patch) { + if (origin.isPatched) { + listener.internalErrorOnElement( + origin, "Patching the same class more than once."); + } + // TODO(johnniwinther): Change to functions on the ElementX class. + origin.patch = patch; + patch.origin = origin; +} + +void tryPatchGetter(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isAbstractField()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_GETTER.error({'name': origin.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.INFO); + return; + } + AbstractFieldElement originField = origin; + if (originField.getter == null) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, originField.getter, patch); +} + +void tryPatchSetter(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isAbstractField()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_SETTER.error({'name': origin.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.INFO); + return; + } + AbstractFieldElement originField = origin; + if (originField.setter == null) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, originField.setter, patch); +} + +void tryPatchConstructor(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isConstructor()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_CONSTRUCTOR.error( + {'constructorName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error( + {'constructorName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, origin, patch); +} + +void tryPatchFunction(leg.DiagnosticListener listener, + Element origin, + FunctionElement patch) { + if (!origin.isFunction()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_FUNCTION.error({'functionName': patch.name}), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( + {'functionName': patch.name}), + api.Diagnostic.INFO); + return; + } + patchFunction(listener, origin, patch); +} + +void patchFunction(leg.DiagnosticListener listener, + FunctionElement origin, + FunctionElement patch) { + if (!origin.modifiers.isExternal()) { + listener.reportMessage( + listener.spanFromSpannable(origin), + leg.MessageKind.PATCH_NON_EXTERNAL.error(), + api.Diagnostic.ERROR); + listener.reportMessage( + listener.spanFromSpannable(patch), + leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( + {'functionName': patch.name}), + api.Diagnostic.INFO); + return; + } + if (origin.isPatched) { + listener.internalErrorOnElement(origin, + "Trying to patch a function more than once."); + } + if (origin.cachedNode != null) { + listener.internalErrorOnElement(origin, + "Trying to patch an already compiled function."); + } + // Don't just assign the patch field. This also updates the cachedNode. + // TODO(johnniwinther): Change to functions on the ElementX class. + origin.setPatch(patch); + patch.origin = origin; +} + +// TODO(johnniwinther): Add unittest when patch is (real) metadata. +bool isPatchElement(Element element) { + // TODO(lrn): More checks needed if we introduce metadata for real. + // In that case, it must have the identifier "native" as metadata. + for (Link link = element.metadata; !link.isEmpty; link = link.tail) { + if (link.head is PatchMetadataAnnotation) return true; + } + return false; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart new file mode 100644 index 0000000..e62aa70 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart
@@ -0,0 +1,3663 @@ +// 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. + +part of resolution; + +abstract class TreeElements { + Element operator[](Node node); + Selector getSelector(Send send); + DartType getType(Node node); + bool isParameterChecked(Element element); + Set<Node> get superUses; +} + +class TreeElementMapping implements TreeElements { + final Element currentElement; + final Map<Node, Selector> selectors = new LinkedHashMap<Node, Selector>(); + final Map<Node, DartType> types = new LinkedHashMap<Node, DartType>(); + final Set<Element> checkedParameters = new Set<Element>(); + final Set<Node> superUses = new Set<Node>(); + + TreeElementMapping(this.currentElement); + + operator []=(Node node, Element element) { + assert(invariant(node, () { + if (node is FunctionExpression) { + return !node.modifiers.isExternal(); + } + return true; + })); + // TODO(johnniwinther): Simplify this invariant to use only declarations in + // [TreeElements]. + assert(invariant(node, () { + if (!element.isErroneous() && currentElement != null && element.isPatch) { + return currentElement.getImplementationLibrary().isPatch; + } + return true; + })); + // TODO(ahe): Investigate why the invariant below doesn't hold. + // assert(invariant(node, + // getTreeElement(node) == element || + // getTreeElement(node) == null, + // message: '${getTreeElement(node)}; $element')); + + setTreeElement(node, element); + } + + operator [](Node node) => getTreeElement(node); + + void remove(Node node) { + setTreeElement(node, null); + } + + void setType(Node node, DartType type) { + types[node] = type; + } + + DartType getType(Node node) => types[node]; + + void setSelector(Node node, Selector selector) { + selectors[node] = selector; + } + + Selector getSelector(Node node) => selectors[node]; + + bool isParameterChecked(Element element) { + return checkedParameters.contains(element); + } +} + +class ResolverTask extends CompilerTask { + ResolverTask(Compiler compiler) : super(compiler); + + String get name => 'Resolver'; + + TreeElements resolve(Element element) { + return measure(() { + if (Elements.isErroneousElement(element)) return null; + + for (MetadataAnnotation metadata in element.metadata) { + metadata.ensureResolved(compiler); + } + + ElementKind kind = element.kind; + if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR) || + identical(kind, ElementKind.FUNCTION) || + identical(kind, ElementKind.GETTER) || + identical(kind, ElementKind.SETTER)) { + return resolveMethodElement(element); + } + + if (identical(kind, ElementKind.FIELD)) return resolveField(element); + + if (identical(kind, ElementKind.PARAMETER) || + identical(kind, ElementKind.FIELD_PARAMETER)) { + return resolveParameter(element); + } + if (element.isClass()) { + ClassElement cls = element; + cls.ensureResolved(compiler); + return null; + } else if (element.isTypedef()) { + TypedefElement typdef = element; + resolveTypedef(typdef); + return null; + } else if (element.isTypeVariable()) { + element.computeType(compiler); + return null; + } + + compiler.unimplemented("resolve($element)", + node: element.parseNode(compiler)); + }); + } + + String constructorNameForDiagnostics(SourceString className, + SourceString constructorName) { + String classNameString = className.slowToString(); + String constructorNameString = constructorName.slowToString(); + return (constructorName == const SourceString('')) + ? classNameString + : "$classNameString.$constructorNameString"; + } + + void resolveRedirectingConstructor(InitializerResolver resolver, + Node node, + FunctionElement constructor, + FunctionElement redirection) { + Set<FunctionElement> seen = new Set<FunctionElement>(); + seen.add(constructor); + while (redirection != null) { + if (seen.contains(redirection)) { + resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE); + return; + } + seen.add(redirection); + + if (redirection.isPatched) { + checkMatchingPatchSignatures(constructor, redirection.patch); + redirection = redirection.patch; + } + redirection = resolver.visitor.resolveConstructorRedirection(redirection); + } + } + + void checkMatchingPatchParameters(FunctionElement origin, + Link<Element> originParameters, + Link<Element> patchParameters) { + while (!originParameters.isEmpty) { + Element originParameter = originParameters.head; + Element patchParameter = patchParameters.head; + // Hack: Use unparser to test parameter equality. This only works because + // we are restricting patch uses and the approach cannot be used + // elsewhere. + String originParameterText = + originParameter.parseNode(compiler).toString(); + String patchParameterText = + patchParameter.parseNode(compiler).toString(); + if (originParameterText != patchParameterText) { + error(originParameter.parseNode(compiler), + MessageKind.PATCH_PARAMETER_MISMATCH, + {'methodName': origin.name, + 'originParameter': originParameterText, + 'patchParameter': patchParameterText}); + } + + originParameters = originParameters.tail; + patchParameters = patchParameters.tail; + } + } + + void checkMatchingPatchSignatures(FunctionElement origin, + FunctionElement patch) { + // TODO(johnniwinther): Show both origin and patch locations on errors. + FunctionExpression originTree = compiler.withCurrentElement(origin, () { + return origin.parseNode(compiler); + }); + FunctionSignature originSignature = compiler.withCurrentElement(origin, () { + return origin.computeSignature(compiler); + }); + FunctionExpression patchTree = compiler.withCurrentElement(patch, () { + return patch.parseNode(compiler); + }); + FunctionSignature patchSignature = compiler.withCurrentElement(patch, () { + return patch.computeSignature(compiler); + }); + + if (originSignature.returnType != patchSignature.returnType) { + compiler.withCurrentElement(patch, () { + Node errorNode = + patchTree.returnType != null ? patchTree.returnType : patchTree; + error(errorNode, MessageKind.PATCH_RETURN_TYPE_MISMATCH, + {'methodName': origin.name, + 'originReturnType': originSignature.returnType, + 'patchReturnType': patchSignature.returnType}); + }); + } + if (originSignature.requiredParameterCount != + patchSignature.requiredParameterCount) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH, + {'methodName': origin.name, + 'originParameterCount': originSignature.requiredParameterCount, + 'patchParameterCount': patchSignature.requiredParameterCount}); + }); + } else { + checkMatchingPatchParameters(origin, + originSignature.requiredParameters, + patchSignature.requiredParameters); + } + if (originSignature.optionalParameterCount != 0 && + patchSignature.optionalParameterCount != 0) { + if (originSignature.optionalParametersAreNamed != + patchSignature.optionalParametersAreNamed) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH, + {'methodName': origin.name}); + }); + } + } + if (originSignature.optionalParameterCount != + patchSignature.optionalParameterCount) { + compiler.withCurrentElement(patch, () { + error(patchTree, + MessageKind.PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH, + {'methodName': origin.name, + 'originParameterCount': originSignature.optionalParameterCount, + 'patchParameterCount': patchSignature.optionalParameterCount}); + }); + } else { + checkMatchingPatchParameters(origin, + originSignature.optionalParameters, + patchSignature.optionalParameters); + } + } + + TreeElements resolveMethodElement(FunctionElement element) { + assert(invariant(element, element.isDeclaration)); + return compiler.withCurrentElement(element, () { + bool isConstructor = + identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR); + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(element); + if (elements != null) { + assert(isConstructor); + return elements; + } + if (element.isPatched) { + checkMatchingPatchSignatures(element, element.patch); + element = element.patch; + } + return compiler.withCurrentElement(element, () { + FunctionExpression tree = element.parseNode(compiler); + if (tree.modifiers.isExternal()) { + error(tree, MessageKind.PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION); + return; + } + if (isConstructor) { + if (tree.returnType != null) { + error(tree, MessageKind.CONSTRUCTOR_WITH_RETURN_TYPE); + } + resolveConstructorImplementation(element, tree); + } + ResolverVisitor visitor = visitorFor(element); + visitor.useElement(tree, element); + visitor.setupFunction(tree, element); + + if (isConstructor) { + // Even if there is no initializer list we still have to do the + // resolution in case there is an implicit super constructor call. + InitializerResolver resolver = new InitializerResolver(visitor); + FunctionElement redirection = + resolver.resolveInitializers(element, tree); + if (redirection != null) { + resolveRedirectingConstructor(resolver, tree, element, redirection); + } + } else if (tree.initializers != null) { + error(tree, MessageKind.FUNCTION_WITH_INITIALIZER); + } + visitBody(visitor, tree.body); + + // Get the resolution tree and check that the resolved + // function doesn't use 'super' if it is mixed into another + // class. This is the part of the 'super' mixin check that + // happens when a function is resolved after the mixin + // application has been performed. + TreeElements resolutionTree = visitor.mapping; + ClassElement enclosingClass = element.getEnclosingClass(); + if (enclosingClass != null) { + Set<MixinApplicationElement> mixinUses = + compiler.world.mixinUses[enclosingClass]; + if (mixinUses != null) { + ClassElement mixin = enclosingClass; + for (MixinApplicationElement mixinApplication in mixinUses) { + checkMixinSuperUses(resolutionTree, mixinApplication, mixin); + } + } + } + return resolutionTree; + }); + }); + } + + /// This method should only be used by this library (or tests of + /// this library). + ResolverVisitor visitorFor(Element element) { + var mapping = new TreeElementMapping(element); + return new ResolverVisitor(compiler, element, mapping); + } + + void visitBody(ResolverVisitor visitor, Statement body) { + visitor.visit(body); + } + + void resolveConstructorImplementation(FunctionElement constructor, + FunctionExpression node) { + if (!identical(constructor.defaultImplementation, constructor)) return; + ClassElement intrface = constructor.getEnclosingClass(); + if (!intrface.isInterface()) return; + DartType defaultType = intrface.defaultClass; + if (defaultType == null) { + error(node, MessageKind.NO_DEFAULT_CLASS, + {'interfaceName': intrface.name}); + } + ClassElement defaultClass = defaultType.element; + defaultClass.ensureResolved(compiler); + assert(defaultClass.resolutionState == STATE_DONE); + assert(defaultClass.supertypeLoadState == STATE_DONE); + if (defaultClass.isInterface()) { + error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': defaultClass.name}); + } + // We have now established the following: + // [intrface] is an interface, let's say "MyInterface". + // [defaultClass] is a class, let's say "MyClass". + + Selector selector; + // If the default class implements the interface then we must use the + // default class' name. Otherwise we look for a factory with the name + // of the interface. + if (defaultClass.implementsInterface(intrface)) { + var constructorNameString = constructor.name.slowToString(); + // Create selector based on constructor.name but where interface + // is replaced with default class name. + // TODO(ahe): Don't use string manipulations here. + int classNameSeparatorIndex = constructorNameString.indexOf('\$'); + if (classNameSeparatorIndex < 0) { + selector = new Selector.callDefaultConstructor( + defaultClass.getLibrary()); + } else { + selector = new Selector.callConstructor( + new SourceString( + constructorNameString.substring(classNameSeparatorIndex + 1)), + defaultClass.getLibrary()); + } + constructor.defaultImplementation = + defaultClass.lookupConstructor(selector); + } else { + selector = + new Selector.callConstructor(constructor.name, + defaultClass.getLibrary()); + constructor.defaultImplementation = + defaultClass.lookupFactoryConstructor(selector); + } + if (constructor.defaultImplementation == null) { + // We failed to find a constructor named either + // "MyInterface.name" or "MyClass.name". + // TODO(aprelev@gmail.com): Use constructorNameForDiagnostics in + // the error message below. + error(node, + MessageKind.CANNOT_FIND_CONSTRUCTOR2, + {'constructorName': selector.name, 'className': defaultClass.name}); + } + } + + TreeElements resolveField(VariableElement element) { + Node tree = element.parseNode(compiler); + if(element.modifiers.isStatic() && element.variables.isTopLevel()) { + error(element.modifiers.getStatic(), + MessageKind.TOP_LEVEL_VARIABLE_DECLARED_STATIC); + } + ResolverVisitor visitor = visitorFor(element); + initializerDo(tree, visitor.visit); + + // Perform various checks as side effect of "computing" the type. + element.computeType(compiler); + + return visitor.mapping; + } + + TreeElements resolveParameter(Element element) { + Node tree = element.parseNode(compiler); + ResolverVisitor visitor = visitorFor(element.enclosingElement); + initializerDo(tree, visitor.visit); + return visitor.mapping; + } + + DartType resolveTypeAnnotation(Element element, TypeAnnotation annotation) { + DartType type = resolveReturnType(element, annotation); + if (type == compiler.types.voidType) { + error(annotation, MessageKind.VOID_NOT_ALLOWED); + } + return type; + } + + DartType resolveReturnType(Element element, TypeAnnotation annotation) { + if (annotation == null) return compiler.types.dynamicType; + DartType result = visitorFor(element).resolveTypeAnnotation(annotation); + if (result == null) { + // TODO(karklose): warning. + return compiler.types.dynamicType; + } + return result; + } + + /** + * Load and resolve the supertypes of [cls]. + * + * Warning: do not call this method directly. It should only be + * called by [resolveClass] and [ClassSupertypeResolver]. + */ + void loadSupertypes(ClassElement cls, Spannable from) { + compiler.withCurrentElement(cls, () => measure(() { + if (cls.supertypeLoadState == STATE_DONE) return; + if (cls.supertypeLoadState == STATE_STARTED) { + compiler.reportErrorCode(from, MessageKind.CYCLIC_CLASS_HIERARCHY, + {'className': cls.name}); + cls.supertypeLoadState = STATE_DONE; + cls.allSupertypes = const Link<DartType>().prepend( + compiler.objectClass.computeType(compiler)); + // TODO(ahe): We should also set cls.supertype here to avoid + // creating a malformed class hierarchy. + return; + } + cls.supertypeLoadState = STATE_STARTED; + compiler.withCurrentElement(cls, () { + // TODO(ahe): Cache the node in cls. + cls.parseNode(compiler).accept( + new ClassSupertypeResolver(compiler, cls)); + if (cls.supertypeLoadState != STATE_DONE) { + cls.supertypeLoadState = STATE_DONE; + } + }); + })); + } + + // TODO(johnniwinther): Remove this queue when resolution has been split into + // syntax and semantic resolution. + ClassElement currentlyResolvedClass; + Queue<ClassElement> pendingClassesToBeResolved = new Queue<ClassElement>(); + + /** + * Resolve the class [element]. + * + * Before calling this method, [element] was constructed by the + * scanner and most fields are null or empty. This method fills in + * these fields and also ensure that the supertypes of [element] are + * resolved. + * + * Warning: Do not call this method directly. Instead use + * [:element.ensureResolved(compiler):]. + */ + void resolveClass(ClassElement element) { + ClassElement previousResolvedClass = currentlyResolvedClass; + currentlyResolvedClass = element; + resolveClassInternal(element); + if (previousResolvedClass == null) { + while (!pendingClassesToBeResolved.isEmpty) { + pendingClassesToBeResolved.removeFirst().ensureResolved(compiler); + } + } + currentlyResolvedClass = previousResolvedClass; + } + + void _ensureClassWillBeResolved(ClassElement element) { + if (currentlyResolvedClass == null) { + element.ensureResolved(compiler); + } else { + pendingClassesToBeResolved.add(element); + } + } + + void resolveClassInternal(ClassElement element) { + if (!element.isPatch) { + compiler.withCurrentElement(element, () => measure(() { + assert(element.resolutionState == STATE_NOT_STARTED); + element.resolutionState = STATE_STARTED; + Node tree = element.parseNode(compiler); + loadSupertypes(element, tree); + + ClassResolverVisitor visitor = + new ClassResolverVisitor(compiler, element); + visitor.visit(tree); + element.resolutionState = STATE_DONE; + })); + if (element.isPatched) { + // Ensure handling patch after origin. + element.patch.ensureResolved(compiler); + } + } else { // Handle patch classes: + element.resolutionState = STATE_STARTED; + // Ensure handling origin before patch. + element.origin.ensureResolved(compiler); + // Ensure that the type is computed. + element.computeType(compiler); + // Copy class hiearchy from origin. + element.supertype = element.origin.supertype; + element.defaultClass = element.origin.defaultClass; + element.interfaces = element.origin.interfaces; + element.allSupertypes = element.origin.allSupertypes; + // Stepwise assignment to ensure invariant. + element.supertypeLoadState = STATE_STARTED; + element.supertypeLoadState = STATE_DONE; + element.resolutionState = STATE_DONE; + // TODO(johnniwinther): Check matching type variables and + // empty extends/implements clauses. + } + for (MetadataAnnotation metadata in element.metadata) { + metadata.ensureResolved(compiler); + } + } + + void checkClass(ClassElement element) { + if (element.isMixinApplication) { + checkMixinApplication(element); + } else { + checkClassMembers(element); + } + } + + void checkMixinApplication(MixinApplicationElement mixinApplication) { + Modifiers modifiers = mixinApplication.modifiers; + int illegalFlags = modifiers.flags & ~Modifiers.FLAG_ABSTRACT; + if (illegalFlags != 0) { + Modifiers illegalModifiers = new Modifiers.withFlags(null, illegalFlags); + compiler.reportErrorCode( + modifiers, + MessageKind.ILLEGAL_MIXIN_APPLICATION_MODIFIERS, + {'modifiers': illegalModifiers}); + } + + // In case of cyclic mixin applications, the mixin chain will have + // been cut. If so, we have already reported the error to the + // user so we just return from here. + ClassElement mixin = mixinApplication.mixin; + if (mixin == null) return; + + // Check that the mixed in class has Object as its superclass. + if (!mixin.superclass.isObject(compiler)) { + compiler.reportErrorCode(mixin, MessageKind.ILLEGAL_MIXIN_SUPERCLASS); + } + + // Check that the mixed in class doesn't have any constructors and + // make sure we aren't mixing in methods that use 'super'. + mixin.forEachLocalMember((Element member) { + if (member.isGenerativeConstructor() && !member.isSynthesized) { + compiler.reportErrorCode(member, MessageKind.ILLEGAL_MIXIN_CONSTRUCTOR); + } else { + // Get the resolution tree and check that the resolved member + // doesn't use 'super'. This is the part of the 'super' mixin + // check that happens when a function is resolved before the + // mixin application has been performed. + checkMixinSuperUses( + compiler.enqueuer.resolution.resolvedElements[member], + mixinApplication, + mixin); + } + }); + } + + void checkMixinSuperUses(TreeElements resolutionTree, + MixinApplicationElement mixinApplication, + ClassElement mixin) { + if (resolutionTree == null) return; + Set<Node> superUses = resolutionTree.superUses; + if (superUses.isEmpty) return; + compiler.reportErrorCode(mixinApplication, + MessageKind.ILLEGAL_MIXIN_WITH_SUPER, + {'className': mixin.name}); + // Show the user the problematic uses of 'super' in the mixin. + for (Node use in superUses) { + CompilationError error = MessageKind.ILLEGAL_MIXIN_SUPER_USE.error(); + compiler.reportMessage(compiler.spanFromNode(use), + error, Diagnostic.INFO); + } + } + + void checkClassMembers(ClassElement cls) { + assert(invariant(cls, cls.isDeclaration)); + if (cls.isObject(compiler)) return; + // TODO(johnniwinther): Should this be done on the implementation element as + // well? + cls.forEachMember((holder, member) { + compiler.withCurrentElement(member, () { + // Perform various checks as side effect of "computing" the type. + member.computeType(compiler); + + // Check modifiers. + if (member.isFunction() && member.modifiers.isFinal()) { + compiler.reportErrorCode( + member, MessageKind.ILLEGAL_FINAL_METHOD_MODIFIER); + } + if (member.isConstructor()) { + final mismatchedFlagsBits = + member.modifiers.flags & + (Modifiers.FLAG_STATIC | Modifiers.FLAG_ABSTRACT); + if (mismatchedFlagsBits != 0) { + final mismatchedFlags = + new Modifiers.withFlags(null, mismatchedFlagsBits); + compiler.reportErrorCode( + member, + MessageKind.ILLEGAL_CONSTRUCTOR_MODIFIERS, + {'modifiers': mismatchedFlags}); + } + checkConstructorNameHack(holder, member); + } + checkAbstractField(member); + checkValidOverride(member, cls.lookupSuperMember(member.name)); + checkUserDefinableOperator(member); + }); + }); + } + + // TODO(ahe): Remove this method. It is only needed while we store + // constructor names as ClassName$id. Once we start storing + // constructors as just id, this will be caught by the general + // mechanism for duplicate members. + /// Check that a constructor name does not conflict with a member. + void checkConstructorNameHack(ClassElement holder, FunctionElement member) { + // If the name of the constructor is the same as the name of the + // class, there cannot be a problem. + if (member.name == holder.name) return; + + SourceString name = + Elements.deconstructConstructorName(member.name, holder); + + // If the name could not be deconstructed, this is is from a + // factory method from a deprecated interface implementation. + if (name == null) return; + + Element otherMember = holder.lookupLocalMember(name); + if (otherMember != null) { + if (compiler.onDeprecatedFeature(member, 'conflicting constructor')) { + compiler.reportMessage( + compiler.spanFromElement(otherMember), + MessageKind.GENERIC.error({'text': 'This member conflicts with a' + ' constructor.'}), + Diagnostic.INFO); + } + } + } + + void checkAbstractField(Element member) { + // Only check for getters. The test can only fail if there is both a setter + // and a getter with the same name, and we only need to check each abstract + // field once, so we just ignore setters. + if (!member.isGetter()) return; + + // Find the associated abstract field. + ClassElement classElement = member.getEnclosingClass(); + Element lookupElement = classElement.lookupLocalMember(member.name); + if (lookupElement == null) { + compiler.internalErrorOnElement(member, + "No abstract field for accessor"); + } else if (!identical(lookupElement.kind, ElementKind.ABSTRACT_FIELD)) { + compiler.internalErrorOnElement( + member, "Inaccessible abstract field for accessor"); + } + AbstractFieldElement field = lookupElement; + + if (field.getter == null) return; + if (field.setter == null) return; + int getterFlags = field.getter.modifiers.flags | Modifiers.FLAG_ABSTRACT; + int setterFlags = field.setter.modifiers.flags | Modifiers.FLAG_ABSTRACT; + if (!identical(getterFlags, setterFlags)) { + final mismatchedFlags = + new Modifiers.withFlags(null, getterFlags ^ setterFlags); + compiler.reportErrorCode( + field.getter, + MessageKind.GETTER_MISMATCH, + {'modifiers': mismatchedFlags}); + compiler.reportErrorCode( + field.setter, + MessageKind.SETTER_MISMATCH, + {'modifiers': mismatchedFlags}); + } + } + + void checkUserDefinableOperator(Element member) { + FunctionElement function = member.asFunctionElement(); + if (function == null) return; + String value = member.name.stringValue; + if (value == null) return; + if (!(isUserDefinableOperator(value) || identical(value, 'unary-'))) return; + + bool isMinus = false; + int requiredParameterCount; + MessageKind messageKind; + FunctionSignature signature = function.computeSignature(compiler); + if (identical(value, 'unary-')) { + isMinus = true; + messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; + requiredParameterCount = 0; + } else if (isMinusOperator(value)) { + isMinus = true; + messageKind = MessageKind.MINUS_OPERATOR_BAD_ARITY; + requiredParameterCount = 1; + } else if (isUnaryOperator(value)) { + messageKind = MessageKind.UNARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 0; + } else if (isBinaryOperator(value)) { + messageKind = MessageKind.BINARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 1; + } else if (isTernaryOperator(value)) { + messageKind = MessageKind.TERNARY_OPERATOR_BAD_ARITY; + requiredParameterCount = 2; + } else { + compiler.internalErrorOnElement(function, + 'Unexpected user defined operator $value'); + } + checkArity(function, requiredParameterCount, messageKind, isMinus); + } + + void checkArity(FunctionElement function, + int requiredParameterCount, MessageKind messageKind, + bool isMinus) { + FunctionExpression node = function.parseNode(compiler); + FunctionSignature signature = function.computeSignature(compiler); + if (signature.requiredParameterCount != requiredParameterCount) { + Node errorNode = node; + if (node.parameters != null) { + if (isMinus || + signature.requiredParameterCount < requiredParameterCount) { + // If there are too few parameters, point to the whole parameter list. + // For instance + // + // int operator +() {} + // ^^ + // + // int operator []=(value) {} + // ^^^^^^^ + // + // For operator -, always point the whole parameter list, like + // + // int operator -(a, b) {} + // ^^^^^^ + // + // instead of + // + // int operator -(a, b) {} + // ^ + // + // since the correction might not be to remove 'b' but instead to + // remove 'a, b'. + errorNode = node.parameters; + } else { + errorNode = node.parameters.nodes.skip(requiredParameterCount).head; + } + } + compiler.reportErrorCode( + errorNode, messageKind, {'operatorName': function.name}); + } + if (signature.optionalParameterCount != 0) { + Node errorNode = + node.parameters.nodes.skip(signature.requiredParameterCount).head; + if (signature.optionalParametersAreNamed) { + compiler.reportErrorCode( + errorNode, + MessageKind.OPERATOR_NAMED_PARAMETERS, + {'operatorName': function.name}); + } else { + compiler.reportErrorCode( + errorNode, + MessageKind.OPERATOR_OPTIONAL_PARAMETERS, + {'operatorName': function.name}); + } + } + } + + reportErrorWithContext(Element errorneousElement, + MessageKind errorMessage, + Element contextElement, + MessageKind contextMessage) { + compiler.reportErrorCode( + errorneousElement, + errorMessage, + {'memberName': contextElement.name, + 'className': contextElement.getEnclosingClass().name}); + compiler.reportMessage( + compiler.spanFromElement(contextElement), + contextMessage.error(), + Diagnostic.INFO); + } + + void checkValidOverride(Element member, Element superMember) { + if (superMember == null) return; + if (member.modifiers.isStatic()) { + reportErrorWithContext( + member, MessageKind.NO_STATIC_OVERRIDE, + superMember, MessageKind.NO_STATIC_OVERRIDE_CONT); + } else { + FunctionElement superFunction = superMember.asFunctionElement(); + FunctionElement function = member.asFunctionElement(); + if (superFunction == null || superFunction.isAccessor()) { + // Field or accessor in super. + if (function != null && !function.isAccessor()) { + // But a plain method in this class. + reportErrorWithContext( + member, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD, + superMember, MessageKind.CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT); + } + } else { + // Instance method in super. + if (function == null || function.isAccessor()) { + // But a field (or accessor) in this class. + reportErrorWithContext( + member, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD, + superMember, MessageKind.CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT); + } else { + // Both are plain instance methods. + if (superFunction.requiredParameterCount(compiler) != + function.requiredParameterCount(compiler)) { + reportErrorWithContext( + member, + MessageKind.BAD_ARITY_OVERRIDE, + superMember, + MessageKind.BAD_ARITY_OVERRIDE_CONT); + } + // TODO(ahe): Check optional parameters. + } + } + } + } + + FunctionSignature resolveSignature(FunctionElement element) { + return compiler.withCurrentElement(element, () { + FunctionExpression node = + compiler.parser.measure(() => element.parseNode(compiler)); + return measure(() => SignatureResolver.analyze( + compiler, node.parameters, node.returnType, element)); + }); + } + + FunctionSignature resolveFunctionExpression(Element element, + FunctionExpression node) { + return measure(() => SignatureResolver.analyze( + compiler, node.parameters, node.returnType, element)); + } + + void resolveTypedef(TypedefElement element) { + if (element.isResolved || element.isBeingResolved) return; + element.isBeingResolved = true; + return compiler.withCurrentElement(element, () { + measure(() { + Typedef node = + compiler.parser.measure(() => element.parseNode(compiler)); + TypedefResolverVisitor visitor = + new TypedefResolverVisitor(compiler, element); + visitor.visit(node); + + element.isBeingResolved = false; + element.isResolved = true; + }); + }); + } + + FunctionType computeFunctionType(Element element, + FunctionSignature signature) { + var parameterTypes = new LinkBuilder<DartType>(); + for (Element parameter in signature.requiredParameters) { + parameterTypes.addLast(parameter.computeType(compiler)); + } + var optionalParameterTypes = const Link<DartType>(); + var namedParameters = const Link<SourceString>(); + var namedParameterTypes = const Link<DartType>(); + if (signature.optionalParametersAreNamed) { + var namedParametersBuilder = new LinkBuilder<SourceString>(); + var namedParameterTypesBuilder = new LinkBuilder<DartType>(); + for (Element parameter in signature.orderedOptionalParameters) { + namedParametersBuilder.addLast(parameter.name); + namedParameterTypesBuilder.addLast(parameter.computeType(compiler)); + } + namedParameters = namedParametersBuilder.toLink(); + namedParameterTypes = namedParameterTypesBuilder.toLink(); + } else { + var optionalParameterTypesBuilder = new LinkBuilder<DartType>(); + for (Element parameter in signature.optionalParameters) { + optionalParameterTypesBuilder.addLast(parameter.computeType(compiler)); + } + optionalParameterTypes = optionalParameterTypesBuilder.toLink(); + } + return new FunctionType(element, + signature.returnType, + parameterTypes.toLink(), + optionalParameterTypes, + namedParameters, + namedParameterTypes); + } + + void resolveMetadataAnnotation(PartialMetadataAnnotation annotation) { + compiler.withCurrentElement(annotation.annotatedElement, () => measure(() { + assert(annotation.resolutionState == STATE_NOT_STARTED); + annotation.resolutionState = STATE_STARTED; + + Node node = annotation.parseNode(compiler); + ResolverVisitor visitor = + visitorFor(annotation.annotatedElement.enclosingElement); + node.accept(visitor); + annotation.value = compiler.metadataHandler.compileNodeWithDefinitions( + node, visitor.mapping, isConst: true); + + annotation.resolutionState = STATE_DONE; + })); + } + + error(Node node, MessageKind kind, [arguments = const {}]) { + ResolutionError message = new ResolutionError(kind, arguments); + compiler.reportError(node, message); + } +} + +class InitializerResolver { + final ResolverVisitor visitor; + final Map<SourceString, Node> initialized; + Link<Node> initializers; + bool hasSuper; + + InitializerResolver(this.visitor) + : initialized = new Map<SourceString, Node>(), hasSuper = false; + + error(Node node, MessageKind kind, [arguments = const {}]) { + visitor.error(node, kind, arguments); + } + + warning(Node node, MessageKind kind, [arguments = const {}]) { + visitor.warning(node, kind, arguments); + } + + bool isFieldInitializer(SendSet node) { + if (node.selector.asIdentifier() == null) return false; + if (node.receiver == null) return true; + if (node.receiver.asIdentifier() == null) return false; + return node.receiver.asIdentifier().isThis(); + } + + void checkForDuplicateInitializers(SourceString name, Node init) { + if (initialized.containsKey(name)) { + error(init, MessageKind.DUPLICATE_INITIALIZER, {'fieldName': name}); + warning(initialized[name], MessageKind.ALREADY_INITIALIZED, + {'fieldName': name}); + } + initialized[name] = init; + } + + void resolveFieldInitializer(FunctionElement constructor, SendSet init) { + // init is of the form [this.]field = value. + final Node selector = init.selector; + final SourceString name = selector.asIdentifier().source; + // Lookup target field. + Element target; + if (isFieldInitializer(init)) { + target = constructor.getEnclosingClass().lookupLocalMember(name); + if (target == null) { + error(selector, MessageKind.CANNOT_RESOLVE, {'name': name}); + } else if (target.kind != ElementKind.FIELD) { + error(selector, MessageKind.NOT_A_FIELD, {'fieldName': name}); + } else if (!target.isInstanceMember()) { + error(selector, MessageKind.INIT_STATIC_FIELD, {'fieldName': name}); + } + } else { + error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); + } + visitor.useElement(init, target); + visitor.world.registerStaticUse(target); + checkForDuplicateInitializers(name, init); + // Resolve initializing value. + visitor.visitInStaticContext(init.arguments.head); + } + + ClassElement getSuperOrThisLookupTarget(FunctionElement constructor, + bool isSuperCall, + Node diagnosticNode) { + ClassElement lookupTarget = constructor.getEnclosingClass(); + if (isSuperCall) { + // Calculate correct lookup target and constructor name. + if (identical(lookupTarget, visitor.compiler.objectClass)) { + error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT); + } else { + return lookupTarget.supertype.element; + } + } + return lookupTarget; + } + + Element resolveSuperOrThisForSend(FunctionElement constructor, + FunctionExpression functionNode, + Send call) { + // Resolve the selector and the arguments. + ResolverTask resolver = visitor.compiler.resolver; + visitor.inStaticContext(() { + visitor.resolveSelector(call); + visitor.resolveArguments(call.argumentsNode); + }); + Selector selector = visitor.mapping.getSelector(call); + bool isSuperCall = Initializers.isSuperConstructorCall(call); + + ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, + isSuperCall, + call); + Selector constructorSelector = + visitor.getRedirectingThisOrSuperConstructorSelector(call); + FunctionElement calledConstructor = + lookupTarget.lookupConstructor(constructorSelector); + + final bool isImplicitSuperCall = false; + final SourceString className = lookupTarget.name; + verifyThatConstructorMatchesCall(calledConstructor, + selector, + isImplicitSuperCall, + call, + className, + constructorSelector); + + visitor.useElement(call, calledConstructor); + visitor.world.registerStaticUse(calledConstructor); + return calledConstructor; + } + + void resolveImplicitSuperConstructorSend(FunctionElement constructor, + FunctionExpression functionNode) { + // If the class has a super resolve the implicit super call. + ClassElement classElement = constructor.getEnclosingClass(); + ClassElement superClass = classElement.superclass; + if (classElement != visitor.compiler.objectClass) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + SourceString constructorName = const SourceString(''); + Selector callToMatch = new Selector.call( + constructorName, + classElement.getLibrary(), + 0); + + final bool isSuperCall = true; + ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor, + isSuperCall, + functionNode); + Selector constructorSelector = new Selector.callDefaultConstructor( + visitor.enclosingElement.getLibrary()); + Element calledConstructor = lookupTarget.lookupConstructor( + constructorSelector); + + final SourceString className = lookupTarget.name; + final bool isImplicitSuperCall = true; + verifyThatConstructorMatchesCall(calledConstructor, + callToMatch, + isImplicitSuperCall, + functionNode, + className, + constructorSelector); + + visitor.world.registerStaticUse(calledConstructor); + } + } + + void verifyThatConstructorMatchesCall( + FunctionElement lookedupConstructor, + Selector call, + bool isImplicitSuperCall, + Node diagnosticNode, + SourceString className, + Selector constructorSelector) { + if (lookedupConstructor == null + || !lookedupConstructor.isGenerativeConstructor()) { + var fullConstructorName = + visitor.compiler.resolver.constructorNameForDiagnostics( + className, + constructorSelector.name); + MessageKind kind = isImplicitSuperCall + ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT + : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR; + error(diagnosticNode, kind, {'constructorName': fullConstructorName}); + } else { + if (!call.applies(lookedupConstructor, visitor.compiler)) { + MessageKind kind = isImplicitSuperCall + ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT + : MessageKind.NO_MATCHING_CONSTRUCTOR; + error(diagnosticNode, kind); + } + } + } + + FunctionElement resolveRedirection(FunctionElement constructor, + FunctionExpression functionNode) { + if (functionNode.initializers == null) return null; + Link<Node> link = functionNode.initializers.nodes; + if (!link.isEmpty && Initializers.isConstructorRedirect(link.head)) { + return resolveSuperOrThisForSend(constructor, functionNode, link.head); + } + return null; + } + + /** + * Resolve all initializers of this constructor. In the case of a redirecting + * constructor, the resolved constructor's function element is returned. + */ + FunctionElement resolveInitializers(FunctionElement constructor, + FunctionExpression functionNode) { + // Keep track of all "this.param" parameters specified for constructor so + // that we can ensure that fields are initialized only once. + FunctionSignature functionParameters = + constructor.computeSignature(visitor.compiler); + functionParameters.forEachParameter((Element element) { + if (identical(element.kind, ElementKind.FIELD_PARAMETER)) { + checkForDuplicateInitializers(element.name, + element.parseNode(visitor.compiler)); + } + }); + + if (functionNode.initializers == null) { + initializers = const Link<Node>(); + } else { + initializers = functionNode.initializers.nodes; + } + FunctionElement result; + bool resolvedSuper = false; + for (Link<Node> link = initializers; + !link.isEmpty; + link = link.tail) { + if (link.head.asSendSet() != null) { + final SendSet init = link.head.asSendSet(); + resolveFieldInitializer(constructor, init); + } else if (link.head.asSend() != null) { + final Send call = link.head.asSend(); + if (Initializers.isSuperConstructorCall(call)) { + if (resolvedSuper) { + error(call, MessageKind.DUPLICATE_SUPER_INITIALIZER); + } + resolveSuperOrThisForSend(constructor, functionNode, call); + resolvedSuper = true; + } else if (Initializers.isConstructorRedirect(call)) { + // Check that there is no body (Language specification 7.5.1). + if (functionNode.hasBody()) { + error(functionNode, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_BODY); + } + // Check that there are no other initializers. + if (!initializers.tail.isEmpty) { + error(call, MessageKind.REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER); + } + return resolveSuperOrThisForSend(constructor, functionNode, call); + } else { + visitor.error(call, MessageKind.CONSTRUCTOR_CALL_EXPECTED); + return null; + } + } else { + error(link.head, MessageKind.INVALID_INITIALIZER); + } + } + if (!resolvedSuper) { + resolveImplicitSuperConstructorSend(constructor, functionNode); + } + return null; // If there was no redirection always return null. + } +} + +class CommonResolverVisitor<R> extends Visitor<R> { + final Compiler compiler; + + CommonResolverVisitor(Compiler this.compiler); + + R visitNode(Node node) { + cancel(node, + 'internal error: Unhandled node: ${node.getObjectDescription()}'); + } + + R visitEmptyStatement(Node node) => null; + + /** Convenience method for visiting nodes that may be null. */ + R visit(Node node) => (node == null) ? null : node.accept(this); + + void error(Node node, MessageKind kind, [Map arguments = const {}]) { + ResolutionError message = new ResolutionError(kind, arguments); + compiler.reportError(node, message); + } + + void warning(Node node, MessageKind kind, [Map arguments = const {}]) { + ResolutionWarning message = new ResolutionWarning(kind, arguments); + compiler.reportWarning(node, message); + } + + void cancel(Node node, String message) { + compiler.cancel(message, node: node); + } + + void internalError(Node node, String message) { + compiler.internalError(message, node: node); + } + + void unimplemented(Node node, String message) { + compiler.unimplemented(message, node: node); + } +} + +abstract class LabelScope { + LabelScope get outer; + LabelElement lookup(String label); +} + +class LabeledStatementLabelScope implements LabelScope { + final LabelScope outer; + final Map<String, LabelElement> labels; + LabeledStatementLabelScope(this.outer, this.labels); + LabelElement lookup(String labelName) { + LabelElement label = labels[labelName]; + if (label != null) return label; + return outer.lookup(labelName); + } +} + +class SwitchLabelScope implements LabelScope { + final LabelScope outer; + final Map<String, LabelElement> caseLabels; + + SwitchLabelScope(this.outer, this.caseLabels); + + LabelElement lookup(String labelName) { + LabelElement result = caseLabels[labelName]; + if (result != null) return result; + return outer.lookup(labelName); + } +} + +class EmptyLabelScope implements LabelScope { + const EmptyLabelScope(); + LabelElement lookup(String label) => null; + LabelScope get outer { + throw 'internal error: empty label scope has no outer'; + } +} + +class StatementScope { + LabelScope labels; + Link<TargetElement> breakTargetStack; + Link<TargetElement> continueTargetStack; + // Used to provide different numbers to statements if one is inside the other. + // Can be used to make otherwise duplicate labels unique. + int nestingLevel = 0; + + StatementScope() + : labels = const EmptyLabelScope(), + breakTargetStack = const Link<TargetElement>(), + continueTargetStack = const Link<TargetElement>(); + + LabelElement lookupLabel(String label) { + return labels.lookup(label); + } + + TargetElement currentBreakTarget() => + breakTargetStack.isEmpty ? null : breakTargetStack.head; + + TargetElement currentContinueTarget() => + continueTargetStack.isEmpty ? null : continueTargetStack.head; + + void enterLabelScope(Map<String, LabelElement> elements) { + labels = new LabeledStatementLabelScope(labels, elements); + nestingLevel++; + } + + void exitLabelScope() { + nestingLevel--; + labels = labels.outer; + } + + void enterLoop(TargetElement element) { + breakTargetStack = breakTargetStack.prepend(element); + continueTargetStack = continueTargetStack.prepend(element); + nestingLevel++; + } + + void exitLoop() { + nestingLevel--; + breakTargetStack = breakTargetStack.tail; + continueTargetStack = continueTargetStack.tail; + } + + void enterSwitch(TargetElement breakElement, + Map<String, LabelElement> continueElements) { + breakTargetStack = breakTargetStack.prepend(breakElement); + labels = new SwitchLabelScope(labels, continueElements); + nestingLevel++; + } + + void exitSwitch() { + nestingLevel--; + breakTargetStack = breakTargetStack.tail; + labels = labels.outer; + } +} + +class TypeResolver { + final Compiler compiler; + + TypeResolver(this.compiler); + + Element resolveTypeName(Scope scope, + SourceString prefixName, + Identifier typeName) { + if (prefixName != null) { + Element e = scope.lookup(prefixName); + if (e != null) { + if (identical(e.kind, ElementKind.PREFIX)) { + // The receiver is a prefix. Lookup in the imported members. + PrefixElement prefix = e; + return prefix.lookupLocalMember(typeName.source); + } else if (identical(e.kind, ElementKind.CLASS)) { + // TODO(johnniwinther): Remove this case. + // The receiver is the class part of a named constructor. + return e; + } + } else { + // The caller creates the ErroneousElement for the MalformedType. + return null; + } + } else { + String stringValue = typeName.source.stringValue; + if (identical(stringValue, 'void')) { + return compiler.types.voidType.element; + } else if (identical(stringValue, 'Dynamic')) { + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + compiler.onDeprecatedFeature(typeName, 'Dynamic'); + return compiler.dynamicClass; + } else if (identical(stringValue, 'dynamic')) { + return compiler.dynamicClass; + } else { + return scope.lookup(typeName.source); + } + } + } + + // TODO(johnniwinther): Change [onFailure] and [whenResolved] to use boolean + // flags instead of closures. + DartType resolveTypeAnnotation( + TypeAnnotation node, + Scope scope, + Element enclosingElement, + {onFailure(Node node, MessageKind kind, [Map arguments]), + whenResolved(Node node, DartType type)}) { + if (onFailure == null) { + onFailure = (n, k, [arguments]) {}; + } + if (whenResolved == null) { + whenResolved = (n, t) {}; + } + if (scope == null) { + compiler.internalError('resolveTypeAnnotation: no scope specified'); + } + return resolveTypeAnnotationInContext(scope, node, enclosingElement, + onFailure, whenResolved); + } + + DartType resolveTypeAnnotationInContext(Scope scope, TypeAnnotation node, + Element enclosingElement, + onFailure, whenResolved) { + Identifier typeName; + SourceString prefixName; + Send send = node.typeName.asSend(); + if (send != null) { + // The type name is of the form [: prefix . identifier :]. + prefixName = send.receiver.asIdentifier().source; + typeName = send.selector.asIdentifier(); + } else { + typeName = node.typeName.asIdentifier(); + } + + Element element = resolveTypeName(scope, prefixName, typeName); + DartType type; + + DartType reportFailureAndCreateType(MessageKind messageKind, + Map messageArguments) { + onFailure(node, messageKind, messageArguments); + var erroneousElement = new ErroneousElementX( + messageKind, messageArguments, typeName.source, enclosingElement); + var arguments = new LinkBuilder<DartType>(); + resolveTypeArguments( + node, null, enclosingElement, + scope, onFailure, whenResolved, arguments); + return new MalformedType(erroneousElement, null, arguments.toLink()); + } + + DartType checkNoTypeArguments(DartType type) { + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, const Link<DartType>(), enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + type, arguments.toLink()); + } + return type; + } + + if (element == null) { + type = reportFailureAndCreateType( + MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node.typeName}); + } else if (element.isAmbiguous()) { + AmbiguousElement ambiguous = element; + type = reportFailureAndCreateType( + ambiguous.messageKind, ambiguous.messageArguments); + } else if (!element.impliesType()) { + type = reportFailureAndCreateType( + MessageKind.NOT_A_TYPE, {'node': node.typeName}); + } else { + if (identical(element, compiler.types.voidType.element) || + identical(element, compiler.types.dynamicType.element)) { + type = checkNoTypeArguments(element.computeType(compiler)); + } else if (element.isClass()) { + ClassElement cls = element; + compiler.resolver._ensureClassWillBeResolved(cls); + element.computeType(compiler); + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, cls.typeVariables, enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + new InterfaceType(cls.declaration, arguments.toLink())); + } else { + if (arguments.isEmpty) { + type = cls.rawType; + } else { + type = new InterfaceType(cls.declaration, arguments.toLink()); + } + } + } else if (element.isTypedef()) { + TypedefElement typdef = element; + // TODO(ahe): Should be [ensureResolved]. + compiler.resolveTypedef(typdef); + var arguments = new LinkBuilder<DartType>(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, typdef.typeVariables, enclosingElement, + scope, onFailure, whenResolved, arguments); + if (hashTypeArgumentMismatch) { + type = new MalformedType( + new ErroneousElementX(MessageKind.TYPE_ARGUMENT_COUNT_MISMATCH, + {'type': node}, typeName.source, enclosingElement), + new TypedefType(typdef, arguments.toLink())); + } else { + if (arguments.isEmpty) { + type = typdef.rawType; + } else { + type = new TypedefType(typdef, arguments.toLink()); + } + } + } else if (element.isTypeVariable()) { + if (enclosingElement.isInStaticMember()) { + compiler.reportWarning(node, + MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER.message( + {'typeVariableName': node})); + type = new MalformedType( + new ErroneousElementX( + MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER, + {'typeVariableName': node}, + typeName.source, enclosingElement), + element.computeType(compiler)); + } else { + type = element.computeType(compiler); + } + type = checkNoTypeArguments(type); + } else { + compiler.cancel("unexpected element kind ${element.kind}", + node: node); + } + } + whenResolved(node, type); + return type; + } + + /** + * Resolves the type arguments of [node] and adds these to [arguments]. + * + * Returns [: true :] if the number of type arguments did not match the + * number of type variables. + */ + bool resolveTypeArguments( + TypeAnnotation node, + Link<DartType> typeVariables, + Element enclosingElement, + Scope scope, + onFailure, whenResolved, + LinkBuilder<DartType> arguments) { + if (node.typeArguments == null) { + return false; + } + bool typeArgumentCountMismatch = false; + for (Link<Node> typeArguments = node.typeArguments.nodes; + !typeArguments.isEmpty; + typeArguments = typeArguments.tail) { + if (typeVariables != null && typeVariables.isEmpty) { + onFailure(typeArguments.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); + typeArgumentCountMismatch = true; + } + DartType argType = resolveTypeAnnotationInContext(scope, + typeArguments.head, + enclosingElement, + onFailure, + whenResolved); + arguments.addLast(argType); + if (typeVariables != null && !typeVariables.isEmpty) { + typeVariables = typeVariables.tail; + } + } + if (typeVariables != null && !typeVariables.isEmpty) { + onFailure(node.typeArguments, MessageKind.MISSING_TYPE_ARGUMENT); + typeArgumentCountMismatch = true; + } + return typeArgumentCountMismatch; + } +} + +/** + * Core implementation of resolution. + * + * Do not subclass or instantiate this class outside this library + * except for testing. + */ +class ResolverVisitor extends CommonResolverVisitor<Element> { + final TreeElementMapping mapping; + Element enclosingElement; + final TypeResolver typeResolver; + bool inInstanceContext; + bool inCheckContext; + bool inCatchBlock; + Scope scope; + ClassElement currentClass; + ExpressionStatement currentExpressionStatement; + bool typeRequired = false; + StatementScope statementScope; + int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION + | ElementCategory.IMPLIES_TYPE; + + ResolverVisitor(Compiler compiler, Element element, this.mapping) + : this.enclosingElement = element, + // When the element is a field, we are actually resolving its + // initial value, which should not have access to instance + // fields. + inInstanceContext = (element.isInstanceMember() && !element.isField()) + || element.isGenerativeConstructor(), + this.currentClass = element.isMember() ? element.getEnclosingClass() + : null, + this.statementScope = new StatementScope(), + typeResolver = new TypeResolver(compiler), + scope = element.buildScope(), + inCheckContext = compiler.enableTypeAssertions, + inCatchBlock = false, + super(compiler); + + ResolutionEnqueuer get world => compiler.enqueuer.resolution; + + Element lookup(Node node, SourceString name) { + Element result = scope.lookup(name); + if (!Elements.isUnresolved(result)) { + if (!inInstanceContext && result.isInstanceMember()) { + compiler.reportErrorCode( + node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); + return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE, + {'name': name}, + name, enclosingElement); + } else if (result.isAmbiguous()) { + AmbiguousElement ambiguous = result; + compiler.reportErrorCode( + node, ambiguous.messageKind, ambiguous.messageArguments); + return new ErroneousElementX(ambiguous.messageKind, + ambiguous.messageArguments, + name, enclosingElement); + } + } + return result; + } + + // Create, or reuse an already created, statement element for a statement. + TargetElement getOrCreateTargetElement(Node statement) { + TargetElement element = mapping[statement]; + if (element == null) { + element = new TargetElementX(statement, + statementScope.nestingLevel, + enclosingElement); + mapping[statement] = element; + } + return element; + } + + doInCheckContext(action()) { + bool wasInCheckContext = inCheckContext; + inCheckContext = true; + var result = action(); + inCheckContext = wasInCheckContext; + return result; + } + + inStaticContext(action()) { + bool wasInstanceContext = inInstanceContext; + inInstanceContext = false; + var result = action(); + inInstanceContext = wasInstanceContext; + return result; + } + + visitInStaticContext(Node node) { + inStaticContext(() => visit(node)); + } + + ErroneousElement warnAndCreateErroneousElement(Node node, + SourceString name, + MessageKind kind, + [Map arguments = const {}]) { + ResolutionWarning warning = new ResolutionWarning(kind, arguments); + compiler.reportWarning(node, warning); + return new ErroneousElementX(kind, arguments, name, enclosingElement); + } + + Element visitIdentifier(Identifier node) { + if (node.isThis()) { + if (!inInstanceContext) { + error(node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node}); + } + return null; + } else if (node.isSuper()) { + if (!inInstanceContext) error(node, MessageKind.NO_SUPER_IN_STATIC); + if ((ElementCategory.SUPER & allowedCategory) == 0) { + error(node, MessageKind.INVALID_USE_OF_SUPER); + } + return null; + } else { + Element element = lookup(node, node.source); + if (element == null) { + if (!inInstanceContext) { + element = warnAndCreateErroneousElement(node, node.source, + MessageKind.CANNOT_RESOLVE, + {'name': node}); + } + } else if (element.isErroneous()) { + // Use the erroneous element. + } else { + if ((element.kind.category & allowedCategory) == 0) { + // TODO(ahe): Improve error message. Need UX input. + error(node, MessageKind.GENERIC, + {'text': "is not an expression $element"}); + } + } + if (!Elements.isUnresolved(element) + && element.kind == ElementKind.CLASS) { + ClassElement classElement = element; + classElement.ensureResolved(compiler); + } + return useElement(node, element); + } + } + + Element visitTypeAnnotation(TypeAnnotation node) { + DartType type = resolveTypeAnnotation(node); + if (type != null) { + if (inCheckContext) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + return type.element; + } + return null; + } + + Element defineElement(Node node, Element element, + {bool doAddToScope: true}) { + compiler.ensure(element != null); + mapping[node] = element; + if (doAddToScope) { + Element existing = scope.add(element); + if (existing != element) { + error(node, MessageKind.DUPLICATE_DEFINITION, {'name': node}); + } + } + return element; + } + + Element useElement(Node node, Element element) { + if (element == null) return null; + return mapping[node] = element; + } + + DartType useType(TypeAnnotation annotation, DartType type) { + if (type != null) { + mapping.setType(annotation, type); + useElement(annotation, type.element); + } + return type; + } + + bool isNamedConstructor(Send node) => node.receiver != null; + + Selector getRedirectingThisOrSuperConstructorSelector(Send node) { + if (isNamedConstructor(node)) { + SourceString constructorName = node.selector.asIdentifier().source; + return new Selector.callConstructor( + constructorName, + enclosingElement.getLibrary()); + } else { + return new Selector.callDefaultConstructor( + enclosingElement.getLibrary()); + } + } + + FunctionElement resolveConstructorRedirection(FunctionElement constructor) { + FunctionExpression node = constructor.parseNode(compiler); + + // A synthetic constructor does not have a node. + if (node == null) return null; + if (node.initializers == null) return null; + Link<Node> initializers = node.initializers.nodes; + if (!initializers.isEmpty && + Initializers.isConstructorRedirect(initializers.head)) { + Selector selector = + getRedirectingThisOrSuperConstructorSelector(initializers.head); + final ClassElement classElement = constructor.getEnclosingClass(); + return classElement.lookupConstructor(selector); + } + return null; + } + + void setupFunction(FunctionExpression node, FunctionElement function) { + scope = new MethodScope(scope, function); + + // Put the parameters in scope. + FunctionSignature functionParameters = + function.computeSignature(compiler); + Link<Node> parameterNodes = (node.parameters == null) + ? const Link<Node>() : node.parameters.nodes; + functionParameters.forEachParameter((Element element) { + if (element == functionParameters.optionalParameters.head) { + NodeList nodes = parameterNodes.head; + parameterNodes = nodes.nodes; + } + VariableDefinitions variableDefinitions = parameterNodes.head; + Node parameterNode = variableDefinitions.definitions.nodes.head; + initializerDo(parameterNode, (n) => n.accept(this)); + // Field parameters (this.x) are not visible inside the constructor. The + // fields they reference are visible, but must be resolved independently. + if (element.kind == ElementKind.FIELD_PARAMETER) { + useElement(parameterNode, element); + } else { + defineElement(variableDefinitions.definitions.nodes.head, element); + } + parameterNodes = parameterNodes.tail; + }); + } + + visitCascade(Cascade node) { + visit(node.expression); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + } + + Element visitClassNode(ClassNode node) { + cancel(node, "shouldn't be called"); + } + + visitIn(Node node, Scope nestedScope) { + Scope oldScope = scope; + scope = nestedScope; + Element element = visit(node); + scope = oldScope; + return element; + } + + /** + * Introduces new default targets for break and continue + * before visiting the body of the loop + */ + visitLoopBodyIn(Node loop, Node body, Scope bodyScope) { + TargetElement element = getOrCreateTargetElement(loop); + statementScope.enterLoop(element); + visitIn(body, bodyScope); + statementScope.exitLoop(); + if (!element.isTarget) { + mapping.remove(loop); + } + } + + visitBlock(Block node) { + visitIn(node.statements, new BlockScope(scope)); + } + + visitDoWhile(DoWhile node) { + visitLoopBodyIn(node, node.body, new BlockScope(scope)); + visit(node.condition); + } + + visitEmptyStatement(EmptyStatement node) { } + + visitExpressionStatement(ExpressionStatement node) { + ExpressionStatement oldExpressionStatement = currentExpressionStatement; + currentExpressionStatement = node; + visit(node.expression); + currentExpressionStatement = oldExpressionStatement; + } + + visitFor(For node) { + Scope blockScope = new BlockScope(scope); + visitIn(node.initializer, blockScope); + visitIn(node.condition, blockScope); + visitIn(node.update, blockScope); + visitLoopBodyIn(node, node.body, blockScope); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + assert(node.function.name != null); + visit(node.function); + FunctionElement functionElement = mapping[node.function]; + // TODO(floitsch): this might lead to two errors complaining about + // shadowing. + defineElement(node, functionElement); + } + + visitFunctionExpression(FunctionExpression node) { + visit(node.returnType); + SourceString name; + if (node.name == null) { + name = const SourceString(""); + } else { + name = node.name.asIdentifier().source; + } + + FunctionElement function = new FunctionElementX.node( + name, node, ElementKind.FUNCTION, Modifiers.EMPTY, + enclosingElement); + Scope oldScope = scope; // The scope is modified by [setupFunction]. + setupFunction(node, function); + defineElement(node, function, doAddToScope: node.name != null); + + Element previousEnclosingElement = enclosingElement; + enclosingElement = function; + // Run the body in a fresh statement scope. + StatementScope oldStatementScope = statementScope; + statementScope = new StatementScope(); + visit(node.body); + statementScope = oldStatementScope; + + scope = oldScope; + enclosingElement = previousEnclosingElement; + + world.registerInstantiatedClass(compiler.functionClass); + } + + visitIf(If node) { + visit(node.condition); + visit(node.thenPart); + visit(node.elsePart); + } + + static bool isLogicalOperator(Identifier op) { + String str = op.source.stringValue; + return (identical(str, '&&') || str == '||' || str == '!'); + } + + Element resolveSend(Send node) { + Selector selector = resolveSelector(node); + if (node.isSuperCall) mapping.superUses.add(node); + + if (node.receiver == null) { + // If this send is of the form "assert(expr);", then + // this is an assertion. + if (selector.isAssert()) { + if (selector.argumentCount != 1) { + error(node.selector, + MessageKind.WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT, + {'argumentCount': selector.argumentCount}); + } else if (selector.namedArgumentCount != 0) { + error(node.selector, + MessageKind.ASSERT_IS_GIVEN_NAMED_ARGUMENTS, + {'argumentCount': selector.namedArgumentCount}); + } + return compiler.assertMethod; + } + + return node.selector.accept(this); + } + + var oldCategory = allowedCategory; + allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER; + Element resolvedReceiver = visit(node.receiver); + allowedCategory = oldCategory; + + Element target; + SourceString name = node.selector.asIdentifier().source; + if (identical(name.stringValue, 'this')) { + error(node.selector, MessageKind.GENERIC, + {'text': "expected an identifier"}); + } else if (node.isSuperCall) { + if (node.isOperator) { + if (isUserDefinableOperator(name.stringValue)) { + name = selector.name; + } else { + error(node.selector, MessageKind.ILLEGAL_SUPER_SEND, {'name': name}); + } + } + if (!inInstanceContext) { + error(node.receiver, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name}); + return null; + } + if (currentClass.supertype == null) { + // This is just to guard against internal errors, so no need + // for a real error message. + error(node.receiver, MessageKind.GENERIC, + {'text': "Object has no superclass"}); + } + // TODO(johnniwinther): Ensure correct behavior if currentClass is a + // patch. + target = currentClass.lookupSuperMember(name); + // [target] may be null which means invoking noSuchMethod on + // super. + } else if (Elements.isUnresolved(resolvedReceiver)) { + return null; + } else if (identical(resolvedReceiver.kind, ElementKind.CLASS)) { + ClassElement receiverClass = resolvedReceiver; + receiverClass.ensureResolved(compiler); + if (node.isOperator) { + // When the resolved receiver is a class, we can have two cases: + // 1) a static send: C.foo, or + // 2) an operator send, where the receiver is a class literal: 'C + 1'. + // The following code that looks up the selector on the resolved + // receiver will treat the second as the invocation of a static operator + // if the resolved receiver is not null. + return null; + } + target = receiverClass.lookupLocalMember(name); + if (target == null) { + // TODO(johnniwinther): With the simplified [TreeElements] invariant, + // try to resolve injected elements if [currentClass] is in the patch + // library of [receiverClass]. + + // TODO(karlklose): this should be reported by the caller of + // [resolveSend] to select better warning messages for getters and + // setters. + return warnAndCreateErroneousElement(node, name, + MessageKind.METHOD_NOT_FOUND, + {'className': receiverClass.name, + 'methodName': name}); + } else if (target.isInstanceMember()) { + error(node, MessageKind.MEMBER_NOT_STATIC, + {'className': receiverClass.name, + 'memberName': name}); + } + } else if (identical(resolvedReceiver.kind, ElementKind.PREFIX)) { + PrefixElement prefix = resolvedReceiver; + target = prefix.lookupLocalMember(name); + if (Elements.isUnresolved(target)) { + return warnAndCreateErroneousElement( + node, name, MessageKind.NO_SUCH_LIBRARY_MEMBER, + {'libraryName': prefix.name, 'memberName': name}); + } else if (target.kind == ElementKind.CLASS) { + ClassElement classElement = target; + classElement.ensureResolved(compiler); + } + } + return target; + } + + DartType resolveTypeTest(Node argument) { + TypeAnnotation node = argument.asTypeAnnotation(); + if (node == null) { + // node is of the form !Type. + node = argument.asSend().receiver.asTypeAnnotation(); + if (node == null) compiler.cancel("malformed send"); + } + return resolveTypeRequired(node); + } + + static Selector computeSendSelector(Send node, LibraryElement library) { + // First determine if this is part of an assignment. + bool isSet = node.asSendSet() != null; + + if (node.isIndex) { + return isSet ? new Selector.indexSet() : new Selector.index(); + } + + if (node.isOperator) { + SourceString source = node.selector.asOperator().source; + String string = source.stringValue; + if (identical(string, '!') || + identical(string, '&&') || identical(string, '||') || + identical(string, 'is') || identical(string, 'as') || + identical(string, '===') || identical(string, '!==') || + identical(string, '?') || + identical(string, '>>>')) { + return null; + } + if (!isUserDefinableOperator(source.stringValue)) { + source = Elements.mapToUserOperator(source); + } + return node.arguments.isEmpty + ? new Selector.unaryOperator(source) + : new Selector.binaryOperator(source); + } + + Identifier identifier = node.selector.asIdentifier(); + if (node.isPropertyAccess) { + assert(!isSet); + return new Selector.getter(identifier.source, library); + } else if (isSet) { + return new Selector.setter(identifier.source, library); + } + + // Compute the arity and the list of named arguments. + int arity = 0; + List<SourceString> named = <SourceString>[]; + for (Link<Node> link = node.argumentsNode.nodes; + !link.isEmpty; + link = link.tail) { + Expression argument = link.head; + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + named.add(namedArgument.name.source); + } + arity++; + } + + // If we're invoking a closure, we do not have an identifier. + return (identifier == null) + ? new Selector.callClosure(arity, named) + : new Selector.call(identifier.source, library, arity, named); + } + + Selector resolveSelector(Send node) { + LibraryElement library = enclosingElement.getLibrary(); + Selector selector = computeSendSelector(node, library); + if (selector != null) mapping.setSelector(node, selector); + return selector; + } + + void resolveArguments(NodeList list) { + if (list == null) return; + List<SourceString> seenNamedArguments = <SourceString>[]; + for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) { + Expression argument = link.head; + visit(argument); + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + SourceString source = namedArgument.name.source; + if (seenNamedArguments.contains(source)) { + error(argument, MessageKind.DUPLICATE_DEFINITION, + {'name': source}); + } + seenNamedArguments.add(source); + } else if (!seenNamedArguments.isEmpty) { + error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); + } + } + } + + visitSend(Send node) { + Element target = resolveSend(node); + if (!Elements.isUnresolved(target) + && target.kind == ElementKind.ABSTRACT_FIELD) { + AbstractFieldElement field = target; + target = field.getter; + if (target == null && !inInstanceContext) { + target = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_GETTER); + } + } + + bool resolvedArguments = false; + if (node.isOperator) { + String operatorString = node.selector.asOperator().source.stringValue; + if (identical(operatorString, 'is') || identical(operatorString, 'as')) { + assert(node.arguments.tail.isEmpty); + DartType type = resolveTypeTest(node.arguments.head); + if (type != null) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + resolvedArguments = true; + } else if (identical(operatorString, '?')) { + Element parameter = mapping[node.receiver]; + if (parameter == null + || !identical(parameter.kind, ElementKind.PARAMETER)) { + error(node.receiver, MessageKind.PARAMETER_NAME_EXPECTED); + } else { + mapping.checkedParameters.add(parameter); + } + } + } + + if (!resolvedArguments) { + resolveArguments(node.argumentsNode); + } + + // If the selector is null, it means that we will not be generating + // code for this as a send. + Selector selector = mapping.getSelector(node); + if (selector == null) return; + + if (node.isCall) { + if (Elements.isUnresolved(target) || + target.isGetter() || + Elements.isClosureSend(node, target)) { + // If we don't know what we're calling or if we are calling a getter, + // we need to register that fact that we may be calling a closure + // with the same arguments. + Selector call = new Selector.callClosureFrom(selector); + world.registerDynamicInvocation(call.name, call); + } else if (target.impliesType()) { + // We call 'call()' on a Type instance returned from the reference to a + // class or typedef literal. We do not need to register this call as a + // dynamic invocation, because we statically know what the target is. + } else if (!selector.applies(target, compiler)) { + warnArgumentMismatch(node, target); + } + + if (target != null && + target.isForeign(compiler) && + selector.name == const SourceString('JS')) { + world.registerJsCall(node, this); + } + } + + // TODO(ngeoffray): Warn if target is null and the send is + // unqualified. + useElement(node, target); + registerSend(selector, target); + if (node.isPropertyAccess) { + // It might be the closurization of a method. + world.registerInstantiatedClass(compiler.functionClass); + } + return node.isPropertyAccess ? target : null; + } + + void warnArgumentMismatch(Send node, Element target) { + // TODO(karlklose): we can be more precise about the reason of the + // mismatch. + warning(node.argumentsNode, MessageKind.INVALID_ARGUMENTS, + {'methodName': target.name}); + } + + /// Callback for native enqueuer to parse a type. Returns [:null:] on error. + DartType resolveTypeFromString(String typeName) { + Element element = scope.lookup(new SourceString(typeName)); + if (element == null) return null; + if (element is! ClassElement) return null; + element.ensureResolved(compiler); + return element.computeType(compiler); + } + + visitSendSet(SendSet node) { + Element target = resolveSend(node); + Element setter = target; + Element getter = target; + SourceString operatorName = node.assignmentOperator.source; + String source = operatorName.stringValue; + bool isComplex = !identical(source, '='); + if (!Elements.isUnresolved(target) + && target.kind == ElementKind.ABSTRACT_FIELD) { + AbstractFieldElement field = target; + setter = field.setter; + getter = field.getter; + if (setter == null && !inInstanceContext) { + setter = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_SETTER); + } + if (isComplex && getter == null && !inInstanceContext) { + getter = + warnAndCreateErroneousElement(node.selector, field.name, + MessageKind.CANNOT_RESOLVE_GETTER); + } + } + + visit(node.argumentsNode); + + // TODO(ngeoffray): Check if the target can be assigned. + // TODO(ngeoffray): Warn if target is null and the send is + // unqualified. + + Selector selector = mapping.getSelector(node); + if (isComplex) { + if (selector.isSetter()) { + // TODO(kasperl): We're registering the getter selector for + // compound assignments on the AST selector node. In the code + // generator, we then fetch it from there when generating the + // getter for a SendSet node. + Selector getterSelector = new Selector.getterFrom(selector); + registerSend(getterSelector, getter); + mapping.setSelector(node.selector, getterSelector); + useElement(node.selector, getter); + } else { + // TODO(kasperl): If [getter] is resolved, it will actually + // refer to the []= operator which isn't the one we want to + // register here. We should consider using some notion of + // abstract indexable element that we can resolve to so we can + // distinguish the two. + assert(selector.isIndexSet()); + registerSend(new Selector.index(), null); + } + + // Make sure we include the + and - operators if we are using + // the ++ and -- ones. Also, if op= form is used, include op itself. + void registerBinaryOperator(SourceString name) { + Selector binop = new Selector.binaryOperator(name); + world.registerDynamicInvocation(binop.name, binop); + } + if (identical(source, '++')) registerBinaryOperator(const SourceString('+')); + if (identical(source, '--')) registerBinaryOperator(const SourceString('-')); + if (source.endsWith('=')) { + registerBinaryOperator(Elements.mapToUserOperator(operatorName)); + } + } + + registerSend(selector, setter); + return useElement(node, setter); + } + + void registerSend(Selector selector, Element target) { + if (target == null || target.isInstanceMember()) { + if (selector.isGetter()) { + world.registerDynamicGetter(selector.name, selector); + } else if (selector.isSetter()) { + world.registerDynamicSetter(selector.name, selector); + } else { + world.registerDynamicInvocation(selector.name, selector); + } + } else if (Elements.isStaticOrTopLevel(target)) { + // TODO(kasperl): It seems like we're not supposed to register + // the use of classes. Wouldn't it be simpler if we just did? + if (!target.isClass()) { + // [target] might be the implementation element and only declaration + // elements may be registered. + world.registerStaticUse(target.declaration); + } + } + } + + visitLiteralInt(LiteralInt node) { + world.registerInstantiatedClass(compiler.intClass); + } + + visitLiteralDouble(LiteralDouble node) { + world.registerInstantiatedClass(compiler.doubleClass); + } + + visitLiteralBool(LiteralBool node) { + world.registerInstantiatedClass(compiler.boolClass); + } + + visitLiteralString(LiteralString node) { + world.registerInstantiatedClass(compiler.stringClass); + } + + visitLiteralNull(LiteralNull node) { + world.registerInstantiatedClass(compiler.nullClass); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + world.registerInstantiatedClass(compiler.stringClass); + node.visitChildren(this); + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + visit(link.head); + } + } + + visitOperator(Operator node) { + unimplemented(node, 'operator'); + } + + visitReturn(Return node) { + if (node.isRedirectingFactoryBody) { + handleRedirectingFactoryBody(node); + } else { + visit(node.expression); + } + } + + void handleRedirectingFactoryBody(Return node) { + if (!enclosingElement.isFactoryConstructor()) { + compiler.reportErrorCode( + node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY); + compiler.reportErrorCode( + enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD); + } + Element redirectionTarget = resolveRedirectingFactory(node); + var type = mapping.getType(node.expression); + if (type is InterfaceType && !type.isRaw) { + unimplemented(node.expression, 'type arguments on redirecting factory'); + } + useElement(node.expression, redirectionTarget); + FunctionElement constructor = enclosingElement; + if (constructor.modifiers.isConst() && + !redirectionTarget.modifiers.isConst()) { + error(node, MessageKind.CONSTRUCTOR_IS_NOT_CONST); + } + constructor.defaultImplementation = redirectionTarget; + if (Elements.isUnresolved(redirectionTarget)) return; + + // TODO(ahe): Check that this doesn't lead to a cycle. For now, + // just make sure that the redirection target isn't itself a + // redirecting factory. + { // This entire block is temporary code per the above TODO. + FunctionElement targetImplementation = redirectionTarget.implementation; + FunctionExpression function = targetImplementation.parseNode(compiler); + if (function.body != null && function.body.asReturn() != null + && function.body.asReturn().isRedirectingFactoryBody) { + unimplemented(node.expression, 'redirecing to redirecting factory'); + } + } + world.registerStaticUse(redirectionTarget); + world.registerInstantiatedClass( + redirectionTarget.enclosingElement.declaration); + } + + visitThrow(Throw node) { + if (!inCatchBlock && node.expression == null) { + error(node, MessageKind.THROW_WITHOUT_EXPRESSION); + } + visit(node.expression); + } + + visitVariableDefinitions(VariableDefinitions node) { + VariableDefinitionsVisitor visitor = + new VariableDefinitionsVisitor(compiler, node, this, + ElementKind.VARIABLE); + // Ensure that we set the type of the [VariableListElement] since it depends + // on the current scope. If the current scope is a [MethodScope] or + // [BlockScope] it will not be available for the + // [VariableListElement.computeType] method. + if (node.type != null) { + visitor.variables.type = resolveTypeAnnotation(node.type); + } else { + visitor.variables.type = compiler.types.dynamicType; + } + visitor.visit(node.definitions); + } + + visitWhile(While node) { + visit(node.condition); + visitLoopBodyIn(node, node.body, new BlockScope(scope)); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + visit(node.expression); + } + + visitNewExpression(NewExpression node) { + Node selector = node.send.selector; + FunctionElement constructor = resolveConstructor(node); + resolveSelector(node.send); + resolveArguments(node.send.argumentsNode); + useElement(node.send, constructor); + if (Elements.isUnresolved(constructor)) return constructor; + // TODO(karlklose): handle optional arguments. + if (node.send.argumentCount() != constructor.parameterCount(compiler)) { + // TODO(ngeoffray): resolution error with wrong number of + // parameters. We cannot do this rigth now because of the + // List constructor. + } + // [constructor] might be the implementation element and only declaration + // elements may be registered. + world.registerStaticUse(constructor.declaration); + compiler.withCurrentElement(constructor, () { + FunctionExpression tree = constructor.parseNode(compiler); + compiler.resolver.resolveConstructorImplementation(constructor, tree); + }); + // [constructor.defaultImplementation] might be the implementation element + // and only declaration elements may be registered. + world.registerStaticUse(constructor.defaultImplementation.declaration); + ClassElement cls = constructor.defaultImplementation.getEnclosingClass(); + // [cls] might be the implementation element and only declaration elements + // may be registered. + world.registerInstantiatedClass(cls.declaration); + // [cls] might be the declaration element and we want to include injected + // members. + cls.implementation.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + world.addToWorkList(member); + }, + includeBackendMembers: false, + includeSuperMembers: true); + return null; + } + + /** + * Try to resolve the constructor that is referred to by [node]. + * Note: this function may return an ErroneousFunctionElement instead of + * [null], if there is no corresponding constructor, class or library. + */ + FunctionElement resolveConstructor(NewExpression node) { + return node.accept(new ConstructorResolver(compiler, this)); + } + + FunctionElement resolveRedirectingFactory(Return node) { + return node.accept(new ConstructorResolver(compiler, this)); + } + + DartType resolveTypeRequired(TypeAnnotation node) { + bool old = typeRequired; + typeRequired = true; + DartType result = resolveTypeAnnotation(node); + typeRequired = old; + return result; + } + + void analyzeTypeArgument(DartType annotation, DartType argument) { + if (argument == null) return; + if (argument.element.isTypeVariable()) { + // Register a dependency between the class where the type + // variable is, and the annotation. If the annotation requires + // runtime type information, then the class of the type variable + // does too. + compiler.world.registerRtiDependency( + annotation.element, + argument.element.enclosingElement); + } else if (argument is InterfaceType) { + InterfaceType type = argument; + type.typeArguments.forEach((DartType argument) { + analyzeTypeArgument(type, argument); + }); + } + } + + DartType resolveTypeAnnotation(TypeAnnotation node) { + Function report = typeRequired ? error : warning; + DartType type = typeResolver.resolveTypeAnnotation( + node, scope, enclosingElement, + onFailure: report, whenResolved: useType); + if (type == null) return null; + if (inCheckContext) { + compiler.enqueuer.resolution.registerIsCheck(type); + } + if (typeRequired || inCheckContext) { + if (type is InterfaceType) { + InterfaceType itf = type; + itf.typeArguments.forEach((DartType argument) { + analyzeTypeArgument(type, argument); + }); + } + // TODO(ngeoffray): Also handle cases like: + // 1) a is T + // 2) T a (in checked mode). + } + return type; + } + + visitModifiers(Modifiers node) { + // TODO(ngeoffray): Implement this. + unimplemented(node, 'modifiers'); + } + + visitLiteralList(LiteralList node) { + world.registerInstantiatedClass(compiler.listClass); + NodeList arguments = node.typeArguments; + if (arguments != null) { + Link<Node> nodes = arguments.nodes; + if (nodes.isEmpty) { + error(arguments, MessageKind.MISSING_TYPE_ARGUMENT); + } else { + resolveTypeRequired(nodes.head); + for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) { + error(nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT); + resolveTypeRequired(nodes.head); + } + } + } + visit(node.elements); + } + + visitConditional(Conditional node) { + node.visitChildren(this); + } + + visitStringInterpolation(StringInterpolation node) { + world.registerInstantiatedClass(compiler.stringClass); + node.visitChildren(this); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + registerImplicitInvocation(const SourceString('toString'), 0); + node.visitChildren(this); + } + + visitBreakStatement(BreakStatement node) { + TargetElement target; + if (node.target == null) { + target = statementScope.currentBreakTarget(); + if (target == null) { + error(node, MessageKind.NO_BREAK_TARGET); + return; + } + target.isBreakTarget = true; + } else { + String labelName = node.target.source.slowToString(); + LabelElement label = statementScope.lookupLabel(labelName); + if (label == null) { + error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); + return; + } + target = label.target; + if (!target.statement.isValidBreakTarget()) { + error(node.target, MessageKind.INVALID_BREAK); + return; + } + label.setBreakTarget(); + mapping[node.target] = label; + } + if (mapping[node] != null) { + // TODO(ahe): I'm not sure why this node already has an element + // that is different from target. I will talk to Lasse and + // figure out what is going on. + mapping.remove(node); + } + mapping[node] = target; + } + + visitContinueStatement(ContinueStatement node) { + TargetElement target; + if (node.target == null) { + target = statementScope.currentContinueTarget(); + if (target == null) { + error(node, MessageKind.NO_CONTINUE_TARGET); + return; + } + target.isContinueTarget = true; + } else { + String labelName = node.target.source.slowToString(); + LabelElement label = statementScope.lookupLabel(labelName); + if (label == null) { + error(node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName}); + return; + } + target = label.target; + if (!target.statement.isValidContinueTarget()) { + error(node.target, MessageKind.INVALID_CONTINUE); + } + // TODO(lrn): Handle continues to switch cases. + if (target.statement is SwitchCase) { + unimplemented(node, "continue to switch case"); + } + label.setContinueTarget(); + mapping[node.target] = label; + } + mapping[node] = target; + } + + registerImplicitInvocation(SourceString name, int arity) { + Selector selector = new Selector.call(name, null, arity); + world.registerDynamicInvocation(name, selector); + } + + registerImplicitFieldGet(SourceString name) { + Selector selector = new Selector.getter(name, null); + world.registerDynamicGetter(name, selector); + } + + visitForIn(ForIn node) { + for (final name in const [ + const SourceString('iterator'), + const SourceString('current')]) { + registerImplicitFieldGet(name); + } + registerImplicitInvocation(const SourceString('moveNext'), 0); + visit(node.expression); + Scope blockScope = new BlockScope(scope); + Node declaration = node.declaredIdentifier; + visitIn(declaration, blockScope); + visitLoopBodyIn(node, node.body, blockScope); + + // TODO(lrn): Also allow a single identifier. + if ((declaration is !Send || declaration.asSend().selector is !Identifier + || declaration.asSend().receiver != null) + && (declaration is !VariableDefinitions || + !declaration.asVariableDefinitions().definitions.nodes.tail.isEmpty)) + { + // The variable declaration is either not an identifier, not a + // declaration, or it's declaring more than one variable. + error(node.declaredIdentifier, MessageKind.INVALID_FOR_IN); + } + } + + visitLabel(Label node) { + // Labels are handled by their containing statements/cases. + } + + visitLabeledStatement(LabeledStatement node) { + Statement body = node.statement; + TargetElement targetElement = getOrCreateTargetElement(body); + Map<String, LabelElement> labelElements = <String, LabelElement>{}; + for (Label label in node.labels) { + String labelName = label.slowToString(); + if (labelElements.containsKey(labelName)) continue; + LabelElement element = targetElement.addLabel(label, labelName); + labelElements[labelName] = element; + } + statementScope.enterLabelScope(labelElements); + visit(node.statement); + statementScope.exitLabelScope(); + labelElements.forEach((String labelName, LabelElement element) { + if (element.isTarget) { + mapping[element.label] = element; + } else { + warning(element.label, MessageKind.UNUSED_LABEL, + {'labelName': labelName}); + } + }); + if (!targetElement.isTarget && identical(mapping[body], targetElement)) { + // If the body is itself a break or continue for another target, it + // might have updated its mapping to the target it actually does target. + mapping.remove(body); + } + } + + visitLiteralMap(LiteralMap node) { + world.registerInstantiatedClass(compiler.mapClass); + node.visitChildren(this); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + node.visitChildren(this); + } + + visitNamedArgument(NamedArgument node) { + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + node.expression.accept(this); + + TargetElement breakElement = getOrCreateTargetElement(node); + Map<String, LabelElement> continueLabels = <String, LabelElement>{}; + Link<Node> cases = node.cases.nodes; + while (!cases.isEmpty) { + SwitchCase switchCase = cases.head; + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is! Label) continue; + Label label = labelOrCase; + String labelName = label.slowToString(); + + LabelElement existingElement = continueLabels[labelName]; + if (existingElement != null) { + // It's an error if the same label occurs twice in the same switch. + warning(label, MessageKind.DUPLICATE_LABEL, {'labelName': labelName}); + error(existingElement.label, MessageKind.EXISTING_LABEL, + {'labelName': labelName}); + } else { + // It's only a warning if it shadows another label. + existingElement = statementScope.lookupLabel(labelName); + if (existingElement != null) { + warning(label, MessageKind.DUPLICATE_LABEL, + {'labelName': labelName}); + warning(existingElement.label, + MessageKind.EXISTING_LABEL, {'labelName': labelName}); + } + } + + TargetElement targetElement = + new TargetElementX(switchCase, + statementScope.nestingLevel, + enclosingElement); + if (mapping[switchCase] != null) { + // TODO(ahe): Talk to Lasse about this. + mapping.remove(switchCase); + } + mapping[switchCase] = targetElement; + + LabelElement labelElement = + new LabelElementX(label, labelName, + targetElement, enclosingElement); + mapping[label] = labelElement; + continueLabels[labelName] = labelElement; + } + cases = cases.tail; + // Test that only the last case, if any, is a default case. + if (switchCase.defaultKeyword != null && !cases.isEmpty) { + error(switchCase, MessageKind.INVALID_CASE_DEFAULT); + } + } + + statementScope.enterSwitch(breakElement, continueLabels); + node.cases.accept(this); + statementScope.exitSwitch(); + + // Clean-up unused labels. + continueLabels.forEach((String key, LabelElement label) { + if (!label.isContinueTarget) { + TargetElement targetElement = label.target; + SwitchCase switchCase = targetElement.statement; + mapping.remove(switchCase); + mapping.remove(label.label); + } + }); + } + + visitSwitchCase(SwitchCase node) { + node.labelsAndCases.accept(this); + visitIn(node.statements, new BlockScope(scope)); + } + + visitCaseMatch(CaseMatch node) { + visit(node.expression); + } + + visitTryStatement(TryStatement node) { + visit(node.tryBlock); + if (node.catchBlocks.isEmpty && node.finallyBlock == null) { + // TODO(ngeoffray): The precise location is + // node.getEndtoken.next. Adjust when issue #1581 is fixed. + error(node, MessageKind.NO_CATCH_NOR_FINALLY); + } + visit(node.catchBlocks); + visit(node.finallyBlock); + } + + visitCatchBlock(CatchBlock node) { + // Check that if catch part is present, then + // it has one or two formal parameters. + if (node.formals != null) { + if (node.formals.isEmpty) { + error(node, MessageKind.EMPTY_CATCH_DECLARATION); + } + if (!node.formals.nodes.tail.isEmpty && + !node.formals.nodes.tail.tail.isEmpty) { + for (Node extra in node.formals.nodes.tail.tail) { + error(extra, MessageKind.EXTRA_CATCH_DECLARATION); + } + } + + // Check that the formals aren't optional and that they have no + // modifiers or type. + for (Link<Node> link = node.formals.nodes; + !link.isEmpty; + link = link.tail) { + // If the formal parameter is a node list, it means that it is a + // sequence of optional parameters. + NodeList nodeList = link.head.asNodeList(); + if (nodeList != null) { + error(nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH); + } else { + VariableDefinitions declaration = link.head; + for (Node modifier in declaration.modifiers.nodes) { + error(modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH); + } + TypeAnnotation type = declaration.type; + if (type != null) { + error(type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH); + } + } + } + } + + Scope blockScope = new BlockScope(scope); + var wasTypeRequired = typeRequired; + typeRequired = true; + doInCheckContext(() => visitIn(node.type, blockScope)); + typeRequired = wasTypeRequired; + visitIn(node.formals, blockScope); + var oldInCatchBlock = inCatchBlock; + inCatchBlock = true; + visitIn(node.block, blockScope); + inCatchBlock = oldInCatchBlock; + } + + visitTypedef(Typedef node) { + unimplemented(node, 'typedef'); + } +} + +class TypeDefinitionVisitor extends CommonResolverVisitor<DartType> { + Scope scope; + TypeDeclarationElement element; + TypeResolver typeResolver; + + TypeDefinitionVisitor(Compiler compiler, TypeDeclarationElement element) + : this.element = element, + scope = Scope.buildEnclosingScope(element), + typeResolver = new TypeResolver(compiler), + super(compiler); + + void resolveTypeVariableBounds(NodeList node) { + if (node == null) return; + + var nameSet = new Set<SourceString>(); + // Resolve the bounds of type variables. + Link<DartType> typeLink = element.typeVariables; + Link<Node> nodeLink = node.nodes; + while (!nodeLink.isEmpty) { + TypeVariableType typeVariable = typeLink.head; + SourceString typeName = typeVariable.name; + TypeVariable typeNode = nodeLink.head; + if (nameSet.contains(typeName)) { + error(typeNode, MessageKind.DUPLICATE_TYPE_VARIABLE_NAME, + {'typeVariableName': typeName}); + } + nameSet.add(typeName); + + TypeVariableElement variableElement = typeVariable.element; + if (typeNode.bound != null) { + DartType boundType = typeResolver.resolveTypeAnnotation( + typeNode.bound, scope, element, onFailure: warning); + if (boundType != null && boundType.element == variableElement) { + // TODO(johnniwinther): Check for more general cycles, like + // [: <A extends B, B extends C, C extends B> :]. + warning(node, MessageKind.CYCLIC_TYPE_VARIABLE, + {'typeVariableName': variableElement.name}); + } else if (boundType != null) { + variableElement.bound = boundType; + } else { + // TODO(johnniwinther): Should be an erroneous type. + variableElement.bound = compiler.objectClass.computeType(compiler); + } + } else { + variableElement.bound = compiler.objectClass.computeType(compiler); + } + nodeLink = nodeLink.tail; + typeLink = typeLink.tail; + } + assert(typeLink.isEmpty); + } +} + +class TypedefResolverVisitor extends TypeDefinitionVisitor { + TypedefElement get element => super.element; + + TypedefResolverVisitor(Compiler compiler, TypedefElement typedefElement) + : super(compiler, typedefElement); + + visitTypedef(Typedef node) { + TypedefType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + resolveTypeVariableBounds(node.typeParameters); + + element.functionSignature = SignatureResolver.analyze( + compiler, node.formals, node.returnType, element); + + element.alias = compiler.computeFunctionType( + element, element.functionSignature); + + // TODO(johnniwinther): Check for cyclic references in the typedef alias. + } +} + +/** + * The implementation of [ResolverTask.resolveClass]. + * + * This visitor has to be extra careful as it is building the basic + * element information, and cannot safely look at other elements as + * this may lead to cycles. + * + * This visitor can assume that the supertypes have already been + * resolved, but it cannot call [ResolverTask.resolveClass] directly + * or indirectly (through [ClassElement.ensureResolved]) for any other + * types. + */ +class ClassResolverVisitor extends TypeDefinitionVisitor { + ClassElement get element => super.element; + + ClassResolverVisitor(Compiler compiler, ClassElement classElement) + : super(compiler, classElement); + + DartType visitClassNode(ClassNode node) { + compiler.ensure(element != null); + compiler.ensure(element.resolutionState == STATE_STARTED); + + InterfaceType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + // TODO(ahe): It is not safe to call resolveTypeVariableBounds yet. + // As a side-effect, this may get us back here trying to + // resolve this class again. + resolveTypeVariableBounds(node.typeParameters); + + // Setup the supertype for the element. + assert(element.supertype == null); + if (node.superclass != null) { + MixinApplication superMixin = node.superclass.asMixinApplication(); + if (superMixin != null) { + DartType supertype = resolveSupertype(element, superMixin.superclass); + Link<Node> link = superMixin.mixins.nodes; + while (!link.isEmpty) { + supertype = applyMixin(supertype, visit(link.head)); + link = link.tail; + } + element.supertype = supertype; + } else { + element.supertype = resolveSupertype(element, node.superclass); + } + } + + // If the super type isn't specified, we make it Object. + final objectElement = compiler.objectClass; + if (!identical(element, objectElement) && element.supertype == null) { + if (objectElement == null) { + compiler.internalError("Internal error: cannot resolve Object", + node: node); + } else { + objectElement.ensureResolved(compiler); + } + element.supertype = objectElement.computeType(compiler); + } + + assert(element.interfaces == null); + element.interfaces = resolveInterfaces(node.interfaces, node.superclass); + calculateAllSupertypes(element); + + if (node.defaultClause != null) { + element.defaultClass = visit(node.defaultClause); + } + element.addDefaultConstructorIfNeeded(compiler); + return element.computeType(compiler); + } + + DartType visitNamedMixinApplication(NamedMixinApplication node) { + compiler.ensure(element != null); + compiler.ensure(element.resolutionState == STATE_STARTED); + + InterfaceType type = element.computeType(compiler); + scope = new TypeDeclarationScope(scope, element); + resolveTypeVariableBounds(node.typeParameters); + + // Generate anonymous mixin application elements for the + // intermediate mixin applications (excluding the last). + DartType supertype = resolveSupertype(element, node.superclass); + Link<Node> link = node.mixins.nodes; + while (!link.tail.isEmpty) { + supertype = applyMixin(supertype, visit(link.head)); + link = link.tail; + } + doApplyMixinTo(element, supertype, visit(link.head)); + return element.computeType(compiler); + } + + DartType applyMixin(DartType supertype, DartType mixinType) { + String superName = supertype.name.slowToString(); + String mixinName = mixinType.name.slowToString(); + ClassElement mixinApplication = new MixinApplicationElementX( + new SourceString("${superName}_${mixinName}"), + element.getCompilationUnit(), + compiler.getNextFreeClassId(), + element.parseNode(compiler), + Modifiers.EMPTY); // TODO(kasperl): Should this be abstract? + doApplyMixinTo(mixinApplication, supertype, mixinType); + mixinApplication.resolutionState = STATE_DONE; + mixinApplication.supertypeLoadState = STATE_DONE; + return mixinApplication.computeType(compiler); + } + + void doApplyMixinTo(MixinApplicationElement mixinApplication, + DartType supertype, + DartType mixinType) { + assert(mixinApplication.supertype == null); + mixinApplication.supertype = supertype; + + // Named mixin application may have an 'implements' clause. + NamedMixinApplication namedMixinApplication = + mixinApplication.parseNode(compiler).asNamedMixinApplication(); + Link<DartType> interfaces = (namedMixinApplication != null) + ? resolveInterfaces(namedMixinApplication.interfaces, + namedMixinApplication.superclass) + : const Link<DartType>(); + + // The class that is the result of a mixin application implements + // the interface of the class that was mixed in so always prepend + // that to the interface list. + interfaces = interfaces.prepend(mixinType); + assert(mixinApplication.interfaces == null); + mixinApplication.interfaces = interfaces; + + assert(mixinApplication.mixin == null); + mixinApplication.mixin = resolveMixinFor(mixinApplication, mixinType); + mixinApplication.addDefaultConstructorIfNeeded(compiler); + calculateAllSupertypes(mixinApplication); + } + + ClassElement resolveMixinFor(MixinApplicationElement mixinApplication, + DartType mixinType) { + ClassElement mixin = mixinType.element; + mixin.ensureResolved(compiler); + + // Check for cycles in the mixin chain. + ClassElement previous = mixinApplication; // For better error messages. + ClassElement current = mixin; + while (current != null && current.isMixinApplication) { + MixinApplicationElement currentMixinApplication = current; + if (currentMixinApplication == mixinApplication) { + compiler.reportErrorCode( + mixinApplication, MessageKind.ILLEGAL_MIXIN_CYCLE, + {'mixinName1': current.name, 'mixinName2': previous.name}); + // We have found a cycle in the mixin chain. Return null as + // the mixin for this application to avoid getting into + // infinite recursion when traversing members. + return null; + } + previous = current; + current = currentMixinApplication.mixin; + } + compiler.world.registerMixinUse(mixinApplication, mixin); + return mixin; + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitTypeAnnotation(TypeAnnotation node) { + return visit(node.typeName); + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitIdentifier(Identifier node) { + Element element = scope.lookup(node.source); + if (element == null) { + error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node}); + return null; + } else if (!element.impliesType() && !element.isTypeVariable()) { + error(node, MessageKind.NOT_A_TYPE, {'node': node}); + return null; + } else { + if (element.isTypeVariable()) { + TypeVariableElement variableElement = element; + return variableElement.type; + } else if (element.isTypedef()) { + compiler.unimplemented('visitIdentifier for typedefs', node: node); + } else { + // TODO(ngeoffray): Use type variables. + return element.computeType(compiler); + } + } + return null; + } + + // TODO(johnniwinther): Remove when default class is no longer supported. + DartType visitSend(Send node) { + Identifier prefix = node.receiver.asIdentifier(); + if (prefix == null) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return null; + } + Element element = scope.lookup(prefix.source); + if (element == null || !identical(element.kind, ElementKind.PREFIX)) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return null; + } + PrefixElement prefixElement = element; + Identifier selector = node.selector.asIdentifier(); + var e = prefixElement.lookupLocalMember(selector.source); + if (e == null || !e.impliesType()) { + error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, + {'typeName': node.selector}); + return null; + } + return e.computeType(compiler); + } + + DartType resolveSupertype(ClassElement cls, TypeAnnotation superclass) { + DartType supertype = typeResolver.resolveTypeAnnotation( + superclass, scope, cls, onFailure: error); + if (supertype != null) { + if (identical(supertype.kind, TypeKind.MALFORMED_TYPE)) { + // Error has already been reported. + return null; + } else if (!identical(supertype.kind, TypeKind.INTERFACE)) { + // TODO(johnniwinther): Handle dynamic. + error(superclass.typeName, MessageKind.CLASS_NAME_EXPECTED); + return null; + } else if (isBlackListed(supertype)) { + error(superclass, MessageKind.CANNOT_EXTEND, {'type': supertype}); + return null; + } + } + return supertype; + } + + Link<DartType> resolveInterfaces(NodeList interfaces, Node superclass) { + Link<DartType> result = const Link<DartType>(); + if (interfaces == null) return result; + for (Link<Node> link = interfaces.nodes; !link.isEmpty; link = link.tail) { + DartType interfaceType = typeResolver.resolveTypeAnnotation( + link.head, scope, element, onFailure: error); + if (interfaceType != null) { + if (identical(interfaceType.kind, TypeKind.MALFORMED_TYPE)) { + // Error has already been reported. + } else if (!identical(interfaceType.kind, TypeKind.INTERFACE)) { + // TODO(johnniwinther): Handle dynamic. + TypeAnnotation typeAnnotation = link.head; + error(typeAnnotation.typeName, MessageKind.CLASS_NAME_EXPECTED); + } else { + if (interfaceType == element.supertype) { + compiler.reportErrorCode( + superclass, + MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS, + {'type': interfaceType}); + compiler.reportErrorCode( + link.head, + MessageKind.DUPLICATE_EXTENDS_IMPLEMENTS, + {'type': interfaceType}); + } + if (result.contains(interfaceType)) { + compiler.reportErrorCode( + link.head, + MessageKind.DUPLICATE_IMPLEMENTS, + {'type': interfaceType}); + } + result = result.prepend(interfaceType); + if (isBlackListed(interfaceType)) { + error(link.head, MessageKind.CANNOT_IMPLEMENT, + {'type': interfaceType}); + } + } + } + } + return result; + } + + void calculateAllSupertypes(ClassElement cls) { + // TODO(karlklose): Check if type arguments match, if a class + // element occurs more than once in the supertypes. + if (cls.allSupertypes != null) return; + final DartType supertype = cls.supertype; + if (supertype != null) { + var allSupertypes = new LinkBuilder<DartType>(); + addAllSupertypes(allSupertypes, supertype); + for (Link<DartType> interfaces = cls.interfaces; + !interfaces.isEmpty; + interfaces = interfaces.tail) { + addAllSupertypes(allSupertypes, interfaces.head); + } + cls.allSupertypes = allSupertypes.toLink(); + } else { + assert(identical(cls, compiler.objectClass)); + cls.allSupertypes = const Link<DartType>(); + } + } + + /** + * Adds [type] and all supertypes of [type] to [builder] while substituting + * type variables. + */ + void addAllSupertypes(LinkBuilder<DartType> builder, InterfaceType type) { + builder.addLast(type); + Link<DartType> typeArguments = type.typeArguments; + ClassElement classElement = type.element; + Link<DartType> typeVariables = classElement.typeVariables; + Link<DartType> supertypes = classElement.allSupertypes; + assert(invariant(element, supertypes != null, + message: "Supertypes not computed on $classElement " + "during resolution of $element")); + while (!supertypes.isEmpty) { + DartType supertype = supertypes.head; + builder.addLast(supertype.subst(typeArguments, typeVariables)); + supertypes = supertypes.tail; + } + } + + isBlackListed(DartType type) { + LibraryElement lib = element.getLibrary(); + return + !identical(lib, compiler.coreLibrary) && + !identical(lib, compiler.jsHelperLibrary) && + !identical(lib, compiler.interceptorsLibrary) && + (identical(type.element, compiler.dynamicClass) || + identical(type.element, compiler.boolClass) || + identical(type.element, compiler.numClass) || + identical(type.element, compiler.intClass) || + identical(type.element, compiler.doubleClass) || + identical(type.element, compiler.stringClass) || + identical(type.element, compiler.nullClass) || + identical(type.element, compiler.functionClass)); + } +} + +class ClassSupertypeResolver extends CommonResolverVisitor { + Scope context; + ClassElement classElement; + + ClassSupertypeResolver(Compiler compiler, ClassElement cls) + : context = Scope.buildEnclosingScope(cls), + this.classElement = cls, + super(compiler); + + void loadSupertype(ClassElement element, Node from) { + compiler.resolver.loadSupertypes(element, from); + element.ensureResolved(compiler); + } + + void visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + link.head.accept(this); + } + } + + void visitClassNode(ClassNode node) { + if (node.superclass == null) { + if (!identical(classElement, compiler.objectClass)) { + loadSupertype(compiler.objectClass, node); + } + } else { + node.superclass.accept(this); + } + visitNodeList(node.interfaces); + } + + void visitMixinApplication(MixinApplication node) { + node.superclass.accept(this); + visitNodeList(node.mixins); + } + + void visitTypeAnnotation(TypeAnnotation node) { + node.typeName.accept(this); + } + + void visitIdentifier(Identifier node) { + Element element = context.lookup(node.source); + if (element == null) { + error(node, MessageKind.CANNOT_RESOLVE_TYPE, {'typeName': node}); + } else if (!element.impliesType()) { + error(node, MessageKind.NOT_A_TYPE, {'node': node}); + } else { + if (element.isClass()) { + loadSupertype(element, node); + } else { + compiler.reportErrorCode(node, MessageKind.CLASS_NAME_EXPECTED); + } + } + } + + void visitSend(Send node) { + Identifier prefix = node.receiver.asIdentifier(); + if (prefix == null) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return; + } + Element element = context.lookup(prefix.source); + if (element == null || !identical(element.kind, ElementKind.PREFIX)) { + error(node.receiver, MessageKind.NOT_A_PREFIX, {'node': node.receiver}); + return; + } + PrefixElement prefixElement = element; + Identifier selector = node.selector.asIdentifier(); + var e = prefixElement.lookupLocalMember(selector.source); + if (e == null || !e.impliesType()) { + error(node.selector, MessageKind.CANNOT_RESOLVE_TYPE, + {'typeName': node.selector}); + return; + } + loadSupertype(e, node); + } +} + +class VariableDefinitionsVisitor extends CommonResolverVisitor<SourceString> { + VariableDefinitions definitions; + ResolverVisitor resolver; + ElementKind kind; + VariableListElement variables; + + VariableDefinitionsVisitor(Compiler compiler, + this.definitions, this.resolver, this.kind) + : super(compiler) { + variables = new VariableListElementX.node( + definitions, ElementKind.VARIABLE_LIST, resolver.enclosingElement); + } + + SourceString visitSendSet(SendSet node) { + assert(node.arguments.tail.isEmpty); // Sanity check + resolver.visit(node.arguments.head); + return visit(node.selector); + } + + SourceString visitIdentifier(Identifier node) { + // The variable is initialized to null. + resolver.world.registerInstantiatedClass(compiler.nullClass); + return node.source; + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + SourceString name = visit(link.head); + VariableElement element = + new VariableElementX(name, variables, kind, link.head); + resolver.defineElement(link.head, element); + } + } +} + +/** + * [SignatureResolver] resolves function signatures. + */ +class SignatureResolver extends CommonResolverVisitor<Element> { + final Element enclosingElement; + Link<Element> optionalParameters = const Link<Element>(); + int optionalParameterCount = 0; + bool optionalParametersAreNamed = false; + VariableDefinitions currentDefinitions; + + SignatureResolver(Compiler compiler, this.enclosingElement) : super(compiler); + + Element visitNodeList(NodeList node) { + // This must be a list of optional arguments. + String value = node.beginToken.stringValue; + if ((!identical(value, '[')) && (!identical(value, '{'))) { + internalError(node, "expected optional parameters"); + } + optionalParametersAreNamed = (identical(value, '{')); + LinkBuilder<Element> elements = analyzeNodes(node.nodes); + optionalParameterCount = elements.length; + optionalParameters = elements.toLink(); + return null; + } + + Element visitVariableDefinitions(VariableDefinitions node) { + Link<Node> definitions = node.definitions.nodes; + if (definitions.isEmpty) { + cancel(node, 'internal error: no parameter definition'); + return null; + } + if (!definitions.tail.isEmpty) { + cancel(definitions.tail.head, 'internal error: extra definition'); + return null; + } + Node definition = definitions.head; + if (definition is NodeList) { + cancel(node, 'optional parameters are not implemented'); + } + + if (currentDefinitions != null) { + cancel(node, 'function type parameters not supported'); + } + currentDefinitions = node; + Element element = definition.accept(this); + currentDefinitions = null; + return element; + } + + Element visitIdentifier(Identifier node) { + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + // Ensure a parameter is not typed 'void'. + variables.computeType(compiler); + return new VariableElementX(node.source, variables, + ElementKind.PARAMETER, node); + } + + SourceString getParameterName(Send node) { + var identifier = node.selector.asIdentifier(); + if (identifier != null) { + // Normal parameter: [:Type name:]. + return identifier.source; + } else { + // Function type parameter: [:void name(DartType arg):]. + var functionExpression = node.selector.asFunctionExpression(); + if (functionExpression != null && + functionExpression.name.asIdentifier() != null) { + return functionExpression.name.asIdentifier().source; + } else { + cancel(node, + 'internal error: unimplemented receiver on parameter send'); + } + } + } + + // The only valid [Send] can be in constructors and must be of the form + // [:this.x:] (where [:x:] represents an instance field). + FieldParameterElement visitSend(Send node) { + FieldParameterElement element; + if (node.receiver.asIdentifier() == null || + !node.receiver.asIdentifier().isThis()) { + error(node, MessageKind.INVALID_PARAMETER); + } else if (!identical(enclosingElement.kind, + ElementKind.GENERATIVE_CONSTRUCTOR)) { + error(node, MessageKind.FIELD_PARAMETER_NOT_ALLOWED); + } else { + SourceString name = getParameterName(node); + Element fieldElement = currentClass.lookupLocalMember(name); + if (fieldElement == null || + !identical(fieldElement.kind, ElementKind.FIELD)) { + error(node, MessageKind.NOT_A_FIELD, {'fieldName': name}); + } else if (!fieldElement.isInstanceMember()) { + error(node, MessageKind.NOT_INSTANCE_FIELD, {'fieldName': name}); + } + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + element = new FieldParameterElementX(name, fieldElement, variables, node); + } + return element; + } + + Element visitSendSet(SendSet node) { + Element element; + if (node.receiver != null) { + element = visitSend(node); + } else if (node.selector.asIdentifier() != null) { + Element variables = new VariableListElementX.node(currentDefinitions, + ElementKind.VARIABLE_LIST, enclosingElement); + element = new VariableElementX(node.selector.asIdentifier().source, + variables, ElementKind.PARAMETER, node); + } + // Visit the value. The compile time constant handler will + // make sure it's a compile time constant. + resolveExpression(node.arguments.head); + return element; + } + + Element visitFunctionExpression(FunctionExpression node) { + // This is a function typed parameter. + // TODO(ahe): Resolve the function type. + return visit(node.name); + } + + LinkBuilder<Element> analyzeNodes(Link<Node> link) { + LinkBuilder<Element> elements = new LinkBuilder<Element>(); + for (; !link.isEmpty; link = link.tail) { + Element element = link.head.accept(this); + if (element != null) { + elements.addLast(element); + } else { + // If parameter is null, the current node should be the last, + // and a list of optional named parameters. + if (!link.tail.isEmpty || (link.head is !NodeList)) { + internalError(link.head, "expected optional parameters"); + } + } + } + return elements; + } + + /** + * Resolves formal parameters and return type to a [FunctionSignature]. + */ + static FunctionSignature analyze(Compiler compiler, + NodeList formalParameters, + Node returnNode, + Element element) { + SignatureResolver visitor = new SignatureResolver(compiler, element); + Link<Element> parameters = const Link<Element>(); + int requiredParameterCount = 0; + if (formalParameters == null) { + if (!element.isGetter()) { + compiler.reportErrorCode(element, MessageKind.MISSING_FORMALS); + } + } else { + if (element.isGetter()) { + if (!identical(formalParameters.getEndToken().next.stringValue, + // TODO(ahe): Remove the check for native keyword. + 'native')) { + if (compiler.rejectDeprecatedFeatures && + // TODO(ahe): Remove isPlatformLibrary check. + !element.getLibrary().isPlatformLibrary) { + compiler.reportErrorCode(formalParameters, + MessageKind.EXTRA_FORMALS); + } else { + compiler.onDeprecatedFeature(formalParameters, 'getter parameters'); + } + } + } + LinkBuilder<Element> parametersBuilder = + visitor.analyzeNodes(formalParameters.nodes); + requiredParameterCount = parametersBuilder.length; + parameters = parametersBuilder.toLink(); + } + DartType returnType = compiler.resolveReturnType(element, returnNode); + if (element.isSetter() && (requiredParameterCount != 1 || + visitor.optionalParameterCount != 0)) { + // If there are no formal parameters, we already reported an error above. + if (formalParameters != null) { + compiler.reportErrorCode(formalParameters, + MessageKind.ILLEGAL_SETTER_FORMALS); + } + } + if (element.isGetter() && (requiredParameterCount != 0 + || visitor.optionalParameterCount != 0)) { + compiler.reportErrorCode(formalParameters, MessageKind.EXTRA_FORMALS); + } + return new FunctionSignatureX(parameters, + visitor.optionalParameters, + requiredParameterCount, + visitor.optionalParameterCount, + visitor.optionalParametersAreNamed, + returnType); + } + + // TODO(ahe): This is temporary. + void resolveExpression(Node node) { + if (node == null) return; + node.accept(new ResolverVisitor(compiler, enclosingElement, + new TreeElementMapping(enclosingElement))); + } + + // TODO(ahe): This is temporary. + ClassElement get currentClass { + return enclosingElement.isMember() + ? enclosingElement.getEnclosingClass() : null; + } +} + +class ConstructorResolver extends CommonResolverVisitor<Element> { + final ResolverVisitor resolver; + bool inConstContext = false; + DartType type; + + ConstructorResolver(Compiler compiler, this.resolver) : super(compiler); + + visitNode(Node node) { + throw 'not supported'; + } + + failOrReturnErroneousElement(Element enclosing, Node diagnosticNode, + SourceString targetName, MessageKind kind, + Map arguments) { + if (inConstContext) { + error(diagnosticNode, kind, arguments); + } else { + ResolutionWarning warning = new ResolutionWarning(kind, arguments); + compiler.reportWarning(diagnosticNode, warning); + return new ErroneousElementX(kind, arguments, targetName, enclosing); + } + } + + Selector createConstructorSelector(SourceString constructorName) { + return constructorName == const SourceString('') + ? new Selector.callDefaultConstructor( + resolver.enclosingElement.getLibrary()) + : new Selector.callConstructor( + constructorName, + resolver.enclosingElement.getLibrary()); + } + + // TODO(ngeoffray): method named lookup should not report errors. + FunctionElement lookupConstructor(ClassElement cls, + Node diagnosticNode, + SourceString constructorName) { + cls.ensureResolved(compiler); + Selector selector = createConstructorSelector(constructorName); + Element result = cls.lookupConstructor(selector); + if (result == null) { + String fullConstructorName = + resolver.compiler.resolver.constructorNameForDiagnostics( + cls.name, + constructorName); + return failOrReturnErroneousElement( + cls, + diagnosticNode, + new SourceString(fullConstructorName), + MessageKind.CANNOT_FIND_CONSTRUCTOR, + {'constructorName': fullConstructorName}); + } else if (inConstContext && !result.modifiers.isConst()) { + error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST); + } + return result; + } + + visitNewExpression(NewExpression node) { + inConstContext = node.isConst(); + Node selector = node.send.selector; + Element e = visit(selector); + return finishConstructorReference(e, node.send.selector, node); + } + + /// Finishes resolution of a constructor reference and records the + /// type of the constructed instance on [expression]. + FunctionElement finishConstructorReference(Element e, + Node diagnosticNode, + Node expression) { + // Find the unnamed constructor if the reference resolved to a + // class. + if (!Elements.isUnresolved(e) && e.isClass()) { + ClassElement cls = e; + cls.ensureResolved(compiler); + if (cls.isInterface() && (cls.defaultClass == null)) { + // TODO(ahe): Remove this check and error message when we + // don't have interfaces anymore. + error(diagnosticNode, + MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': cls.name}); + } + // The unnamed constructor may not exist, so [e] may become unresolved. + e = lookupConstructor(cls, diagnosticNode, const SourceString('')); + } + if (type == null) { + if (Elements.isUnresolved(e)) { + type = compiler.dynamicClass.computeType(compiler); + } else { + type = e.getEnclosingClass().computeType(compiler).asRaw(); + } + } + resolver.mapping.setType(expression, type); + return e; + } + + visitTypeAnnotation(TypeAnnotation node) { + assert(invariant(node, type == null)); + type = resolver.resolveTypeRequired(node); + return resolver.mapping[node]; + } + + visitSend(Send node) { + Element e = visit(node.receiver); + if (Elements.isUnresolved(e)) return e; + Identifier name = node.selector.asIdentifier(); + if (name == null) internalError(node.selector, 'unexpected node'); + + if (identical(e.kind, ElementKind.CLASS)) { + ClassElement cls = e; + cls.ensureResolved(compiler); + if (cls.isInterface() && (cls.defaultClass == null)) { + error(node.receiver, + MessageKind.CANNOT_INSTANTIATE_INTERFACE, + {'interfaceName': cls.name}); + } + return lookupConstructor(cls, name, name.source); + } else if (identical(e.kind, ElementKind.PREFIX)) { + PrefixElement prefix = e; + e = prefix.lookupLocalMember(name.source); + if (e == null) { + return failOrReturnErroneousElement(resolver.enclosingElement, name, + name.source, + MessageKind.CANNOT_RESOLVE, + {'name': name}); + } else if (!identical(e.kind, ElementKind.CLASS)) { + error(node, MessageKind.NOT_A_TYPE, {'node': name}); + } + } else { + internalError(node.receiver, 'unexpected element $e'); + } + return e; + } + + Element visitIdentifier(Identifier node) { + SourceString name = node.source; + Element e = resolver.lookup(node, name); + // TODO(johnniwinther): Change errors to warnings, cf. 11.11.1. + if (e == null) { + return failOrReturnErroneousElement(resolver.enclosingElement, node, name, + MessageKind.CANNOT_RESOLVE, + {'name': name}); + } else if (e.isErroneous()) { + return e; + } else if (identical(e.kind, ElementKind.TYPEDEF)) { + error(node, MessageKind.CANNOT_INSTANTIATE_TYPEDEF, + {'typedefName': name}); + } else if (identical(e.kind, ElementKind.TYPE_VARIABLE)) { + error(node, MessageKind.CANNOT_INSTANTIATE_TYPE_VARIABLE, + {'typeVariableName': name}); + } else if (!identical(e.kind, ElementKind.CLASS) + && !identical(e.kind, ElementKind.PREFIX)) { + error(node, MessageKind.NOT_A_TYPE, {'node': name}); + } + return e; + } + + /// Assumed to be called by [resolveRedirectingFactory]. + Element visitReturn(Return node) { + Node expression = node.expression; + return finishConstructorReference(visit(expression), + expression, expression); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart new file mode 100644 index 0000000..130486c --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart
@@ -0,0 +1,30 @@ +// 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. + +library resolution; + +import 'dart:collection' show Queue, LinkedHashMap; + +import '../dart2jslib.dart' hide Diagnostic; +import '../dart_types.dart'; +import '../../compiler.dart' show Diagnostic; +import '../tree/tree.dart'; +import '../elements/elements.dart'; +import '../elements/modelx.dart' + show FunctionElementX, + ErroneousElementX, + VariableElementX, + FieldParameterElementX, + VariableListElementX, + FunctionSignatureX, + LabelElementX, + TargetElementX, + MixinApplicationElementX; +import '../util/util.dart'; +import '../scanner/scannerlib.dart' show PartialMetadataAnnotation; + +import 'secret_tree_element.dart' show getTreeElement, setTreeElement; + +part 'members.dart'; +part 'scope.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart new file mode 100644 index 0000000..92e7840 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart
@@ -0,0 +1,163 @@ +// 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. + +part of resolution; + +abstract class Scope { + /** + * Adds [element] to this scope. This operation is only allowed on mutable + * scopes such as [MethodScope] and [BlockScope]. + */ + Element add(Element element); + + /** + * Looks up the [Element] for [name] in this scope. + */ + Element lookup(SourceString name); + + static Scope buildEnclosingScope(Element element) { + return element.enclosingElement != null + ? element.enclosingElement.buildScope() : element.buildScope(); + } +} + +abstract class NestedScope extends Scope { + final Scope parent; + + NestedScope(this.parent); + + Element lookup(SourceString name) { + Element result = localLookup(name); + if (result != null) return result; + return parent.lookup(name); + } + + Element localLookup(SourceString name); + + static Scope buildEnclosingScope(Element element) { + return element.enclosingElement != null + ? element.enclosingElement.buildScope() : element.buildScope(); + } +} + +/** + * [TypeDeclarationScope] defines the outer scope of a type declaration in + * which the declared type variables and the entities in the enclosing scope are + * available but where declared and inherited members are not available. This + * scope is only used for class/interface declarations during resolution of the + * class hierarchy. In all other cases [ClassScope] is used. + */ +class TypeDeclarationScope extends NestedScope { + final TypeDeclarationElement element; + + TypeDeclarationScope(parent, this.element) + : super(parent) { + assert(parent != null); + } + + Element add(Element newElement) { + throw "Cannot add element to TypeDeclarationScope"; + } + + Element lookupTypeVariable(SourceString name) { + Link<DartType> typeVariableLink = element.typeVariables; + while (!typeVariableLink.isEmpty) { + TypeVariableType typeVariable = typeVariableLink.head; + if (typeVariable.name == name) { + return typeVariable.element; + } + typeVariableLink = typeVariableLink.tail; + } + return null; + } + + Element localLookup(SourceString name) => lookupTypeVariable(name); + + String toString() => + 'TypeDeclarationScope($element)'; +} + +abstract class MutableScope extends NestedScope { + final Map<SourceString, Element> elements; + + MutableScope(Scope parent) + : super(parent), + this.elements = new Map<SourceString, Element>() { + assert(parent != null); + } + + Element add(Element newElement) { + if (elements.containsKey(newElement.name)) { + return elements[newElement.name]; + } + elements[newElement.name] = newElement; + return newElement; + } + + Element localLookup(SourceString name) => elements[name]; +} + +class MethodScope extends MutableScope { + final Element element; + + MethodScope(Scope parent, this.element) + : super(parent); + + String toString() => 'MethodScope($element${elements.keys.toList()})'; +} + +class BlockScope extends MutableScope { + BlockScope(Scope parent) : super(parent); + + String toString() => 'BlockScope(${elements.keys.toList()})'; +} + +/** + * [ClassScope] defines the inner scope of a class/interface declaration in + * which declared members, declared type variables, entities in the enclosing + * scope and inherited members are available, in the given order. + */ +class ClassScope extends TypeDeclarationScope { + ClassElement get element => super.element; + + ClassScope(Scope parentScope, ClassElement element) + : super(parentScope, element) { + assert(parent != null); + } + + Element localLookup(SourceString name) { + Element result = element.lookupLocalMember(name); + if (result != null) return result; + return super.localLookup(name); + } + + Element lookup(SourceString name) { + Element result = localLookup(name); + if (result != null) return result; + result = parent.lookup(name); + if (result != null) return result; + return element.lookupSuperMember(name); + } + + Element add(Element newElement) { + throw "Cannot add an element in a class scope"; + } + + String toString() => 'ClassScope($element)'; +} + +class LibraryScope implements Scope { + final LibraryElement library; + + LibraryScope(LibraryElement this.library); + + Element localLookup(SourceString name) => library.find(name); + Element lookup(SourceString name) => localLookup(name); + + Element add(Element newElement) { + throw "Cannot add an element to a library scope"; + } + + String toString() => 'LibraryScope($library)'; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart new file mode 100644 index 0000000..e8ee0f1 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart
@@ -0,0 +1,46 @@ +// 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. + +/** + * Encapsulates the field [TreeElementMixin._element]. + * + * This library is an implementation detail of dart2js, and should not + * be imported except by resolution and tree node libraries, or for + * testing. + * + * We have taken great care to ensure AST nodes can be cached between + * compiler instances. Part of this is requires that we always access + * resolution results through [TreeElements]. + * + * So please, do not add additional elements to this library, and do + * not import it. + */ +library secret_tree_element; + +/** + * The superclass of all AST nodes. + */ +abstract class TreeElementMixin { + // Deliberately using [Object] here to thwart code completion. + // You're not really supposed to access this field anyways. + Object _element; +} + +/** + * Do not call this method directly. Instead, use an instance of + * [TreeElements]. + * + * Using [Object] as return type to thwart code completion. + */ +Object getTreeElement(TreeElementMixin node) { + return node._element; +} + +/** + * Do not call this method directly. Instead, use an instance of + * [TreeElements]. + */ +void setTreeElement(TreeElementMixin node, Object value) { + node._element = value; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart b/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart new file mode 100644 index 0000000..3bfadb2 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart
@@ -0,0 +1,67 @@ +// 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. + +part of dart2js; + +abstract class ResolvedVisitor<R> extends Visitor<R> { + TreeElements elements; + + ResolvedVisitor(this.elements); + + R visitSend(Send node) { + if (node.isSuperCall) { + return visitSuperSend(node); + } else if (node.isOperator) { + return visitOperatorSend(node); + } else if (node.isPropertyAccess) { + Element element = elements[node]; + if (!Elements.isUnresolved(element) && element.impliesType()) { + // A reference to a class literal, typedef or type variable. + return visitTypeReferenceSend(node); + } else { + return visitGetterSend(node); + } + } else if (Elements.isClosureSend(node, elements[node])) { + return visitClosureSend(node); + } else { + Element element = elements[node]; + if (Elements.isUnresolved(element)) { + if (element == null) { + // Example: f() with 'f' unbound. + // This can only happen inside an instance method. + return visitDynamicSend(node); + } else { + return visitStaticSend(node); + } + } else if (element.impliesType()) { + // A reference to a class literal, typedef or type variable. + return visitTypeReferenceSend(node); + } else if (element.isInstanceMember()) { + // Example: f() with 'f' bound to instance method. + return visitDynamicSend(node); + } else if (!element.isInstanceMember()) { + // Example: A.f() or f() with 'f' bound to a static function. + // Also includes new A() or new A.named() which is treated like a + // static call to a factory. + return visitStaticSend(node); + } else { + internalError("Cannot generate code for send", node: node); + } + } + } + + R visitSuperSend(Send node); + R visitOperatorSend(Send node); + R visitGetterSend(Send node); + R visitClosureSend(Send node); + R visitDynamicSend(Send node); + R visitStaticSend(Send node); + R visitTypeReferenceSend(Send node); + + void internalError(String reason, {Node node}); + + R visitNode(Node node) { + internalError("Unhandled node", node: node); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart new file mode 100644 index 0000000..01f8e9e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart
@@ -0,0 +1,183 @@ +// Copyright (c) 2011, 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. + +part of scanner_implementation; + +abstract +class ArrayBasedScanner<S extends SourceString> extends AbstractScanner<S> { + int get charOffset => byteOffset + extraCharOffset; + final Token tokens; + Token tail; + int tokenStart; + int byteOffset; + final bool includeComments; + + /** Since the input is UTF8, some characters are represented by more + * than one byte. [extraCharOffset] tracks the difference. */ + int extraCharOffset; + Link<BeginGroupToken> groupingStack = const Link<BeginGroupToken>(); + + ArrayBasedScanner(this.includeComments) + : this.extraCharOffset = 0, + this.tokenStart = -1, + this.byteOffset = -1, + this.tokens = new Token(EOF_INFO, -1) { + this.tail = this.tokens; + } + + int advance() { + int next = nextByte(); + return next; + } + + int select(int choice, PrecedenceInfo yes, PrecedenceInfo no) { + int next = advance(); + if (identical(next, choice)) { + appendPrecedenceToken(yes); + return advance(); + } else { + appendPrecedenceToken(no); + return next; + } + } + + void appendPrecedenceToken(PrecedenceInfo info) { + tail.next = new Token(info, tokenStart); + tail = tail.next; + } + + void appendStringToken(PrecedenceInfo info, String value) { + tail.next = new StringToken(info, value, tokenStart); + tail = tail.next; + } + + void appendKeywordToken(Keyword keyword) { + String syntax = keyword.syntax; + + // Type parameters and arguments cannot contain 'this' or 'super'. + if (identical(syntax, 'this') || identical(syntax, 'super')) discardOpenLt(); + tail.next = new KeywordToken(keyword, tokenStart); + tail = tail.next; + } + + void appendEofToken() { + tail.next = new Token(EOF_INFO, charOffset); + tail = tail.next; + // EOF points to itself so there's always infinite look-ahead. + tail.next = tail; + discardOpenLt(); + while (!groupingStack.isEmpty) { + unmatchedBeginGroup(groupingStack.head); + groupingStack = groupingStack.tail; + } + } + + void beginToken() { + tokenStart = charOffset; + } + + Token firstToken() { + return tokens.next; + } + + Token previousToken() { + return tail; + } + + void addToCharOffset(int offset) { + extraCharOffset += offset; + } + + void appendWhiteSpace(int next) { + // Do nothing, we don't collect white space. + } + + void appendBeginGroup(PrecedenceInfo info, String value) { + Token token = new BeginGroupToken(info, value, tokenStart); + tail.next = token; + tail = tail.next; + if (!identical(info.kind, LT_TOKEN)) discardOpenLt(); + groupingStack = groupingStack.prepend(token); + } + + int appendEndGroup(PrecedenceInfo info, String value, int openKind) { + assert(!identical(openKind, LT_TOKEN)); + appendStringToken(info, value); + discardOpenLt(); + if (groupingStack.isEmpty) { + return advance(); + } + BeginGroupToken begin = groupingStack.head; + if (!identical(begin.kind, openKind)) { + if (!identical(openKind, OPEN_CURLY_BRACKET_TOKEN) || + !identical(begin.kind, STRING_INTERPOLATION_TOKEN)) { + // Not ending string interpolation. + return error(new SourceString('Unmatched ${begin.stringValue}')); + } + // We're ending an interpolated expression. + begin.endGroup = tail; + groupingStack = groupingStack.tail; + // Using "start-of-text" to signal that we're back in string + // scanning mode. + return $STX; + } + begin.endGroup = tail; + groupingStack = groupingStack.tail; + return advance(); + } + + void appendGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendGtGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendGtGtGt(PrecedenceInfo info, String value) { + appendStringToken(info, value); + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + if (groupingStack.isEmpty) return; + if (identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack.head.endGroup = tail; + groupingStack = groupingStack.tail; + } + } + + void appendComment() { + if (!includeComments) return; + SourceString value = utf8String(tokenStart, -1); + appendByteStringToken(COMMENT_INFO, value); + } + + void discardOpenLt() { + while (!groupingStack.isEmpty + && identical(groupingStack.head.kind, LT_TOKEN)) { + groupingStack = groupingStack.tail; + } + } + + void unmatchedBeginGroup(BeginGroupToken begin); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart new file mode 100644 index 0000000..4cf9eeb --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart
@@ -0,0 +1,40 @@ +// Copyright (c) 2011, 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. + +part of parser; + +/** + * Scanner that reads from a byte array and creates tokens that points + * to the same array. + */ +class ByteArrayScanner extends ArrayBasedScanner<ByteString> { + final List<int> bytes; + + ByteArrayScanner(List<int> this.bytes, [bool includeComments = false]) + : super(includeComments); + + int nextByte() => byteAt(++byteOffset); + + int peek() => byteAt(byteOffset + 1); + + int byteAt(int index) => bytes[index]; + + AsciiString asciiString(int start, int offset) { + return AsciiString.of(bytes, start, byteOffset - start + offset); + } + + Utf8String utf8String(int start, int offset) { + return Utf8String.of(bytes, start, byteOffset - start + offset + 1); + } + + void appendByteStringToken(PrecedenceInfo info, ByteString value) { + tail.next = new ByteStringToken(info, value, tokenStart); + tail = tail.next; + } + + // This method should be equivalent to the one in super. However, + // this is a *HOT* method and Dart VM performs better if it is easy + // to inline. + int advance() => bytes[++byteOffset]; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart new file mode 100644 index 0000000..4d79898 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart
@@ -0,0 +1,154 @@ +// Copyright (c) 2011, 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. + +/** + * An abstract string representation. + */ +abstract class ByteString extends Iterable<int> implements SourceString { + final List<int> bytes; + final int offset; + final int length; + int _hashCode; + + ByteString(List<int> this.bytes, int this.offset, int this.length); + + String get charset; + + String slowToString() => new String.fromCharCodes( + new Utf8Decoder(bytes, offset, length).decodeRest()); + + String toString() => "ByteString(${slowToString()})"; + + bool operator ==(other) { + throw "should be overridden in subclass"; + } + + Iterator<int> get iterator => new Utf8Decoder(bytes, offset, length); + + int get hashCode { + if (_hashCode == null) { + _hashCode = computeHashCode(); + } + return _hashCode; + } + + int computeHashCode() { + int code = 1; + int end = offset + length; + for (int i = offset; i < end; i++) { + code += 19 * code + bytes[i]; + } + return code; + } + + printOn(StringBuffer sb) { + sb.add(slowToString()); + } + + bool get isEmpty => length == 0; + bool isPrivate() => !isEmpty && identical(bytes[offset], $_); + + String get stringValue => null; +} + +/** + * A string that consists purely of 7bit ASCII characters. + */ +class AsciiString extends ByteString { + final String charset = "ASCII"; + + AsciiString(List<int> bytes, int offset, int length) + : super(bytes, offset, length); + + static AsciiString of(List<int> bytes, int offset, int length) { + AsciiString string = new AsciiString(bytes, offset, length); + return string; + } + + Iterator<int> get iterator => new AsciiStringIterator(bytes); + + SourceString copyWithoutQuotes(int initial, int terminal) { + return new AsciiString(bytes, offset + initial, + length - initial - terminal); + } + + + static AsciiString fromString(String string) { + List<int> bytes = string.charCodes; + return AsciiString.of(bytes, 0, bytes.length); + } +} + + +class AsciiStringIterator implements Iterator<int> { + final List<int> bytes; + int offset; + final int end; + int _current; + + AsciiStringIterator(List<int> bytes) + : this.bytes = bytes, offset = 0, end = bytes.length; + AsciiStringIterator.range(List<int> bytes, int from, int length) + : this.bytes = bytes, offset = from, end = from + length; + + int get current => _current; + bool moveNext() { + if (offset < end) { + _current = bytes[offset++]; + return true; + } + _current = null; + return false; + } +} + + +/** + * A string that consists of characters that can be encoded as UTF-8. + */ +class Utf8String extends ByteString { + final String charset = "UTF8"; + + Utf8String(List<int> bytes, int offset, int length) + : super(bytes, offset, length); + + static Utf8String of(List<int> bytes, int offset, int length) { + return new Utf8String(bytes, offset, length); + } + + static Utf8String fromString(String string) { + throw "not implemented yet"; + } + + Iterator<int> get iterator => new Utf8Decoder(bytes, 0, length); + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert((){ + // Only allow dropping ASCII characters, to guarantee that + // the resulting Utf8String is still valid. + for (int i = 0; i < initial; i++) { + if (bytes[offset + i] >= 0x80) return false; + } + for (int i = 0; i < terminal; i++) { + if (bytes[offset + length - terminal + i] >= 0x80) return false; + } + return true; + }); + // TODO(lrn): Check that first and last bytes use the same type of quotes. + return new Utf8String(bytes, offset + initial, + length - initial - terminal); + } +} + +/** + * A ByteString-valued token. + */ +class ByteStringToken extends Token { + final ByteString value; + + ByteStringToken(PrecedenceInfo info, ByteString this.value, int charOffset) + : super(info, charOffset); + + String toString() => value.toString(); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart new file mode 100644 index 0000000..1e359aa --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart
@@ -0,0 +1,197 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class ClassElementParser extends PartialParser { + ClassElementParser(Listener listener) : super(listener); + + Token parseClassBody(Token token) => fullParseClassBody(token); +} + +class PartialClassElement extends ClassElementX { + final Token beginToken; + final Token endToken; + ClassNode cachedNode; + + PartialClassElement(SourceString name, + Token this.beginToken, + Token this.endToken, + Element enclosing, + int id) + : super(name, enclosing, id, STATE_NOT_STARTED); + + void set supertypeLoadState(int state) { + assert(state == supertypeLoadState + 1); + assert(state <= STATE_DONE); + super.supertypeLoadState = state; + } + + void set resolutionState(int state) { + assert(state == resolutionState + 1); + assert(state <= STATE_DONE); + super.resolutionState = state; + } + + ClassNode parseNode(Compiler compiler) { + if (cachedNode != null) return cachedNode; + compiler.withCurrentElement(this, () { + compiler.parser.measure(() { + MemberListener listener = new MemberListener(compiler, this); + Parser parser = new ClassElementParser(listener); + Token token = parser.parseTopLevelDeclaration(beginToken); + assert(identical(token, endToken.next)); + cachedNode = listener.popNode(); + assert(listener.nodes.isEmpty); + }); + compiler.patchParser.measure(() { + if (isPatched) { + // TODO(lrn): Perhaps extract functionality so it doesn't + // need compiler. + compiler.patchParser.parsePatchClassNode(patch); + } + }); + }); + return cachedNode; + } + + Token position() => beginToken; + + // TODO(johnniwinther): Ensure that modifiers are always available. + Modifiers get modifiers => + cachedNode != null ? cachedNode.modifiers : Modifiers.EMPTY; + + bool isInterface() => identical(beginToken.stringValue, "interface"); +} + +class MemberListener extends NodeListener { + final ClassElement enclosingElement; + + MemberListener(DiagnosticListener listener, + Element enclosingElement) + : this.enclosingElement = enclosingElement, + super(listener, enclosingElement.getCompilationUnit()); + + bool isConstructorName(Node nameNode) { + if (enclosingElement == null || + enclosingElement.kind != ElementKind.CLASS) { + return false; + } + SourceString name; + if (nameNode.asIdentifier() != null) { + name = nameNode.asIdentifier().source; + } else { + Send send = nameNode.asSend(); + name = send.receiver.asIdentifier().source; + } + return enclosingElement.name == name; + } + + SourceString getMethodNameHack(Node methodName) { + Send send = methodName.asSend(); + if (send == null) return methodName.asIdentifier().source; + Identifier receiver = send.receiver.asIdentifier(); + Identifier selector = send.selector.asIdentifier(); + Operator operator = selector.asOperator(); + if (operator != null) { + assert(identical(receiver.source.stringValue, 'operator')); + // TODO(ahe): It is a hack to compare to ')', but it beats + // parsing the node. + bool isUnary = identical(operator.token.next.next.stringValue, ')'); + return Elements.constructOperatorName(operator.source, isUnary); + } else { + if (receiver == null) { + listener.cancel('library prefix in named factory constructor not ' + 'implemented', node: send.receiver); + } + if (receiver.source != enclosingElement.name) { + listener.onDeprecatedFeature(receiver, 'interface factories'); + } + return Elements.constructConstructorName(receiver.source, + selector.source); + } + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + super.endMethod(getOrSet, beginToken, endToken); + FunctionExpression method = popNode(); + pushNode(null); + bool isConstructor = isConstructorName(method.name); + SourceString name = getMethodNameHack(method.name); + ElementKind kind = ElementKind.FUNCTION; + if (isConstructor) { + if (getOrSet != null) { + recoverableError('illegal modifier', token: getOrSet); + } + kind = ElementKind.GENERATIVE_CONSTRUCTOR; + } else if (getOrSet != null) { + kind = (identical(getOrSet.stringValue, 'get')) + ? ElementKind.GETTER : ElementKind.SETTER; + } + Element memberElement = + new PartialFunctionElement(name, beginToken, getOrSet, endToken, + kind, method.modifiers, enclosingElement); + addMember(memberElement); + } + + void endFactoryMethod(Token beginToken, Token endToken) { + super.endFactoryMethod(beginToken, endToken); + FunctionExpression method = popNode(); + pushNode(null); + SourceString name = getMethodNameHack(method.name); + Identifier singleIdentifierName = method.name.asIdentifier(); + if (singleIdentifierName != null && singleIdentifierName.source == name) { + if (name != enclosingElement.name) { + listener.onDeprecatedFeature(method.name, 'interface factories'); + } + } + ElementKind kind = ElementKind.FUNCTION; + Element memberElement = + new PartialFunctionElement(name, beginToken, null, endToken, + kind, method.modifiers, enclosingElement); + addMember(memberElement); + } + + void endFields(int count, Token beginToken, Token endToken) { + super.endFields(count, beginToken, endToken); + VariableDefinitions variableDefinitions = popNode(); + Modifiers modifiers = variableDefinitions.modifiers; + pushNode(null); + void buildFieldElement(SourceString name, Element fields) { + Element element = + new VariableElementX(name, fields, ElementKind.FIELD, null); + addMember(element); + } + buildFieldElements(modifiers, variableDefinitions.definitions, + enclosingElement, + buildFieldElement, beginToken, endToken); + } + + void endInitializer(Token assignmentOperator) { + pushNode(null); // Super expects an expression, but + // ClassElementParser just skips expressions. + super.endInitializer(assignmentOperator); + } + + void endInitializers(int count, Token beginToken, Token endToken) { + pushNode(null); + } + + void addMember(Element memberElement) { + for (Link link = metadata; !link.isEmpty; link = link.tail) { + memberElement.addMetadata(link.head); + } + metadata = const Link<MetadataAnnotation>(); + enclosingElement.addMember(memberElement, listener); + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + popNode(); // Discard arguments. + if (periodBeforeName != null) { + popNode(); // Discard name. + } + popNode(); // Discard node (Send or Identifier). + pushMetadata(new PartialMetadataAnnotation(beginToken, endToken)); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart new file mode 100644 index 0000000..5d3c9a8 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart
@@ -0,0 +1,235 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +/** + * A keyword in the Dart programming language. + */ +class Keyword extends Iterable<int> implements SourceString { + static const List<Keyword> values = const <Keyword> [ + const Keyword("assert"), + const Keyword("break"), + const Keyword("case"), + const Keyword("catch"), + const Keyword("class"), + const Keyword("const"), + const Keyword("continue"), + const Keyword("default"), + const Keyword("do"), + const Keyword("else"), + const Keyword("extends"), + const Keyword("false"), + const Keyword("final"), + const Keyword("finally"), + const Keyword("for"), + const Keyword("if"), + const Keyword("in"), + const Keyword("new"), + const Keyword("null"), + const Keyword("return"), + const Keyword("super"), + const Keyword("switch"), + const Keyword("this"), + const Keyword("throw"), + const Keyword("true"), + const Keyword("try"), + const Keyword("var"), + const Keyword("void"), + const Keyword("while"), + const Keyword("with"), + + // TODO(ahe): Don't think this is a reserved word. + // See: http://dartbug.com/5579 + const Keyword("is", info: IS_INFO), + + const Keyword("abstract", isBuiltIn: true), + const Keyword("as", info: AS_INFO, isBuiltIn: true), + const Keyword("dynamic", isBuiltIn: true), + const Keyword("export", isBuiltIn: true), + const Keyword("external", isBuiltIn: true), + const Keyword("factory", isBuiltIn: true), + const Keyword("get", isBuiltIn: true), + const Keyword("implements", isBuiltIn: true), + const Keyword("import", isBuiltIn: true), + const Keyword("interface", isBuiltIn: true), + const Keyword("library", isBuiltIn: true), + const Keyword("operator", isBuiltIn: true), + const Keyword("part", isBuiltIn: true), + const Keyword("set", isBuiltIn: true), + const Keyword("static", isBuiltIn: true), + const Keyword("typedef", isBuiltIn: true), + + const Keyword("hide", isPseudo: true), + const Keyword("native", isPseudo: true), + const Keyword("of", isPseudo: true), + const Keyword("on", isPseudo: true), + const Keyword("show", isPseudo: true), + const Keyword("source", isPseudo: true) ]; + + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + static const DYNAMIC_DEPRECATED = const Keyword("Dynamic", isBuiltIn: true); + + final String syntax; + final bool isPseudo; + final bool isBuiltIn; + final PrecedenceInfo info; + + static Map<String, Keyword> _keywords; + static Map<String, Keyword> get keywords { + if (_keywords == null) { + _keywords = computeKeywordMap(); + } + return _keywords; + } + + const Keyword(String this.syntax, + {bool this.isPseudo: false, + bool this.isBuiltIn: false, + PrecedenceInfo this.info: KEYWORD_INFO}); + + static Map<String, Keyword> computeKeywordMap() { + Map<String, Keyword> result = new LinkedHashMap<String, Keyword>(); + for (Keyword keyword in values) { + result[keyword.syntax] = keyword; + } + return result; + } + + int get hashCode => syntax.hashCode; + + bool operator ==(other) { + return other is SourceString && toString() == other.slowToString(); + } + + Iterator<int> get iterator => new StringCodeIterator(syntax); + + void printOn(StringBuffer sb) { + sb.add(syntax); + } + + String toString() => syntax; + String slowToString() => syntax; + String get stringValue => syntax; + + SourceString copyWithoutQuotes(int initial, int terminal) { + // TODO(lrn): consider remodelling to avoid having this method in keywords. + return this; + } + + bool get isEmpty => false; + bool isPrivate() => false; +} + +/** + * Abstract state in a state machine for scanning keywords. + */ +abstract class KeywordState { + bool isLeaf(); + KeywordState next(int c); + Keyword get keyword; + + static KeywordState _KEYWORD_STATE; + static KeywordState get KEYWORD_STATE { + if (_KEYWORD_STATE == null) { + List<String> strings = + new List<String>.fixedLength(Keyword.values.length); + for (int i = 0; i < Keyword.values.length; i++) { + strings[i] = Keyword.values[i].syntax; + } + strings.sort((a,b) => a.compareTo(b)); + _KEYWORD_STATE = computeKeywordStateTable(0, strings, 0, strings.length); + } + return _KEYWORD_STATE; + } + + static KeywordState computeKeywordStateTable(int start, List<String> strings, + int offset, int length) { + List<KeywordState> result = new List<KeywordState>.fixedLength(26); + assert(length != 0); + int chunk = 0; + int chunkStart = -1; + bool isLeaf = false; + for (int i = offset; i < offset + length; i++) { + if (strings[i].length == start) { + isLeaf = true; + } + if (strings[i].length > start) { + int c = strings[i].charCodeAt(start); + if (chunk != c) { + if (chunkStart != -1) { + assert(result[chunk - $a] == null); + result[chunk - $a] = computeKeywordStateTable(start + 1, strings, + chunkStart, + i - chunkStart); + } + chunkStart = i; + chunk = c; + } + } + } + if (chunkStart != -1) { + assert(result[chunk - $a] == null); + result[chunk - $a] = + computeKeywordStateTable(start + 1, strings, chunkStart, + offset + length - chunkStart); + } else { + assert(length == 1); + return new LeafKeywordState(strings[offset]); + } + if (isLeaf) { + return new ArrayKeywordState(result, strings[offset]); + } else { + return new ArrayKeywordState(result, null); + } + } +} + +/** + * A state with multiple outgoing transitions. + */ +class ArrayKeywordState extends KeywordState { + final List<KeywordState> table; + final Keyword keyword; + + ArrayKeywordState(List<KeywordState> this.table, String syntax) + : keyword = (syntax == null) ? null : Keyword.keywords[syntax]; + + bool isLeaf() => false; + + KeywordState next(int c) => table[c - $a]; + + String toString() { + StringBuffer sb = new StringBuffer(); + sb.add("["); + if (keyword != null) { + sb.add("*"); + sb.add(keyword); + sb.add(" "); + } + List<KeywordState> foo = table; + for (int i = 0; i < foo.length; i++) { + if (foo[i] != null) { + sb.add("${new String.fromCharCodes([i + $a])}: ${foo[i]}; "); + } + } + sb.add("]"); + return sb.toString(); + } +} + +/** + * A state that has no outgoing transitions. + */ +class LeafKeywordState extends KeywordState { + final Keyword keyword; + + LeafKeywordState(String syntax) : keyword = Keyword.keywords[syntax]; + + bool isLeaf() => true; + + KeywordState next(int c) => null; + + String toString() => keyword.syntax; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart new file mode 100644 index 0000000..e535f62 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart
@@ -0,0 +1,2064 @@ +// 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. + +part of scanner; + +const bool VERBOSE = false; + +/** + * A parser event listener that does nothing except throw exceptions + * on parser errors. + */ +class Listener { + void beginArgumentDefinitionTest(Token token) { + } + + void endArgumentDefinitionTest(Token beginToken, Token endToken) { + } + + void beginArguments(Token token) { + } + + void endArguments(int count, Token beginToken, Token endToken) { + } + + void beginBlock(Token token) { + } + + void endBlock(int count, Token beginToken, Token endToken) { + } + + void beginCascade(Token token) { + } + + void endCascade() { + } + + void beginClassBody(Token token) { + } + + void endClassBody(int memberCount, Token beginToken, Token endToken) { + } + + void beginClassDeclaration(Token token) { + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + } + + void beginCombinators(Token token) { + } + + void endCombinators(int count) { + } + + void beginCompilationUnit(Token token) { + } + + void endCompilationUnit(int count, Token token) { + } + + void beginConstructorReference(Token start) { + } + + void endConstructorReference(Token start, Token periodBeforeName, + Token endToken) { + } + + void beginDoWhileStatement(Token token) { + } + + void endDoWhileStatement(Token doKeyword, Token whileKeyword, + Token endToken) { + } + + void beginExport(Token token) { + } + + void endExport(Token exportKeyword, Token semicolon) { + } + + void beginExpressionStatement(Token token) { + } + + void endExpressionStatement(Token token) { + } + + void beginDefaultClause(Token token) { + } + + void handleNoDefaultClause(Token token) { + } + + void endDefaultClause(Token defaultKeyword) { + } + + void beginFactoryMethod(Token token) { + } + + void endFactoryMethod(Token beginToken, Token endToken) { + } + + void beginFormalParameter(Token token) { + } + + void endFormalParameter(Token thisKeyword) { + } + + void handleNoFormalParameters(Token token) { + } + + void beginFormalParameters(Token token) { + } + + void endFormalParameters(int count, Token beginToken, Token endToken) { + } + + void endFields(int count, Token beginToken, Token endToken) { + } + + void beginForStatement(Token token) { + } + + void endForStatement(int updateExpressionCount, + Token beginToken, Token endToken) { + } + + void endForIn(Token beginToken, Token inKeyword, Token endToken) { + } + + void beginFunction(Token token) { + } + + void endFunction(Token getOrSet, Token endToken) { + } + + void beginFunctionDeclaration(Token token) { + } + + void endFunctionDeclaration(Token token) { + } + + void beginFunctionBody(Token token) { + } + + void endFunctionBody(int count, Token beginToken, Token endToken) { + } + + void handleNoFunctionBody(Token token) { + } + + void beginFunctionName(Token token) { + } + + void endFunctionName(Token token) { + } + + void beginFunctionTypeAlias(Token token) { + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + } + + void beginMixinApplication(Token token) { + } + + void endMixinApplication() { + } + + void beginNamedMixinApplication(Token token) { + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + } + + void beginHide(Token hideKeyword) { + } + + void endHide(Token hideKeyword) { + } + + void beginIdentifierList(Token token) { + } + + void endIdentifierList(int count) { + } + + void beginTypeList(Token token) { + } + + void endTypeList(int count) { + } + + void beginIfStatement(Token token) { + } + + void endIfStatement(Token ifToken, Token elseToken) { + } + + void beginImport(Token importKeyword) { + } + + void endImport(Token importKeyword, Token asKeyword, Token semicolon) { + } + + void beginInitializedIdentifier(Token token) { + } + + void endInitializedIdentifier() { + } + + void beginInitializer(Token token) { + } + + void endInitializer(Token assignmentOperator) { + } + + void beginInitializers(Token token) { + } + + void endInitializers(int count, Token beginToken, Token endToken) { + } + + void handleNoInitializers() { + } + + void beginInterface(Token token) { + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + } + + void handleLabel(Token token) { + } + + void beginLabeledStatement(Token token, int labelCount) { + } + + void endLabeledStatement(int labelCount) { + } + + void beginLibraryName(Token token) { + } + + void endLibraryName(Token libraryKeyword, Token semicolon) { + } + + void beginLiteralMapEntry(Token token) { + } + + void endLiteralMapEntry(Token colon, Token endToken) { + } + + void beginLiteralString(Token token) { + } + + void endLiteralString(int interpolationCount) { + } + + void handleStringJuxtaposition(int literalCount) { + } + + void beginMember(Token token) { + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + } + + void beginMetadata(Token token) { + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + } + + void beginOptionalFormalParameters(Token token) { + } + + void endOptionalFormalParameters(int count, + Token beginToken, Token endToken) { + } + + void beginPart(Token token) { + } + + void endPart(Token partKeyword, Token semicolon) { + } + + void beginPartOf(Token token) { + } + + void endPartOf(Token partKeyword, Token semicolon) { + } + + void beginRedirectingFactoryBody(Token token) { + } + + void endRedirectingFactoryBody(Token beginToken, Token endToken) { + } + + void beginReturnStatement(Token token) { + } + + void endReturnStatement(bool hasExpression, + Token beginToken, Token endToken) { + } + + void beginScriptTag(Token token) { + } + + void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) { + } + + void beginSend(Token token) { + } + + void endSend(Token token) { + } + + void beginShow(Token showKeyword) { + } + + void endShow(Token showKeyword) { + } + + void beginSwitchStatement(Token token) { + } + + void endSwitchStatement(Token switchKeyword, Token endToken) { + } + + void beginSwitchBlock(Token token) { + } + + void endSwitchBlock(int caseCount, Token beginToken, Token endToken) { + } + + void beginThrowStatement(Token token) { + } + + void endThrowStatement(Token throwToken, Token endToken) { + } + + void endRethrowStatement(Token throwToken, Token endToken) { + } + + void endTopLevelDeclaration(Token token) { + } + + void beginTopLevelMember(Token token) { + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + } + + void beginTryStatement(Token token) { + } + + void handleCaseMatch(Token caseKeyword, Token colon) { + } + + void handleCatchBlock(Token onKeyword, Token catchKeyword) { + } + + void handleFinallyBlock(Token finallyKeyword) { + } + + void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) { + } + + void endType(Token beginToken, Token endToken) { + } + + void beginTypeArguments(Token token) { + } + + void endTypeArguments(int count, Token beginToken, Token endToken) { + } + + void handleNoTypeArguments(Token token) { + } + + void beginTypeVariable(Token token) { + } + + void endTypeVariable(Token token) { + } + + void beginTypeVariables(Token token) { + } + + void endTypeVariables(int count, Token beginToken, Token endToken) { + } + + void beginUnamedFunction(Token token) { + } + + void endUnamedFunction(Token token) { + } + + void beginVariablesDeclaration(Token token) { + } + + void endVariablesDeclaration(int count, Token endToken) { + } + + void beginWhileStatement(Token token) { + } + + void endWhileStatement(Token whileKeyword, Token endToken) { + } + + void handleAsOperator(Token operathor, Token endToken) { + // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed. + } + + void handleAssignmentExpression(Token token) { + } + + void handleBinaryExpression(Token token) { + } + + void handleConditionalExpression(Token question, Token colon) { + } + + void handleConstExpression(Token token) { + } + + void handleFunctionTypedFormalParameter(Token token) { + } + + void handleIdentifier(Token token) { + } + + void handleIndexedExpression(Token openCurlyBracket, + Token closeCurlyBracket) { + } + + void handleIsOperator(Token operathor, Token not, Token endToken) { + // TODO(ahe): Rename [operathor] to "operator" when VM bug is fixed. + } + + void handleLiteralBool(Token token) { + } + + void handleBreakStatement(bool hasTarget, + Token breakKeyword, Token endToken) { + } + + void handleContinueStatement(bool hasTarget, + Token continueKeyword, Token endToken) { + } + + void handleEmptyStatement(Token token) { + } + + void handleAssertStatement(Token assertKeyword, Token semicolonToken) { + } + + /** Called with either the token containing a double literal, or + * an immediately preceding "unary plus" token. + */ + void handleLiteralDouble(Token token) { + } + + /** Called with either the token containing an integer literal, + * or an immediately preceding "unary plus" token. + */ + void handleLiteralInt(Token token) { + } + + void handleLiteralList(int count, Token beginToken, Token constKeyword, + Token endToken) { + } + + void handleLiteralMap(int count, Token beginToken, Token constKeyword, + Token endToken) { + } + + void handleLiteralNull(Token token) { + } + + void handleModifier(Token token) { + } + + void handleModifiers(int count) { + } + + void handleNamedArgument(Token colon) { + } + + void handleNewExpression(Token token) { + } + + void handleNoArguments(Token token) { + } + + void handleNoExpression(Token token) { + } + + void handleNoType(Token token) { + } + + void handleNoTypeVariables(Token token) { + } + + void handleOperatorName(Token operatorKeyword, Token token) { + } + + void handleParenthesizedExpression(BeginGroupToken token) { + } + + void handleQualified(Token period) { + } + + void handleStringPart(Token token) { + } + + void handleSuperExpression(Token token) { + } + + void handleSwitchCase(int labelCount, int expressionCount, + Token defaultKeyword, int statementCount, + Token firstToken, Token endToken) { + } + + void handleThisExpression(Token token) { + } + + void handleUnaryPostfixAssignmentExpression(Token token) { + } + + void handleUnaryPrefixExpression(Token token) { + } + + void handleUnaryPrefixAssignmentExpression(Token token) { + } + + void handleValuedFormalParameter(Token equals, Token token) { + } + + void handleVoidKeyword(Token token) { + } + + Token expected(String string, Token token) { + error("expected '$string', but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + void expectedIdentifier(Token token) { + error("expected identifier, but got '${token.slowToString()}'", token); + } + + Token expectedType(Token token) { + error("expected a type, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedExpression(Token token) { + error("expected an expression, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token unexpected(Token token) { + error("unexpected token '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedBlockToSkip(Token token) { + error("expected a block, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedFunctionBody(Token token) { + error("expected a function body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedClassBody(Token token) { + error("expected a class body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Token expectedClassBodyToSkip(Token token) { + error("expected a class body, but got '${token.slowToString()}'", token); + return skipToEof(token); + } + + Link<Token> expectedDeclaration(Token token) { + error("expected a declaration, but got '${token.slowToString()}'", token); + return const Link<Token>(); + } + + Token unmatched(Token token) { + error("unmatched '${token.slowToString()}'", token); + return skipToEof(token); + } + + skipToEof(Token token) { + while (!identical(token.info, EOF_INFO)) { + token = token.next; + } + return token; + } + + void recoverableError(String message, {Token token, Node node}) { + if (token == null && node != null) { + token = node.getBeginToken(); + } + error(message, token); + } + + void error(String message, Token token) { + throw new ParserError("$message @ ${token.charOffset}"); + } +} + +class ParserError { + final String reason; + ParserError(this.reason); + toString() => reason; +} + +typedef int IdGenerator(); + +/** + * A parser event listener designed to work with [PartialParser]. It + * builds elements representing the top-level declarations found in + * the parsed compilation unit and records them in + * [compilationUnitElement]. + */ +class ElementListener extends Listener { + final IdGenerator idGenerator; + final DiagnosticListener listener; + final CompilationUnitElement compilationUnitElement; + final StringValidator stringValidator; + Link<StringQuoting> interpolationScope; + + Link<Node> nodes = const Link<Node>(); + + Link<MetadataAnnotation> metadata = const Link<MetadataAnnotation>(); + + ElementListener(DiagnosticListener listener, + this.compilationUnitElement, + this.idGenerator) + : this.listener = listener, + stringValidator = new StringValidator(listener), + interpolationScope = const Link<StringQuoting>(); + + void pushQuoting(StringQuoting quoting) { + interpolationScope = interpolationScope.prepend(quoting); + } + + StringQuoting popQuoting() { + StringQuoting result = interpolationScope.head; + interpolationScope = interpolationScope.tail; + return result; + } + + StringNode popLiteralString() { + StringNode node = popNode(); + // TODO(lrn): Handle interpolations in script tags. + if (node.isInterpolation) { + listener.cancel("String interpolation not supported in library tags", + node: node); + return null; + } + return node; + } + + bool allowLibraryTags() { + // Library tags are only allowed in the library file itself, not + // in sourced files. + LibraryElement library = compilationUnitElement.getLibrary(); + return !compilationUnitElement.hasMembers + && library.entryCompilationUnit == compilationUnitElement; + } + + void endLibraryName(Token libraryKeyword, Token semicolon) { + Expression name = popNode(); + addLibraryTag(new LibraryName(libraryKeyword, name, + popMetadata(compilationUnitElement))); + } + + void endImport(Token importKeyword, Token asKeyword, Token semicolon) { + NodeList combinators = popNode(); + Identifier prefix; + if (asKeyword != null) { + prefix = popNode(); + } + StringNode uri = popLiteralString(); + addLibraryTag(new Import(importKeyword, uri, prefix, combinators, + popMetadata(compilationUnitElement))); + } + + void endExport(Token exportKeyword, Token semicolon) { + NodeList combinators = popNode(); + StringNode uri = popNode(); + addLibraryTag(new Export(exportKeyword, uri, combinators, + popMetadata(compilationUnitElement))); + } + + void endCombinators(int count) { + if (0 == count) { + pushNode(null); + } else { + pushNode(makeNodeList(count, null, null, " ")); + } + } + + void endHide(Token hideKeyword) => pushCombinator(hideKeyword); + + void endShow(Token showKeyword) => pushCombinator(showKeyword); + + void pushCombinator(Token keywordToken) { + NodeList identifiers = popNode(); + pushNode(new Combinator(identifiers, keywordToken)); + } + + void endIdentifierList(int count) { + pushNode(makeNodeList(count, null, null, ",")); + } + + void endTypeList(int count) { + pushNode(makeNodeList(count, null, null, ",")); + } + + void endPart(Token partKeyword, Token semicolon) { + StringNode uri = popLiteralString(); + addLibraryTag(new Part(partKeyword, uri, + popMetadata(compilationUnitElement))); + } + + void endPartOf(Token partKeyword, Token semicolon) { + Expression name = popNode(); + addPartOfTag(new PartOf(partKeyword, name, + popMetadata(compilationUnitElement))); + } + + void addPartOfTag(PartOf tag) { + compilationUnitElement.setPartOf(tag, listener); + } + + void endScriptTag(bool hasPrefix, Token beginToken, Token endToken) { + LiteralString prefix = null; + Identifier argumentName = null; + if (hasPrefix) { + prefix = popLiteralString(); + argumentName = popNode(); + } + LiteralString firstArgument = popLiteralString(); + Identifier tag = popNode(); + ScriptTag scriptTag = new ScriptTag(tag, firstArgument, argumentName, + prefix, beginToken, endToken); + if (const SourceString('import') == tag.source || + const SourceString('source') == tag.source || + const SourceString('library') == tag.source) { + addScriptTag(scriptTag); + } else { + recoverableError('unknown tag: ${tag.source.slowToString()}', node: tag); + } + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + if (periodBeforeName != null) { + popNode(); // Discard name. + } + popNode(); // Discard node (Send or Identifier). + pushMetadata(new PartialMetadataAnnotation(beginToken, endToken)); + } + + void endTopLevelDeclaration(Token token) { + if (!metadata.isEmpty) { + recoverableError('Error: Metadata not supported here.', + token: metadata.head.beginToken); + metadata = const Link<MetadataAnnotation>(); + } + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + SourceString nativeTagInfo = native.checkForNativeClass(this); + NodeList interfaces = + makeNodeList(interfacesCount, implementsKeyword, null, ","); + Node supertype = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + int id = idGenerator(); + ClassElement element = new PartialClassElement( + name.source, beginToken, endToken, compilationUnitElement, id); + element.nativeTagInfo = nativeTagInfo; + pushElement(element); + rejectBuiltInIdentifier(name); + } + + void rejectBuiltInIdentifier(Identifier name) { + if (name.source is Keyword) { + Keyword keyword = name.source; + if (!keyword.isPseudo) { + recoverableError('illegal name ${keyword.syntax}', node: name); + } + } + } + + void endDefaultClause(Token defaultKeyword) { + NodeList typeParameters = popNode(); + Node name = popNode(); + pushNode(new TypeAnnotation(name, typeParameters)); + } + + void handleNoDefaultClause(Token token) { + pushNode(null); + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + // TODO(ahe): Record the defaultClause. + Node defaultClause = popNode(); + NodeList supertypes = + makeNodeList(supertypeCount, extendsKeyword, null, ","); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + int id = idGenerator(); + pushElement(new PartialClassElement( + name.source, interfaceKeyword, endToken, compilationUnitElement, id)); + rejectBuiltInIdentifier(name); + listener.onDeprecatedFeature(interfaceKeyword, 'interface declarations'); + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + NodeList typeVariables = popNode(); // TOOD(karlklose): do not throw away. + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushElement(new PartialTypedefElement(name.source, compilationUnitElement, + typedefKeyword)); + rejectBuiltInIdentifier(name); + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + NodeList interfaces = (implementsKeyword != null) ? popNode() : null; + MixinApplication mixinApplication = popNode(); + Modifiers modifiers = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + NamedMixinApplication namedMixinApplication = new NamedMixinApplication( + name, typeParameters, modifiers, mixinApplication, interfaces, + typedefKeyword, endToken); + + int id = idGenerator(); + Element enclosing = compilationUnitElement; + pushElement(new MixinApplicationElementX(name.source, enclosing, id, + namedMixinApplication, + modifiers)); + rejectBuiltInIdentifier(name); + } + + void endMixinApplication() { + NodeList mixins = popNode(); + TypeAnnotation superclass = popNode(); + pushNode(new MixinApplication(superclass, mixins)); + } + + void handleVoidKeyword(Token token) { + pushNode(new TypeAnnotation(new Identifier(token), null)); + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + Identifier name = popNode(); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + ElementKind kind; + if (getOrSet == null) { + kind = ElementKind.FUNCTION; + } else if (identical(getOrSet.stringValue, 'get')) { + kind = ElementKind.GETTER; + } else if (identical(getOrSet.stringValue, 'set')) { + kind = ElementKind.SETTER; + } + pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet, + endToken, kind, + modifiers, compilationUnitElement)); + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + void buildFieldElement(SourceString name, Element fields) { + pushElement(new VariableElementX(name, fields, ElementKind.FIELD, null)); + } + NodeList variables = makeNodeList(count, null, null, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + buildFieldElements(modifiers, variables, compilationUnitElement, + buildFieldElement, + beginToken, endToken); + } + + void buildFieldElements(Modifiers modifiers, + NodeList variables, + Element enclosingElement, + void buildFieldElement(SourceString name, + Element fields), + Token beginToken, Token endToken) { + Element fields = new PartialFieldListElement(beginToken, + endToken, + modifiers, + enclosingElement); + for (Link<Node> variableNodes = variables.nodes; + !variableNodes.isEmpty; + variableNodes = variableNodes.tail) { + Expression initializedIdentifier = variableNodes.head; + Identifier identifier = initializedIdentifier.asIdentifier(); + if (identifier == null) { + identifier = initializedIdentifier.asSendSet().selector.asIdentifier(); + } + SourceString name = identifier.source; + buildFieldElement(name, fields); + } + } + + void handleIdentifier(Token token) { + pushNode(new Identifier(token)); + } + + void handleQualified(Token period) { + Identifier last = popNode(); + Expression first = popNode(); + pushNode(new Send(first, last)); + } + + void handleNoType(Token token) { + pushNode(null); + } + + void endTypeVariable(Token token) { + TypeAnnotation bound = popNode(); + Identifier name = popNode(); + pushNode(new TypeVariable(name, bound)); + } + + void endTypeVariables(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleNoTypeVariables(token) { + pushNode(null); + } + + void endTypeArguments(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleNoTypeArguments(Token token) { + pushNode(null); + } + + void endType(Token beginToken, Token endToken) { + NodeList typeArguments = popNode(); + Expression typeName = popNode(); + pushNode(new TypeAnnotation(typeName, typeArguments)); + } + + void handleParenthesizedExpression(BeginGroupToken token) { + Expression expression = popNode(); + pushNode(new ParenthesizedExpression(expression, token)); + } + + void handleModifier(Token token) { + pushNode(new Identifier(token)); + } + + void handleModifiers(int count) { + if (count == 0) { + pushNode(Modifiers.EMPTY); + } else { + NodeList modifierNodes = makeNodeList(count, null, null, ' '); + pushNode(new Modifiers(modifierNodes)); + } + } + + Token expected(String string, Token token) { + listener.cancel("expected '$string', but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + + void expectedIdentifier(Token token) { + listener.cancel("expected identifier, but got '${token.slowToString()}'", + token: token); + pushNode(null); + } + + Token expectedType(Token token) { + listener.cancel("expected a type, but got '${token.slowToString()}'", + token: token); + pushNode(null); + return skipToEof(token); + } + + Token expectedExpression(Token token) { + listener.cancel("expected an expression, but got '${token.slowToString()}'", + token: token); + pushNode(null); + return skipToEof(token); + } + + Token unexpected(Token token) { + String message = "unexpected token '${token.slowToString()}'"; + if (token.info == BAD_INPUT_INFO) { + message = token.stringValue; + } + listener.cancel(message, token: token); + return skipToEof(token); + } + + Token expectedBlockToSkip(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeBlockToSkip(this, token); + } else { + return unexpected(token); + } + } + + Token expectedFunctionBody(Token token) { + String printString = token.slowToString(); + listener.cancel("expected a function body, but got '$printString'", + token: token); + return skipToEof(token); + } + + Token expectedClassBody(Token token) { + listener.cancel("expected a class body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + + Token expectedClassBodyToSkip(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeClassBodyToSkip(this, token); + } else { + return unexpected(token); + } + } + + Link<Token> expectedDeclaration(Token token) { + listener.cancel("expected a declaration, but got '${token.slowToString()}'", + token: token); + return const Link<Token>(); + } + + Token unmatched(Token token) { + listener.cancel("unmatched '${token.slowToString()}'", token: token); + return skipToEof(token); + } + + void recoverableError(String message, {Token token, Node node}) { + listener.cancel(message, token: token, node: node); + } + + void pushElement(Element element) { + popMetadata(element); + compilationUnitElement.addMember(element, listener); + } + + Link<MetadataAnnotation> popMetadata(Element element) { + var result = metadata; + for (Link link = metadata; !link.isEmpty; link = link.tail) { + element.addMetadata(link.head); + } + metadata = const Link<MetadataAnnotation>(); + return result; + } + + void pushMetadata(MetadataAnnotation annotation) { + metadata = metadata.prepend(annotation); + } + + // TODO(ahe): Remove this method. + void addScriptTag(ScriptTag tag) { + listener.onDeprecatedFeature(tag, '# tags'); + addLibraryTag(tag.toLibraryTag()); + } + + void addLibraryTag(LibraryTag tag) { + if (!allowLibraryTags()) { + recoverableError('library tags not allowed here', node: tag); + } + compilationUnitElement.getImplementationLibrary().addTag(tag, listener); + } + + void pushNode(Node node) { + nodes = nodes.prepend(node); + if (VERBOSE) log("push $nodes"); + } + + Node popNode() { + assert(!nodes.isEmpty); + Node node = nodes.head; + nodes = nodes.tail; + if (VERBOSE) log("pop $nodes"); + return node; + } + + Node peekNode() { + assert(!nodes.isEmpty); + Node node = nodes.head; + if (VERBOSE) log("peek $node"); + return node; + } + + void log(message) { + print(message); + } + + NodeList makeNodeList(int count, Token beginToken, Token endToken, + String delimiter) { + Link<Node> poppedNodes = const Link<Node>(); + for (; count > 0; --count) { + // This effectively reverses the order of nodes so they end up + // in correct (source) order. + poppedNodes = poppedNodes.prepend(popNode()); + } + SourceString sourceDelimiter = + (delimiter == null) ? null : new SourceString(delimiter); + return new NodeList(beginToken, poppedNodes, endToken, sourceDelimiter); + } + + void beginLiteralString(Token token) { + SourceString source = token.value; + StringQuoting quoting = StringValidator.quotingFromString(source); + pushQuoting(quoting); + // Just wrap the token for now. At the end of the interpolation, + // when we know how many there are, go back and validate the tokens. + pushNode(new LiteralString(token, null)); + } + + void handleStringPart(Token token) { + // Just push an unvalidated token now, and replace it when we know the + // end of the interpolation. + pushNode(new LiteralString(token, null)); + } + + void endLiteralString(int count) { + StringQuoting quoting = popQuoting(); + + Link<StringInterpolationPart> parts = + const Link<StringInterpolationPart>(); + // Parts of the string interpolation are popped in reverse order, + // starting with the last literal string part. + bool isLast = true; + for (int i = 0; i < count; i++) { + LiteralString string = popNode(); + DartString validation = + stringValidator.validateInterpolationPart(string.token, quoting, + isFirst: false, + isLast: isLast); + // Replace the unvalidated LiteralString with a new LiteralString + // object that has the validation result included. + string = new LiteralString(string.token, validation); + Expression expression = popNode(); + parts = parts.prepend(new StringInterpolationPart(expression, string)); + isLast = false; + } + + LiteralString string = popNode(); + DartString validation = + stringValidator.validateInterpolationPart(string.token, quoting, + isFirst: true, + isLast: isLast); + string = new LiteralString(string.token, validation); + if (isLast) { + pushNode(string); + } else { + NodeList partNodes = + new NodeList(null, parts, null, const SourceString("")); + pushNode(new StringInterpolation(string, partNodes)); + } + } + + void handleStringJuxtaposition(int stringCount) { + assert(stringCount != 0); + Expression accumulator = popNode(); + stringCount--; + while (stringCount > 0) { + Expression expression = popNode(); + accumulator = new StringJuxtaposition(expression, accumulator); + stringCount--; + } + pushNode(accumulator); + } +} + +class NodeListener extends ElementListener { + NodeListener(DiagnosticListener listener, CompilationUnitElement element) + : super(listener, element, null); + + void addLibraryTag(LibraryTag tag) { + pushNode(tag); + } + + void addPartOfTag(PartOf tag) { + pushNode(tag); + } + + void endArgumentDefinitionTest(Token beginToken, Token endToken) { + pushNode(new Send.prefix(popNode(), new Operator(beginToken))); + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + NodeList body = popNode(); + NodeList interfaces = + makeNodeList(interfacesCount, implementsKeyword, null, ","); + Node supertype = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + Modifiers modifiers = popNode(); + pushNode(new ClassNode(modifiers, name, typeParameters, supertype, + interfaces, null, beginToken, extendsKeyword, body, + endToken)); + } + + void endCompilationUnit(int count, Token token) { + pushNode(makeNodeList(count, null, null, '\n')); + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + NodeList formals = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushNode(new Typedef(returnType, name, typeParameters, formals, + typedefKeyword, endToken)); + } + + void endNamedMixinApplication(Token typedefKeyword, + Token implementsKeyword, + Token endToken) { + NodeList interfaces = (implementsKeyword != null) ? popNode() : null; + Node mixinApplication = popNode(); + Modifiers modifiers = popNode(); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + pushNode(new NamedMixinApplication(name, typeParameters, + modifiers, mixinApplication, + interfaces, + typedefKeyword, endToken)); + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + NodeList body = popNode(); + TypeAnnotation defaultClause = popNode(); + NodeList supertypes = makeNodeList(supertypeCount, extendsKeyword, + null, ','); + NodeList typeParameters = popNode(); + Identifier name = popNode(); + pushNode(new ClassNode(Modifiers.EMPTY, name, typeParameters, null, + supertypes, defaultClause, interfaceKeyword, null, + body, endToken)); + } + + void endClassBody(int memberCount, Token beginToken, Token endToken) { + pushNode(makeNodeList(memberCount, beginToken, endToken, null)); + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + NodeList variables = makeNodeList(count, null, endToken, ","); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(null, modifiers, variables)); + } + + void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) { + Statement body = popNode(); + NodeList formalParameters = popNode(); + Identifier name = popNode(); + Modifiers modifiers = popNode(); + ElementKind kind; + if (getOrSet == null) { + kind = ElementKind.FUNCTION; + } else if (identical(getOrSet.stringValue, 'get')) { + kind = ElementKind.GETTER; + } else if (identical(getOrSet.stringValue, 'set')) { + kind = ElementKind.SETTER; + } + pushElement(new PartialFunctionElement(name.source, beginToken, getOrSet, + endToken, kind, + modifiers, compilationUnitElement)); + } + + void endFormalParameter(Token thisKeyword) { + Expression name = popNode(); + if (thisKeyword != null) { + Identifier thisIdentifier = new Identifier(thisKeyword); + if (name.asSend() == null) { + name = new Send(thisIdentifier, name); + } else { + name = name.asSend().copyWithReceiver(thisIdentifier); + } + } + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode( + new VariableDefinitions(type, modifiers, new NodeList.singleton(name))); + } + + void endFormalParameters(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ",")); + } + + void handleNoFormalParameters(Token token) { + pushNode(null); + } + + void endArguments(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ",")); + } + + void handleNoArguments(Token token) { + pushNode(null); + } + + void endConstructorReference(Token start, Token periodBeforeName, + Token endToken) { + Identifier name = null; + if (periodBeforeName != null) { + name = popNode(); + } + NodeList typeArguments = popNode(); + Node classReference = popNode(); + if (typeArguments != null) { + classReference = new TypeAnnotation(classReference, typeArguments); + } else { + Identifier identifier = classReference.asIdentifier(); + Send send = classReference.asSend(); + if (identifier != null) { + // TODO(ahe): Should be: + // classReference = new Send(null, identifier); + classReference = identifier; + } else if (send != null) { + classReference = send; + } else { + internalError(node: classReference); + } + } + Node constructor = classReference; + if (name != null) { + // Either typeName<args>.name or x.y.name. + constructor = new Send(classReference, name); + } + pushNode(constructor); + } + + void endRedirectingFactoryBody(Token beginToken, + Token endToken) { + pushNode(new Return(beginToken, endToken, popNode())); + } + + void endReturnStatement(bool hasExpression, + Token beginToken, Token endToken) { + Expression expression = hasExpression ? popNode() : null; + pushNode(new Return(beginToken, endToken, expression)); + } + + void endExpressionStatement(Token token) { + pushNode(new ExpressionStatement(popNode(), token)); + } + + void handleOnError(Token token, var errorInformation) { + listener.cancel("internal error: '${token.value}': ${errorInformation}", + token: token); + } + + Token expectedFunctionBody(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeFunctionBody(this, token); + } else { + listener.cancel( + "expected a function body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + } + + Token expectedClassBody(Token token) { + if (identical(token.stringValue, 'native')) { + return native.handleNativeClassBody(this, token); + } else { + listener.cancel( + "expected a class body, but got '${token.slowToString()}'", + token: token); + return skipToEof(token); + } + } + + void handleLiteralInt(Token token) { + pushNode(new LiteralInt(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralDouble(Token token) { + pushNode(new LiteralDouble(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralBool(Token token) { + pushNode(new LiteralBool(token, (t, e) => handleOnError(t, e))); + } + + void handleLiteralNull(Token token) { + pushNode(new LiteralNull(token)); + } + + void handleBinaryExpression(Token token) { + Node argument = popNode(); + Node receiver = popNode(); + String tokenString = token.stringValue; + if (identical(tokenString, '.') || identical(tokenString, '..')) { + Send argumentSend = argument.asSend(); + if (argumentSend == null) { + // TODO(ahe): The parser should diagnose this problem, not + // this listener. + listener.cancel('Syntax error: Expected an identifier.', + node: argument); + } + if (argumentSend.receiver != null) internalError(node: argument); + if (argument is SendSet) internalError(node: argument); + pushNode(argument.asSend().copyWithReceiver(receiver)); + } else { + NodeList arguments = new NodeList.singleton(argument); + pushNode(new Send(receiver, new Operator(token), arguments)); + } + if (identical(tokenString, '===') || identical(tokenString, '!==')) { + listener.onDeprecatedFeature(token, tokenString); + } + } + + void beginCascade(Token token) { + pushNode(new CascadeReceiver(popNode(), token)); + } + + void endCascade() { + pushNode(new Cascade(popNode())); + } + + void handleAsOperator(Token operathor, Token endToken) { + TypeAnnotation type = popNode(); + Expression expression = popNode(); + NodeList arguments = new NodeList.singleton(type); + pushNode(new Send(expression, new Operator(operathor), arguments)); + } + + void handleAssignmentExpression(Token token) { + Node arg = popNode(); + Node node = popNode(); + Send send = node.asSend(); + if (send == null || !(send.isPropertyAccess || send.isIndex)) { + reportNotAssignable(node); + } + if (send.asSendSet() != null) internalError(node: send); + NodeList arguments; + if (send.isIndex) { + Link<Node> link = const Link<Node>().prepend(arg); + link = link.prepend(send.arguments.head); + arguments = new NodeList(null, link); + } else { + arguments = new NodeList.singleton(arg); + } + Operator op = new Operator(token); + pushNode(new SendSet(send.receiver, send.selector, op, arguments)); + } + + void reportNotAssignable(Node node) { + // TODO(ahe): The parser should diagnose this problem, not this + // listener. + listener.cancel('Syntax error: Not assignable.', node: node); + } + + void handleConditionalExpression(Token question, Token colon) { + Node elseExpression = popNode(); + Node thenExpression = popNode(); + Node condition = popNode(); + pushNode(new Conditional( + condition, thenExpression, elseExpression, question, colon)); + } + + void endSend(Token token) { + NodeList arguments = popNode(); + Node selector = popNode(); + // TODO(ahe): Handle receiver. + pushNode(new Send(null, selector, arguments)); + } + + void endFunctionBody(int count, Token beginToken, Token endToken) { + if (count == 0 && beginToken == null) { + pushNode(new EmptyStatement(endToken)); + } else { + pushNode(new Block(makeNodeList(count, beginToken, endToken, null))); + } + } + + void handleNoFunctionBody(Token token) { + pushNode(null); + } + + void endFunction(Token getOrSet, Token endToken) { + Statement body = popNode(); + NodeList initializers = popNode(); + NodeList formals = popNode(); + // The name can be an identifier or a send in case of named constructors. + Expression name = popNode(); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new FunctionExpression(name, formals, body, type, + modifiers, initializers, getOrSet)); + } + + void endFunctionDeclaration(Token endToken) { + pushNode(new FunctionDeclaration(popNode())); + } + + void endVariablesDeclaration(int count, Token endToken) { + // TODO(ahe): Pick one name for this concept, either + // VariablesDeclaration or VariableDefinitions. + NodeList variables = makeNodeList(count, null, endToken, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(type, modifiers, variables)); + } + + void endInitializer(Token assignmentOperator) { + Expression initializer = popNode(); + NodeList arguments = new NodeList.singleton(initializer); + Expression name = popNode(); + Operator op = new Operator(assignmentOperator); + pushNode(new SendSet(null, name, op, arguments)); + } + + void endIfStatement(Token ifToken, Token elseToken) { + Statement elsePart = (elseToken == null) ? null : popNode(); + Statement thenPart = popNode(); + ParenthesizedExpression condition = popNode(); + pushNode(new If(condition, thenPart, elsePart, ifToken, elseToken)); + } + + void endForStatement(int updateExpressionCount, + Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList updates = makeNodeList(updateExpressionCount, null, null, ','); + Statement condition = popNode(); + Node initializer = popNode(); + pushNode(new For(initializer, condition, updates, body, beginToken)); + } + + void handleNoExpression(Token token) { + pushNode(null); + } + + void endDoWhileStatement(Token doKeyword, Token whileKeyword, + Token endToken) { + Expression condition = popNode(); + Statement body = popNode(); + pushNode(new DoWhile(body, condition, doKeyword, whileKeyword, endToken)); + } + + void endWhileStatement(Token whileKeyword, Token endToken) { + Statement body = popNode(); + Expression condition = popNode(); + pushNode(new While(condition, body, whileKeyword)); + } + + void endBlock(int count, Token beginToken, Token endToken) { + pushNode(new Block(makeNodeList(count, beginToken, endToken, null))); + } + + void endThrowStatement(Token throwToken, Token endToken) { + Expression expression = popNode(); + pushNode(new Throw(expression, throwToken, endToken)); + } + + void endRethrowStatement(Token throwToken, Token endToken) { + pushNode(new Throw(null, throwToken, endToken)); + } + + void handleUnaryPrefixExpression(Token token) { + pushNode(new Send.prefix(popNode(), new Operator(token))); + } + + void handleSuperExpression(Token token) { + pushNode(new Identifier(token)); + } + + void handleThisExpression(Token token) { + pushNode(new Identifier(token)); + } + + void handleUnaryAssignmentExpression(Token token, bool isPrefix) { + Node node = popNode(); + Send send = node.asSend(); + if (send == null) { + reportNotAssignable(node); + } + if (!(send.isPropertyAccess || send.isIndex)) { + reportNotAssignable(node); + } + if (send.asSendSet() != null) internalError(node: send); + Node argument = null; + if (send.isIndex) argument = send.arguments.head; + Operator op = new Operator(token); + + if (isPrefix) { + pushNode(new SendSet.prefix(send.receiver, send.selector, op, argument)); + } else { + pushNode(new SendSet.postfix(send.receiver, send.selector, op, argument)); + } + } + + void handleUnaryPostfixAssignmentExpression(Token token) { + handleUnaryAssignmentExpression(token, false); + } + + void handleUnaryPrefixAssignmentExpression(Token token) { + handleUnaryAssignmentExpression(token, true); + } + + void endInitializers(int count, Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, null, ',')); + } + + void handleNoInitializers() { + pushNode(null); + } + + void endFields(int count, Token beginToken, Token endToken) { + NodeList variables = makeNodeList(count, null, endToken, ","); + TypeAnnotation type = popNode(); + Modifiers modifiers = popNode(); + pushNode(new VariableDefinitions(type, modifiers, variables)); + } + + void endMethod(Token getOrSet, Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList initializers = popNode(); + NodeList formalParameters = popNode(); + Expression name = popNode(); + TypeAnnotation returnType = popNode(); + Modifiers modifiers = popNode(); + pushNode(new FunctionExpression(name, formalParameters, body, returnType, + modifiers, initializers, getOrSet)); + } + + void handleLiteralMap(int count, Token beginToken, Token constKeyword, + Token endToken) { + NodeList entries = makeNodeList(count, beginToken, endToken, ','); + NodeList typeArguments = popNode(); + pushNode(new LiteralMap(typeArguments, entries, constKeyword)); + } + + void endLiteralMapEntry(Token colon, Token endToken) { + Expression value = popNode(); + Expression key = popNode(); + if (key.asStringNode() == null) { + recoverableError('expected a string', node: key); + } + pushNode(new LiteralMapEntry(key, colon, value)); + } + + void handleLiteralList(int count, Token beginToken, Token constKeyword, + Token endToken) { + NodeList elements = makeNodeList(count, beginToken, endToken, ','); + pushNode(new LiteralList(popNode(), elements, constKeyword)); + } + + void handleIndexedExpression(Token openSquareBracket, + Token closeSquareBracket) { + NodeList arguments = + makeNodeList(1, openSquareBracket, closeSquareBracket, null); + Node receiver = popNode(); + Token token = + new StringToken(INDEX_INFO, '[]', openSquareBracket.charOffset); + Node selector = new Operator(token); + pushNode(new Send(receiver, selector, arguments)); + } + + void handleNewExpression(Token token) { + NodeList arguments = popNode(); + Node name = popNode(); + pushNode(new NewExpression(token, new Send(null, name, arguments))); + } + + void handleConstExpression(Token token) { + // [token] carries the 'const' information. + handleNewExpression(token); + } + + void handleOperatorName(Token operatorKeyword, Token token) { + Operator op = new Operator(token); + pushNode(new Send(new Identifier(operatorKeyword), op, null)); + } + + void handleNamedArgument(Token colon) { + Expression expression = popNode(); + Identifier name = popNode(); + pushNode(new NamedArgument(name, colon, expression)); + } + + void endOptionalFormalParameters(int count, + Token beginToken, Token endToken) { + pushNode(makeNodeList(count, beginToken, endToken, ',')); + } + + void handleFunctionTypedFormalParameter(Token endToken) { + NodeList formals = popNode(); + Identifier name = popNode(); + TypeAnnotation returnType = popNode(); + pushNode(null); // Signal "no type" to endFormalParameter. + pushNode(new FunctionExpression(name, formals, null, returnType, + Modifiers.EMPTY, null, null)); + } + + void handleValuedFormalParameter(Token equals, Token token) { + Expression defaultValue = popNode(); + Expression parameterName = popNode(); + pushNode(new SendSet(null, parameterName, new Operator(equals), + new NodeList.singleton(defaultValue))); + } + + void endTryStatement(int catchCount, Token tryKeyword, Token finallyKeyword) { + Block finallyBlock = null; + if (finallyKeyword != null) { + finallyBlock = popNode(); + } + NodeList catchBlocks = makeNodeList(catchCount, null, null, null); + Block tryBlock = popNode(); + pushNode(new TryStatement(tryBlock, catchBlocks, finallyBlock, + tryKeyword, finallyKeyword)); + } + + void handleCaseMatch(Token caseKeyword, Token colon) { + pushNode(new CaseMatch(caseKeyword, popNode(), colon)); + } + + void handleCatchBlock(Token onKeyword, Token catchKeyword) { + Block block = popNode(); + NodeList formals = catchKeyword != null? popNode(): null; + TypeAnnotation type = onKeyword != null ? popNode() : null; + pushNode(new CatchBlock(type, formals, block, onKeyword, catchKeyword)); + } + + void endSwitchStatement(Token switchKeyword, Token endToken) { + NodeList cases = popNode(); + ParenthesizedExpression expression = popNode(); + pushNode(new SwitchStatement(expression, cases, switchKeyword)); + } + + void endSwitchBlock(int caseCount, Token beginToken, Token endToken) { + Link<Node> caseNodes = const Link<Node>(); + while (caseCount > 0) { + SwitchCase switchCase = popNode(); + caseNodes = caseNodes.prepend(switchCase); + caseCount--; + } + pushNode(new NodeList(beginToken, caseNodes, endToken, null)); + } + + void handleSwitchCase(int labelCount, int caseCount, + Token defaultKeyword, int statementCount, + Token firstToken, Token endToken) { + NodeList statements = makeNodeList(statementCount, null, null, null); + NodeList labelsAndCases = + makeNodeList(labelCount + caseCount, null, null, null); + pushNode(new SwitchCase(labelsAndCases, defaultKeyword, statements, + firstToken)); + } + + void handleBreakStatement(bool hasTarget, + Token breakKeyword, Token endToken) { + Identifier target = null; + if (hasTarget) { + target = popNode(); + } + pushNode(new BreakStatement(target, breakKeyword, endToken)); + } + + void handleContinueStatement(bool hasTarget, + Token continueKeyword, Token endToken) { + Identifier target = null; + if (hasTarget) { + target = popNode(); + } + pushNode(new ContinueStatement(target, continueKeyword, endToken)); + } + + void handleEmptyStatement(Token token) { + pushNode(new EmptyStatement(token)); + } + + void endFactoryMethod(Token beginToken, Token endToken) { + Statement body = popNode(); + NodeList formals = popNode(); + Node name = popNode(); + + // TODO(ahe): Move this parsing to the parser. + int modifierCount = 0; + Token modifier = beginToken; + if (modifier.stringValue == "external") { + handleModifier(modifier); + modifierCount++; + modifier = modifier.next; + } + if (modifier.stringValue == "const") { + handleModifier(modifier); + modifierCount++; + modifier = modifier.next; + } + assert(modifier.stringValue == "factory"); + handleModifier(modifier); + modifierCount++; + handleModifiers(modifierCount); + Modifiers modifiers = popNode(); + + pushNode(new FunctionExpression(name, formals, body, null, + modifiers, null, null)); + } + + void endForIn(Token beginToken, Token inKeyword, Token endToken) { + Statement body = popNode(); + Expression expression = popNode(); + Node declaredIdentifier = popNode(); + pushNode(new ForIn(declaredIdentifier, expression, body, + beginToken, inKeyword)); + } + + void endMetadata(Token beginToken, Token periodBeforeName, Token endToken) { + NodeList arguments = popNode(); + if (arguments == null) { + // This is a constant expression. + Identifier name; + if (periodBeforeName != null) { + name = popNode(); + } + NodeList typeArguments = popNode(); + Node receiver = popNode(); + if (typeArguments != null) { + receiver = new TypeAnnotation(receiver, typeArguments); + recoverableError('Error: type arguments are not allowed here', + node: typeArguments); + } else { + Identifier identifier = receiver.asIdentifier(); + Send send = receiver.asSend(); + if (identifier != null) { + receiver = new Send(null, identifier); + } else if (send == null) { + internalError(node: receiver); + } + } + Send send = receiver; + if (name != null) { + send = new Send(receiver, name); + } + pushNode(send); + } else { + // This is a const constructor call. + endConstructorReference(beginToken, periodBeforeName, endToken); + Node constructor = popNode(); + pushNode(new NewExpression(beginToken, + new Send(null, constructor, arguments))); + } + } + + void handleAssertStatement(Token assertKeyword, Token semicolonToken) { + NodeList arguments = popNode(); + Node selector = new Identifier(assertKeyword); + Node send = new Send(null, selector, arguments); + pushNode(new ExpressionStatement(send, semicolonToken)); + } + + void endUnamedFunction(Token token) { + Statement body = popNode(); + NodeList formals = popNode(); + pushNode(new FunctionExpression(null, formals, body, null, + Modifiers.EMPTY, null, null)); + } + + void handleIsOperator(Token operathor, Token not, Token endToken) { + TypeAnnotation type = popNode(); + Expression expression = popNode(); + Node argument; + if (not != null) { + argument = new Send.prefix(type, new Operator(not)); + } else { + argument = type; + } + + NodeList arguments = new NodeList.singleton(argument); + pushNode(new Send(expression, new Operator(operathor), arguments)); + } + + void handleLabel(Token colon) { + Identifier name = popNode(); + pushNode(new Label(name, colon)); + } + + void endLabeledStatement(int labelCount) { + Statement statement = popNode(); + NodeList labels = makeNodeList(labelCount, null, null, null); + pushNode(new LabeledStatement(labels, statement)); + } + + void log(message) { + listener.log(message); + } + + void internalError({Token token, Node node}) { + // TODO(ahe): This should call listener.internalError. + Spannable spannable = (token == null) ? node : token; + throw new SpannableAssertionFailure(spannable, 'internal error in parser'); + } +} + +class PartialFunctionElement extends FunctionElementX { + final Token beginToken; + final Token getOrSet; + final Token endToken; + + PartialFunctionElement(SourceString name, + Token this.beginToken, + Token this.getOrSet, + Token this.endToken, + ElementKind kind, + Modifiers modifiers, + Element enclosing) + : super(name, kind, modifiers, enclosing); + + FunctionExpression parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + parseFunction(Parser p) { + if (isMember() && modifiers.isFactory()) { + p.parseFactoryMethod(beginToken); + } else { + p.parseFunction(beginToken, getOrSet); + } + } + cachedNode = parse(listener, getCompilationUnit(), parseFunction); + return cachedNode; + } + + Token position() { + return findMyName(beginToken); + } + + PartialFunctionElement cloneTo(Element enclosing, + DiagnosticListener listener) { + if (patch != null) { + listener.cancel("Cloning a patched function.", element: this); + } + PartialFunctionElement result = new PartialFunctionElement( + name, beginToken, getOrSet, endToken, kind, modifiers, enclosing); + return result; + } +} + +class PartialFieldListElement extends VariableListElementX { + final Token beginToken; + final Token endToken; + + PartialFieldListElement(Token this.beginToken, + Token this.endToken, + Modifiers modifiers, + Element enclosing) + : super(ElementKind.VARIABLE_LIST, modifiers, enclosing); + + VariableDefinitions parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + getCompilationUnit(), + (p) => p.parseVariablesDeclaration(beginToken)); + if (!cachedNode.modifiers.isVar() && + !cachedNode.modifiers.isFinal() && + !cachedNode.modifiers.isConst() && + cachedNode.type == null) { + listener.cancel('A field declaration must start with var, final, ' + 'const, or a type annotation.', + node: cachedNode); + } + return cachedNode; + } + + Token position() => beginToken; // findMyName doesn't work. I'm nameless. + + PartialFieldListElement cloneTo(Element enclosing, + DiagnosticListener listener) { + PartialFieldListElement result = new PartialFieldListElement( + beginToken, endToken, modifiers, enclosing); + return result; + } +} + +class PartialTypedefElement extends TypedefElementX { + final Token token; + + PartialTypedefElement(SourceString name, Element enclosing, this.token) + : super(name, enclosing); + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + getCompilationUnit(), + (p) => p.parseTopLevelDeclaration(token)); + return cachedNode; + } + + position() => findMyName(token); + + PartialTypedefElement cloneTo(Element enclosing, + DiagnosticListener listener) { + PartialTypedefElement result = + new PartialTypedefElement(name, enclosing, token); + return result; + } +} + +/// A [MetadataAnnotation] which is constructed on demand. +class PartialMetadataAnnotation extends MetadataAnnotationX { + final Token beginToken; + final Token tokenAfterEndToken; + Expression cachedNode; + Constant value; + + PartialMetadataAnnotation(this.beginToken, this.tokenAfterEndToken); + + Token get endToken { + Token token = beginToken; + while (token.kind != EOF_TOKEN) { + if (identical(token.next, tokenAfterEndToken)) return token; + token = token.next; + } + } + + Node parseNode(DiagnosticListener listener) { + if (cachedNode != null) return cachedNode; + cachedNode = parse(listener, + annotatedElement.getCompilationUnit(), + (p) => p.parseMetadata(beginToken)); + return cachedNode; + } +} + +Node parse(DiagnosticListener diagnosticListener, + CompilationUnitElement element, + doParse(Parser parser)) { + NodeListener listener = new NodeListener(diagnosticListener, element); + doParse(new Parser(listener)); + Node node = listener.popNode(); + assert(listener.nodes.isEmpty); + return node; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart new file mode 100644 index 0000000..5a85e00 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart
@@ -0,0 +1,2231 @@ +// 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. + +part of scanner; + +/** + * An event generating parser of Dart programs. This parser expects + * all tokens in a linked list (aka a token stream). + * + * The class [Scanner] is used to generate a token stream. See the + * file scanner.dart. + * + * Subclasses of the class [Listener] are used to listen to events. + * + * Most methods of this class belong in one of two major categories: + * parse metods and peek methods. Parse methods all have the prefix + * parse, and peek methods all have the prefix peek. + * + * Parse methods generate events (by calling methods on [listener]) + * and return the next token to parse. Peek methods do not generate + * events (except for errors) and may return null. + * + * Parse methods are generally named parseGrammarProductionSuffix. The + * suffix can be one of "opt", or "star". "opt" means zero or one + * matches, "star" means zero or more matches. For example, + * [parseMetadataStar] corresponds to this grammar snippet: [: + * metadata* :], and [parseTypeOpt] corresponds to: [: type? :]. + */ +class Parser { + final Listener listener; + bool mayParseFunctionExpressions = true; + + Parser(this.listener); + + Token parseUnit(Token token) { + listener.beginCompilationUnit(token); + int count = 0; + while (!identical(token.kind, EOF_TOKEN)) { + token = parseTopLevelDeclaration(token); + listener.endTopLevelDeclaration(token); + count++; + } + listener.endCompilationUnit(count, token); + return token; + } + + Token parseTopLevelDeclaration(Token token) { + token = parseMetadataStar(token); + final String value = token.stringValue; + if (identical(value, 'interface')) { + return parseInterface(token); + } else if ((identical(value, 'abstract')) || (identical(value, 'class'))) { + return parseClass(token); + } else if (identical(value, 'typedef')) { + return parseTypedef(token); + } else if (identical(value, '#')) { + return parseScriptTags(token); + } else if (identical(value, 'library')) { + return parseLibraryName(token); + } else if (identical(value, 'import')) { + return parseImport(token); + } else if (identical(value, 'export')) { + return parseExport(token); + } else if (identical(value, 'part')) { + return parsePartOrPartOf(token); + } else { + return parseTopLevelMember(token); + } + } + + /// library qualified ';' + Token parseLibraryName(Token token) { + Token libraryKeyword = token; + listener.beginLibraryName(libraryKeyword); + assert(optional('library', token)); + token = parseQualified(token.next); + Token semicolon = token; + token = expect(';', token); + listener.endLibraryName(libraryKeyword, semicolon); + return token; + } + + /// import uri (as identifier)? combinator* ';' + Token parseImport(Token token) { + Token importKeyword = token; + listener.beginImport(importKeyword); + assert(optional('import', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + Token asKeyword; + if (optional('as', token)) { + asKeyword = token; + token = parseIdentifier(token.next); + } + token = parseCombinators(token); + Token semicolon = token; + token = expect(';', token); + listener.endImport(importKeyword, asKeyword, semicolon); + return token; + } + + /// export uri combinator* ';' + Token parseExport(Token token) { + Token exportKeyword = token; + listener.beginExport(exportKeyword); + assert(optional('export', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + token = parseCombinators(token); + Token semicolon = token; + token = expect(';', token); + listener.endExport(exportKeyword, semicolon); + return token; + } + + Token parseCombinators(Token token) { + listener.beginCombinators(token); + int count = 0; + while (true) { + String value = token.stringValue; + if (identical('hide', value)) { + token = parseHide(token); + } else if (identical('show', value)) { + token = parseShow(token); + } else { + listener.endCombinators(count); + return token; + } + count++; + } + } + + /// hide identifierList + Token parseHide(Token token) { + Token hideKeyword = token; + listener.beginHide(hideKeyword); + assert(optional('hide', token)); + token = parseIdentifierList(token.next); + listener.endHide(hideKeyword); + return token; + } + + /// show identifierList + Token parseShow(Token token) { + Token showKeyword = token; + listener.beginShow(showKeyword); + assert(optional('show', token)); + token = parseIdentifierList(token.next); + listener.endShow(showKeyword); + return token; + } + + /// identifier (, identifier)* + Token parseIdentifierList(Token token) { + listener.beginIdentifierList(token); + token = parseIdentifier(token); + int count = 1; + while (optional(',', token)) { + token = parseIdentifier(token.next); + count++; + } + listener.endIdentifierList(count); + return token; + } + + /// type (, type)* + Token parseTypeList(Token token) { + listener.beginTypeList(token); + token = parseType(token); + int count = 1; + while (optional(',', token)) { + token = parseType(token.next); + count++; + } + listener.endTypeList(count); + return token; + } + + Token parsePartOrPartOf(Token token) { + assert(optional('part', token)); + if (optional('of', token.next)) { + return parsePartOf(token); + } else { + return parsePart(token); + } + } + + Token parsePart(Token token) { + Token partKeyword = token; + listener.beginPart(token); + assert(optional('part', token)); + token = parseLiteralStringOrRecoverExpression(token.next); + Token semicolon = token; + token = expect(';', token); + listener.endPart(partKeyword, semicolon); + return token; + } + + Token parsePartOf(Token token) { + listener.beginPartOf(token); + assert(optional('part', token)); + assert(optional('of', token.next)); + Token partKeyword = token; + token = parseQualified(token.next.next); + Token semicolon = token; + token = expect(';', token); + listener.endPartOf(partKeyword, semicolon); + return token; + } + + Token parseMetadataStar(Token token) { + while (optional('@', token)) { + token = parseMetadata(token); + } + return token; + } + + /** + * Parse + * [: '@' qualified (‘.’ identifier)? (arguments)? :] + */ + Token parseMetadata(Token token) { + listener.beginMetadata(token); + Token atToken = token; + assert(optional('@', token)); + token = parseIdentifier(token.next); + token = parseQualifiedRestOpt(token); + token = parseTypeArgumentsOpt(token); + Token period = null; + if (optional('.', token)) { + period = token; + token = parseIdentifier(token.next); + } + token = parseArgumentsOpt(token); + listener.endMetadata(atToken, period, token); + return token; + } + + Token parseInterface(Token token) { + Token interfaceKeyword = token; + listener.beginInterface(token); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + int supertypeCount = 0; + Token extendsKeyword = null; + if (optional('extends', token)) { + extendsKeyword = token; + do { + token = parseType(token.next); + ++supertypeCount; + } while (optional(',', token)); + } + token = parseDefaultClauseOpt(token); + token = parseInterfaceBody(token); + listener.endInterface(supertypeCount, interfaceKeyword, + extendsKeyword, token); + return token.next; + } + + Token parseInterfaceBody(Token token) { + return parseClassBody(token); + } + + Token parseTypedef(Token token) { + Token typedefKeyword = token; + if (optional('=', peekAfterType(token.next))) { + listener.beginNamedMixinApplication(token); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + token = expect('=', token); + token = parseModifiers(token); + token = parseMixinApplication(token); + Token implementsKeyword = null; + if (optional('implements', token)) { + implementsKeyword = token; + token = parseTypeList(token.next); + } + listener.endNamedMixinApplication( + typedefKeyword, implementsKeyword, token); + } else { + listener.beginFunctionTypeAlias(token); + token = parseReturnTypeOpt(token.next); + token = parseIdentifier(token); + token = parseTypeVariablesOpt(token); + token = parseFormalParameters(token); + listener.endFunctionTypeAlias(typedefKeyword, token); + } + return expect(';', token); + } + + Token parseMixinApplication(Token token) { + listener.beginMixinApplication(token); + token = parseType(token); + token = expect('with', token); + token = parseTypeList(token); + listener.endMixinApplication(); + return token; + } + + Token parseReturnTypeOpt(Token token) { + if (identical(token.stringValue, 'void')) { + listener.handleVoidKeyword(token); + return token.next; + } else { + return parseTypeOpt(token); + } + } + + Token parseFormalParametersOpt(Token token) { + if (optional('(', token)) { + return parseFormalParameters(token); + } else { + listener.handleNoFormalParameters(token); + return token; + } + } + + Token parseFormalParameters(Token token) { + Token begin = token; + listener.beginFormalParameters(begin); + expect('(', token); + int parameterCount = 0; + if (optional(')', token.next)) { + listener.endFormalParameters(parameterCount, begin, token.next); + return token.next.next; + } + do { + ++parameterCount; + token = token.next; + String value = token.stringValue; + if (identical(value, '[')) { + token = parseOptionalFormalParameters(token, false); + break; + } else if (identical(value, '{')) { + token = parseOptionalFormalParameters(token, true); + break; + } + token = parseFormalParameter(token); + } while (optional(',', token)); + listener.endFormalParameters(parameterCount, begin, token); + return expect(')', token); + } + + Token parseFormalParameter(Token token) { + listener.beginFormalParameter(token); + token = parseModifiers(token); + // TODO(ahe): Validate that there are formal parameters if void. + token = parseReturnTypeOpt(token); + Token thisKeyword = null; + if (optional('this', token)) { + thisKeyword = token; + // TODO(ahe): Validate field initializers are only used in + // constructors, and not for function-typed arguments. + token = expect('.', token.next); + } + token = parseIdentifier(token); + if (optional('(', token)) { + token = parseFormalParameters(token); + listener.handleFunctionTypedFormalParameter(token); + } + String value = token.stringValue; + if ((identical('=', value)) || (identical(':', value))) { + // TODO(ahe): Validate that these are only used for optional parameters. + Token equal = token; + token = parseExpression(token.next); + listener.handleValuedFormalParameter(equal, token); + } + listener.endFormalParameter(thisKeyword); + return token; + } + + Token parseOptionalFormalParameters(Token token, bool isNamed) { + Token begin = token; + listener.beginOptionalFormalParameters(begin); + assert((isNamed && optional('{', token)) || optional('[', token)); + int parameterCount = 0; + do { + token = token.next; + token = parseFormalParameter(token); + ++parameterCount; + } while (optional(',', token)); + listener.endOptionalFormalParameters(parameterCount, begin, token); + if (isNamed) { + return expect('}', token); + } else { + return expect(']', token); + } + } + + Token parseTypeOpt(Token token) { + String value = token.stringValue; + if (!identical(value, 'this')) { + Token peek = peekAfterExpectedType(token); + if (peek.isIdentifier() || optional('this', peek)) { + return parseType(token); + } + } + listener.handleNoType(token); + return token; + } + + bool isValidTypeReference(Token token) { + final kind = token.kind; + if (identical(kind, IDENTIFIER_TOKEN)) return true; + if (identical(kind, KEYWORD_TOKEN)) { + Keyword keyword = token.value; + String value = keyword.stringValue; + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + return keyword.isPseudo + || (identical(value, 'dynamic')) + || (identical(value, 'Dynamic')) + || (identical(value, 'void')); + } + return false; + } + + Token parseDefaultClauseOpt(Token token) { + if (isDefaultKeyword(token)) { + // TODO(ahe): Remove support for 'factory' in this position. + Token defaultKeyword = token; + listener.beginDefaultClause(defaultKeyword); + token = parseIdentifier(token.next); + token = parseQualifiedRestOpt(token); + token = parseTypeVariablesOpt(token); + listener.endDefaultClause(defaultKeyword); + } else { + listener.handleNoDefaultClause(token); + } + return token; + } + + Token parseQualified(Token token) { + token = parseIdentifier(token); + while (optional('.', token)) { + token = parseQualifiedRest(token); + } + return token; + } + + Token parseQualifiedRestOpt(Token token) { + if (optional('.', token)) { + return parseQualifiedRest(token); + } else { + return token; + } + } + + Token parseQualifiedRest(Token token) { + assert(optional('.', token)); + Token period = token; + token = parseIdentifier(token.next); + listener.handleQualified(period); + return token; + } + + bool isDefaultKeyword(Token token) { + String value = token.stringValue; + if (identical(value, 'default')) return true; + if (identical(value, 'factory')) { + listener.recoverableError("expected 'default'", token: token); + return true; + } + return false; + } + + Token skipBlock(Token token) { + if (!optional('{', token)) { + return listener.expectedBlockToSkip(token); + } + BeginGroupToken beginGroupToken = token; + Token endGroup = beginGroupToken.endGroup; + if (endGroup == null) { + return listener.unmatched(beginGroupToken); + } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { + return listener.unmatched(beginGroupToken); + } + return beginGroupToken.endGroup; + } + + Token parseClass(Token token) { + Token begin = token; + listener.beginClassDeclaration(token); + int modifierCount = 0; + if (optional('abstract', token)) { + listener.handleModifier(token); + modifierCount++; + token = token.next; + } + listener.handleModifiers(modifierCount); + token = parseIdentifier(token.next); + token = parseTypeVariablesOpt(token); + Token extendsKeyword; + if (optional('extends', token)) { + extendsKeyword = token; + if (optional('with', peekAfterType(token.next))) { + token = parseMixinApplication(token.next); + } else { + token = parseType(token.next); + } + } else { + extendsKeyword = null; + listener.handleNoType(token); + } + Token implementsKeyword; + int interfacesCount = 0; + if (optional('implements', token)) { + implementsKeyword = token; + do { + token = parseType(token.next); + ++interfacesCount; + } while (optional(',', token)); + } + token = parseClassBody(token); + listener.endClassDeclaration(interfacesCount, begin, extendsKeyword, + implementsKeyword, token); + return token.next; + } + + Token parseStringPart(Token token) { + if (identical(token.kind, STRING_TOKEN)) { + listener.handleStringPart(token); + return token.next; + } else { + return listener.expected('string', token); + } + } + + Token parseIdentifier(Token token) { + if (token.isIdentifier()) { + listener.handleIdentifier(token); + } else { + listener.expectedIdentifier(token); + } + return token.next; + } + + Token expect(String string, Token token) { + if (!identical(string, token.stringValue)) { + return listener.expected(string, token); + } + return token.next; + } + + Token parseTypeVariable(Token token) { + listener.beginTypeVariable(token); + token = parseIdentifier(token); + if (optional('extends', token)) { + token = parseType(token.next); + } else { + listener.handleNoType(token); + } + listener.endTypeVariable(token); + return token; + } + + /** + * Returns true if the stringValue of the [token] is [value]. + */ + bool optional(String value, Token token) { + return identical(value, token.stringValue); + } + + /** + * Returns true if the stringValue of the [token] is either [value1], + * [value2], [value3], or [value4]. + */ + bool isOneOf4(Token token, + String value1, String value2, String value3, String value4) { + String stringValue = token.stringValue; + return identical(value1, stringValue) || + identical(value2, stringValue) || + identical(value3, stringValue) || + identical(value4, stringValue); + } + + bool notEofOrValue(String value, Token token) { + return !identical(token.kind, EOF_TOKEN) && + !identical(value, token.stringValue); + } + + Token parseType(Token token) { + Token begin = token; + if (isValidTypeReference(token)) { + token = parseIdentifier(token); + token = parseQualifiedRestOpt(token); + } else { + token = listener.expectedType(token); + } + token = parseTypeArgumentsOpt(token); + listener.endType(begin, token); + return token; + } + + Token parseTypeArgumentsOpt(Token token) { + return parseStuff(token, + (t) => listener.beginTypeArguments(t), + (t) => parseType(t), + (c, bt, et) => listener.endTypeArguments(c, bt, et), + (t) => listener.handleNoTypeArguments(t)); + } + + Token parseTypeVariablesOpt(Token token) { + return parseStuff(token, + (t) => listener.beginTypeVariables(t), + (t) => parseTypeVariable(t), + (c, bt, et) => listener.endTypeVariables(c, bt, et), + (t) => listener.handleNoTypeVariables(t)); + } + + // TODO(ahe): Clean this up. + Token parseStuff(Token token, Function beginStuff, Function stuffParser, + Function endStuff, Function handleNoStuff) { + if (optional('<', token)) { + Token begin = token; + beginStuff(begin); + int count = 0; + do { + token = stuffParser(token.next); + ++count; + } while (optional(',', token)); + Token next = token.next; + if (identical(token.stringValue, '>>')) { + token = new Token(GT_INFO, token.charOffset); + token.next = new Token(GT_INFO, token.charOffset + 1); + token.next.next = next; + } else if (identical(token.stringValue, '>>>')) { + token = new Token(GT_INFO, token.charOffset); + token.next = new Token(GT_GT_INFO, token.charOffset + 1); + token.next.next = next; + } + endStuff(count, begin, token); + return expect('>', token); + } + handleNoStuff(token); + return token; + } + + Token parseTopLevelMember(Token token) { + Token start = token; + listener.beginTopLevelMember(token); + + Link<Token> identifiers = findMemberName(token); + if (identifiers.isEmpty) { + return listener.unexpected(start); + } + Token name = identifiers.head; + identifiers = identifiers.tail; + Token getOrSet; + if (!identifiers.isEmpty) { + String value = identifiers.head.stringValue; + if ((identical(value, 'get')) || (identical(value, 'set'))) { + getOrSet = identifiers.head; + identifiers = identifiers.tail; + } + } + Token type; + if (!identifiers.isEmpty) { + if (isValidTypeReference(identifiers.head)) { + type = identifiers.head; + identifiers = identifiers.tail; + } + } + parseModifierList(identifiers.reverse()); + if (type == null) { + listener.handleNoType(token); + } else { + parseReturnTypeOpt(type); + } + token = parseIdentifier(name); + + bool isField; + while (true) { + // Loop to allow the listener to rewrite the token stream for + // error handling. + final String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '{')) + || (identical(value, '=>'))) { + isField = false; + break; + } else if ((identical(value, '=')) || (identical(value, ','))) { + isField = true; + break; + } else if (identical(value, ';')) { + if (getOrSet != null) { + // If we found a "get" keyword, this must be an abstract + // getter. + isField = (!identical(getOrSet.stringValue, 'get')); + // TODO(ahe): This feels like a hack. + } else { + isField = true; + } + break; + } else { + token = listener.unexpected(token); + if (identical(token.kind, EOF_TOKEN)) { + // TODO(ahe): This is a hack. It would be better to tell the + // listener more explicitly that it must pop an identifier. + listener.endTopLevelFields(1, start, token); + return token; + } + } + } + if (isField) { + int fieldCount = 1; + token = parseVariableInitializerOpt(token); + while (optional(',', token)) { + token = parseIdentifier(token.next); + token = parseVariableInitializerOpt(token); + ++fieldCount; + } + expectSemicolon(token); + listener.endTopLevelFields(fieldCount, start, token); + } else { + token = parseFormalParametersOpt(token); + token = parseFunctionBody(token, false); + listener.endTopLevelMethod(start, getOrSet, token); + } + return token.next; + } + + Link<Token> findMemberName(Token token) { + Token start = token; + Link<Token> identifiers = const Link<Token>(); + while (!identical(token.kind, EOF_TOKEN)) { + String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '{')) + || (identical(value, '=>'))) { + // A method. + return identifiers; + } else if ((identical(value, '=')) || (identical(value, ';')) + || (identical(value, ','))) { + // A field or abstract getter. + return identifiers; + } + identifiers = identifiers.prepend(token); + if (isValidTypeReference(token)) { + // type ... + if (optional('.', token.next)) { + // type '.' ... + if (token.next.next.isIdentifier()) { + // type '.' identifier + token = token.next.next; + } + } + if (optional('<', token.next)) { + if (token.next is BeginGroupToken) { + BeginGroupToken beginGroup = token.next; + token = beginGroup.endGroup; + } + } + } + token = token.next; + } + return listener.expectedDeclaration(start); + } + + Token parseVariableInitializerOpt(Token token) { + if (optional('=', token)) { + Token assignment = token; + listener.beginInitializer(token); + token = parseExpression(token.next); + listener.endInitializer(assignment); + } + return token; + } + + Token parseInitializersOpt(Token token) { + if (optional(':', token)) { + return parseInitializers(token); + } else { + listener.handleNoInitializers(); + return token; + } + } + + Token parseInitializers(Token token) { + Token begin = token; + listener.beginInitializers(begin); + expect(':', token); + int count = 0; + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = false; + do { + token = parseExpression(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.endInitializers(count, begin, token); + return token; + } + + Token parseScriptTags(Token token) { + Token begin = token; + listener.beginScriptTag(token); + token = parseIdentifier(token.next); + token = expect('(', token); + token = parseLiteralStringOrRecoverExpression(token); + bool hasPrefix = false; + if (optional(',', token)) { + hasPrefix = true; + token = parseIdentifier(token.next); + token = expect(':', token); + token = parseLiteralStringOrRecoverExpression(token); + } + token = expect(')', token); + listener.endScriptTag(hasPrefix, begin, token); + return expectSemicolon(token); + } + + Token parseLiteralStringOrRecoverExpression(Token token) { + if (identical(token.kind, STRING_TOKEN)) { + return parseLiteralString(token); + } else { + listener.recoverableError("unexpected", token: token); + return parseExpression(token); + } + } + + Token expectSemicolon(Token token) { + return expect(';', token); + } + + bool isModifier(Token token) { + final String value = token.stringValue; + return (identical('final', value)) || + (identical('var', value)) || + (identical('const', value)) || + (identical('abstract', value)) || + (identical('static', value)) || + (identical('external', value)); + } + + Token parseModifier(Token token) { + assert(isModifier(token)); + listener.handleModifier(token); + return token.next; + } + + void parseModifierList(Link<Token> tokens) { + int count = 0; + for (; !tokens.isEmpty; tokens = tokens.tail) { + Token token = tokens.head; + if (isModifier(token)) { + parseModifier(token); + } else { + listener.unexpected(token); + } + count++; + } + listener.handleModifiers(count); + } + + Token parseModifiers(Token token) { + int count = 0; + while (identical(token.kind, KEYWORD_TOKEN)) { + if (!isModifier(token)) + break; + token = parseModifier(token); + count++; + } + listener.handleModifiers(count); + return token; + } + + Token peekAfterType(Token token) { + // TODO(ahe): Also handle var? + // We are looking at "identifier ...". + Token peek = token.next; + if (identical(peek.kind, PERIOD_TOKEN)) { + if (peek.next.isIdentifier()) { + // Look past a library prefix. + peek = peek.next.next; + } + } + // We are looking at "qualified ...". + if (identical(peek.kind, LT_TOKEN)) { + // Possibly generic type. + // We are looking at "qualified '<'". + BeginGroupToken beginGroupToken = peek; + Token gtToken = beginGroupToken.endGroup; + if (gtToken != null) { + // We are looking at "qualified '<' ... '>' ...". + return gtToken.next; + } + } + return peek; + } + + /** + * Returns the token after the type which is expected to begin at [token]. + * If [token] is not the start of a type, [Listener.unexpectedType] is called. + */ + Token peekAfterExpectedType(Token token) { + if (!identical('void', token.stringValue) && !token.isIdentifier()) { + return listener.expectedType(token); + } + return peekAfterType(token); + } + + Token parseClassBody(Token token) { + Token begin = token; + listener.beginClassBody(token); + if (!optional('{', token)) { + token = listener.expectedClassBody(token); + } + token = token.next; + int count = 0; + while (notEofOrValue('}', token)) { + token = parseMember(token); + ++count; + } + expect('}', token); + listener.endClassBody(count, begin, token); + return token; + } + + bool isGetOrSet(Token token) { + final String value = token.stringValue; + return (identical(value, 'get')) || (identical(value, 'set')); + } + + bool isFactoryDeclaration(Token token) { + if (optional('external', token)) token = token.next; + if (optional('const', token)) token = token.next; + return optional('factory', token); + } + + Token parseMember(Token token) { + token = parseMetadataStar(token); + String value = token.stringValue; + if (isFactoryDeclaration(token)) { + return parseFactoryMethod(token); + } + Token start = token; + listener.beginMember(token); + + Link<Token> identifiers = findMemberName(token); + if (identifiers.isEmpty) { + return listener.unexpected(start); + } + Token name = identifiers.head; + identifiers = identifiers.tail; + if (!identifiers.isEmpty) { + if (optional('operator', identifiers.head)) { + name = identifiers.head; + identifiers = identifiers.tail; + } + } + Token getOrSet; + if (!identifiers.isEmpty) { + if (isGetOrSet(identifiers.head)) { + getOrSet = identifiers.head; + identifiers = identifiers.tail; + } + } + Token type; + if (!identifiers.isEmpty) { + if (isValidTypeReference(identifiers.head)) { + type = identifiers.head; + identifiers = identifiers.tail; + } + } + parseModifierList(identifiers.reverse()); + if (type == null) { + listener.handleNoType(token); + } else { + parseReturnTypeOpt(type); + } + + if (optional('operator', name)) { + token = parseOperatorName(name); + } else { + token = parseIdentifier(name); + } + bool isField; + while (true) { + // Loop to allow the listener to rewrite the token stream for + // error handling. + final String value = token.stringValue; + if ((identical(value, '(')) || (identical(value, '.')) + || (identical(value, '{')) || (identical(value, '=>'))) { + isField = false; + break; + } else if (identical(value, ';')) { + if (getOrSet != null) { + // If we found a "get" keyword, this must be an abstract + // getter. + isField = (!identical(getOrSet.stringValue, 'get')); + // TODO(ahe): This feels like a hack. + } else { + isField = true; + } + break; + } else if ((identical(value, '=')) || (identical(value, ','))) { + isField = true; + break; + } else { + token = listener.unexpected(token); + if (identical(token.kind, EOF_TOKEN)) { + // TODO(ahe): This is a hack, see parseTopLevelMember. + listener.endFields(1, start, token); + return token; + } + } + } + if (isField) { + int fieldCount = 1; + token = parseVariableInitializerOpt(token); + if (getOrSet != null) { + listener.recoverableError("unexpected", token: getOrSet); + } + while (optional(',', token)) { + // TODO(ahe): Count these. + token = parseIdentifier(token.next); + token = parseVariableInitializerOpt(token); + ++fieldCount; + } + expectSemicolon(token); + listener.endFields(fieldCount, start, token); + } else { + token = parseQualifiedRestOpt(token); + token = parseFormalParametersOpt(token); + token = parseInitializersOpt(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endMethod(getOrSet, start, token); + } + return token.next; + } + + Token parseFactoryMethod(Token token) { + assert(isFactoryDeclaration(token)); + Token start = token; + if (identical(token.stringValue, 'external')) token = token.next; + Token constKeyword = null; + if (optional('const', token)) { + constKeyword = token; + token = token.next; + } + Token factoryKeyword = token; + listener.beginFactoryMethod(factoryKeyword); + token = token.next; // Skip 'factory'. + token = parseConstructorReference(token); + token = parseFormalParameters(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endFactoryMethod(start, token); + return token.next; + } + + Token parseOperatorName(Token token) { + assert(optional('operator', token)); + if (isUserDefinableOperator(token.next.stringValue)) { + Token operator = token; + token = token.next; + listener.handleOperatorName(operator, token); + return token.next; + } else { + return parseIdentifier(token); + } + } + + Token parseFunction(Token token, Token getOrSet) { + listener.beginFunction(token); + token = parseModifiers(token); + if (identical(getOrSet, token)) token = token.next; + if (optional('operator', token)) { + listener.handleNoType(token); + listener.beginFunctionName(token); + token = parseOperatorName(token); + } else { + token = parseReturnTypeOpt(token); + if (identical(getOrSet, token)) token = token.next; + listener.beginFunctionName(token); + if (optional('operator', token)) { + token = parseOperatorName(token); + } else { + token = parseIdentifier(token); + } + } + token = parseQualifiedRestOpt(token); + listener.endFunctionName(token); + token = parseFormalParametersOpt(token); + token = parseInitializersOpt(token); + if (optional('=', token)) { + token = parseRedirectingFactoryBody(token); + } else { + token = parseFunctionBody(token, false); + } + listener.endFunction(getOrSet, token); + return token.next; + } + + Token parseUnamedFunction(Token token) { + listener.beginUnamedFunction(token); + token = parseFormalParameters(token); + bool isBlock = optional('{', token); + token = parseFunctionBody(token, true); + listener.endUnamedFunction(token); + return isBlock ? token.next : token; + } + + Token parseFunctionDeclaration(Token token) { + listener.beginFunctionDeclaration(token); + token = parseFunction(token, null); + listener.endFunctionDeclaration(token); + return token; + } + + Token parseFunctionExpression(Token token) { + listener.beginFunction(token); + listener.handleModifiers(0); + token = parseReturnTypeOpt(token); + listener.beginFunctionName(token); + token = parseIdentifier(token); + listener.endFunctionName(token); + token = parseFormalParameters(token); + listener.handleNoInitializers(); + bool isBlock = optional('{', token); + token = parseFunctionBody(token, true); + listener.endFunction(null, token); + return isBlock ? token.next : token; + } + + Token parseConstructorReference(Token token) { + Token start = token; + listener.beginConstructorReference(start); + token = parseIdentifier(token); + token = parseQualifiedRestOpt(token); + token = parseTypeArgumentsOpt(token); + Token period = null; + if (optional('.', token)) { + period = token; + token = parseIdentifier(token.next); + } + listener.endConstructorReference(start, period, token); + return token; + } + + Token parseRedirectingFactoryBody(Token token) { + listener.beginRedirectingFactoryBody(token); + assert(optional('=', token)); + Token equals = token; + token = parseConstructorReference(token.next); + Token semicolon = token; + expectSemicolon(token); + listener.endRedirectingFactoryBody(equals, semicolon); + return token; + } + + Token parseFunctionBody(Token token, bool isExpression) { + if (optional(';', token)) { + listener.endFunctionBody(0, null, token); + return token; + } else if (optional('=>', token)) { + Token begin = token; + token = parseExpression(token.next); + if (!isExpression) { + expectSemicolon(token); + listener.endReturnStatement(true, begin, token); + } else { + listener.endReturnStatement(true, begin, null); + } + return token; + } + Token begin = token; + int statementCount = 0; + if (!optional('{', token)) { + return listener.expectedFunctionBody(token); + } + + listener.beginFunctionBody(begin); + token = token.next; + while (notEofOrValue('}', token)) { + token = parseStatement(token); + ++statementCount; + } + listener.endFunctionBody(statementCount, begin, token); + expect('}', token); + return token; + } + + Token parseStatement(Token token) { + final value = token.stringValue; + if (identical(token.kind, IDENTIFIER_TOKEN)) { + return parseExpressionStatementOrDeclaration(token); + } else if (identical(value, '{')) { + return parseBlock(token); + } else if (identical(value, 'return')) { + return parseReturnStatement(token); + } else if (identical(value, 'var') || identical(value, 'final')) { + return parseVariablesDeclaration(token); + } else if (identical(value, 'if')) { + return parseIfStatement(token); + } else if (identical(value, 'for')) { + return parseForStatement(token); + } else if (identical(value, 'throw')) { + return parseThrowStatement(token); + } else if (identical(value, 'void')) { + return parseExpressionStatementOrDeclaration(token); + } else if (identical(value, 'while')) { + return parseWhileStatement(token); + } else if (identical(value, 'do')) { + return parseDoWhileStatement(token); + } else if (identical(value, 'try')) { + return parseTryStatement(token); + } else if (identical(value, 'switch')) { + return parseSwitchStatement(token); + } else if (identical(value, 'break')) { + return parseBreakStatement(token); + } else if (identical(value, 'continue')) { + return parseContinueStatement(token); + } else if (identical(value, 'assert')) { + return parseAssertStatement(token); + } else if (identical(value, ';')) { + return parseEmptyStatement(token); + } else if (identical(value, 'const')) { + return parseExpressionStatementOrConstDeclaration(token); + } else if (token.isIdentifier()) { + return parseExpressionStatementOrDeclaration(token); + } else { + return parseExpressionStatement(token); + } + } + + Token parseReturnStatement(Token token) { + Token begin = token; + listener.beginReturnStatement(begin); + assert(identical('return', token.stringValue)); + token = token.next; + if (optional(';', token)) { + listener.endReturnStatement(false, begin, token); + } else { + token = parseExpression(token); + listener.endReturnStatement(true, begin, token); + } + return expectSemicolon(token); + } + + Token peekIdentifierAfterType(Token token) { + Token peek = peekAfterType(token); + if (peek != null && peek.isIdentifier()) { + // We are looking at "type identifier". + return peek; + } else { + return null; + } + } + + Token peekIdentifierAfterOptionalType(Token token) { + Token peek = peekIdentifierAfterType(token); + if (peek != null) { + // We are looking at "type identifier". + return peek; + } else if (token.isIdentifier()) { + // We are looking at "identifier". + return token; + } else { + return null; + } + } + + Token parseExpressionStatementOrDeclaration(Token token) { + assert(token.isIdentifier() || identical(token.stringValue, 'void')); + Token identifier = peekIdentifierAfterType(token); + if (identifier != null) { + assert(identifier.isIdentifier()); + Token afterId = identifier.next; + int afterIdKind = afterId.kind; + if (identical(afterIdKind, EQ_TOKEN) || + identical(afterIdKind, SEMICOLON_TOKEN) || + identical(afterIdKind, COMMA_TOKEN)) { + // We are looking at "type identifier" followed by '=', ';', ','. + return parseVariablesDeclaration(token); + } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) { + // We are looking at "type identifier '('". + BeginGroupToken beginParen = afterId; + Token endParen = beginParen.endGroup; + Token afterParens = endParen.next; + if (optional('{', afterParens) || optional('=>', afterParens)) { + // We are looking at "type identifier '(' ... ')'" followed + // by '=>' or '{'. + return parseFunctionDeclaration(token); + } + } + // Fall-through to expression statement. + } else { + if (optional(':', token.next)) { + return parseLabeledStatement(token); + } else if (optional('(', token.next)) { + BeginGroupToken begin = token.next; + String afterParens = begin.endGroup.next.stringValue; + if (identical(afterParens, '{') || identical(afterParens, '=>')) { + return parseFunctionDeclaration(token); + } + } + } + return parseExpressionStatement(token); + } + + Token parseExpressionStatementOrConstDeclaration(Token token) { + assert(identical(token.stringValue, 'const')); + if (isModifier(token.next)) { + return parseVariablesDeclaration(token); + } + Token identifier = peekIdentifierAfterOptionalType(token.next); + if (identifier != null) { + assert(identifier.isIdentifier()); + Token afterId = identifier.next; + int afterIdKind = afterId.kind; + if (identical(afterIdKind, EQ_TOKEN) || + identical(afterIdKind, SEMICOLON_TOKEN) || + identical(afterIdKind, COMMA_TOKEN)) { + // We are looking at "const type identifier" followed by '=', ';', or + // ','. + return parseVariablesDeclaration(token); + } + // Fall-through to expression statement. + } + return parseExpressionStatement(token); + } + + Token parseLabel(Token token) { + token = parseIdentifier(token); + Token colon = token; + token = expect(':', token); + listener.handleLabel(colon); + return token; + } + + Token parseLabeledStatement(Token token) { + int labelCount = 0; + do { + token = parseLabel(token); + labelCount++; + } while (token.isIdentifier() && optional(':', token.next)); + listener.beginLabeledStatement(token, labelCount); + token = parseStatement(token); + listener.endLabeledStatement(labelCount); + return token; + } + + Token parseExpressionStatement(Token token) { + listener.beginExpressionStatement(token); + token = parseExpression(token); + listener.endExpressionStatement(token); + return expectSemicolon(token); + } + + Token parseExpression(Token token) { + return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true); + } + + Token parseExpressionWithoutCascade(Token token) { + return parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false); + } + + Token parseConditionalExpressionRest(Token token) { + assert(optional('?', token)); + Token question = token; + token = parseExpressionWithoutCascade(token.next); + Token colon = token; + token = expect(':', token); + token = parseExpressionWithoutCascade(token); + listener.handleConditionalExpression(question, colon); + return token; + } + + Token parsePrecedenceExpression(Token token, int precedence, + bool allowCascades) { + assert(precedence >= 1); + assert(precedence <= POSTFIX_PRECEDENCE); + token = parseUnaryExpression(token, allowCascades); + PrecedenceInfo info = token.info; + int tokenLevel = info.precedence; + for (int level = tokenLevel; level >= precedence; --level) { + while (identical(tokenLevel, level)) { + Token operator = token; + if (identical(tokenLevel, CASCADE_PRECEDENCE)) { + if (!allowCascades) { + return token; + } + token = parseCascadeExpression(token); + } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) { + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, level, allowCascades); + listener.handleAssignmentExpression(operator); + } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) { + if (identical(info, PERIOD_INFO)) { + // Left associative, so we recurse at the next higher + // precedence level. However, POSTFIX_PRECEDENCE is the + // highest level, so we just call parseUnaryExpression + // directly. + token = parseUnaryExpression(token.next, allowCascades); + listener.handleBinaryExpression(operator); + } else if ((identical(info, OPEN_PAREN_INFO)) || + (identical(info, OPEN_SQUARE_BRACKET_INFO))) { + token = parseArgumentOrIndexStar(token); + } else if ((identical(info, PLUS_PLUS_INFO)) || + (identical(info, MINUS_MINUS_INFO))) { + listener.handleUnaryPostfixAssignmentExpression(token); + token = token.next; + } else { + token = listener.unexpected(token); + } + } else if (identical(info, IS_INFO)) { + token = parseIsOperatorRest(token); + } else if (identical(info, AS_INFO)) { + token = parseAsOperatorRest(token); + } else if (identical(info, QUESTION_INFO)) { + token = parseConditionalExpressionRest(token); + } else { + // Left associative, so we recurse at the next higher + // precedence level. + token = parsePrecedenceExpression(token.next, level + 1, + allowCascades); + listener.handleBinaryExpression(operator); + } + info = token.info; + tokenLevel = info.precedence; + } + } + return token; + } + + Token parseCascadeExpression(Token token) { + listener.beginCascade(token); + assert(optional('..', token)); + Token cascadeOperator = token; + token = token.next; + if (optional('[', token)) { + token = parseArgumentOrIndexStar(token); + } else if (token.isIdentifier()) { + token = parseSend(token); + listener.handleBinaryExpression(cascadeOperator); + } else { + return listener.unexpected(token); + } + Token mark; + do { + mark = token; + if (optional('.', token)) { + Token period = token; + token = parseSend(token.next); + listener.handleBinaryExpression(period); + } + token = parseArgumentOrIndexStar(token); + } while (!identical(mark, token)); + + if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) { + Token assignment = token; + token = parseExpressionWithoutCascade(token.next); + listener.handleAssignmentExpression(assignment); + } + listener.endCascade(); + return token; + } + + Token parseUnaryExpression(Token token, bool allowCascades) { + String value = token.stringValue; + // Prefix: + if (identical(value, '+')) { + // Dart only allows "prefix plus" as an initial part of a + // decimal literal. We scan it as a separate token and let + // the parser listener combine it with the digits. + Token next = token.next; + if (identical(next.charOffset, token.charOffset + 1)) { + if (identical(next.kind, INT_TOKEN)) { + listener.handleLiteralInt(token); + return next.next; + } + if (identical(next.kind, DOUBLE_TOKEN)) { + listener.handleLiteralDouble(token); + return next.next; + } + } + listener.recoverableError("Unexpected token '+'", token: token); + return parsePrecedenceExpression(next, POSTFIX_PRECEDENCE, + allowCascades); + } else if ((identical(value, '!')) || + (identical(value, '-')) || + (identical(value, '~'))) { + Token operator = token; + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE, + allowCascades); + listener.handleUnaryPrefixExpression(operator); + } else if ((identical(value, '++')) || identical(value, '--')) { + // TODO(ahe): Validate this is used correctly. + Token operator = token; + // Right associative, so we recurse at the same precedence + // level. + token = parsePrecedenceExpression(token.next, POSTFIX_PRECEDENCE, + allowCascades); + listener.handleUnaryPrefixAssignmentExpression(operator); + } else { + token = parsePrimary(token); + } + return token; + } + + Token parseArgumentOrIndexStar(Token token) { + while (true) { + if (optional('[', token)) { + Token openSquareBracket = token; + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + token = parseExpression(token.next); + mayParseFunctionExpressions = old; + listener.handleIndexedExpression(openSquareBracket, token); + token = expect(']', token); + } else if (optional('(', token)) { + token = parseArguments(token); + listener.endSend(token); + } else { + break; + } + } + return token; + } + + Token parsePrimary(Token token) { + final kind = token.kind; + if (identical(kind, IDENTIFIER_TOKEN)) { + return parseSendOrFunctionLiteral(token); + } else if (identical(kind, INT_TOKEN) + || identical(kind, HEXADECIMAL_TOKEN)) { + return parseLiteralInt(token); + } else if (identical(kind, DOUBLE_TOKEN)) { + return parseLiteralDouble(token); + } else if (identical(kind, STRING_TOKEN)) { + return parseLiteralString(token); + } else if (identical(kind, KEYWORD_TOKEN)) { + final value = token.stringValue; + if ((identical(value, 'true')) || (identical(value, 'false'))) { + return parseLiteralBool(token); + } else if (identical(value, 'null')) { + return parseLiteralNull(token); + } else if (identical(value, 'this')) { + return parseThisExpression(token); + } else if (identical(value, 'super')) { + return parseSuperExpression(token); + } else if (identical(value, 'new')) { + return parseNewExpression(token); + } else if (identical(value, 'const')) { + return parseConstExpression(token); + } else if (identical(value, 'void')) { + return parseFunctionExpression(token); + } else if (token.isIdentifier()) { + return parseSendOrFunctionLiteral(token); + } else { + return listener.expectedExpression(token); + } + } else if (identical(kind, OPEN_PAREN_TOKEN)) { + return parseParenthesizedExpressionOrFunctionLiteral(token); + } else if ((identical(kind, LT_TOKEN)) || + (identical(kind, OPEN_SQUARE_BRACKET_TOKEN)) || + (identical(kind, OPEN_CURLY_BRACKET_TOKEN)) || + identical(token.stringValue, '[]')) { + return parseLiteralListOrMap(token); + } else if (identical(kind, QUESTION_TOKEN)) { + return parseArgumentDefinitionTest(token); + } else { + return listener.expectedExpression(token); + } + } + + Token parseArgumentDefinitionTest(Token token) { + Token questionToken = token; + listener.beginArgumentDefinitionTest(questionToken); + assert(optional('?', token)); + token = parseIdentifier(token.next); + listener.endArgumentDefinitionTest(questionToken, token); + return token; + } + + Token parseParenthesizedExpressionOrFunctionLiteral(Token token) { + BeginGroupToken beginGroup = token; + int kind = beginGroup.endGroup.next.kind; + if (mayParseFunctionExpressions && + (identical(kind, FUNCTION_TOKEN) + || identical(kind, OPEN_CURLY_BRACKET_TOKEN))) { + return parseUnamedFunction(token); + } else { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + token = parseParenthesizedExpression(token); + mayParseFunctionExpressions = old; + return token; + } + } + + Token parseParenthesizedExpression(Token token) { + var begin = token; + token = expect('(', token); + token = parseExpression(token); + if (!identical(begin.endGroup, token)) { + listener.unexpected(token); + token = begin.endGroup; + } + listener.handleParenthesizedExpression(begin); + return expect(')', token); + } + + Token parseThisExpression(Token token) { + listener.handleThisExpression(token); + token = token.next; + if (optional('(', token)) { + // Constructor forwarding. + token = parseArguments(token); + listener.endSend(token); + } + return token; + } + + Token parseSuperExpression(Token token) { + listener.handleSuperExpression(token); + token = token.next; + if (optional('(', token)) { + // Super constructor. + token = parseArguments(token); + listener.endSend(token); + } + return token; + } + + Token parseLiteralListOrMap(Token token) { + Token constKeyword = null; + if (optional('const', token)) { + constKeyword = token; + token = token.next; + } + token = parseTypeArgumentsOpt(token); + Token beginToken = token; + int count = 0; + if (optional('{', token)) { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + if (optional('}', token.next)) { + token = token.next; + break; + } + token = parseMapLiteralEntry(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.handleLiteralMap(count, beginToken, constKeyword, token); + return expect('}', token); + } else if (optional('[', token)) { + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + if (optional(']', token.next)) { + token = token.next; + break; + } + token = parseExpression(token.next); + ++count; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.handleLiteralList(count, beginToken, constKeyword, token); + return expect(']', token); + } else if (optional('[]', token)) { + listener.handleLiteralList(0, token, constKeyword, token); + return token.next; + } else { + listener.unexpected(token); + } + } + + Token parseMapLiteralEntry(Token token) { + listener.beginLiteralMapEntry(token); + // Assume the listener rejects non-string keys. + token = parseExpression(token); + Token colon = token; + token = expect(':', token); + token = parseExpression(token); + listener.endLiteralMapEntry(colon, token); + return token; + } + + Token parseSendOrFunctionLiteral(Token token) { + if (!mayParseFunctionExpressions) return parseSend(token); + Token peek = peekAfterExpectedType(token); + if (identical(peek.kind, IDENTIFIER_TOKEN) && isFunctionDeclaration(peek.next)) { + return parseFunctionExpression(token); + } else if (isFunctionDeclaration(token.next)) { + return parseFunctionExpression(token); + } else { + return parseSend(token); + } + } + + bool isFunctionDeclaration(Token token) { + if (optional('(', token)) { + BeginGroupToken begin = token; + String afterParens = begin.endGroup.next.stringValue; + if (identical(afterParens, '{') || identical(afterParens, '=>')) { + return true; + } + } + return false; + } + + Token parseRequiredArguments(Token token) { + if (optional('(', token)) { + token = parseArguments(token); + } else { + listener.handleNoArguments(token); + token = listener.unexpected(token); + } + return token; + } + + Token parseNewExpression(Token token) { + Token newKeyword = token; + token = expect('new', token); + token = parseConstructorReference(token); + token = parseRequiredArguments(token); + listener.handleNewExpression(newKeyword); + return token; + } + + Token parseConstExpression(Token token) { + Token constKeyword = token; + token = expect('const', token); + final String value = token.stringValue; + if ((identical(value, '<')) || + (identical(value, '[')) || + (identical(value, '[]')) || + (identical(value, '{'))) { + return parseLiteralListOrMap(constKeyword); + } + token = parseConstructorReference(token); + token = parseRequiredArguments(token); + listener.handleConstExpression(constKeyword); + return token; + } + + Token parseLiteralInt(Token token) { + listener.handleLiteralInt(token); + return token.next; + } + + Token parseLiteralDouble(Token token) { + listener.handleLiteralDouble(token); + return token.next; + } + + Token parseLiteralString(Token token) { + token = parseSingleLiteralString(token); + int count = 1; + while (identical(token.kind, STRING_TOKEN)) { + token = parseSingleLiteralString(token); + count++; + } + if (count > 1) { + listener.handleStringJuxtaposition(count); + } + return token; + } + + /** + * Only called when [:token.kind === STRING_TOKEN:]. + */ + Token parseSingleLiteralString(Token token) { + listener.beginLiteralString(token); + // Parsing the prefix, for instance 'x of 'x${id}y${id}z' + token = token.next; + int interpolationCount = 0; + var kind = token.kind; + while (kind != EOF_TOKEN) { + if (identical(kind, STRING_INTERPOLATION_TOKEN)) { + // Parsing ${expression}. + token = token.next; + token = parseExpression(token); + token = expect('}', token); + } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) { + // Parsing $identifier. + token = token.next; + token = parseExpression(token); + } else { + break; + } + ++interpolationCount; + // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z' + token = parseStringPart(token); + kind = token.kind; + } + listener.endLiteralString(interpolationCount); + return token; + } + + Token parseLiteralBool(Token token) { + listener.handleLiteralBool(token); + return token.next; + } + + Token parseLiteralNull(Token token) { + listener.handleLiteralNull(token); + return token.next; + } + + Token parseSend(Token token) { + listener.beginSend(token); + token = parseIdentifier(token); + token = parseArgumentsOpt(token); + listener.endSend(token); + return token; + } + + Token parseArgumentsOpt(Token token) { + if (!optional('(', token)) { + listener.handleNoArguments(token); + return token; + } else { + return parseArguments(token); + } + } + + Token parseArguments(Token token) { + Token begin = token; + listener.beginArguments(begin); + assert(identical('(', token.stringValue)); + int argumentCount = 0; + if (optional(')', token.next)) { + listener.endArguments(argumentCount, begin, token.next); + return token.next.next; + } + bool old = mayParseFunctionExpressions; + mayParseFunctionExpressions = true; + do { + Token colon = null; + if (optional(':', token.next.next)) { + token = parseIdentifier(token.next); + colon = token; + } + token = parseExpression(token.next); + if (colon != null) listener.handleNamedArgument(colon); + ++argumentCount; + } while (optional(',', token)); + mayParseFunctionExpressions = old; + listener.endArguments(argumentCount, begin, token); + return expect(')', token); + } + + Token parseIsOperatorRest(Token token) { + assert(optional('is', token)); + Token operator = token; + Token not = null; + if (optional('!', token.next)) { + token = token.next; + not = token; + } + token = parseType(token.next); + listener.handleIsOperator(operator, not, token); + String value = token.stringValue; + if (identical(value, 'is') || identical(value, 'as')) { + // The is- and as-operators cannot be chained, but they can take part of + // expressions like: foo is Foo || foo is Bar. + listener.unexpected(token); + } + return token; + } + + Token parseAsOperatorRest(Token token) { + assert(optional('as', token)); + Token operator = token; + token = parseType(token.next); + listener.handleAsOperator(operator, token); + String value = token.stringValue; + if (identical(value, 'is') || identical(value, 'as')) { + // The is- and as-operators cannot be chained. + listener.unexpected(token); + } + return token; + } + + Token parseVariablesDeclaration(Token token) { + return parseVariablesDeclarationMaybeSemicolon(token, true); + } + + Token parseVariablesDeclarationNoSemicolon(Token token) { + return parseVariablesDeclarationMaybeSemicolon(token, false); + } + + Token parseVariablesDeclarationMaybeSemicolon(Token token, + bool endWithSemicolon) { + int count = 1; + listener.beginVariablesDeclaration(token); + token = parseModifiers(token); + token = parseTypeOpt(token); + token = parseOptionallyInitializedIdentifier(token); + while (optional(',', token)) { + token = parseOptionallyInitializedIdentifier(token.next); + ++count; + } + if (endWithSemicolon) { + Token semicolon = token; + token = expectSemicolon(semicolon); + listener.endVariablesDeclaration(count, semicolon); + return token; + } else { + listener.endVariablesDeclaration(count, null); + return token; + } + } + + Token parseOptionallyInitializedIdentifier(Token token) { + listener.beginInitializedIdentifier(token); + token = parseIdentifier(token); + token = parseVariableInitializerOpt(token); + listener.endInitializedIdentifier(); + return token; + } + + Token parseIfStatement(Token token) { + Token ifToken = token; + listener.beginIfStatement(ifToken); + token = expect('if', token); + token = parseParenthesizedExpression(token); + token = parseStatement(token); + Token elseToken = null; + if (optional('else', token)) { + elseToken = token; + token = parseStatement(token.next); + } + listener.endIfStatement(ifToken, elseToken); + return token; + } + + Token parseForStatement(Token token) { + Token forToken = token; + listener.beginForStatement(forToken); + token = expect('for', token); + token = expect('(', token); + token = parseVariablesDeclarationOrExpressionOpt(token); + if (optional('in', token)) { + return parseForInRest(forToken, token); + } else { + return parseForRest(forToken, token); + } + } + + Token parseVariablesDeclarationOrExpressionOpt(Token token) { + final String value = token.stringValue; + if (identical(value, ';')) { + listener.handleNoExpression(token); + return token; + } else if ((identical(value, 'var')) || (identical(value, 'final'))) { + return parseVariablesDeclarationNoSemicolon(token); + } + Token identifier = peekIdentifierAfterType(token); + if (identifier != null) { + assert(identifier.isIdentifier()); + if (isOneOf4(identifier.next, '=', ';', ',', 'in')) { + return parseVariablesDeclarationNoSemicolon(token); + } + } + return parseExpression(token); + } + + Token parseForRest(Token forToken, Token token) { + token = expectSemicolon(token); + if (optional(';', token)) { + token = parseEmptyStatement(token); + } else { + token = parseExpressionStatement(token); + } + int expressionCount = 0; + while (true) { + if (optional(')', token)) break; + token = parseExpression(token); + ++expressionCount; + if (optional(',', token)) { + token = token.next; + } else { + break; + } + } + token = expect(')', token); + token = parseStatement(token); + listener.endForStatement(expressionCount, forToken, token); + return token; + } + + Token parseForInRest(Token forToken, Token token) { + assert(optional('in', token)); + Token inKeyword = token; + token = parseExpression(token.next); + token = expect(')', token); + token = parseStatement(token); + listener.endForIn(forToken, inKeyword, token); + return token; + } + + Token parseWhileStatement(Token token) { + Token whileToken = token; + listener.beginWhileStatement(whileToken); + token = expect('while', token); + token = parseParenthesizedExpression(token); + token = parseStatement(token); + listener.endWhileStatement(whileToken, token); + return token; + } + + Token parseDoWhileStatement(Token token) { + Token doToken = token; + listener.beginDoWhileStatement(doToken); + token = expect('do', token); + token = parseStatement(token); + Token whileToken = token; + token = expect('while', token); + token = parseParenthesizedExpression(token); + listener.endDoWhileStatement(doToken, whileToken, token); + return expectSemicolon(token); + } + + Token parseBlock(Token token) { + Token begin = token; + listener.beginBlock(begin); + int statementCount = 0; + token = expect('{', token); + while (notEofOrValue('}', token)) { + token = parseStatement(token); + ++statementCount; + } + listener.endBlock(statementCount, begin, token); + return expect('}', token); + } + + Token parseThrowStatement(Token token) { + Token throwToken = token; + listener.beginThrowStatement(throwToken); + token = expect('throw', token); + if (optional(';', token)) { + listener.endRethrowStatement(throwToken, token); + return token.next; + } else { + token = parseExpression(token); + listener.endThrowStatement(throwToken, token); + return expectSemicolon(token); + } + } + + Token parseTryStatement(Token token) { + assert(optional('try', token)); + Token tryKeyword = token; + listener.beginTryStatement(tryKeyword); + token = parseBlock(token.next); + int catchCount = 0; + + String value = token.stringValue; + while (identical(value, 'catch') || identical(value, 'on')) { + var onKeyword = null; + if (identical(value, 'on')) { + // on qualified catchPart? + onKeyword = token; + token = parseType(token.next); + value = token.stringValue; + } + Token catchKeyword = null; + if (identical(value, 'catch')) { + catchKeyword = token; + // TODO(ahe): Validate the "parameters". + token = parseFormalParameters(token.next); + } + token = parseBlock(token); + ++catchCount; + listener.handleCatchBlock(onKeyword, catchKeyword); + value = token.stringValue; // while condition + } + + Token finallyKeyword = null; + if (optional('finally', token)) { + finallyKeyword = token; + token = parseBlock(token.next); + listener.handleFinallyBlock(finallyKeyword); + } + listener.endTryStatement(catchCount, tryKeyword, finallyKeyword); + return token; + } + + Token parseSwitchStatement(Token token) { + assert(optional('switch', token)); + Token switchKeyword = token; + listener.beginSwitchStatement(switchKeyword); + token = parseParenthesizedExpression(token.next); + token = parseSwitchBlock(token); + listener.endSwitchStatement(switchKeyword, token); + return token.next; + } + + Token parseSwitchBlock(Token token) { + Token begin = token; + listener.beginSwitchBlock(begin); + token = expect('{', token); + int caseCount = 0; + while (!identical(token.kind, EOF_TOKEN)) { + if (optional('}', token)) { + break; + } + token = parseSwitchCase(token); + ++caseCount; + } + listener.endSwitchBlock(caseCount, begin, token); + expect('}', token); + return token; + } + + /** + * Peek after the following labels (if any). The following token + * is used to determine if the labels belong to a statement or a + * switch case. + */ + Token peekPastLabels(Token token) { + while (token.isIdentifier() && optional(':', token.next)) { + token = token.next.next; + } + return token; + } + + /** + * Parse a group of labels, cases and possibly a default keyword and + * the statements that they select. + */ + Token parseSwitchCase(Token token) { + Token begin = token; + Token defaultKeyword = null; + int expressionCount = 0; + int labelCount = 0; + Token peek = peekPastLabels(token); + while (true) { + // Loop until we find something that can't be part of a switch case. + String value = peek.stringValue; + if (identical(value, 'default')) { + while (!identical(token, peek)) { + token = parseLabel(token); + labelCount++; + } + defaultKeyword = token; + token = expect(':', token.next); + peek = token; + break; + } else if (identical(value, 'case')) { + while (!identical(token, peek)) { + token = parseLabel(token); + labelCount++; + } + Token caseKeyword = token; + token = parseExpression(token.next); + Token colonToken = token; + token = expect(':', token); + listener.handleCaseMatch(caseKeyword, colonToken); + expressionCount++; + peek = peekPastLabels(token); + } else { + if (expressionCount == 0) { + listener.expected("case", token); + } + break; + } + } + // Finally zero or more statements. + int statementCount = 0; + while (!identical(token.kind, EOF_TOKEN)) { + String value = peek.stringValue; + if ((identical(value, 'case')) || + (identical(value, 'default')) || + ((identical(value, '}')) && (identical(token, peek)))) { + // A label just before "}" will be handled as a statement error. + break; + } else { + token = parseStatement(token); + } + statementCount++; + peek = peekPastLabels(token); + } + listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword, + statementCount, begin, token); + return token; + } + + Token parseBreakStatement(Token token) { + assert(optional('break', token)); + Token breakKeyword = token; + token = token.next; + bool hasTarget = false; + if (token.isIdentifier()) { + token = parseIdentifier(token); + hasTarget = true; + } + listener.handleBreakStatement(hasTarget, breakKeyword, token); + return expectSemicolon(token); + } + + Token parseAssertStatement(Token token) { + Token assertKeyword = token; + token = expect('assert', token); + expect('(', token); + token = parseArguments(token); + listener.handleAssertStatement(assertKeyword, token); + return expectSemicolon(token); + } + + Token parseContinueStatement(Token token) { + assert(optional('continue', token)); + Token continueKeyword = token; + token = token.next; + bool hasTarget = false; + if (token.isIdentifier()) { + token = parseIdentifier(token); + hasTarget = true; + } + listener.handleContinueStatement(hasTarget, continueKeyword, token); + return expectSemicolon(token); + } + + Token parseEmptyStatement(Token token) { + listener.handleEmptyStatement(token); + return expectSemicolon(token); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart new file mode 100644 index 0000000..eca572b --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart
@@ -0,0 +1,14 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class ParserTask extends CompilerTask { + ParserTask(Compiler compiler) : super(compiler); + String get name => 'Parser'; + + Node parse(Element element) { + return measure(() => element.parseNode(compiler)); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart new file mode 100644 index 0000000..02f409a --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart
@@ -0,0 +1,130 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +class PartialParser extends Parser { + PartialParser(Listener listener) : super(listener); + + Token parseClassBody(Token token) => skipClassBody(token); + + Token fullParseClassBody(Token token) => super.parseClassBody(token); + + Token parseExpression(Token token) => skipExpression(token); + + Token parseArgumentsOpt(Token token) { + // This method is overridden for two reasons: + // 1. Avoid generating events for arguments. + // 2. Avoid calling skip expression for each argument (which doesn't work). + if (optional('(', token)) { + BeginGroupToken begin = token; + return begin.endGroup.next; + } else { + return token; + } + } + + Token skipExpression(Token token) { + while (true) { + final kind = token.kind; + final value = token.stringValue; + if ((identical(kind, EOF_TOKEN)) || + (identical(value, ';')) || + (identical(value, ',')) || + (identical(value, ']'))) + return token; + if (identical(value, '=')) { + var nextValue = token.next.stringValue; + if (identical(nextValue, 'const')) { + token = token.next; + nextValue = token.next.stringValue; + } + if (identical(nextValue, '{')) { + // Handle cases like this: + // class Foo { + // var map; + // Foo() : map = {}; + // } + BeginGroupToken begin = token.next; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + continue; + } + if (identical(nextValue, '<')) { + // Handle cases like this: + // class Foo { + // var map; + // Foo() : map = <String, Foo>{}; + // } + BeginGroupToken begin = token.next; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + if (identical(token.stringValue, '{')) { + begin = token; + token = (begin.endGroup != null) ? begin.endGroup : token; + token = token.next; + } + continue; + } + } + if (!mayParseFunctionExpressions && identical(value, '{')) return token; + if (token is BeginGroupToken) { + BeginGroupToken begin = token; + token = (begin.endGroup != null) ? begin.endGroup : token; + } + token = token.next; + } + } + + Token skipClassBody(Token token) { + if (!optional('{', token)) { + return listener.expectedClassBodyToSkip(token); + } + BeginGroupToken beginGroupToken = token; + Token endGroup = beginGroupToken.endGroup; + if (endGroup == null) { + return listener.unmatched(beginGroupToken); + } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { + return listener.unmatched(beginGroupToken); + } + return endGroup; + } + + Token parseFunctionBody(Token token, bool isExpression) { + assert(!isExpression); + String value = token.stringValue; + if (identical(value, ';')) { + // No body. + } else if (identical(value, '=>')) { + token = parseExpression(token.next); + expectSemicolon(token); + } else if (value == '=') { + token = parseRedirectingFactoryBody(token); + expectSemicolon(token); + } else { + token = skipBlock(token); + } + // There is no "skipped function body event", so we use + // handleNoFunctionBody instead. + listener.handleNoFunctionBody(token); + return token; + } + + Token parseFormalParameters(Token token) => skipFormals(token); + + Token skipFormals(Token token) { + listener.beginOptionalFormalParameters(token); + if (!optional('(', token)) { + if (optional(';', token)) { + listener.recoverableError("expected '('", token: token); + return token; + } + return listener.unexpected(token); + } + BeginGroupToken beginGroupToken = token; + Token endToken = beginGroupToken.endGroup; + listener.endFormalParameters(0, token, endToken); + return endToken.next; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart new file mode 100644 index 0000000..123b650 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart
@@ -0,0 +1,875 @@ +// 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. + +part of scanner; + +abstract class Scanner { + Token tokenize(); +} + +/** + * Common base class for a Dart scanner. + */ +abstract class AbstractScanner<T extends SourceString> implements Scanner { + int advance(); + int nextByte(); + + /** + * Returns the current character or byte depending on the underlying input + * kind. For example, [StringScanner] operates on [String] and thus returns + * characters (Unicode codepoints represented as int) whereas + * [ByteArrayScanner] operates on byte arrays and thus returns bytes. + */ + int peek(); + + /** + * Appends a fixed token based on whether the current char is [choice] or not. + * If the current char is [choice] a fixed token whose kind and content + * is determined by [yes] is appended, otherwise a fixed token whose kind + * and content is determined by [no] is appended. + */ + int select(int choice, PrecedenceInfo yes, PrecedenceInfo no); + + /** + * Appends a fixed token whose kind and content is determined by [info]. + */ + void appendPrecedenceToken(PrecedenceInfo info); + + /** + * Appends a token whose kind is determined by [info] and content is [value]. + */ + void appendStringToken(PrecedenceInfo info, String value); + + /** + * Appends a token whose kind is determined by [info] and content is defined + * by the SourceString [value]. + */ + void appendByteStringToken(PrecedenceInfo info, T value); + + /** + * Appends a keyword token whose kind is determined by [keyword]. + */ + void appendKeywordToken(Keyword keyword); + void appendWhiteSpace(int next); + void appendEofToken(); + + /** + * Creates an ASCII SourceString whose content begins at the source byte + * offset [start] and ends at [offset] bytes from the current byte offset of + * the scanner. For example, if the current byte offset is 10, + * [:asciiString(0,-1):] creates an ASCII SourceString whose content is found + * at the [0,9[ byte interval of the source text. + */ + T asciiString(int start, int offset); + T utf8String(int start, int offset); + Token firstToken(); + Token previousToken(); + void beginToken(); + void addToCharOffset(int offset); + int get charOffset; + int get byteOffset; + void appendBeginGroup(PrecedenceInfo info, String value); + int appendEndGroup(PrecedenceInfo info, String value, int openKind); + void appendGt(PrecedenceInfo info, String value); + void appendGtGt(PrecedenceInfo info, String value); + void appendGtGtGt(PrecedenceInfo info, String value); + void appendComment(); + + /** + * We call this method to discard '<' from the "grouping" stack + * (maintained by subclasses). + * + * [PartialParser.skipExpression] relies on the fact that we do not + * create groups for stuff like: + * [:a = b < c, d = e > f:]. + * + * In other words, this method is called when the scanner recognizes + * something which cannot possibly be part of a type + * parameter/argument list. + */ + void discardOpenLt(); + + // TODO(ahe): Move this class to implementation. + + Token tokenize() { + int next = advance(); + while (!identical(next, $EOF)) { + next = bigSwitch(next); + } + appendEofToken(); + return firstToken(); + } + + int bigSwitch(int next) { + beginToken(); + if (identical(next, $SPACE) || identical(next, $TAB) + || identical(next, $LF) || identical(next, $CR)) { + appendWhiteSpace(next); + next = advance(); + while (identical(next, $SPACE)) { + appendWhiteSpace(next); + next = advance(); + } + return next; + } + + if ($a <= next && next <= $z) { + if (identical($r, next)) { + return tokenizeRawStringKeywordOrIdentifier(next); + } + return tokenizeKeywordOrIdentifier(next, true); + } + + if (($A <= next && next <= $Z) || identical(next, $_) || identical(next, $$)) { + return tokenizeIdentifier(next, byteOffset, true); + } + + if (identical(next, $LT)) { + return tokenizeLessThan(next); + } + + if (identical(next, $GT)) { + return tokenizeGreaterThan(next); + } + + if (identical(next, $EQ)) { + return tokenizeEquals(next); + } + + if (identical(next, $BANG)) { + return tokenizeExclamation(next); + } + + if (identical(next, $PLUS)) { + return tokenizePlus(next); + } + + if (identical(next, $MINUS)) { + return tokenizeMinus(next); + } + + if (identical(next, $STAR)) { + return tokenizeMultiply(next); + } + + if (identical(next, $PERCENT)) { + return tokenizePercent(next); + } + + if (identical(next, $AMPERSAND)) { + return tokenizeAmpersand(next); + } + + if (identical(next, $BAR)) { + return tokenizeBar(next); + } + + if (identical(next, $CARET)) { + return tokenizeCaret(next); + } + + if (identical(next, $OPEN_SQUARE_BRACKET)) { + return tokenizeOpenSquareBracket(next); + } + + if (identical(next, $TILDE)) { + return tokenizeTilde(next); + } + + if (identical(next, $BACKSLASH)) { + appendPrecedenceToken(BACKSLASH_INFO); + return advance(); + } + + if (identical(next, $HASH)) { + return tokenizeTag(next); + } + + if (identical(next, $OPEN_PAREN)) { + appendBeginGroup(OPEN_PAREN_INFO, "("); + return advance(); + } + + if (identical(next, $CLOSE_PAREN)) { + return appendEndGroup(CLOSE_PAREN_INFO, ")", OPEN_PAREN_TOKEN); + } + + if (identical(next, $COMMA)) { + appendPrecedenceToken(COMMA_INFO); + return advance(); + } + + if (identical(next, $COLON)) { + appendPrecedenceToken(COLON_INFO); + return advance(); + } + + if (identical(next, $SEMICOLON)) { + appendPrecedenceToken(SEMICOLON_INFO); + // Type parameters and arguments cannot contain semicolon. + discardOpenLt(); + return advance(); + } + + if (identical(next, $QUESTION)) { + appendPrecedenceToken(QUESTION_INFO); + return advance(); + } + + if (identical(next, $CLOSE_SQUARE_BRACKET)) { + return appendEndGroup(CLOSE_SQUARE_BRACKET_INFO, "]", + OPEN_SQUARE_BRACKET_TOKEN); + } + + if (identical(next, $BACKPING)) { + appendPrecedenceToken(BACKPING_INFO); + return advance(); + } + + if (identical(next, $OPEN_CURLY_BRACKET)) { + appendBeginGroup(OPEN_CURLY_BRACKET_INFO, "{"); + return advance(); + } + + if (identical(next, $CLOSE_CURLY_BRACKET)) { + return appendEndGroup(CLOSE_CURLY_BRACKET_INFO, "}", + OPEN_CURLY_BRACKET_TOKEN); + } + + if (identical(next, $SLASH)) { + return tokenizeSlashOrComment(next); + } + + if (identical(next, $AT)) { + return tokenizeAt(next); + } + + if (identical(next, $DQ) || identical(next, $SQ)) { + return tokenizeString(next, byteOffset, false); + } + + if (identical(next, $PERIOD)) { + return tokenizeDotsOrNumber(next); + } + + if (identical(next, $0)) { + return tokenizeHexOrNumber(next); + } + + // TODO(ahe): Would a range check be faster? + if (identical(next, $1) || identical(next, $2) || identical(next, $3) + || identical(next, $4) || identical(next, $5) || identical(next, $6) + || identical(next, $7) || identical(next, $8) || identical(next, $9)) { + return tokenizeNumber(next); + } + + if (identical(next, $EOF)) { + return $EOF; + } + if (next < 0x1f) { + return error(new SourceString("unexpected character $next")); + } + + // The following are non-ASCII characters. + + if (identical(next, $NBSP)) { + appendWhiteSpace(next); + return advance(); + } + + return tokenizeIdentifier(next, byteOffset, true); + } + + int tokenizeTag(int next) { + // # or #!.*[\n\r] + if (byteOffset == 0) { + if (identical(peek(), $BANG)) { + do { + next = advance(); + } while (!identical(next, $LF) && !identical(next, $CR) && !identical(next, $EOF)); + return next; + } + } + appendPrecedenceToken(HASH_INFO); + return advance(); + } + + int tokenizeTilde(int next) { + // ~ ~/ ~/= + next = advance(); + if (identical(next, $SLASH)) { + return select($EQ, TILDE_SLASH_EQ_INFO, TILDE_SLASH_INFO); + } else { + appendPrecedenceToken(TILDE_INFO); + return next; + } + } + + int tokenizeOpenSquareBracket(int next) { + // [ [] []= + next = advance(); + if (identical(next, $CLOSE_SQUARE_BRACKET)) { + Token token = previousToken(); + if (token is KeywordToken && identical(token.value.stringValue, 'operator')) { + return select($EQ, INDEX_EQ_INFO, INDEX_INFO); + } + } + appendBeginGroup(OPEN_SQUARE_BRACKET_INFO, "["); + return next; + } + + int tokenizeCaret(int next) { + // ^ ^= + return select($EQ, CARET_EQ_INFO, CARET_INFO); + } + + int tokenizeBar(int next) { + // | || |= + next = advance(); + if (identical(next, $BAR)) { + appendPrecedenceToken(BAR_BAR_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(BAR_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(BAR_INFO); + return next; + } + } + + int tokenizeAmpersand(int next) { + // && &= & + next = advance(); + if (identical(next, $AMPERSAND)) { + appendPrecedenceToken(AMPERSAND_AMPERSAND_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(AMPERSAND_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(AMPERSAND_INFO); + return next; + } + } + + int tokenizePercent(int next) { + // % %= + return select($EQ, PERCENT_EQ_INFO, PERCENT_INFO); + } + + int tokenizeMultiply(int next) { + // * *= + return select($EQ, STAR_EQ_INFO, STAR_INFO); + } + + int tokenizeMinus(int next) { + // - -- -= + next = advance(); + if (identical(next, $MINUS)) { + appendPrecedenceToken(MINUS_MINUS_INFO); + return advance(); + } else if (identical(next, $EQ)) { + appendPrecedenceToken(MINUS_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(MINUS_INFO); + return next; + } + } + + + int tokenizePlus(int next) { + // + ++ += + next = advance(); + if (identical($PLUS, next)) { + appendPrecedenceToken(PLUS_PLUS_INFO); + return advance(); + } else if (identical($EQ, next)) { + appendPrecedenceToken(PLUS_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(PLUS_INFO); + return next; + } + } + + int tokenizeExclamation(int next) { + // ! != !== + next = advance(); + if (identical(next, $EQ)) { + return select($EQ, BANG_EQ_EQ_INFO, BANG_EQ_INFO); + } + appendPrecedenceToken(BANG_INFO); + return next; + } + + int tokenizeEquals(int next) { + // = == === + + // Type parameters and arguments cannot contain any token that + // starts with '='. + discardOpenLt(); + + next = advance(); + if (identical(next, $EQ)) { + return select($EQ, EQ_EQ_EQ_INFO, EQ_EQ_INFO); + } else if (identical(next, $GT)) { + appendPrecedenceToken(FUNCTION_INFO); + return advance(); + } + appendPrecedenceToken(EQ_INFO); + return next; + } + + int tokenizeGreaterThan(int next) { + // > >= >> >>= >>> >>>= + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(GT_EQ_INFO); + return advance(); + } else if (identical($GT, next)) { + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(GT_GT_EQ_INFO); + return advance(); + } else { + appendGtGt(GT_GT_INFO, ">>"); + return next; + } + } else { + appendGt(GT_INFO, ">"); + return next; + } + } + + int tokenizeLessThan(int next) { + // < <= << <<= + next = advance(); + if (identical($EQ, next)) { + appendPrecedenceToken(LT_EQ_INFO); + return advance(); + } else if (identical($LT, next)) { + return select($EQ, LT_LT_EQ_INFO, LT_LT_INFO); + } else { + appendBeginGroup(LT_INFO, "<"); + return next; + } + } + + int tokenizeNumber(int next) { + int start = byteOffset; + while (true) { + next = advance(); + if ($0 <= next && next <= $9) { + continue; + } else if (identical(next, $PERIOD)) { + return tokenizeFractionPart(advance(), start); + } else if (identical(next, $e) || identical(next, $E) + || identical(next, $d) || identical(next, $D)) { + return tokenizeFractionPart(next, start); + } else { + appendByteStringToken(INT_INFO, asciiString(start, 0)); + return next; + } + } + } + + int tokenizeHexOrNumber(int next) { + int x = peek(); + if (identical(x, $x) || identical(x, $X)) { + advance(); + return tokenizeHex(x); + } + return tokenizeNumber(next); + } + + int tokenizeHex(int next) { + int start = byteOffset - 1; + bool hasDigits = false; + while (true) { + next = advance(); + if (($0 <= next && next <= $9) + || ($A <= next && next <= $F) + || ($a <= next && next <= $f)) { + hasDigits = true; + } else { + if (!hasDigits) { + return error(const SourceString("hex digit expected")); + } + appendByteStringToken(HEXADECIMAL_INFO, asciiString(start, 0)); + return next; + } + } + } + + int tokenizeDotsOrNumber(int next) { + int start = byteOffset; + next = advance(); + if (($0 <= next && next <= $9)) { + return tokenizeFractionPart(next, start); + } else if (identical($PERIOD, next)) { + return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO); + } else { + appendPrecedenceToken(PERIOD_INFO); + return next; + } + } + + int tokenizeFractionPart(int next, int start) { + bool done = false; + bool hasDigit = false; + LOOP: while (!done) { + if ($0 <= next && next <= $9) { + hasDigit = true; + } else if (identical($e, next) || identical($E, next)) { + hasDigit = true; + next = tokenizeExponent(advance()); + done = true; + continue LOOP; + } else { + done = true; + continue LOOP; + } + next = advance(); + } + if (!hasDigit) { + appendByteStringToken(INT_INFO, asciiString(start, -1)); + if (identical($PERIOD, next)) { + return select($PERIOD, PERIOD_PERIOD_PERIOD_INFO, PERIOD_PERIOD_INFO); + } + // TODO(ahe): Wrong offset for the period. + appendPrecedenceToken(PERIOD_INFO); + return bigSwitch(next); + } + if (identical(next, $d) || identical(next, $D)) { + next = advance(); + } + appendByteStringToken(DOUBLE_INFO, asciiString(start, 0)); + return next; + } + + int tokenizeExponent(int next) { + if (identical(next, $PLUS) || identical(next, $MINUS)) { + next = advance(); + } + bool hasDigits = false; + while (true) { + if ($0 <= next && next <= $9) { + hasDigits = true; + } else { + if (!hasDigits) { + return error(const SourceString("digit expected")); + } + return next; + } + next = advance(); + } + } + + int tokenizeSlashOrComment(int next) { + next = advance(); + if (identical($STAR, next)) { + return tokenizeMultiLineComment(next); + } else if (identical($SLASH, next)) { + return tokenizeSingleLineComment(next); + } else if (identical($EQ, next)) { + appendPrecedenceToken(SLASH_EQ_INFO); + return advance(); + } else { + appendPrecedenceToken(SLASH_INFO); + return next; + } + } + + int tokenizeSingleLineComment(int next) { + while (true) { + next = advance(); + if (identical($LF, next) || identical($CR, next) || identical($EOF, next)) { + appendComment(); + return next; + } + } + } + + int tokenizeMultiLineComment(int next) { + int nesting = 1; + next = advance(); + while (true) { + if (identical($EOF, next)) { + // TODO(ahe): Report error. + return next; + } else if (identical($STAR, next)) { + next = advance(); + if (identical($SLASH, next)) { + --nesting; + if (0 == nesting) { + next = advance(); + appendComment(); + return next; + } else { + next = advance(); + } + } + } else if (identical($SLASH, next)) { + next = advance(); + if (identical($STAR, next)) { + next = advance(); + ++nesting; + } + } else { + next = advance(); + } + } + } + + int tokenizeRawStringKeywordOrIdentifier(int next) { + int nextnext = peek(); + if (identical(nextnext, $DQ) || identical(nextnext, $SQ)) { + int start = byteOffset; + next = advance(); + return tokenizeString(next, start, true); + } + return tokenizeKeywordOrIdentifier(next, true); + } + + int tokenizeKeywordOrIdentifier(int next, bool allowDollar) { + KeywordState state = KeywordState.KEYWORD_STATE; + int start = byteOffset; + while (state != null && $a <= next && next <= $z) { + state = state.next(next); + next = advance(); + } + if (state == null || state.keyword == null) { + return tokenizeIdentifier(next, start, allowDollar); + } + if (($A <= next && next <= $Z) || + ($0 <= next && next <= $9) || + identical(next, $_) || + identical(next, $$)) { + return tokenizeIdentifier(next, start, allowDollar); + } else if (next < 128) { + appendKeywordToken(state.keyword); + return next; + } else { + return tokenizeIdentifier(next, start, allowDollar); + } + } + + int tokenizeIdentifier(int next, int start, bool allowDollar) { + bool isAscii = true; + + // TODO(aprelev@gmail.com): Remove deprecated Dynamic keyword support. + bool isDynamicBuiltIn = false; + + if (identical(next, $D)) { + next = advance(); + if (identical(next, $y)) { + next = advance(); + if (identical(next, $n)) { + next = advance(); + if (identical(next, $a)) { + next = advance(); + if (identical(next, $m)) { + next = advance(); + if (identical(next, $i)) { + next = advance(); + if (identical(next, $c)) { + isDynamicBuiltIn = true; + next = advance(); + } + } + } + } + } + } + } + + while (true) { + if (($a <= next && next <= $z) || + ($A <= next && next <= $Z) || + ($0 <= next && next <= $9) || + identical(next, $_) || + (identical(next, $$) && allowDollar)) { + isDynamicBuiltIn = false; + next = advance(); + } else if ((next < 128) || (identical(next, $NBSP))) { + // Identifier ends here. + if (start == byteOffset) { + return error(const SourceString("expected identifier")); + } else if (isDynamicBuiltIn) { + appendKeywordToken(Keyword.DYNAMIC_DEPRECATED); + } else if (isAscii) { + appendByteStringToken(IDENTIFIER_INFO, asciiString(start, 0)); + } else { + appendByteStringToken(BAD_INPUT_INFO, utf8String(start, -1)); + } + return next; + } else { + isDynamicBuiltIn = false; + int nonAsciiStart = byteOffset; + do { + next = nextByte(); + if (identical(next, $NBSP)) break; + } while (next > 127); + String string = utf8String(nonAsciiStart, -1).slowToString(); + isAscii = false; + int byteLength = nonAsciiStart - byteOffset; + addToCharOffset(string.length - byteLength); + } + } + } + + int tokenizeAt(int next) { + int start = byteOffset; + next = advance(); + appendPrecedenceToken(AT_INFO); + return next; + } + + int tokenizeString(int next, int start, bool raw) { + int quoteChar = next; + next = advance(); + if (identical(quoteChar, next)) { + next = advance(); + if (identical(quoteChar, next)) { + // Multiline string. + return tokenizeMultiLineString(quoteChar, start, raw); + } else { + // Empty string. + appendByteStringToken(STRING_INFO, utf8String(start, -1)); + return next; + } + } + if (raw) { + return tokenizeSingleLineRawString(next, quoteChar, start); + } else { + return tokenizeSingleLineString(next, quoteChar, start); + } + } + + static bool isHexDigit(int character) { + if ($0 <= character && character <= $9) return true; + character |= 0x20; + return ($a <= character && character <= $f); + } + + int tokenizeSingleLineString(int next, int quoteChar, int start) { + while (!identical(next, quoteChar)) { + if (identical(next, $BACKSLASH)) { + next = advance(); + } else if (identical(next, $$)) { + next = tokenizeStringInterpolation(start); + start = byteOffset; + continue; + } + if (next <= $CR + && (identical(next, $LF) || identical(next, $CR) || identical(next, $EOF))) { + return error(const SourceString("unterminated string literal")); + } + next = advance(); + } + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + + int tokenizeStringInterpolation(int start) { + appendByteStringToken(STRING_INFO, utf8String(start, -1)); + beginToken(); // $ starts here. + int next = advance(); + if (identical(next, $OPEN_CURLY_BRACKET)) { + return tokenizeInterpolatedExpression(next, start); + } else { + return tokenizeInterpolatedIdentifier(next, start); + } + } + + int tokenizeInterpolatedExpression(int next, int start) { + appendBeginGroup(STRING_INTERPOLATION_INFO, "\${"); + beginToken(); // The expression starts here. + next = advance(); + while (!identical(next, $EOF) && !identical(next, $STX)) { + next = bigSwitch(next); + } + if (identical(next, $EOF)) return next; + next = advance(); + beginToken(); // The string interpolation suffix starts here. + return next; + } + + int tokenizeInterpolatedIdentifier(int next, int start) { + appendPrecedenceToken(STRING_INTERPOLATION_IDENTIFIER_INFO); + beginToken(); // The identifier starts here. + next = tokenizeKeywordOrIdentifier(next, false); + beginToken(); // The string interpolation suffix starts here. + return next; + } + + int tokenizeSingleLineRawString(int next, int quoteChar, int start) { + next = advance(); + while (next != $EOF) { + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } else if (identical(next, $LF) || identical(next, $CR)) { + return error(const SourceString("unterminated string literal")); + } + next = advance(); + } + return error(const SourceString("unterminated string literal")); + } + + int tokenizeMultiLineRawString(int quoteChar, int start) { + int next = advance(); + outer: while (!identical(next, $EOF)) { + while (!identical(next, quoteChar)) { + next = advance(); + if (identical(next, $EOF)) break outer; + } + next = advance(); + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + } + } + return error(const SourceString("unterminated string literal")); + } + + int tokenizeMultiLineString(int quoteChar, int start, bool raw) { + if (raw) return tokenizeMultiLineRawString(quoteChar, start); + int next = advance(); + while (!identical(next, $EOF)) { + if (identical(next, $$)) { + next = tokenizeStringInterpolation(start); + start = byteOffset; + continue; + } + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + next = advance(); + if (identical(next, quoteChar)) { + appendByteStringToken(STRING_INFO, utf8String(start, 0)); + return advance(); + } + } + continue; + } + if (identical(next, $BACKSLASH)) { + next = advance(); + if (identical(next, $EOF)) break; + } + next = advance(); + } + return error(const SourceString("unterminated string literal")); + } + + int error(SourceString message) { + appendByteStringToken(BAD_INPUT_INFO, message); + return advance(); // Ensure progress. + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart new file mode 100644 index 0000000..1bd8327 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart
@@ -0,0 +1,11 @@ +// Copyright (c) 2011, 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. + +library scanner_implementation; + +import 'scannerlib.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +part 'array_based_scanner.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart new file mode 100644 index 0000000..0738355 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart
@@ -0,0 +1,53 @@ +// 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. + +part of scanner; + +class ScannerTask extends CompilerTask { + ScannerTask(Compiler compiler) : super(compiler); + String get name => 'Scanner'; + + void scanLibrary(LibraryElement library) { + var compilationUnit = library.entryCompilationUnit; + var canonicalUri = library.canonicalUri.toString(); + var resolvedUri = compilationUnit.script.uri.toString(); + if (canonicalUri == resolvedUri) { + compiler.log("scanning library $canonicalUri"); + } else { + compiler.log("scanning library $canonicalUri ($resolvedUri)"); + } + scan(compilationUnit); + } + + void scan(CompilationUnitElement compilationUnit) { + measure(() { + scanElements(compilationUnit); + }); + } + + void scanElements(CompilationUnitElement compilationUnit) { + Script script = compilationUnit.script; + Token tokens = new StringScanner(script.text, + includeComments: compiler.preserveComments).tokenize(); + if (compiler.preserveComments) { + tokens = compiler.processAndStripComments(tokens); + } + compiler.dietParser.dietParse(compilationUnit, tokens); + } +} + +class DietParserTask extends CompilerTask { + DietParserTask(Compiler compiler) : super(compiler); + final String name = 'Diet Parser'; + + dietParse(CompilationUnitElement compilationUnit, Token tokens) { + measure(() { + Function idGenerator = compiler.getNextFreeClassId; + ElementListener listener = + new ElementListener(compiler, compilationUnit, idGenerator); + PartialParser parser = new PartialParser(listener); + parser.parseUnit(tokens); + }); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart new file mode 100644 index 0000000..e3cd591 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart
@@ -0,0 +1,38 @@ +// Copyright (c) 2011, 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. + +library scanner; + +import 'dart:collection' show LinkedHashMap; +import 'dart:uri'; + +import 'scanner_implementation.dart'; +import '../elements/elements.dart'; +import '../elements/modelx.dart' + show FunctionElementX, + TypedefElementX, + VariableElementX, + VariableListElementX, + ClassElementX, + MetadataAnnotationX, + MixinApplicationElementX; +import '../dart2jslib.dart'; +import '../native_handler.dart' as native; +import '../string_validator.dart'; +import '../tree/tree.dart'; +import '../util/characters.dart'; +import '../util/util.dart'; +// TODO(ahe): Rename prefix to 'api' when VM bug is fixed. +import '../../compiler.dart' as api_s; + +part 'class_element_parser.dart'; +part 'keyword.dart'; +part 'listener.dart'; +part 'parser.dart'; +part 'parser_task.dart'; +part 'partial_parser.dart'; +part 'scanner.dart'; +part 'scanner_task.dart'; +part 'string_scanner.dart'; +part 'token.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart new file mode 100644 index 0000000..0fa3489 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart
@@ -0,0 +1,106 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +/** + * Scanner that reads from a String and creates tokens that points to + * substrings. + */ +class StringScanner extends ArrayBasedScanner<SourceString> { + final String string; + + StringScanner(String this.string, {bool includeComments: false}) + : super(includeComments); + + int nextByte() => charAt(++byteOffset); + + int peek() => charAt(byteOffset + 1); + + int charAt(index) + => (string.length > index) ? string.charCodeAt(index) : $EOF; + + SourceString asciiString(int start, int offset) { + return new SubstringWrapper(string, start, byteOffset + offset); + } + + SourceString utf8String(int start, int offset) { + return new SubstringWrapper(string, start, byteOffset + offset + 1); + } + + void appendByteStringToken(PrecedenceInfo info, SourceString value) { + // assert(kind != $a || keywords.get(value) == null); + tail.next = new StringToken.fromSource(info, value, tokenStart); + tail = tail.next; + } + + void unmatchedBeginGroup(BeginGroupToken begin) { + SourceString error = new SourceString('unmatched "${begin.stringValue}"'); + Token close = + new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset); + // We want to ensure that unmatched BeginGroupTokens are reported + // as errors. However, the rest of the parser assume the groups + // are well-balanced and will never look at the endGroup + // token. This is a nice property that allows us to skip quickly + // over correct code. By inserting an additional error token in + // the stream, we can keep ignoring endGroup tokens. + Token next = + new StringToken.fromSource(BAD_INPUT_INFO, error, begin.charOffset); + begin.endGroup = close; + close.next = next; + next.next = begin.next; + } +} + +class SubstringWrapper extends Iterable<int> implements SourceString { + final String internalString; + final int begin; + final int end; + int cashedHash = 0; + String cachedSubString; + + SubstringWrapper(String this.internalString, + int this.begin, int this.end); + + int get hashCode { + if (0 == cashedHash) { + cashedHash = slowToString().hashCode; + } + return cashedHash; + } + + bool operator ==(other) { + return other is SourceString && slowToString() == other.slowToString(); + } + + void printOn(StringBuffer sb) { + sb.add(internalString.substring(begin, end)); + } + + String slowToString() { + if (cachedSubString == null) { + cachedSubString = internalString.substring(begin, end); + } + return cachedSubString; + } + + String toString() => "SubstringWrapper(${slowToString()})"; + + String get stringValue => null; + + Iterator<int> get iterator => + new StringCodeIterator.substring(internalString, begin, end); + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert(0 <= initial); + assert(0 <= terminal); + assert(initial + terminal <= internalString.length); + return new SubstringWrapper(internalString, + begin + initial, end - terminal); + } + + bool get isEmpty => begin == end; + + bool isPrivate() => !isEmpty && identical(internalString.charCodeAt(begin), $_); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart new file mode 100644 index 0000000..a236eae --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart
@@ -0,0 +1,530 @@ +// Copyright (c) 2011, 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. + +part of scanner; + +const int EOF_TOKEN = 0; + +const int KEYWORD_TOKEN = $k; +const int IDENTIFIER_TOKEN = $a; +const int BAD_INPUT_TOKEN = $X; +const int DOUBLE_TOKEN = $d; +const int INT_TOKEN = $i; +const int HEXADECIMAL_TOKEN = $x; +const int STRING_TOKEN = $SQ; + +const int AMPERSAND_TOKEN = $AMPERSAND; +const int BACKPING_TOKEN = $BACKPING; +const int BACKSLASH_TOKEN = $BACKSLASH; +const int BANG_TOKEN = $BANG; +const int BAR_TOKEN = $BAR; +const int COLON_TOKEN = $COLON; +const int COMMA_TOKEN = $COMMA; +const int EQ_TOKEN = $EQ; +const int GT_TOKEN = $GT; +const int HASH_TOKEN = $HASH; +const int OPEN_CURLY_BRACKET_TOKEN = $OPEN_CURLY_BRACKET; +const int OPEN_SQUARE_BRACKET_TOKEN = $OPEN_SQUARE_BRACKET; +const int OPEN_PAREN_TOKEN = $OPEN_PAREN; +const int LT_TOKEN = $LT; +const int MINUS_TOKEN = $MINUS; +const int PERIOD_TOKEN = $PERIOD; +const int PLUS_TOKEN = $PLUS; +const int QUESTION_TOKEN = $QUESTION; +const int AT_TOKEN = $AT; +const int CLOSE_CURLY_BRACKET_TOKEN = $CLOSE_CURLY_BRACKET; +const int CLOSE_SQUARE_BRACKET_TOKEN = $CLOSE_SQUARE_BRACKET; +const int CLOSE_PAREN_TOKEN = $CLOSE_PAREN; +const int SEMICOLON_TOKEN = $SEMICOLON; +const int SLASH_TOKEN = $SLASH; +const int TILDE_TOKEN = $TILDE; +const int STAR_TOKEN = $STAR; +const int PERCENT_TOKEN = $PERCENT; +const int CARET_TOKEN = $CARET; + +const int STRING_INTERPOLATION_TOKEN = 128; +const int LT_EQ_TOKEN = STRING_INTERPOLATION_TOKEN + 1; +const int FUNCTION_TOKEN = LT_EQ_TOKEN + 1; +const int SLASH_EQ_TOKEN = FUNCTION_TOKEN + 1; +const int PERIOD_PERIOD_PERIOD_TOKEN = SLASH_EQ_TOKEN + 1; +const int PERIOD_PERIOD_TOKEN = PERIOD_PERIOD_PERIOD_TOKEN + 1; +const int EQ_EQ_EQ_TOKEN = PERIOD_PERIOD_TOKEN + 1; +const int EQ_EQ_TOKEN = EQ_EQ_EQ_TOKEN + 1; +const int LT_LT_EQ_TOKEN = EQ_EQ_TOKEN + 1; +const int LT_LT_TOKEN = LT_LT_EQ_TOKEN + 1; +const int GT_EQ_TOKEN = LT_LT_TOKEN + 1; +const int GT_GT_EQ_TOKEN = GT_EQ_TOKEN + 1; +const int INDEX_EQ_TOKEN = GT_GT_EQ_TOKEN + 1; +const int INDEX_TOKEN = INDEX_EQ_TOKEN + 1; +const int BANG_EQ_EQ_TOKEN = INDEX_TOKEN + 1; +const int BANG_EQ_TOKEN = BANG_EQ_EQ_TOKEN + 1; +const int AMPERSAND_AMPERSAND_TOKEN = BANG_EQ_TOKEN + 1; +const int AMPERSAND_EQ_TOKEN = AMPERSAND_AMPERSAND_TOKEN + 1; +const int BAR_BAR_TOKEN = AMPERSAND_EQ_TOKEN + 1; +const int BAR_EQ_TOKEN = BAR_BAR_TOKEN + 1; +const int STAR_EQ_TOKEN = BAR_EQ_TOKEN + 1; +const int PLUS_PLUS_TOKEN = STAR_EQ_TOKEN + 1; +const int PLUS_EQ_TOKEN = PLUS_PLUS_TOKEN + 1; +const int MINUS_MINUS_TOKEN = PLUS_EQ_TOKEN + 1; +const int MINUS_EQ_TOKEN = MINUS_MINUS_TOKEN + 1; +const int TILDE_SLASH_EQ_TOKEN = MINUS_EQ_TOKEN + 1; +const int TILDE_SLASH_TOKEN = TILDE_SLASH_EQ_TOKEN + 1; +const int PERCENT_EQ_TOKEN = TILDE_SLASH_TOKEN + 1; +const int GT_GT_TOKEN = PERCENT_EQ_TOKEN + 1; +const int CARET_EQ_TOKEN = GT_GT_TOKEN + 1; +const int COMMENT_TOKEN = CARET_EQ_TOKEN + 1; +const int STRING_INTERPOLATION_IDENTIFIER_TOKEN = COMMENT_TOKEN + 1; + +// TODO(ahe): Get rid of this. +const int UNKNOWN_TOKEN = 1024; + +/** + * A token that doubles as a linked list. + */ +class Token implements Spannable { + /** + * The precedence info for this token. [info] determines the kind and the + * precedence level of this token. + */ + final PrecedenceInfo info; + + /** + * The character offset of the start of this token within the source text. + */ + final int charOffset; + + /** + * The next token in the token stream. + */ + Token next; + + Token(PrecedenceInfo this.info, int this.charOffset); + + get value => info.value; + + /** + * Returns the string value for keywords and symbols. For instance 'class' for + * the [CLASS] keyword token and '*' for a [Token] based on [STAR_INFO]. For + * other tokens, such identifiers, strings, numbers, etc, [stringValue] + * returns [:null:]. + * + * [stringValue] should only be used for testing keywords and symbols. + */ + String get stringValue => info.value.stringValue; + + /** + * The kind enum of this token as determined by its [info]. + */ + int get kind => info.kind; + + /** + * The precedence level for this token. + */ + int get precedence => info.precedence; + + bool isIdentifier() => identical(kind, IDENTIFIER_TOKEN); + + /** + * Returns a textual representation of this token to be used for debugging + * purposes. The resulting string might contain information about the + * structure of the token, for example 'StringToken(foo)' for the identifier + * token 'foo'. Use [slowToString] for the text actually parsed by the token. + */ + String toString() => info.value.toString(); + + /** + * The text parsed by this token. + */ + String slowToString() => toString(); + + /** + * The number of characters parsed by this token. + */ + int get slowCharCount { + if (info == BAD_INPUT_INFO) { + // This is a token that wraps around an error message. Return 1 + // instead of the size of the length of the error message. + return 1; + } else { + return slowToString().length; + } + } +} + +/** + * A keyword token. + */ +class KeywordToken extends Token { + final Keyword value; + String get stringValue => value.syntax; + + KeywordToken(Keyword value, int charOffset) + : this.value = value, super(value.info, charOffset); + + bool isIdentifier() => value.isPseudo || value.isBuiltIn; + + String toString() => value.syntax; +} + +/** + * A String-valued token. + */ +class StringToken extends Token { + final SourceString value; + String get stringValue => value.stringValue; + + StringToken(PrecedenceInfo info, String value, int charOffset) + : this.fromSource(info, new SourceString(value), charOffset); + + StringToken.fromSource(PrecedenceInfo info, this.value, int charOffset) + : super(info, charOffset); + + String toString() => "StringToken(${value.slowToString()})"; + + String slowToString() => value.slowToString(); +} + +abstract class SourceString extends Iterable<int> { + const factory SourceString(String string) = StringWrapper; + + void printOn(StringBuffer sb); + + /** Gives a [SourceString] that is not including the [initial] first and + * [terminal] last characters. This is only intended to be used to remove + * quotes from string literals (including an initial '@' for raw strings). + */ + SourceString copyWithoutQuotes(int initial, int terminal); + + String get stringValue; + + String slowToString(); + + bool get isEmpty; + + bool isPrivate(); +} + +class StringWrapper extends Iterable<int> implements SourceString { + final String stringValue; + + const StringWrapper(String this.stringValue); + + int get hashCode => stringValue.hashCode; + + bool operator ==(other) { + return other is SourceString && toString() == other.slowToString(); + } + + Iterator<int> get iterator => new StringCodeIterator(stringValue); + + void printOn(StringBuffer sb) { + sb.add(stringValue); + } + + String toString() => stringValue; + + String slowToString() => stringValue; + + SourceString copyWithoutQuotes(int initial, int terminal) { + assert(0 <= initial); + assert(0 <= terminal); + assert(initial + terminal <= stringValue.length); + return new StringWrapper( + stringValue.substring(initial, stringValue.length - terminal)); + } + + bool get isEmpty => stringValue.isEmpty; + + bool isPrivate() => !isEmpty && identical(stringValue.charCodeAt(0), $_); +} + +class StringCodeIterator implements Iterator<int> { + final String string; + int index; + final int end; + int _current; + + StringCodeIterator(String string) : + this.string = string, index = 0, end = string.length; + + StringCodeIterator.substring(this.string, this.index, this.end) { + assert(0 <= index); + assert(index <= end); + assert(end <= string.length); + } + + int get current => _current; + + bool moveNext() { + _current = null; + if (index >= end) return false; + _current = string.charCodeAt(index++); + return true; + } +} + +class BeginGroupToken extends StringToken { + Token endGroup; + BeginGroupToken(PrecedenceInfo info, String value, int charOffset) + : super(info, value, charOffset); +} + +bool isUserDefinableOperator(String value) { + return + isBinaryOperator(value) || + isMinusOperator(value) || + isTernaryOperator(value) || + isUnaryOperator(value); +} + +bool isUnaryOperator(String value) => identical(value, '~'); + +bool isBinaryOperator(String value) { + return + (identical(value, '==')) || + (identical(value, '[]')) || + (identical(value, '*')) || + (identical(value, '/')) || + (identical(value, '%')) || + (identical(value, '~/')) || + (identical(value, '+')) || + (identical(value, '<<')) || + (identical(value, '>>')) || + (identical(value, '>=')) || + (identical(value, '>')) || + (identical(value, '<=')) || + (identical(value, '<')) || + (identical(value, '&')) || + (identical(value, '^')) || + (identical(value, '|')); +} + +bool isTernaryOperator(String value) => identical(value, '[]='); + +bool isMinusOperator(String value) => identical(value, '-'); + +class PrecedenceInfo { + final SourceString value; + final int precedence; + final int kind; + + const PrecedenceInfo(this.value, this.precedence, this.kind); + + toString() => 'PrecedenceInfo($value, $precedence, $kind)'; +} + +// TODO(ahe): The following are not tokens in Dart. +const PrecedenceInfo BACKPING_INFO = + const PrecedenceInfo(const SourceString('`'), 0, BACKPING_TOKEN); +const PrecedenceInfo BACKSLASH_INFO = + const PrecedenceInfo(const SourceString('\\'), 0, BACKSLASH_TOKEN); +const PrecedenceInfo PERIOD_PERIOD_PERIOD_INFO = + const PrecedenceInfo(const SourceString('...'), 0, + PERIOD_PERIOD_PERIOD_TOKEN); + +/** + * The cascade operator has the lowest precedence of any operator + * except assignment. + */ +const int CASCADE_PRECEDENCE = 2; +const PrecedenceInfo PERIOD_PERIOD_INFO = + const PrecedenceInfo(const SourceString('..'), CASCADE_PRECEDENCE, + PERIOD_PERIOD_TOKEN); + +const PrecedenceInfo BANG_INFO = + const PrecedenceInfo(const SourceString('!'), 0, BANG_TOKEN); +const PrecedenceInfo COLON_INFO = + const PrecedenceInfo(const SourceString(':'), 0, COLON_TOKEN); +const PrecedenceInfo INDEX_INFO = + const PrecedenceInfo(const SourceString('[]'), 0, INDEX_TOKEN); +const PrecedenceInfo MINUS_MINUS_INFO = + const PrecedenceInfo(const SourceString('--'), POSTFIX_PRECEDENCE, + MINUS_MINUS_TOKEN); +const PrecedenceInfo PLUS_PLUS_INFO = + const PrecedenceInfo(const SourceString('++'), POSTFIX_PRECEDENCE, + PLUS_PLUS_TOKEN); +const PrecedenceInfo TILDE_INFO = + const PrecedenceInfo(const SourceString('~'), 0, TILDE_TOKEN); + +const PrecedenceInfo FUNCTION_INFO = + const PrecedenceInfo(const SourceString('=>'), 0, FUNCTION_TOKEN); +const PrecedenceInfo HASH_INFO = + const PrecedenceInfo(const SourceString('#'), 0, HASH_TOKEN); +const PrecedenceInfo INDEX_EQ_INFO = + const PrecedenceInfo(const SourceString('[]='), 0, INDEX_EQ_TOKEN); +const PrecedenceInfo SEMICOLON_INFO = + const PrecedenceInfo(const SourceString(';'), 0, SEMICOLON_TOKEN); +const PrecedenceInfo COMMA_INFO = + const PrecedenceInfo(const SourceString(','), 0, COMMA_TOKEN); + +const PrecedenceInfo AT_INFO = + const PrecedenceInfo(const SourceString('@'), 0, AT_TOKEN); + +// Assignment operators. +const int ASSIGNMENT_PRECEDENCE = 1; +const PrecedenceInfo AMPERSAND_EQ_INFO = + const PrecedenceInfo(const SourceString('&='), + ASSIGNMENT_PRECEDENCE, AMPERSAND_EQ_TOKEN); +const PrecedenceInfo BAR_EQ_INFO = + const PrecedenceInfo(const SourceString('|='), + ASSIGNMENT_PRECEDENCE, BAR_EQ_TOKEN); +const PrecedenceInfo CARET_EQ_INFO = + const PrecedenceInfo(const SourceString('^='), + ASSIGNMENT_PRECEDENCE, CARET_EQ_TOKEN); +const PrecedenceInfo EQ_INFO = + const PrecedenceInfo(const SourceString('='), + ASSIGNMENT_PRECEDENCE, EQ_TOKEN); +const PrecedenceInfo GT_GT_EQ_INFO = + const PrecedenceInfo(const SourceString('>>='), + ASSIGNMENT_PRECEDENCE, GT_GT_EQ_TOKEN); +const PrecedenceInfo LT_LT_EQ_INFO = + const PrecedenceInfo(const SourceString('<<='), + ASSIGNMENT_PRECEDENCE, LT_LT_EQ_TOKEN); +const PrecedenceInfo MINUS_EQ_INFO = + const PrecedenceInfo(const SourceString('-='), + ASSIGNMENT_PRECEDENCE, MINUS_EQ_TOKEN); +const PrecedenceInfo PERCENT_EQ_INFO = + const PrecedenceInfo(const SourceString('%='), + ASSIGNMENT_PRECEDENCE, PERCENT_EQ_TOKEN); +const PrecedenceInfo PLUS_EQ_INFO = + const PrecedenceInfo(const SourceString('+='), + ASSIGNMENT_PRECEDENCE, PLUS_EQ_TOKEN); +const PrecedenceInfo SLASH_EQ_INFO = + const PrecedenceInfo(const SourceString('/='), + ASSIGNMENT_PRECEDENCE, SLASH_EQ_TOKEN); +const PrecedenceInfo STAR_EQ_INFO = + const PrecedenceInfo(const SourceString('*='), + ASSIGNMENT_PRECEDENCE, STAR_EQ_TOKEN); +const PrecedenceInfo TILDE_SLASH_EQ_INFO = + const PrecedenceInfo(const SourceString('~/='), + ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN); + +const PrecedenceInfo QUESTION_INFO = + const PrecedenceInfo(const SourceString('?'), 3, QUESTION_TOKEN); + +const PrecedenceInfo BAR_BAR_INFO = + const PrecedenceInfo(const SourceString('||'), 4, BAR_BAR_TOKEN); + +const PrecedenceInfo AMPERSAND_AMPERSAND_INFO = + const PrecedenceInfo(const SourceString('&&'), 5, AMPERSAND_AMPERSAND_TOKEN); + +const PrecedenceInfo BAR_INFO = + const PrecedenceInfo(const SourceString('|'), 6, BAR_TOKEN); + +const PrecedenceInfo CARET_INFO = + const PrecedenceInfo(const SourceString('^'), 7, CARET_TOKEN); + +const PrecedenceInfo AMPERSAND_INFO = + const PrecedenceInfo(const SourceString('&'), 8, AMPERSAND_TOKEN); + +// Equality operators. +const PrecedenceInfo BANG_EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('!=='), 9, BANG_EQ_EQ_TOKEN); +const PrecedenceInfo BANG_EQ_INFO = + const PrecedenceInfo(const SourceString('!='), 9, BANG_EQ_TOKEN); +const PrecedenceInfo EQ_EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('==='), 9, EQ_EQ_EQ_TOKEN); +const PrecedenceInfo EQ_EQ_INFO = + const PrecedenceInfo(const SourceString('=='), 9, EQ_EQ_TOKEN); + +// Relational operators. +const PrecedenceInfo GT_EQ_INFO = + const PrecedenceInfo(const SourceString('>='), 10, GT_EQ_TOKEN); +const PrecedenceInfo GT_INFO = + const PrecedenceInfo(const SourceString('>'), 10, GT_TOKEN); +const PrecedenceInfo IS_INFO = + const PrecedenceInfo(const SourceString('is'), 10, KEYWORD_TOKEN); +const PrecedenceInfo AS_INFO = + const PrecedenceInfo(const SourceString('as'), 10, KEYWORD_TOKEN); +const PrecedenceInfo LT_EQ_INFO = + const PrecedenceInfo(const SourceString('<='), 10, LT_EQ_TOKEN); +const PrecedenceInfo LT_INFO = + const PrecedenceInfo(const SourceString('<'), 10, LT_TOKEN); + +// Shift operators. +const PrecedenceInfo GT_GT_INFO = + const PrecedenceInfo(const SourceString('>>'), 11, GT_GT_TOKEN); +const PrecedenceInfo LT_LT_INFO = + const PrecedenceInfo(const SourceString('<<'), 11, LT_LT_TOKEN); + +// Additive operators. +const PrecedenceInfo MINUS_INFO = + const PrecedenceInfo(const SourceString('-'), 12, MINUS_TOKEN); +const PrecedenceInfo PLUS_INFO = + const PrecedenceInfo(const SourceString('+'), 12, PLUS_TOKEN); + +// Multiplicative operators. +const PrecedenceInfo PERCENT_INFO = + const PrecedenceInfo(const SourceString('%'), 13, PERCENT_TOKEN); +const PrecedenceInfo SLASH_INFO = + const PrecedenceInfo(const SourceString('/'), 13, SLASH_TOKEN); +const PrecedenceInfo STAR_INFO = + const PrecedenceInfo(const SourceString('*'), 13, STAR_TOKEN); +const PrecedenceInfo TILDE_SLASH_INFO = + const PrecedenceInfo(const SourceString('~/'), 13, TILDE_SLASH_TOKEN); + +const int POSTFIX_PRECEDENCE = 14; +const PrecedenceInfo PERIOD_INFO = + const PrecedenceInfo(const SourceString('.'), POSTFIX_PRECEDENCE, + PERIOD_TOKEN); + +const PrecedenceInfo KEYWORD_INFO = + const PrecedenceInfo(const SourceString('keyword'), 0, KEYWORD_TOKEN); + +const PrecedenceInfo EOF_INFO = + const PrecedenceInfo(const SourceString('EOF'), 0, EOF_TOKEN); + +const PrecedenceInfo IDENTIFIER_INFO = + const PrecedenceInfo(const SourceString('identifier'), 0, IDENTIFIER_TOKEN); + +const PrecedenceInfo BAD_INPUT_INFO = + const PrecedenceInfo(const SourceString('malformed input'), 0, + BAD_INPUT_TOKEN); + +const PrecedenceInfo OPEN_PAREN_INFO = + const PrecedenceInfo(const SourceString('('), POSTFIX_PRECEDENCE, + OPEN_PAREN_TOKEN); + +const PrecedenceInfo CLOSE_PAREN_INFO = + const PrecedenceInfo(const SourceString(')'), 0, CLOSE_PAREN_TOKEN); + +const PrecedenceInfo OPEN_CURLY_BRACKET_INFO = + const PrecedenceInfo(const SourceString('{'), 0, OPEN_CURLY_BRACKET_TOKEN); + +const PrecedenceInfo CLOSE_CURLY_BRACKET_INFO = + const PrecedenceInfo(const SourceString('}'), 0, CLOSE_CURLY_BRACKET_TOKEN); + +const PrecedenceInfo INT_INFO = + const PrecedenceInfo(const SourceString('int'), 0, INT_TOKEN); + +const PrecedenceInfo STRING_INFO = + const PrecedenceInfo(const SourceString('string'), 0, STRING_TOKEN); + +const PrecedenceInfo OPEN_SQUARE_BRACKET_INFO = + const PrecedenceInfo(const SourceString('['), POSTFIX_PRECEDENCE, + OPEN_SQUARE_BRACKET_TOKEN); + +const PrecedenceInfo CLOSE_SQUARE_BRACKET_INFO = + const PrecedenceInfo(const SourceString(']'), 0, CLOSE_SQUARE_BRACKET_TOKEN); + +const PrecedenceInfo DOUBLE_INFO = + const PrecedenceInfo(const SourceString('double'), 0, DOUBLE_TOKEN); + +const PrecedenceInfo STRING_INTERPOLATION_INFO = + const PrecedenceInfo(const SourceString('\${'), 0, + STRING_INTERPOLATION_TOKEN); + +const PrecedenceInfo STRING_INTERPOLATION_IDENTIFIER_INFO = + const PrecedenceInfo(const SourceString('\$'), 0, + STRING_INTERPOLATION_IDENTIFIER_TOKEN); + +const PrecedenceInfo HEXADECIMAL_INFO = + const PrecedenceInfo(const SourceString('hexadecimal'), 0, HEXADECIMAL_TOKEN); + +const PrecedenceInfo COMMENT_INFO = + const PrecedenceInfo(const SourceString('comment'), 0, COMMENT_TOKEN); + +// For reporting lexical errors. +const PrecedenceInfo ERROR_INFO = + const PrecedenceInfo(const SourceString('?'), 0, UNKNOWN_TOKEN);
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/script.dart b/pkgs/markdown/test/lib/src/compiler/implementation/script.dart new file mode 100644 index 0000000..b130616 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/script.dart
@@ -0,0 +1,24 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class Script { + // TODO(kasperl): Once MockFile in tests/compiler/dart2js/parser_helper.dart + // implements SourceFile, we should be able to type the [file] field as + // such. + final file; + + /** + * The readable URI from which this script was loaded. + * + * See [LibraryLoader] for terminology on URIs. + */ + final Uri uri; + + Script(this.uri, this.file); + + String get text => (file == null) ? null : file.text; + String get name => (file == null) ? null : file.filename; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart new file mode 100644 index 0000000..14a4175 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart
@@ -0,0 +1,105 @@ +// 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. + +library source_file; + +import 'dart:math'; + +import 'colors.dart' as colors; + +/** + * Represents a file of source code. + */ +class SourceFile { + + /** The name of the file. */ + final String filename; + + /** The text content of the file. */ + final String text; + + List<int> _lineStarts; + + SourceFile(this.filename, this.text); + + List<int> get lineStarts { + if (_lineStarts == null) { + var starts = [0]; + var index = 0; + while (index < text.length) { + index = text.indexOf('\n', index) + 1; + if (index <= 0) break; + starts.add(index); + } + starts.add(text.length + 1); + _lineStarts = starts; + } + return _lineStarts; + } + + int getLine(int position) { + List<int> starts = lineStarts; + if (position < 0 || starts.last <= position) { + throw 'bad position #$position in file $filename with ' + 'length ${text.length}.'; + } + int first = 0; + int count = starts.length; + while (count > 1) { + int step = count ~/ 2; + int middle = first + step; + int lineStart = starts[middle]; + if (position < lineStart) { + count = step; + } else { + first = middle; + count -= step; + } + } + return first; + } + + int getColumn(int line, int position) { + return position - lineStarts[line]; + } + + /** + * Create a pretty string representation from a character position + * in the file. + */ + String getLocationMessage(String message, int start, int end, + bool includeText, String color(String x)) { + var line = getLine(start); + var column = getColumn(line, start); + + var buf = new StringBuffer( + '${filename}:${line + 1}:${column + 1}: $message'); + if (includeText) { + buf.add('\n'); + var textLine; + // +1 for 0-indexing, +1 again to avoid the last line of the file + if ((line + 2) < _lineStarts.length) { + textLine = text.substring(_lineStarts[line], _lineStarts[line+1]); + } else { + textLine = '${text.substring(_lineStarts[line])}\n'; + } + + int toColumn = min(column + (end-start), textLine.length); + buf.add(textLine.substring(0, column)); + buf.add(color(textLine.substring(column, toColumn))); + buf.add(textLine.substring(toColumn)); + + int i = 0; + for (; i < column; i++) { + buf.add(' '); + } + + for (; i < toColumn; i++) { + buf.add(color('^')); + } + } + + return buf.toString(); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart new file mode 100644 index 0000000..c100694 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart
@@ -0,0 +1,125 @@ +// 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. + +library source_file_provider; + +import 'dart:async'; +import 'dart:uri'; +import 'dart:io'; +import 'dart:utf'; + +import '../compiler.dart' as api show Diagnostic; +import 'dart2js.dart' show AbortLeg; +import 'colors.dart' as colors; +import 'source_file.dart'; +import 'filenames.dart'; +import 'util/uri_extras.dart'; + +String readAll(String filename) { + var file = (new File(filename)).openSync(FileMode.READ); + var length = file.lengthSync(); + var buffer = new List<int>.fixedLength(length); + var bytes = file.readListSync(buffer, 0, length); + file.closeSync(); + return new String.fromCharCodes(new Utf8Decoder(buffer).decodeRest()); +} + +class SourceFileProvider { + bool isWindows = (Platform.operatingSystem == 'windows'); + Uri cwd = getCurrentDirectory(); + Map<String, SourceFile> sourceFiles = <String, SourceFile>{}; + int dartCharactersRead = 0; + + Future<String> readStringFromUri(Uri resourceUri) { + if (resourceUri.scheme != 'file') { + throw new ArgumentError("Unknown scheme in uri '$resourceUri'"); + } + String source; + try { + source = readAll(uriPathToNative(resourceUri.path)); + } on FileIOException catch (ex) { + throw 'Error: Cannot read "${relativize(cwd, resourceUri, isWindows)}" ' + '(${ex.osError}).'; + } + dartCharactersRead += source.length; + sourceFiles[resourceUri.toString()] = + new SourceFile(relativize(cwd, resourceUri, isWindows), source); + return new Future.immediate(source); + } +} + +void silentDiagnosticHandler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { +} + +class FormattingDiagnosticHandler { + final SourceFileProvider provider; + bool showWarnings = true; + bool verbose = false; + bool isAborting = false; + bool enableColors = false; + bool throwOnError = false; + + final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal; + final int INFO = + api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal; + + FormattingDiagnosticHandler(SourceFileProvider this.provider); + + void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) { + if (!verbose && identical(kind, api.Diagnostic.VERBOSE_INFO)) return; + if (enableColors) { + print('${colors.green("info:")} $message'); + } else { + print('info: $message'); + } + } + + void diagnosticHandler(Uri uri, int begin, int end, String message, + api.Diagnostic kind) { + // TODO(ahe): Remove this when source map is handled differently. + if (identical(kind.name, 'source map')) return; + + if (isAborting) return; + isAborting = identical(kind, api.Diagnostic.CRASH); + bool fatal = (kind.ordinal & FATAL) != 0; + bool isInfo = (kind.ordinal & INFO) != 0; + if (isInfo && uri == null && !identical(kind, api.Diagnostic.INFO)) { + info(message, kind); + return; + } + var color; + if (!enableColors) { + color = (x) => x; + } else if (identical(kind, api.Diagnostic.ERROR)) { + color = colors.red; + } else if (identical(kind, api.Diagnostic.WARNING)) { + color = colors.magenta; + } else if (identical(kind, api.Diagnostic.LINT)) { + color = colors.magenta; + } else if (identical(kind, api.Diagnostic.CRASH)) { + color = colors.red; + } else if (identical(kind, api.Diagnostic.INFO)) { + color = colors.green; + } else { + throw 'Unknown kind: $kind (${kind.ordinal})'; + } + if (uri == null) { + assert(fatal); + print(color(message)); + } else if (fatal || showWarnings) { + SourceFile file = provider.sourceFiles[uri.toString()]; + if (file == null) { + throw '$uri: file is null'; + } + print(file.getLocationMessage(color(message), begin, end, true, color)); + } + if (fatal && throwOnError) { + isAborting = true; + throw new AbortLeg(message); + } + } +} + +
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart b/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart new file mode 100644 index 0000000..a2cd6fc --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart
@@ -0,0 +1,192 @@ +// 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. + +library source_map_builder; + +import 'util/util.dart'; +import 'scanner/scannerlib.dart' show Token; +import 'source_file.dart'; + +class SourceMapBuilder { + static const int VLQ_BASE_SHIFT = 5; + static const int VLQ_BASE_MASK = (1 << 5) - 1; + static const int VLQ_CONTINUATION_BIT = 1 << 5; + static const int VLQ_CONTINUATION_MASK = 1 << 5; + static const String BASE64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn' + 'opqrstuvwxyz0123456789+/'; + + List<SourceMapEntry> entries; + + Map<String, int> sourceUrlMap; + List<String> sourceUrlList; + Map<String, int> sourceNameMap; + List<String> sourceNameList; + + int previousTargetLine; + int previousTargetColumn; + int previousSourceUrlIndex; + int previousSourceLine; + int previousSourceColumn; + int previousSourceNameIndex; + bool firstEntryInLine; + + SourceMapBuilder() { + entries = new List<SourceMapEntry>(); + + sourceUrlMap = new Map<String, int>(); + sourceUrlList = new List<String>(); + sourceNameMap = new Map<String, int>(); + sourceNameList = new List<String>(); + + previousTargetLine = 0; + previousTargetColumn = 0; + previousSourceUrlIndex = 0; + previousSourceLine = 0; + previousSourceColumn = 0; + previousSourceNameIndex = 0; + firstEntryInLine = true; + } + + void addMapping(int targetOffset, SourceFileLocation sourceLocation) { + entries.add(new SourceMapEntry(sourceLocation, targetOffset)); + } + + void printStringListOn(List<String> strings, StringBuffer buffer) { + bool first = true; + buffer.add('['); + for (String string in strings) { + if (!first) buffer.add(','); + buffer.add('"'); + writeJsonEscapedCharsOn(string, buffer); + buffer.add('"'); + first = false; + } + buffer.add(']'); + } + + String build(SourceFile targetFile) { + StringBuffer mappingsBuffer = new StringBuffer(); + entries.forEach((SourceMapEntry entry) => writeEntry(entry, targetFile, + mappingsBuffer)); + StringBuffer buffer = new StringBuffer(); + buffer.add('{\n'); + buffer.add(' "version": 3,\n'); + buffer.add(' "sourceRoot": "",\n'); + buffer.add(' "sources": '); + printStringListOn(sourceUrlList, buffer); + buffer.add(',\n'); + buffer.add(' "names": '); + printStringListOn(sourceNameList, buffer); + buffer.add(',\n'); + buffer.add(' "mappings": "'); + buffer.add(mappingsBuffer); + buffer.add('"\n}\n'); + return buffer.toString(); + } + + void writeEntry(SourceMapEntry entry, SourceFile targetFile, StringBuffer output) { + int targetLine = targetFile.getLine(entry.targetOffset); + int targetColumn = targetFile.getColumn(targetLine, entry.targetOffset); + + if (targetLine > previousTargetLine) { + for (int i = previousTargetLine; i < targetLine; ++i) { + output.add(';'); + } + previousTargetLine = targetLine; + previousTargetColumn = 0; + firstEntryInLine = true; + } + + if (!firstEntryInLine) { + output.add(','); + } + firstEntryInLine = false; + + encodeVLQ(output, targetColumn - previousTargetColumn); + previousTargetColumn = targetColumn; + + if (entry.sourceLocation == null) return; + + String sourceUrl = entry.sourceLocation.getSourceUrl(); + int sourceLine = entry.sourceLocation.getLine(); + int sourceColumn = entry.sourceLocation.getColumn(); + String sourceName = entry.sourceLocation.getSourceName(); + + int sourceUrlIndex = indexOf(sourceUrlList, sourceUrl, sourceUrlMap); + encodeVLQ(output, sourceUrlIndex - previousSourceUrlIndex); + previousSourceUrlIndex = sourceUrlIndex; + + encodeVLQ(output, sourceLine - previousSourceLine); + previousSourceLine = sourceLine; + encodeVLQ(output, sourceColumn - previousSourceColumn); + previousSourceColumn = sourceColumn; + + if (sourceName == null) { + return; + } + + int sourceNameIndex = indexOf(sourceNameList, sourceName, sourceNameMap); + encodeVLQ(output, sourceNameIndex - previousSourceNameIndex); + previousSourceNameIndex = sourceNameIndex; + } + + int indexOf(List<String> list, String value, Map<String, int> map) { + return map.putIfAbsent(value, () { + int index = list.length; + map[value] = index; + list.add(value); + return index; + }); + } + + static void encodeVLQ(StringBuffer output, int value) { + int signBit = 0; + if (value < 0) { + signBit = 1; + value = -value; + } + value = (value << 1) | signBit; + do { + int digit = value & VLQ_BASE_MASK; + value >>= VLQ_BASE_SHIFT; + if (value > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + output.add(BASE64_DIGITS[digit]); + } while (value > 0); + } +} + +class SourceMapEntry { + SourceFileLocation sourceLocation; + int targetOffset; + + SourceMapEntry(this.sourceLocation, this.targetOffset); +} + +class SourceFileLocation { + SourceFile sourceFile; + Token token; + int line; + + SourceFileLocation(this.sourceFile, this.token) { + assert(isValid()); + } + + String getSourceUrl() => sourceFile.filename; + + int getLine() { + if (line == null) line = sourceFile.getLine(token.charOffset); + return line; + } + + int getColumn() => sourceFile.getColumn(getLine(), token.charOffset); + + String getSourceName() { + if (token.isIdentifier()) return token.slowToString(); + return null; + } + + bool isValid() => token.charOffset < sourceFile.text.length; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart new file mode 100644 index 0000000..0e10ecf --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart
@@ -0,0 +1,630 @@ +// 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. + +part of ssa; + +class BailoutInfo { + int instructionId; + int bailoutId; + BailoutInfo(this.instructionId, this.bailoutId); +} + +/** + * Keeps track of the execution environment for instructions. An + * execution environment contains the SSA instructions that are live. + */ +class Environment { + final Set<HInstruction> lives; + final List<HBasicBlock> loopMarkers; + Environment() : lives = new Set<HInstruction>(), + loopMarkers = new List<HBasicBlock>(); + Environment.from(Environment other) + : lives = new Set<HInstruction>.from(other.lives), + loopMarkers = new List<HBasicBlock>.from(other.loopMarkers); + + void remove(HInstruction instruction) { + lives.remove(instruction); + } + + void add(HInstruction instruction) { + // If the instruction is a check, we add its checked input + // instead. This allows sharing the same environment between + // different type guards. + // + // Also, we don't need to add code motion invariant instructions + // in the live set (because we generate them at use-site), except + // for parameters that are not 'this', which is always passed as + // the receiver. + if (instruction is HCheck) { + add(instruction.checkedInput); + } else if (!instruction.isCodeMotionInvariant() + || (instruction is HParameterValue && instruction is !HThis)) { + lives.add(instruction); + } else { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + add(instruction.inputs[i]); + } + } + } + + void addAll(Environment other) { + lives.addAll(other.lives); + } + + bool get isEmpty => lives.isEmpty && loopMarkers.isEmpty; +} + + +/** + * Visits the graph in dominator order and inserts TypeGuards in places where + * we consider the guard to be of value. + * + * Might modify the [types] in an inconsistent way. No further analysis should + * rely on them. + */ +class SsaTypeGuardInserter extends HGraphVisitor implements OptimizationPhase { + final Compiler compiler; + final String name = 'SsaTypeGuardInserter'; + final CodegenWorkItem work; + final HTypeMap types; + bool calledInLoop = false; + bool isRecursiveMethod = false; + int stateId = 1; + + SsaTypeGuardInserter(this.compiler, this.work, this.types); + + void visitGraph(HGraph graph) { + isRecursiveMethod = graph.isRecursiveMethod; + calledInLoop = graph.calledInLoop; + work.guards = <HTypeGuard>[]; + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + block.forEachPhi(visitInstruction); + + HInstruction instruction = block.first; + while (instruction != null) { + // Note that visitInstruction (from the phis and here) might insert an + // HTypeGuard instruction. We have to skip those. + if (instruction is !HTypeGuard) visitInstruction(instruction); + instruction = instruction.next; + } + } + + // Primitive types that are not null are valuable. These include + // indexable arrays. + bool typeValuable(HType type) { + return type.isPrimitive() && !type.isNull(); + } + + bool get hasTypeGuards => work.guards.length != 0; + + bool typeGuardWouldBeValuable(HInstruction instruction, + HType speculativeType) { + // If the type itself is not valuable, do not generate a guard for it. + if (!typeValuable(speculativeType)) return false; + + // Do not insert a type guard if the instruction has a type + // annotation that disagrees with the speculated type. + Element source = instruction.sourceElement; + if (source != null) { + DartType sourceType = source.computeType(compiler); + DartType speculatedType = speculativeType.computeType(compiler); + JavaScriptBackend backend = compiler.backend; + if (speculatedType != null) { + // Use the num type instead of JSNumber because JSNumber + // is not assignment compatible with int and double, but we + // still want to generate a type guard. + if (speculatedType.element == backend.jsNumberClass) { + speculatedType = compiler.numClass.computeType(compiler); + } + if (!compiler.types.isAssignable(speculatedType, sourceType)) { + return false; + } + } + } + + // Insert type guards for recursive methods. + if (isRecursiveMethod) return true; + + // Insert type guards if there are uses in loops. + bool isNested(HBasicBlock inner, HBasicBlock outer) { + if (identical(inner, outer)) return false; + if (outer == null) return true; + while (inner != null) { + if (identical(inner, outer)) return true; + inner = inner.parentLoopHeader; + } + return false; + } + + // If the instruction is not in a loop then the header will be null. + HBasicBlock currentLoopHeader = instruction.block.enclosingLoopHeader; + for (HInstruction user in instruction.usedBy) { + HBasicBlock userLoopHeader = user.block.enclosingLoopHeader; + if (isNested(userLoopHeader, currentLoopHeader)) return true; + } + + bool isIndexOperatorOnIndexablePrimitive(instruction, types) { + return instruction is HIndex + || (instruction is HInvokeDynamicMethod + && instruction.isIndexOperatorOnIndexablePrimitive(types)); + } + + // To speed up computations on values loaded from arrays, we + // insert type guards for builtin array indexing operations in + // nested loops. Since this can blow up code size quite + // significantly, we only do it if type guards have already been + // inserted for this method. The code size price for an additional + // type guard is much smaller than the first one that causes the + // generation of a bailout method. + if (hasTypeGuards + && isIndexOperatorOnIndexablePrimitive(instruction, types)) { + HBasicBlock loopHeader = instruction.block.enclosingLoopHeader; + if (loopHeader != null && loopHeader.parentLoopHeader != null) { + return true; + } + } + + // If the instruction is used by a phi where a guard would be + // valuable, put the guard on that instruction. + for (HInstruction user in instruction.usedBy) { + if (user is HPhi + && user.block.id > instruction.id + && typeGuardWouldBeValuable(user, speculativeType)) { + return true; + } + } + + // Insert type guards if the method is likely to be called in a + // loop. + return calledInLoop; + } + + bool shouldInsertTypeGuard(HInstruction instruction, + HType speculativeType, + HType computedType) { + if (!speculativeType.isUseful()) return false; + // If the types agree we don't need to check. + if (speculativeType == computedType) return false; + // If a bailout check is more expensive than doing the actual operation + // don't do it either. + return typeGuardWouldBeValuable(instruction, speculativeType); + } + + void visitInstruction(HInstruction instruction) { + HType speculativeType = types[instruction]; + HType computedType = instruction.computeTypeFromInputTypes(types, compiler); + // Currently the type in [types] is the speculative type each instruction + // would like to have. We start by recomputing the type non-speculatively. + // If we add a type guard then the guard will expose the speculative type. + // If we don't add a type guard then this avoids that subsequent + // instructions use the wrong (speculative) type. + // + // Note that just setting the speculative type of the instruction is not + // complete since the type could lead to a phi node which in turn could + // change the speculative type. In this case we might miss some guards we + // would have liked to insert. Most of the time this should however be + // fine, due to dominator-order visiting. + types[instruction] = computedType; + + if (shouldInsertTypeGuard(instruction, speculativeType, computedType)) { + HInstruction insertionPoint; + if (instruction is HPhi) { + insertionPoint = instruction.block.first; + } else if (instruction is HParameterValue) { + // We insert the type guard at the end of the entry block + // because if a parameter is live, it must be kept in the live + // environment. Not doing so would mean we could visit a + // parameter and remove it from the environment before + // visiting a type guard. + insertionPoint = instruction.block.last; + } else { + insertionPoint = instruction.next; + } + // If the previous instruction is also a type guard, then both + // guards have the same environment, and can therefore share the + // same state id. + HBailoutTarget target; + int state; + if (insertionPoint.previous is HTypeGuard) { + HTypeGuard other = insertionPoint.previous; + target = other.bailoutTarget; + } else { + state = stateId++; + target = new HBailoutTarget(state); + insertionPoint.block.addBefore(insertionPoint, target); + } + HTypeGuard guard = new HTypeGuard(speculativeType, instruction, target); + types[guard] = speculativeType; + work.guards.add(guard); + instruction.block.rewrite(instruction, guard); + insertionPoint.block.addBefore(insertionPoint, guard); + } + } +} + +/** + * Computes the environment for each SSA instruction: visits the graph + * in post-dominator order. Removes an instruction from the environment + * and adds its inputs to the environment at the instruction's + * definition. + * + * At the end of the computation, insert type guards in the graph. + */ +class SsaEnvironmentBuilder extends HBaseVisitor implements OptimizationPhase { + final Compiler compiler; + final String name = 'SsaEnvironmentBuilder'; + + final Map<HBailoutTarget, Environment> capturedEnvironments; + final Map<HBasicBlock, Environment> liveInstructions; + Environment environment; + /** + * The set of current loop headers that dominate the current block. + */ + Set<HBasicBlock> loopMarkers; + + SsaEnvironmentBuilder(Compiler this.compiler) + : capturedEnvironments = new Map<HBailoutTarget, Environment>(), + liveInstructions = new Map<HBasicBlock, Environment>(), + loopMarkers = new Set<HBasicBlock>(); + + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + if (!liveInstructions[graph.entry].isEmpty) { + compiler.internalError('Bailout environment computation', + node: compiler.currentElement.parseNode(compiler)); + } + updateLoopMarkers(); + insertCapturedEnvironments(); + } + + void updateLoopMarkers() { + // If the block is a loop header, we need to merge the loop + // header's live instructions into every environment that contains + // the loop marker. + // For example with the following loop (read the example in + // reverse): + // + // while (true) { <-- (4) update the marker with the environment + // use(x); <-- (3) environment = {x} + // bailout; <-- (2) has the marker when computed + // } <-- (1) create a loop marker + // + // The bailout instruction first captures the marker, but it + // will be replaced by the live environment at the loop entry, + // in this case {x}. + capturedEnvironments.forEach((ignoredInstruction, env) { + env.loopMarkers.forEach((HBasicBlock header) { + env.addAll(liveInstructions[header]); + }); + env.loopMarkers.clear(); + }); + } + + void visitBasicBlock(HBasicBlock block) { + environment = new Environment(); + + // Add to the environment the live instructions of its successor, as well as + // the inputs of the phis of the successor that flow from this block. + for (int i = 0; i < block.successors.length; i++) { + HBasicBlock successor = block.successors[i]; + Environment successorEnv = liveInstructions[successor]; + if (successorEnv != null) { + environment.addAll(successorEnv); + } else { + // If we haven't computed the liveInstructions of that successor, we + // know it must be a loop header. + assert(successor.isLoopHeader()); + assert(!block.isLoopHeader()); + loopMarkers.add(successor); + } + + int index = successor.predecessors.indexOf(block); + for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) { + environment.add(phi.inputs[index]); + } + } + + if (block.isLoopHeader()) { + loopMarkers.remove(block); + } + + // If the block is a loop header, we're adding all [loopMarkers] + // after removing it from the list of [loopMarkers], because + // it will just recompute the loop phis. + environment.loopMarkers.addAll(loopMarkers); + + // Iterate over all instructions to remove an instruction from the + // environment and add its inputs. + HInstruction instruction = block.last; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.previous; + } + + // We just remove the phis from the environment. The inputs of the + // phis will be put in the environment of the predecessors. + for (HPhi phi = block.phis.first; phi != null; phi = phi.next) { + environment.remove(phi); + } + + // Finally save the liveInstructions of that block. + liveInstructions[block] = environment; + } + + void visitBailoutTarget(HBailoutTarget target) { + visitInstruction(target); + capturedEnvironments[target] = new Environment.from(environment); + } + + void visitInstruction(HInstruction instruction) { + environment.remove(instruction); + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + environment.add(instruction.inputs[i]); + } + } + + /** + * Stores all live variables in the bailout target and the guards. + */ + void insertCapturedEnvironments() { + capturedEnvironments.forEach((HBailoutTarget target, Environment env) { + assert(target.inputs.length == 0); + target.inputs.addAll(env.lives); + // TODO(floitsch): we should add the bailout-target's input variables + // as input to the guards only in the optimized version. The + // non-optimized version does not use the bailout guards and it is + // unnecessary to keep the variables alive until the check. + for (HTypeGuard guard in target.usedBy) { + // A type-guard initially only has two inputs: the guarded instruction + // and the bailout-target. Only after adding the environment is it + // allowed to have more inputs. + assert(guard.inputs.length == 2); + guard.inputs.addAll(env.lives); + } + for (HInstruction live in env.lives) { + live.usedBy.add(target); + live.usedBy.addAll(target.usedBy); + } + }); + } +} + +/** + * Propagates bailout information to blocks that need it. This visitor + * is run before codegen, to know which blocks have to deal with + * bailouts. + */ +class SsaBailoutPropagator extends HBaseVisitor { + final Compiler compiler; + /** + * A list to propagate bailout information to blocks that start a + * guarded or labeled list of statements. Currently, these blocks + * are: + * - first block of a then branch, + * - first block of an else branch, + * - a loop header, + * - labeled block. + */ + final List<HBasicBlock> blocks; + + /** + * The current subgraph we are visiting. + */ + SubGraph subGraph; + + /** + * The current block information we are visiting. + */ + HBlockInformation currentBlockInformation; + + /** + * Max number of arguments to the bailout (not counting the state). + */ + int bailoutArity; + /** + * A map from variables to their names. These are the names in the + * unoptimized (bailout) version of the function. Their names could be + * different in the optimized version. + */ + VariableNames variableNames; + /** + * Maps from the variable names to their positions in the argument list of the + * bailout instruction. Because of the way the variable allocator works, + * several variables can end up with the same name (if their live ranges do + * not overlap), therefore they can have the same position in the bailout + * argument list + */ + Map<String, int> parameterNames; + + /** + * If set to true, the graph has either multiple bailouts in + * different places, or a bailout inside an if or a loop. For such a + * graph, the code generator will emit a generic switch. + */ + bool hasComplexBailoutTargets = false; + + /** + * The first type guard in the graph. + */ + HBailoutTarget firstBailoutTarget; + + /** + * If set, it is the first block in the graph where we generate + * code. Blocks before this one are dead code in the bailout + * version. + */ + + SsaBailoutPropagator(this.compiler, this.variableNames) + : blocks = <HBasicBlock>[], + bailoutArity = 0, + parameterNames = new Map<String, int>(); + + void visitGraph(HGraph graph) { + subGraph = new SubGraph(graph.entry, graph.exit); + visitBasicBlock(graph.entry); + if (!blocks.isEmpty) { + compiler.internalError('Bailout propagation', + node: compiler.currentElement.parseNode(compiler)); + } + } + + /** + * Returns true if we can visit the given [blockFlow]. False + * otherwise. Currently, try/catch and switch are not in bailout + * methods, so this method only deals with loops and labeled blocks. + * If [blockFlow] is a labeled block or a loop, we also visit the + * continuation of the block flow. + */ + bool handleBlockFlow(HBlockFlow blockFlow) { + HBlockInformation body = blockFlow.body; + + // We reach here again when starting to visit a subgraph. Just + // return to visiting the block. + if (currentBlockInformation == body) return false; + + HBlockInformation oldInformation = currentBlockInformation; + if (body is HLabeledBlockInformation) { + currentBlockInformation = body; + HLabeledBlockInformation info = body; + visitStatements(info.body, newFlow: true); + } else if (body is HLoopBlockInformation) { + currentBlockInformation = body; + HLoopBlockInformation info = body; + if (info.initializer != null) { + visitExpression(info.initializer); + } + blocks.addLast(info.loopHeader); + if (!info.isDoWhile()) { + visitExpression(info.condition); + } + visitStatements(info.body, newFlow: false); + if (info.isDoWhile()) { + visitExpression(info.condition); + } + if (info.updates != null) { + visitExpression(info.updates); + } + blocks.removeLast(); + } else { + assert(body is! HTryBlockInformation); + assert(body is! HSwitchBlockInformation); + // [HIfBlockInformation] is handled by visitIf. + return false; + } + + currentBlockInformation = oldInformation; + if (blockFlow.continuation != null) { + visitBasicBlock(blockFlow.continuation); + } + return true; + } + + void visitBasicBlock(HBasicBlock block) { + // Abort traversal if we are leaving the currently active sub-graph. + if (!subGraph.contains(block)) return; + + HBlockFlow blockFlow = block.blockFlow; + if (blockFlow != null && handleBlockFlow(blockFlow)) return; + + HInstruction instruction = block.first; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.next; + } + } + + void visitExpression(HSubExpressionBlockInformation info) { + visitSubGraph(info.subExpression); + } + + /** + * Visit the statements in [info]. If [newFlow] is true, we add the + * first block of [statements] to the list of [blocks]. + */ + void visitStatements(HSubGraphBlockInformation info, {bool newFlow}) { + SubGraph graph = info.subGraph; + if (newFlow) blocks.addLast(graph.start); + visitSubGraph(graph); + if (newFlow) blocks.removeLast(); + } + + void visitSubGraph(SubGraph graph) { + SubGraph oldSubGraph = subGraph; + subGraph = graph; + visitBasicBlock(graph.start); + subGraph = oldSubGraph; + } + + void visitIf(HIf instruction) { + int preVisitedBlocks = 0; + HIfBlockInformation info = instruction.blockInformation.body; + visitStatements(info.thenGraph, newFlow: true); + preVisitedBlocks++; + visitStatements(info.elseGraph, newFlow: true); + preVisitedBlocks++; + + HBasicBlock joinBlock = instruction.joinBlock; + if (joinBlock != null + && !identical(joinBlock.dominator, instruction.block)) { + // The join block is dominated by a block in one of the branches. + // The subgraph traversal never reached it, so we visit it here + // instead. + visitBasicBlock(joinBlock); + } + + // Visit all the dominated blocks that are not part of the then or else + // branches, and is not the join block. + // Depending on how the then/else branches terminate + // (e.g., return/throw/break) there can be any number of these. + List<HBasicBlock> dominated = instruction.block.dominatedBlocks; + int dominatedCount = dominated.length; + for (int i = preVisitedBlocks; i < dominatedCount; i++) { + HBasicBlock dominatedBlock = dominated[i]; + visitBasicBlock(dominatedBlock); + } + } + + void visitGoto(HGoto goto) { + HBasicBlock block = goto.block; + HBasicBlock successor = block.successors[0]; + if (identical(successor.dominator, block)) { + visitBasicBlock(block.successors[0]); + } + } + + void visitLoopBranch(HLoopBranch branch) { + // For a do-while loop, the body has already been visited. + if (!branch.isDoWhile()) { + visitBasicBlock(branch.block.dominatedBlocks[0]); + } + } + + visitBailoutTarget(HBailoutTarget target) { + int inputLength = target.inputs.length; + for (HInstruction input in target.inputs) { + String inputName = variableNames.getName(input); + int position = parameterNames[inputName]; + if (position == null) { + position = parameterNames[inputName] = bailoutArity++; + } + } + + if (blocks.isEmpty) { + if (firstBailoutTarget == null) { + firstBailoutTarget = target; + } else { + hasComplexBailoutTargets = true; + } + } else { + hasComplexBailoutTargets = true; + blocks.forEach((HBasicBlock block) { + block.bailoutTargets.add(target); + }); + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart new file mode 100644 index 0000000..ac19fea --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart
@@ -0,0 +1,5034 @@ +// 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. + +part of ssa; + +/** + * A special element for the extra parameter taken by intercepted + * methods. We need to override [Element.computeType] because our + * optimizers may look at its declared type. + */ +class InterceptedElement extends ElementX { + final HType ssaType; + InterceptedElement(this.ssaType, Element enclosing) + : super(const SourceString('receiver'), + ElementKind.PARAMETER, + enclosing); + + DartType computeType(Compiler compiler) => ssaType.computeType(compiler); +} + +class SsaBuilderTask extends CompilerTask { + final CodeEmitterTask emitter; + // Loop tracking information. + final Set<FunctionElement> functionsCalledInLoop; + final Map<SourceString, Selector> selectorsCalledInLoop; + final JavaScriptBackend backend; + + String get name => 'SSA builder'; + + SsaBuilderTask(JavaScriptBackend backend) + : emitter = backend.emitter, + functionsCalledInLoop = new Set<FunctionElement>(), + selectorsCalledInLoop = new Map<SourceString, Selector>(), + backend = backend, + super(backend.compiler); + + HGraph build(CodegenWorkItem work) { + return measure(() { + Element element = work.element.implementation; + HInstruction.idCounter = 0; + ConstantSystem constantSystem = compiler.backend.constantSystem; + SsaBuilder builder = new SsaBuilder(constantSystem, this, work); + HGraph graph; + ElementKind kind = element.kind; + if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR)) { + graph = compileConstructor(builder, work); + } else if (identical(kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY) || + identical(kind, ElementKind.FUNCTION) || + identical(kind, ElementKind.GETTER) || + identical(kind, ElementKind.SETTER)) { + graph = builder.buildMethod(element); + } else if (identical(kind, ElementKind.FIELD)) { + graph = builder.buildLazyInitializer(element); + } else { + compiler.internalErrorOnElement(element, + 'unexpected element kind $kind'); + } + assert(graph.isValid()); + if (!identical(kind, ElementKind.FIELD)) { + bool inLoop = functionsCalledInLoop.contains(element.declaration); + if (!inLoop) { + Selector selector = selectorsCalledInLoop[element.name]; + inLoop = selector != null && selector.applies(element, compiler); + } + graph.calledInLoop = inLoop; + + // If there is an estimate of the parameter types assume these types + // when compiling. + // TODO(karlklose,ngeoffray): add a check to make sure that element is + // of type FunctionElement. + FunctionElement function = element; + OptionalParameterTypes defaultValueTypes = null; + FunctionSignature signature = function.computeSignature(compiler); + if (signature.optionalParameterCount > 0) { + defaultValueTypes = + new OptionalParameterTypes(signature.optionalParameterCount); + int index = 0; + signature.forEachOptionalParameter((Element parameter) { + Constant defaultValue = builder.compileVariable(parameter); + HType type = HGraph.mapConstantTypeToSsaType(defaultValue); + defaultValueTypes.update(index, parameter.name, type); + index++; + }); + } else { + // TODO(ahe): I have disabled type optimizations for + // optional arguments as the types are stored in the wrong + // order. + HTypeList parameterTypes = + backend.optimisticParameterTypes(element.declaration, + defaultValueTypes); + if (!parameterTypes.allUnknown) { + int i = 0; + signature.forEachParameter((Element param) { + builder.parameters[param].guaranteedType = parameterTypes[i++]; + }); + } + backend.registerParameterTypesOptimization( + element.declaration, parameterTypes, defaultValueTypes); + } + } + + if (compiler.tracer.enabled) { + String name; + if (element.isMember()) { + String className = element.getEnclosingClass().name.slowToString(); + String memberName = element.name.slowToString(); + name = "$className.$memberName"; + if (element.isGenerativeConstructorBody()) { + name = "$name (body)"; + } + } else { + name = "${element.name.slowToString()}"; + } + compiler.tracer.traceCompilation(name, work.compilationContext); + compiler.tracer.traceGraph('builder', graph); + } + return graph; + }); + } + + HGraph compileConstructor(SsaBuilder builder, CodegenWorkItem work) { + // The body of the constructor will be generated in a separate function. + final ClassElement classElement = work.element.getEnclosingClass(); + return builder.buildFactory(classElement.implementation, + work.element.implementation); + } +} + +/** + * Keeps track of locals (including parameters and phis) when building. The + * 'this' reference is treated as parameter and hence handled by this class, + * too. + */ +class LocalsHandler { + /** + * The values of locals that can be directly accessed (without redirections + * to boxes or closure-fields). + * + * [directLocals] is iterated, so it is a [LinkedHashMap] to make the + * iteration order a function only of insertions and not a function of + * e.g. Element hash codes. I'd prefer to use a SortedMap but some elements + * don't have source locations for [Elements.compareByPosition]. + */ + LinkedHashMap<Element, HInstruction> directLocals; + Map<Element, Element> redirectionMapping; + SsaBuilder builder; + ClosureClassMap closureData; + + LocalsHandler(this.builder) + : directLocals = new LinkedHashMap<Element, HInstruction>(), + redirectionMapping = new Map<Element, Element>(); + + get typesTask => builder.compiler.typesTask; + + /** + * Creates a new [LocalsHandler] based on [other]. We only need to + * copy the [directLocals], since the other fields can be shared + * throughout the AST visit. + */ + LocalsHandler.from(LocalsHandler other) + : directLocals = + new LinkedHashMap<Element, HInstruction>.from(other.directLocals), + redirectionMapping = other.redirectionMapping, + builder = other.builder, + closureData = other.closureData; + + /** + * Redirects accesses from element [from] to element [to]. The [to] element + * must be a boxed variable or a variable that is stored in a closure-field. + */ + void redirectElement(Element from, Element to) { + assert(redirectionMapping[from] == null); + redirectionMapping[from] = to; + assert(isStoredInClosureField(from) || isBoxed(from)); + } + + HInstruction createBox() { + // TODO(floitsch): Clean up this hack. Should we create a box-object by + // just creating an empty object literal? + HInstruction box = new HForeign(const LiteralDartString("{}"), + HType.UNKNOWN, + <HInstruction>[]); + builder.add(box); + return box; + } + + /** + * If the scope (function or loop) [node] has captured variables then this + * method creates a box and sets up the redirections. + */ + void enterScope(Node node, Element element) { + // See if any variable in the top-scope of the function is captured. If yes + // we need to create a box-object. + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData != null) { + HInstruction box; + // The scope has captured variables. + if (element != null && element.isGenerativeConstructorBody()) { + // The box is passed as a parameter to a generative + // constructor body. + box = builder.addParameter(scopeData.boxElement); + } else { + box = createBox(); + } + // Add the box to the known locals. + directLocals[scopeData.boxElement] = box; + // Make sure that accesses to the boxed locals go into the box. We also + // need to make sure that parameters are copied into the box if necessary. + scopeData.capturedVariableMapping.forEach((Element from, Element to) { + // The [from] can only be a parameter for function-scopes and not + // loop scopes. + if (from.isParameter() && !element.isGenerativeConstructorBody()) { + // Now that the redirection is set up, the update to the local will + // write the parameter value into the box. + // Store the captured parameter in the box. Get the current value + // before we put the redirection in place. + // We don't need to update the local for a generative + // constructor body, because it receives a box that already + // contains the updates as the last parameter. + HInstruction instruction = readLocal(from); + redirectElement(from, to); + updateLocal(from, instruction); + } else { + redirectElement(from, to); + } + }); + } + } + + /** + * Replaces the current box with a new box and copies over the given list + * of elements from the old box into the new box. + */ + void updateCaptureBox(Element boxElement, List<Element> toBeCopiedElements) { + // Create a new box and copy over the values from the old box into the + // new one. + HInstruction oldBox = readLocal(boxElement); + HInstruction newBox = createBox(); + for (Element boxedVariable in toBeCopiedElements) { + // [readLocal] uses the [boxElement] to find its box. By replacing it + // behind its back we can still get to the old values. + updateLocal(boxElement, oldBox); + HInstruction oldValue = readLocal(boxedVariable); + updateLocal(boxElement, newBox); + updateLocal(boxedVariable, oldValue); + } + updateLocal(boxElement, newBox); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + void startFunction(Element element, Expression node) { + assert(invariant(node, element.isImplementation)); + Compiler compiler = builder.compiler; + closureData = compiler.closureToClassMapper.computeClosureToClassMapping( + element, node, builder.elements); + + if (element is FunctionElement) { + FunctionElement functionElement = element; + FunctionSignature params = functionElement.computeSignature(compiler); + params.orderedForEachParameter((Element parameterElement) { + if (element.isGenerativeConstructorBody()) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData != null + && scopeData.capturedVariableMapping.containsKey( + parameterElement)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to have it. + return; + } + } + HInstruction parameter = builder.addParameter(parameterElement); + builder.parameters[parameterElement] = parameter; + directLocals[parameterElement] = parameter; + parameter.guaranteedType = + builder.mapInferredType( + typesTask.getGuaranteedTypeOfElement(parameterElement)); + }); + } + + enterScope(node, element); + + // If the freeVariableMapping is not empty, then this function was a + // nested closure that captures variables. Redirect the captured + // variables to fields in the closure. + closureData.freeVariableMapping.forEach((Element from, Element to) { + redirectElement(from, to); + }); + if (closureData.isClosure()) { + // Inside closure redirect references to itself to [:this:]. + HThis thisInstruction = new HThis(closureData.thisElement); + builder.graph.thisInstruction = thisInstruction; + builder.graph.entry.addAtEntry(thisInstruction); + updateLocal(closureData.closureElement, thisInstruction); + } else if (element.isInstanceMember() + || element.isGenerativeConstructor()) { + // Once closures have been mapped to classes their instance members might + // not have any thisElement if the closure was created inside a static + // context. + ClassElement cls = element.getEnclosingClass(); + DartType type = cls.computeType(builder.compiler); + HThis thisInstruction = new HThis(closureData.thisElement, + new HBoundedType.nonNull(type)); + builder.graph.thisInstruction = thisInstruction; + builder.graph.entry.addAtEntry(thisInstruction); + directLocals[closureData.thisElement] = thisInstruction; + } + + // If this method is an intercepted method, add the extra + // parameter to it, that is the actual receiver. + ClassElement cls = element.getEnclosingClass(); + if (builder.backend.isInterceptorClass(cls)) { + HType type = HType.UNKNOWN; + if (cls == builder.backend.jsArrayClass) { + type = HType.READABLE_ARRAY; + } else if (cls == builder.backend.jsStringClass) { + type = HType.STRING; + } else if (cls == builder.backend.jsNumberClass) { + type = HType.NUMBER; + } else if (cls == builder.backend.jsIntClass) { + type = HType.INTEGER; + } else if (cls == builder.backend.jsDoubleClass) { + type = HType.DOUBLE; + } else if (cls == builder.backend.jsNullClass) { + type = HType.NULL; + } else if (cls == builder.backend.jsBoolClass) { + type = HType.BOOLEAN; + } + Element parameter = new InterceptedElement(type, element); + HParameterValue value = new HParameterValue(parameter); + builder.graph.entry.addAfter( + directLocals[closureData.thisElement], value); + directLocals[closureData.thisElement] = value; + value.guaranteedType = type; + } + } + + bool hasValueForDirectLocal(Element element) { + assert(element != null); + assert(isAccessedDirectly(element)); + return directLocals[element] != null; + } + + /** + * Returns true if the local can be accessed directly. Boxed variables or + * captured variables that are stored in the closure-field return [false]. + */ + bool isAccessedDirectly(Element element) { + assert(element != null); + return redirectionMapping[element] == null + && !closureData.usedVariablesInTry.contains(element); + } + + bool isStoredInClosureField(Element element) { + assert(element != null); + if (isAccessedDirectly(element)) return false; + Element redirectTarget = redirectionMapping[element]; + if (redirectTarget == null) return false; + if (redirectTarget.isMember()) { + assert(redirectTarget is ClosureFieldElement); + return true; + } + return false; + } + + bool isBoxed(Element element) { + if (isAccessedDirectly(element)) return false; + if (isStoredInClosureField(element)) return false; + return redirectionMapping[element] != null; + } + + bool isUsedInTry(Element element) { + return closureData.usedVariablesInTry.contains(element); + } + + /** + * Returns an [HInstruction] for the given element. If the element is + * boxed or stored in a closure then the method generates code to retrieve + * the value. + */ + HInstruction readLocal(Element element) { + if (isAccessedDirectly(element)) { + if (directLocals[element] == null) { + builder.compiler.internalError("Cannot find value $element", + element: element); + } + return directLocals[element]; + } else if (isStoredInClosureField(element)) { + Element redirect = redirectionMapping[element]; + HInstruction receiver = readLocal(closureData.closureElement); + HInstruction fieldGet = new HFieldGet(redirect, receiver); + builder.add(fieldGet); + return fieldGet; + } else if (isBoxed(element)) { + Element redirect = redirectionMapping[element]; + // In the function that declares the captured variable the box is + // accessed as direct local. Inside the nested closure the box is + // accessed through a closure-field. + // Calling [readLocal] makes sure we generate the correct code to get + // the box. + assert(redirect.enclosingElement.isVariable()); + HInstruction box = readLocal(redirect.enclosingElement); + HInstruction lookup = new HFieldGet(redirect, box); + builder.add(lookup); + return lookup; + } else { + assert(isUsedInTry(element)); + HLocalValue local = getLocal(element); + HInstruction variable = new HLocalGet(element, local); + builder.add(variable); + return variable; + } + } + + HType cachedTypeOfThis; + + HInstruction readThis() { + HInstruction res = readLocal(closureData.thisElement); + if (res.guaranteedType == null) { + if (cachedTypeOfThis == null) { + assert(closureData.isClosure()); + Element element = closureData.thisElement; + ClassElement cls = element.enclosingElement.getEnclosingClass(); + DartType type = cls.computeType(builder.compiler); + cachedTypeOfThis = new HBoundedType.nonNull(type); + } + res.guaranteedType = cachedTypeOfThis; + } + return res; + } + + HLocalValue getLocal(Element element) { + // If the element is a parameter, we already have a + // HParameterValue for it. We cannot create another one because + // it could then have another name than the real parameter. And + // the other one would not know it is just a copy of the real + // parameter. + if (element.isParameter()) return builder.parameters[element]; + + return builder.activationVariables.putIfAbsent(element, () { + HLocalValue local = new HLocalValue(element); + builder.graph.entry.addAtExit(local); + return local; + }); + } + + /** + * Sets the [element] to [value]. If the element is boxed or stored in a + * closure then the method generates code to set the value. + */ + void updateLocal(Element element, HInstruction value) { + assert(!isStoredInClosureField(element)); + if (isAccessedDirectly(element)) { + directLocals[element] = value; + } else if (isBoxed(element)) { + Element redirect = redirectionMapping[element]; + // The box itself could be captured, or be local. A local variable that + // is captured will be boxed, but the box itself will be a local. + // Inside the closure the box is stored in a closure-field and cannot + // be accessed directly. + assert(redirect.enclosingElement.isVariable()); + HInstruction box = readLocal(redirect.enclosingElement); + builder.add(new HFieldSet(redirect, box, value)); + } else { + assert(isUsedInTry(element)); + HLocalValue local = getLocal(element); + builder.add(new HLocalSet(element, local, value)); + } + } + + /** + * This function must be called before visiting any children of the loop. In + * particular it needs to be called before executing the initializers. + * + * The [LocalsHandler] will make the boxes and updates at the right moment. + * The builder just needs to call [enterLoopBody] and [enterLoopUpdates] (for + * [For] loops) at the correct places. For phi-handling [beginLoopHeader] and + * [endLoop] must also be called. + * + * The correct place for the box depends on the given loop. In most cases + * the box will be created when entering the loop-body: while, do-while, and + * for-in (assuming the call to [:next:] is inside the body) can always be + * constructed this way. + * + * Things are slightly more complicated for [For] loops. If no declared + * loop variable is boxed then the loop-body approach works here too. If a + * loop-variable is boxed we need to introduce a new box for the + * loop-variable before we enter the initializer so that the initializer + * writes the values into the box. In any case we need to create the box + * before the condition since the condition could box the variable. + * Since the first box is created outside the actual loop we have a second + * location where a box is created: just before the updates. This is + * necessary since updates are considered to be part of the next iteration + * (and can again capture variables). + * + * For example the following Dart code prints 1 3 -- 3 4. + * + * var fs = []; + * for (var i = 0; i < 3; (f() { fs.add(f); print(i); i++; })()) { + * i++; + * } + * print("--"); + * for (var i = 0; i < 2; i++) fs[i](); + * + * We solve this by emitting the following code (only for [For] loops): + * <Create box> <== move the first box creation outside the loop. + * <initializer>; + * loop-entry: + * if (!<condition>) goto loop-exit; + * <body> + * <update box> // create a new box and copy the captured loop-variables. + * <updates> + * goto loop-entry; + * loop-exit: + */ + void startLoop(Node node) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + if (scopeData.hasBoxedLoopVariables()) { + // If there are boxed loop variables then we set up the box and + // redirections already now. This way the initializer can write its + // values into the box. + // For other loops the box will be created when entering the body. + enterScope(node, null); + } + } + + void beginLoopHeader(Node node, HBasicBlock loopEntry) { + // Create a copy because we modify the map while iterating over it. + Map<Element, HInstruction> saved = + new LinkedHashMap<Element, HInstruction>.from(directLocals); + + // Create phis for all elements in the definitions environment. + saved.forEach((Element element, HInstruction instruction) { + if (isAccessedDirectly(element)) { + // We know 'this' cannot be modified. + if (!identical(element, closureData.thisElement)) { + HPhi phi = new HPhi.singleInput(element, instruction); + loopEntry.addPhi(phi); + directLocals[element] = phi; + } else { + directLocals[element] = instruction; + } + } + }); + } + + void enterLoopBody(Node node) { + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + // If there are no declared boxed loop variables then we did not create the + // box before the initializer and we have to create the box now. + if (!scopeData.hasBoxedLoopVariables()) { + enterScope(node, null); + } + } + + void enterLoopUpdates(Loop node) { + // If there are declared boxed loop variables then the updates might have + // access to the box and we must switch to a new box before executing the + // updates. + // In all other cases a new box will be created when entering the body of + // the next iteration. + ClosureScope scopeData = closureData.capturingScopes[node]; + if (scopeData == null) return; + if (scopeData.hasBoxedLoopVariables()) { + updateCaptureBox(scopeData.boxElement, scopeData.boxedLoopVariables); + } + } + + void endLoop(HBasicBlock loopEntry) { + // If the loop has an aborting body, we don't update the loop + // phis. + if (loopEntry.predecessors.length == 1) return; + loopEntry.forEachPhi((HPhi phi) { + Element element = phi.sourceElement; + HInstruction postLoopDefinition = directLocals[element]; + phi.addInput(postLoopDefinition); + }); + } + + /** + * Merge [otherLocals] into this locals handler, creating phi-nodes when + * there is a conflict. + * If a phi node is necessary, it will use this handler's instruction as the + * first input, and the otherLocals instruction as the second. + */ + void mergeWith(LocalsHandler otherLocals, HBasicBlock joinBlock) { + // If an element is in one map but not the other we can safely + // ignore it. It means that a variable was declared in the + // block. Since variable declarations are scoped the declared + // variable cannot be alive outside the block. Note: this is only + // true for nodes where we do joins. + Map<Element, HInstruction> joinedLocals = + new LinkedHashMap<Element, HInstruction>(); + otherLocals.directLocals.forEach((element, instruction) { + // We know 'this' cannot be modified. + if (identical(element, closureData.thisElement)) { + assert(directLocals[element] == instruction); + joinedLocals[element] = instruction; + } else { + HInstruction mine = directLocals[element]; + if (mine == null) return; + if (identical(instruction, mine)) { + joinedLocals[element] = instruction; + } else { + HInstruction phi = + new HPhi.manyInputs(element, <HInstruction>[mine, instruction]); + joinBlock.addPhi(phi); + joinedLocals[element] = phi; + } + } + }); + directLocals = joinedLocals; + } + + /** + * The current localsHandler is not used for its values, only for its + * declared variables. This is a way to exclude local values from the + * result when they are no longer in scope. + * Returns the new LocalsHandler to use (may not be [this]). + */ + LocalsHandler mergeMultiple(List<LocalsHandler> locals, + HBasicBlock joinBlock) { + assert(locals.length > 0); + if (locals.length == 1) return locals[0]; + Map<Element, HInstruction> joinedLocals = + new LinkedHashMap<Element,HInstruction>(); + HInstruction thisValue = null; + directLocals.forEach((Element element, HInstruction instruction) { + if (!identical(element, closureData.thisElement)) { + HPhi phi = new HPhi.noInputs(element); + joinedLocals[element] = phi; + joinBlock.addPhi(phi); + } else { + // We know that "this" never changes, if it's there. + // Save it for later. While merging, there is no phi for "this", + // so we don't have to special case it in the merge loop. + thisValue = instruction; + } + }); + for (LocalsHandler local in locals) { + local.directLocals.forEach((Element element, HInstruction instruction) { + HPhi phi = joinedLocals[element]; + if (phi != null) { + phi.addInput(instruction); + } + }); + } + if (thisValue != null) { + // If there was a "this" for the scope, add it to the new locals. + joinedLocals[closureData.thisElement] = thisValue; + } + directLocals = joinedLocals; + return this; + } +} + + +// Represents a single break/continue instruction. +class JumpHandlerEntry { + final HJump jumpInstruction; + final LocalsHandler locals; + bool isBreak() => jumpInstruction is HBreak; + bool isContinue() => jumpInstruction is HContinue; + JumpHandlerEntry(this.jumpInstruction, this.locals); +} + + +abstract class JumpHandler { + factory JumpHandler(SsaBuilder builder, TargetElement target) { + return new TargetJumpHandler(builder, target); + } + void generateBreak([LabelElement label]); + void generateContinue([LabelElement label]); + void forEachBreak(void action(HBreak instruction, LocalsHandler locals)); + void forEachContinue(void action(HContinue instruction, + LocalsHandler locals)); + bool hasAnyContinue(); + bool hasAnyBreak(); + void close(); + final TargetElement target; + List<LabelElement> labels(); +} + +// Insert break handler used to avoid null checks when a target isn't +// used as the target of a break, and therefore doesn't need a break +// handler associated with it. +class NullJumpHandler implements JumpHandler { + final Compiler compiler; + + NullJumpHandler(this.compiler); + + void generateBreak([LabelElement label]) { + compiler.internalError('generateBreak should not be called'); + } + + void generateContinue([LabelElement label]) { + compiler.internalError('generateContinue should not be called'); + } + + void forEachBreak(Function ignored) { } + void forEachContinue(Function ignored) { } + void close() { } + bool hasAnyContinue() => false; + bool hasAnyBreak() => false; + + List<LabelElement> labels() => const <LabelElement>[]; + TargetElement get target => null; +} + +// Records breaks until a target block is available. +// Breaks are always forward jumps. +// Continues in loops are implemented as breaks of the body. +// Continues in switches is currently not handled. +class TargetJumpHandler implements JumpHandler { + final SsaBuilder builder; + final TargetElement target; + final List<JumpHandlerEntry> jumps; + + TargetJumpHandler(SsaBuilder builder, this.target) + : this.builder = builder, + jumps = <JumpHandlerEntry>[] { + assert(builder.jumpTargets[target] == null); + builder.jumpTargets[target] = this; + } + + void generateBreak([LabelElement label]) { + HInstruction breakInstruction; + if (label == null) { + breakInstruction = new HBreak(target); + } else { + breakInstruction = new HBreak.toLabel(label); + } + LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); + builder.close(breakInstruction); + jumps.add(new JumpHandlerEntry(breakInstruction, locals)); + } + + void generateContinue([LabelElement label]) { + HInstruction continueInstruction; + if (label == null) { + continueInstruction = new HContinue(target); + } else { + continueInstruction = new HContinue.toLabel(label); + } + LocalsHandler locals = new LocalsHandler.from(builder.localsHandler); + builder.close(continueInstruction); + jumps.add(new JumpHandlerEntry(continueInstruction, locals)); + } + + void forEachBreak(Function action) { + for (JumpHandlerEntry entry in jumps) { + if (entry.isBreak()) action(entry.jumpInstruction, entry.locals); + } + } + + void forEachContinue(Function action) { + for (JumpHandlerEntry entry in jumps) { + if (entry.isContinue()) action(entry.jumpInstruction, entry.locals); + } + } + + bool hasAnyContinue() { + for (JumpHandlerEntry entry in jumps) { + if (entry.isContinue()) return true; + } + return false; + } + + bool hasAnyBreak() { + for (JumpHandlerEntry entry in jumps) { + if (entry.isBreak()) return true; + } + return false; + } + + void close() { + // The mapping from TargetElement to JumpHandler is no longer needed. + builder.jumpTargets.remove(target); + } + + List<LabelElement> labels() { + List<LabelElement> result = null; + for (LabelElement element in target.labels) { + if (result == null) result = <LabelElement>[]; + result.add(element); + } + return (result == null) ? const <LabelElement>[] : result; + } +} + +class SsaBuilder extends ResolvedVisitor implements Visitor { + final SsaBuilderTask builder; + final JavaScriptBackend backend; + final CodegenWorkItem work; + final ConstantSystem constantSystem; + HGraph graph; + LocalsHandler localsHandler; + HInstruction rethrowableException; + Map<Element, HInstruction> parameters; + final RuntimeTypeInformation rti; + HParameterValue lastAddedParameter; + + Map<TargetElement, JumpHandler> jumpTargets; + + /** + * Variables stored in the current activation. These variables are + * being updated in try/catch blocks, and should be + * accessed indirectly through [HLocalGet] and [HLocalSet]. + */ + Map<Element, HLocalValue> activationVariables; + + // We build the Ssa graph by simulating a stack machine. + List<HInstruction> stack; + + // The current block to add instructions to. Might be null, if we are + // visiting dead code. + HBasicBlock current; + // The most recently opened block. Has the same value as [current] while + // the block is open, but unlike [current], it isn't cleared when the current + // block is closed. + HBasicBlock lastOpenedBlock; + + final List<Element> sourceElementStack; + + Element get currentElement => sourceElementStack.last.declaration; + Compiler get compiler => builder.compiler; + CodeEmitterTask get emitter => builder.emitter; + + SsaBuilder(this.constantSystem, SsaBuilderTask builder, CodegenWorkItem work) + : this.builder = builder, + this.backend = builder.backend, + this.work = work, + graph = new HGraph(), + stack = new List<HInstruction>(), + activationVariables = new Map<Element, HLocalValue>(), + jumpTargets = new Map<TargetElement, JumpHandler>(), + parameters = new Map<Element, HInstruction>(), + sourceElementStack = <Element>[work.element], + inliningStack = <InliningState>[], + rti = builder.backend.rti, + super(work.resolutionTree) { + localsHandler = new LocalsHandler(this); + } + + static const MAX_INLINING_DEPTH = 3; + static const MAX_INLINING_SOURCE_SIZE = 128; + List<InliningState> inliningStack; + Element returnElement; + DartType returnType; + bool inTryStatement = false; + + /** + * Compiles compile-time constants. Never returns [:null:]. If the + * initial value is not a compile-time constants, it reports an + * internal error. + */ + Constant compileConstant(VariableElement element) { + return compiler.constantHandler.compileConstant(element); + } + + Constant compileVariable(VariableElement element) { + return compiler.constantHandler.compileVariable(element); + } + + bool isLazilyInitialized(VariableElement element) { + Constant initialValue = compileVariable(element); + return initialValue == null; + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [functionElement] must be an implementation element. + */ + HGraph buildMethod(FunctionElement functionElement) { + assert(invariant(functionElement, functionElement.isImplementation)); + FunctionExpression function = functionElement.parseNode(compiler); + assert(function != null); + assert(!function.modifiers.isExternal()); + assert(elements[function] != null); + openFunction(functionElement, function); + SourceString name = functionElement.name; + // If [functionElement] is operator== we explicitely add a null + // check at the beginning of the method. This is to avoid having + // call sites do the null check. + if (name == const SourceString('==')) { + handleIf( + function, + () { + HParameterValue parameter = parameters.values.first; + push(new HIdentity( + parameter, graph.addConstantNull(constantSystem))); + }, + () { + HReturn ret = new HReturn( + graph.addConstantBool(false, constantSystem)); + close(ret).addSuccessor(graph.exit); + }, + null); + } + function.body.accept(this); + return closeFunction(); + } + + HGraph buildLazyInitializer(VariableElement variable) { + SendSet node = variable.parseNode(compiler); + openFunction(variable, node); + Link<Node> link = node.arguments; + assert(!link.isEmpty && link.tail.isEmpty); + visit(link.head); + HInstruction value = pop(); + value = potentiallyCheckType(value, variable.computeType(compiler)); + close(new HReturn(value)).addSuccessor(graph.exit); + return closeFunction(); + } + + /** + * Returns the constructor body associated with the given constructor or + * creates a new constructor body, if none can be found. + * + * Returns [:null:] if the constructor does not have a body. + */ + ConstructorBodyElement getConstructorBody(FunctionElement constructor) { + assert(constructor.isGenerativeConstructor()); + assert(invariant(constructor, constructor.isImplementation)); + if (constructor.isSynthesized) return null; + FunctionExpression node = constructor.parseNode(compiler); + // If we know the body doesn't have any code, we don't generate it. + if (!node.hasBody()) return null; + if (node.hasEmptyBody()) return null; + ClassElement classElement = constructor.getEnclosingClass(); + ConstructorBodyElement bodyElement; + classElement.forEachBackendMember((Element backendMember) { + if (backendMember.isGenerativeConstructorBody()) { + ConstructorBodyElement body = backendMember; + if (body.constructor == constructor) { + // TODO(kasperl): Find a way of stopping the iteration + // through the backend members. + bodyElement = backendMember; + } + } + }); + if (bodyElement == null) { + bodyElement = new ConstructorBodyElementX(constructor); + // [:resolveMethodElement:] require the passed element to be a + // declaration. + TreeElements treeElements = + compiler.enqueuer.resolution.getCachedElements( + constructor.declaration); + classElement.addBackendMember(bodyElement); + + if (constructor.isPatch) { + // Create origin body element for patched constructors. + bodyElement.origin = new ConstructorBodyElementX(constructor.origin); + bodyElement.origin.patch = bodyElement; + classElement.origin.addBackendMember(bodyElement.origin); + } + compiler.enqueuer.codegen.addToWorkList(bodyElement.declaration, + treeElements); + } + assert(bodyElement.isGenerativeConstructorBody()); + return bodyElement; + } + + HParameterValue addParameter(Element element) { + HParameterValue result = new HParameterValue(element); + if (lastAddedParameter == null) { + graph.entry.addBefore(graph.entry.first, result); + } else { + graph.entry.addAfter(lastAddedParameter, result); + } + lastAddedParameter = result; + return result; + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + InliningState enterInlinedMethod(PartialFunctionElement function, + Selector selector, + Link<Node> arguments, + Node currentNode) { + assert(invariant(function, function.isImplementation)); + + // Once we start to compile the arguments we must be sure that we don't + // abort. + List<HInstruction> compiledArguments = new List<HInstruction>(); + bool succeeded = addStaticSendArgumentsToList(selector, + arguments, + function, + compiledArguments); + assert(succeeded); + + FunctionSignature signature = function.computeSignature(compiler); + int index = 0; + signature.orderedForEachParameter((Element parameter) { + HInstruction argument = compiledArguments[index++]; + localsHandler.updateLocal(parameter, argument); + potentiallyCheckType(argument, parameter.computeType(compiler)); + }); + + if (function.isConstructor()) { + ClassElement enclosing = function.getEnclosingClass(); + if (compiler.world.needsRti(enclosing)) { + assert(currentNode is NewExpression); + InterfaceType type = elements.getType(currentNode); + Link<DartType> typeVariable = enclosing.typeVariables; + type.typeArguments.forEach((DartType argument) { + HInstruction instruction = + analyzeTypeArgument(argument, currentNode); + localsHandler.updateLocal(typeVariable.head.element, instruction); + typeVariable = typeVariable.tail; + }); + while (!typeVariable.isEmpty) { + localsHandler.updateLocal(typeVariable.head.element, + graph.addConstantNull(constantSystem)); + typeVariable = typeVariable.tail; + } + } + } + InliningState state = + new InliningState(function, returnElement, returnType, elements, stack); + + // TODO(kasperl): Bad smell. We shouldn't be constructing elements here. + returnElement = new ElementX(const SourceString("result"), + ElementKind.VARIABLE, + function); + localsHandler.updateLocal(returnElement, + graph.addConstantNull(constantSystem)); + elements = compiler.enqueuer.resolution.getCachedElements(function); + assert(elements != null); + returnType = signature.returnType; + stack = <HInstruction>[]; + inliningStack.add(state); + return state; + } + + void leaveInlinedMethod(InliningState state) { + InliningState poppedState = inliningStack.removeLast(); + assert(state == poppedState); + elements = state.oldElements; + stack.add(localsHandler.readLocal(returnElement)); + returnElement = state.oldReturnElement; + returnType = state.oldReturnType; + assert(stack.length == 1); + state.oldStack.add(stack[0]); + stack = state.oldStack; + } + + /** + * Try to inline [element] within the currect context of the + * builder. The insertion point is the state of the builder. + */ + bool tryInlineMethod(Element element, + Selector selector, + Link<Node> arguments, + Node currentNode) { + if (compiler.disableInlining) return false; + // Ensure that [element] is an implementation element. + element = element.implementation; + // TODO(floitsch): we should be able to inline inside lazy initializers. + if (!currentElement.isFunction()) return false; + // TODO(floitsch): find a cleaner way to know if the element is a function + // containing nodes. + // [PartialFunctionElement]s are [FunctionElement]s that have [Node]s. + if (element is !PartialFunctionElement) return false; + // TODO(ngeoffray): try to inline generative constructors. They + // don't have any body, which make it more difficult. + if (element.isGenerativeConstructor()) return false; + if (inliningStack.length > MAX_INLINING_DEPTH) return false; + // Don't inline recursive calls. We use the same elements for the inlined + // functions and would thus clobber our local variables. + // Use [:element.declaration:] since [work.element] is always a declaration. + if (currentElement == element.declaration) return false; + for (int i = 0; i < inliningStack.length; i++) { + if (inliningStack[i].function == element) return false; + } + PartialFunctionElement function = element; + int sourceSize = + function.endToken.charOffset - function.beginToken.charOffset; + if (sourceSize > MAX_INLINING_SOURCE_SIZE) return false; + if (!selector.applies(function, compiler)) return false; + FunctionExpression functionExpression = function.parseNode(compiler); + TreeElements newElements = + compiler.enqueuer.resolution.getCachedElements(function); + if (newElements == null) { + compiler.internalError("Element not resolved: $function"); + } + if (!InlineWeeder.canBeInlined(functionExpression, newElements)) { + return false; + } + + InliningState state = enterInlinedMethod( + function, selector, arguments, currentNode); + inlinedFrom(element, () { + functionExpression.body.accept(this); + }); + leaveInlinedMethod(state); + return true; + } + + inlinedFrom(Element element, f()) { + return compiler.withCurrentElement(element, () { + sourceElementStack.add(element); + var result = f(); + sourceElementStack.removeLast(); + return result; + }); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [constructor] and [constructors] must all be implementation + * elements. + */ + void inlineSuperOrRedirect(FunctionElement constructor, + Selector selector, + Link<Node> arguments, + List<FunctionElement> constructors, + Map<Element, HInstruction> fieldValues, + FunctionElement inlinedFromElement) { + compiler.withCurrentElement(constructor, () { + assert(invariant(constructor, constructor.isImplementation)); + constructors.addLast(constructor); + + List<HInstruction> compiledArguments = new List<HInstruction>(); + bool succeeded = + inlinedFrom(inlinedFromElement, + () => addStaticSendArgumentsToList(selector, + arguments, + constructor, + compiledArguments)); + if (!succeeded) { + // Non-matching super and redirects are compile-time errors and thus + // checked by the resolver. + compiler.internalError( + "Parameters and arguments didn't match for super/redirect call", + element: constructor); + } + + sourceElementStack.add(constructor.enclosingElement); + buildFieldInitializers(constructor.enclosingElement.implementation, + fieldValues); + sourceElementStack.removeLast(); + + int index = 0; + FunctionSignature params = constructor.computeSignature(compiler); + params.orderedForEachParameter((Element parameter) { + HInstruction argument = compiledArguments[index++]; + // Because we are inlining the initializer, we must update + // what was given as parameter. This will be used in case + // there is a parameter check expression in the initializer. + parameters[parameter] = argument; + localsHandler.updateLocal(parameter, argument); + // Don't forget to update the field, if the parameter is of the + // form [:this.x:]. + if (parameter.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParameterElement = parameter; + fieldValues[fieldParameterElement.fieldElement] = argument; + } + }); + + // Build the initializers in the context of the new constructor. + TreeElements oldElements = elements; + elements = + compiler.enqueuer.resolution.getCachedElements(constructor); + + ClosureClassMap oldClosureData = localsHandler.closureData; + Node node = constructor.parseNode(compiler); + ClosureClassMap newClosureData = + compiler.closureToClassMapper.computeClosureToClassMapping( + constructor, node, elements); + // The [:this:] element now refers to the one in the new closure + // data, that is the [:this:] of the super constructor. We + // update the element to refer to the current [:this:]. + localsHandler.updateLocal(newClosureData.thisElement, + localsHandler.readThis()); + localsHandler.closureData = newClosureData; + + params.orderedForEachParameter((Element parameterElement) { + if (elements.isParameterChecked(parameterElement)) { + addParameterCheckInstruction(parameterElement); + } + }); + localsHandler.enterScope(node, constructor); + buildInitializers(constructor, constructors, fieldValues); + localsHandler.closureData = oldClosureData; + elements = oldElements; + }); + } + + /** + * Run through the initializers and inline all field initializers. Recursively + * inlines super initializers. + * + * The constructors of the inlined initializers is added to [constructors] + * with sub constructors having a lower index than super constructors. + * + * Invariant: The [constructor] and elements in [constructors] must all be + * implementation elements. + */ + void buildInitializers(FunctionElement constructor, + List<FunctionElement> constructors, + Map<Element, HInstruction> fieldValues) { + assert(invariant(constructor, constructor.isImplementation)); + FunctionExpression functionNode = constructor.parseNode(compiler); + + bool foundSuperOrRedirect = false; + + if (functionNode.initializers != null) { + Link<Node> initializers = functionNode.initializers.nodes; + for (Link<Node> link = initializers; !link.isEmpty; link = link.tail) { + assert(link.head is Send); + if (link.head is !SendSet) { + // A super initializer or constructor redirection. + Send call = link.head; + assert(Initializers.isSuperConstructorCall(call) || + Initializers.isConstructorRedirect(call)); + FunctionElement target = elements[call]; + Selector selector = elements.getSelector(call); + Link<Node> arguments = call.arguments; + inlineSuperOrRedirect(target, selector, arguments, constructors, + fieldValues, constructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link<Node> arguments = init.arguments; + assert(!arguments.isEmpty && arguments.tail.isEmpty); + sourceElementStack.add(constructor); + visit(arguments.head); + sourceElementStack.removeLast(); + fieldValues[elements[init]] = pop(); + } + } + } + + if (!foundSuperOrRedirect) { + // No super initializer found. Try to find the default constructor if + // the class is not Object. + ClassElement enclosingClass = constructor.getEnclosingClass(); + ClassElement superClass = enclosingClass.superclass; + if (!enclosingClass.isObject(compiler)) { + assert(superClass != null); + assert(superClass.resolutionState == STATE_DONE); + Selector selector = + new Selector.callDefaultConstructor(enclosingClass.getLibrary()); + // TODO(johnniwinther): Should we find injected constructors as well? + FunctionElement target = superClass.lookupConstructor(selector); + if (target == null) { + compiler.internalError("no default constructor available"); + } + inlineSuperOrRedirect(target.implementation, + selector, + const Link<Node>(), + constructors, + fieldValues, + constructor); + } + } + } + + /** + * Run through the fields of [cls] and add their potential + * initializers. + * + * Invariant: [classElement] must be an implementation element. + */ + void buildFieldInitializers(ClassElement classElement, + Map<Element, HInstruction> fieldValues) { + assert(invariant(classElement, classElement.isImplementation)); + classElement.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + TreeElements definitions = compiler.analyzeElement(member); + Node node = member.parseNode(compiler); + SendSet assignment = node.asSendSet(); + HInstruction value; + if (assignment == null) { + value = graph.addConstantNull(constantSystem); + } else { + Node right = assignment.arguments.head; + TreeElements savedElements = elements; + elements = definitions; + right.accept(this); + elements = savedElements; + value = pop(); + } + fieldValues[member] = value; + }, + includeBackendMembers: true, + includeSuperMembers: false); + } + + + /** + * Build the factory function corresponding to the constructor + * [functionElement]: + * - Initialize fields with the values of the field initializers of the + * current constructor and super constructors or constructors redirected + * to, starting from the current constructor. + * - Call the the constructor bodies, starting from the constructor(s) in the + * super class(es). + * + * Invariant: Both [classElement] and [functionElement] must be + * implementation elements. + */ + HGraph buildFactory(ClassElement classElement, + FunctionElement functionElement) { + assert(invariant(classElement, classElement.isImplementation)); + assert(invariant(functionElement, functionElement.isImplementation)); + FunctionExpression function = functionElement.parseNode(compiler); + // Note that constructors (like any other static function) do not need + // to deal with optional arguments. It is the callers job to provide all + // arguments as if they were positional. + + // The initializer list could contain closures. + openFunction(functionElement, function); + + Map<Element, HInstruction> fieldValues = new Map<Element, HInstruction>(); + + // Compile the possible initialization code for local fields and + // super fields. + buildFieldInitializers(classElement, fieldValues); + + // Compile field-parameters such as [:this.x:]. + FunctionSignature params = functionElement.computeSignature(compiler); + params.orderedForEachParameter((Element element) { + if (element.kind == ElementKind.FIELD_PARAMETER) { + // If the [element] is a field-parameter then + // initialize the field element with its value. + FieldParameterElement fieldParameterElement = element; + HInstruction parameterValue = localsHandler.readLocal(element); + fieldValues[fieldParameterElement.fieldElement] = parameterValue; + } + }); + + // Analyze the constructor and all referenced constructors and collect + // initializers and constructor bodies. + List<FunctionElement> constructors = <FunctionElement>[functionElement]; + buildInitializers(functionElement, constructors, fieldValues); + + // Call the JavaScript constructor with the fields as argument. + List<HInstruction> constructorArguments = <HInstruction>[]; + classElement.forEachInstanceField( + (ClassElement enclosingClass, Element member) { + constructorArguments.add(potentiallyCheckType( + fieldValues[member], member.computeType(compiler))); + }, + includeBackendMembers: true, + includeSuperMembers: true); + + InterfaceType type = classElement.computeType(compiler); + HType ssaType = new HBoundedType.exact(type); + HForeignNew newObject = new HForeignNew(classElement, + ssaType, + constructorArguments); + add(newObject); + + // Create the runtime type information, if needed. + List<HInstruction> inputs = <HInstruction>[]; + if (compiler.world.needsRti(classElement)) { + classElement.typeVariables.forEach((TypeVariableType typeVariable) { + inputs.add(localsHandler.directLocals[typeVariable.element]); + }); + callSetRuntimeTypeInfo(classElement, inputs, newObject); + } + + // Generate calls to the constructor bodies. + for (int index = constructors.length - 1; index >= 0; index--) { + FunctionElement constructor = constructors[index]; + assert(invariant(functionElement, constructor.isImplementation)); + ConstructorBodyElement body = getConstructorBody(constructor); + if (body == null) continue; + List bodyCallInputs = <HInstruction>[]; + bodyCallInputs.add(newObject); + FunctionSignature functionSignature = body.computeSignature(compiler); + functionSignature.orderedForEachParameter((parameter) { + if (!localsHandler.isBoxed(parameter)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to pass it. + bodyCallInputs.add(localsHandler.readLocal(parameter)); + } + }); + + // If parameters are checked, we pass the already computed + // boolean to the constructor body. + TreeElements elements = + compiler.enqueuer.resolution.getCachedElements(constructor); + Node node = constructor.parseNode(compiler); + ClosureClassMap parameterClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(node); + functionSignature.orderedForEachParameter((parameter) { + if (elements.isParameterChecked(parameter)) { + Element fieldCheck = + parameterClosureData.parametersWithSentinel[parameter]; + bodyCallInputs.add(localsHandler.readLocal(fieldCheck)); + } + }); + + // If there are locals that escape (ie used in closures), we + // pass the box to the constructor. + ClosureScope scopeData = parameterClosureData.capturingScopes[node]; + if (scopeData != null) { + bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement)); + } + + // TODO(ahe): The constructor name is statically resolved. See + // SsaCodeGenerator.visitInvokeDynamicMethod. Is there a cleaner + // way to do this? + SourceString name = + new SourceString(backend.namer.getName(body.declaration)); + // TODO(kasperl): This seems fishy. We shouldn't be inventing all + // these selectors. Maybe the resolver can do more of the work + // for us here? + LibraryElement library = body.getLibrary(); + Selector selector = new Selector.call( + name, library, bodyCallInputs.length - 1); + HInvokeDynamic invoke = + new HInvokeDynamicMethod(selector, bodyCallInputs); + invoke.element = body; + add(invoke); + } + close(new HReturn(newObject)).addSuccessor(graph.exit); + return closeFunction(); + } + + void addParameterCheckInstruction(Element element) { + HInstruction check; + Element checkResultElement = + localsHandler.closureData.parametersWithSentinel[element]; + if (currentElement.isGenerativeConstructorBody()) { + // A generative constructor body receives extra parameters that + // indicate if a parameter was passed to the factory. + check = addParameter(checkResultElement); + } else { + // This is the code we emit for a parameter that is being checked + // on whether it was given at value at the call site: + // + // foo([a = 42]) { + // if (?a) print('parameter passed $a'); + // } + // + // foo([a = 42]) { + // var t1 = identical(a, sentinel); + // if (t1) a = 42; + // if (!t1) print('parameter passed ' + a); + // } + + // Fetch the original default value of [element]; + Constant constant = compileVariable(element); + HConstant defaultValue = constant == null + ? graph.addConstantNull(constantSystem) + : graph.addConstant(constant); + + // Emit the equality check with the sentinel. + HConstant sentinel = graph.addConstant(SentinelConstant.SENTINEL); + HInstruction operand = parameters[element]; + check = new HIdentity(sentinel, operand); + add(check); + + // If the check succeeds, we must update the parameter with the + // default value. + handleIf(element.parseNode(compiler), + () => stack.add(check), + () => localsHandler.updateLocal(element, defaultValue), + null); + + // Create the instruction that parameter checks will use. + check = new HNot(check); + add(check); + } + + localsHandler.updateLocal(checkResultElement, check); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [functionElement] must be the implementation element. + */ + void openFunction(Element element, Expression node) { + assert(invariant(element, element.isImplementation)); + HBasicBlock block = graph.addNewBlock(); + open(graph.entry); + + localsHandler.startFunction(element, node); + close(new HGoto()).addSuccessor(block); + + open(block); + + if (element is FunctionElement) { + FunctionElement functionElement = element; + FunctionSignature signature = functionElement.computeSignature(compiler); + signature.orderedForEachParameter((Element parameterElement) { + if (elements.isParameterChecked(parameterElement)) { + addParameterCheckInstruction(parameterElement); + } + }); + + // Put the type checks in the first successor of the entry, + // because that is where the type guards will also be inserted. + // This way we ensure that a type guard will dominate the type + // check. + signature.orderedForEachParameter((Element parameterElement) { + if (element.isGenerativeConstructorBody()) { + ClosureScope scopeData = + localsHandler.closureData.capturingScopes[node]; + if (scopeData != null + && scopeData.capturedVariableMapping.containsKey( + parameterElement)) { + // The parameter will be a field in the box passed as the + // last parameter. So no need to have it. + return; + } + } + HInstruction newParameter = potentiallyCheckType( + localsHandler.directLocals[parameterElement], + parameterElement.computeType(compiler)); + localsHandler.directLocals[parameterElement] = newParameter; + }); + + returnType = signature.returnType; + } else { + // Otherwise it is a lazy initializer which does not have parameters. + assert(element is VariableElement); + } + + // Add the type parameters of the class as parameters of this + // method. + var enclosing = element.enclosingElement; + if (element.isConstructor() && compiler.world.needsRti(enclosing)) { + enclosing.typeVariables.forEach((TypeVariableType typeVariable) { + HParameterValue param = addParameter(typeVariable.element); + localsHandler.directLocals[typeVariable.element] = param; + }); + } + } + + HInstruction potentiallyCheckType( + HInstruction original, DartType type, + { int kind: HTypeConversion.CHECKED_MODE_CHECK }) { + if (!compiler.enableTypeAssertions) return original; + HInstruction other = original.convertType(compiler, type, kind); + if (other != original) add(other); + return other; + } + + HGraph closeFunction() { + // TODO(kasperl): Make this goto an implicit return. + if (!isAborted()) close(new HGoto()).addSuccessor(graph.exit); + graph.finalize(); + return graph; + } + + HBasicBlock addNewBlock() { + HBasicBlock block = graph.addNewBlock(); + // If adding a new block during building of an expression, it is due to + // conditional expressions or short-circuit logical operators. + return block; + } + + void open(HBasicBlock block) { + block.open(); + current = block; + lastOpenedBlock = block; + } + + HBasicBlock close(HControlFlow end) { + HBasicBlock result = current; + current.close(end); + current = null; + return result; + } + + void goto(HBasicBlock from, HBasicBlock to) { + from.close(new HGoto()); + from.addSuccessor(to); + } + + bool isAborted() { + return current == null; + } + + /** + * Creates a new block, transitions to it from any current block, and + * opens the new block. + */ + HBasicBlock openNewBlock() { + HBasicBlock newBlock = addNewBlock(); + if (!isAborted()) goto(current, newBlock); + open(newBlock); + return newBlock; + } + + void add(HInstruction instruction) { + current.add(instruction); + } + + void addWithPosition(HInstruction instruction, Node node) { + add(attachPosition(instruction, node)); + } + + void push(HInstruction instruction) { + add(instruction); + stack.add(instruction); + } + + void pushWithPosition(HInstruction instruction, Node node) { + push(attachPosition(instruction, node)); + } + + HInstruction pop() { + return stack.removeLast(); + } + + void dup() { + stack.add(stack.last); + } + + HInstruction popBoolified() { + HInstruction value = pop(); + if (compiler.enableTypeAssertions) { + return potentiallyCheckType( + value, + compiler.boolClass.computeType(compiler), + kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK); + } + HInstruction result = new HBoolify(value); + add(result); + return result; + } + + HInstruction attachPosition(HInstruction target, Node node) { + target.sourcePosition = sourceFileLocationForBeginToken(node); + return target; + } + + SourceFileLocation sourceFileLocationForBeginToken(Node node) => + sourceFileLocationForToken(node, node.getBeginToken()); + + SourceFileLocation sourceFileLocationForEndToken(Node node) => + sourceFileLocationForToken(node, node.getEndToken()); + + SourceFileLocation sourceFileLocationForToken(Node node, Token token) { + Element element = sourceElementStack.last; + // TODO(johnniwinther): remove the 'element.patch' hack. + if (element is FunctionElement) { + FunctionElement functionElement = element; + if (functionElement.patch != null) element = functionElement.patch; + } + Script script = element.getCompilationUnit().script; + SourceFile sourceFile = script.file; + SourceFileLocation location = new SourceFileLocation(sourceFile, token); + if (!location.isValid()) { + throw MessageKind.INVALID_SOURCE_FILE_LOCATION.message( + {'offset': token.charOffset, + 'fileName': sourceFile.filename, + 'length': sourceFile.text.length}); + } + return location; +} + + void visit(Node node) { + if (node != null) node.accept(this); + } + + visitBlock(Block node) { + for (Link<Node> link = node.statements.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + if (isAborted()) { + // The block has been aborted by a return or a throw. + if (!stack.isEmpty) compiler.cancel('non-empty instruction stack'); + return; + } + } + assert(!current.isClosed()); + if (!stack.isEmpty) compiler.cancel('non-empty instruction stack'); + } + + visitClassNode(ClassNode node) { + compiler.internalError('visitClassNode should not be called', node: node); + } + + visitExpressionStatement(ExpressionStatement node) { + visit(node.expression); + pop(); + } + + /** + * Creates a new loop-header block. The previous [current] block + * is closed with an [HGoto] and replaced by the newly created block. + * Also notifies the locals handler that we're entering a loop. + */ + JumpHandler beginLoopHeader(Node node) { + assert(!isAborted()); + HBasicBlock previousBlock = close(new HGoto()); + + JumpHandler jumpHandler = createJumpHandler(node); + HBasicBlock loopEntry = graph.addNewLoopHeaderBlock( + jumpHandler.target, + jumpHandler.labels()); + previousBlock.addSuccessor(loopEntry); + open(loopEntry); + + localsHandler.beginLoopHeader(node, loopEntry); + return jumpHandler; + } + + /** + * Ends the loop: + * - creates a new block and adds it as successor to the [branchBlock]. + * - opens the new block (setting as [current]). + * - notifies the locals handler that we're exiting a loop. + */ + void endLoop(HBasicBlock loopEntry, + HBasicBlock branchBlock, + JumpHandler jumpHandler, + LocalsHandler savedLocals) { + if (branchBlock == null && !jumpHandler.hasAnyBreak()) return; + + HBasicBlock loopExitBlock = addNewBlock(); + assert(branchBlock == null || branchBlock.successors.length == 1); + List<LocalsHandler> breakLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { + breakInstruction.block.addSuccessor(loopExitBlock); + breakLocals.add(locals); + }); + if (branchBlock != null) { + branchBlock.addSuccessor(loopExitBlock); + } + open(loopExitBlock); + localsHandler.endLoop(loopEntry); + if (!breakLocals.isEmpty) { + breakLocals.add(savedLocals); + localsHandler = savedLocals.mergeMultiple(breakLocals, loopExitBlock); + } else { + localsHandler = savedLocals; + } + } + + HSubGraphBlockInformation wrapStatementGraph(SubGraph statements) { + if (statements == null) return null; + return new HSubGraphBlockInformation(statements); + } + + HSubExpressionBlockInformation wrapExpressionGraph(SubExpression expression) { + if (expression == null) return null; + return new HSubExpressionBlockInformation(expression); + } + + // For while loops, initializer and update are null. + // The condition function must return a boolean result. + // None of the functions must leave anything on the stack. + void handleLoop(Node loop, + void initialize(), + HInstruction condition(), + void update(), + void body()) { + // Generate: + // <initializer> + // loop-entry: + // if (!<condition>) goto loop-exit; + // <body> + // <updates> + // goto loop-entry; + // loop-exit: + + localsHandler.startLoop(loop); + + // The initializer. + SubExpression initializerGraph = null; + HBasicBlock startBlock; + if (initialize != null) { + HBasicBlock initializerBlock = openNewBlock(); + startBlock = initializerBlock; + initialize(); + assert(!isAborted()); + initializerGraph = + new SubExpression(initializerBlock, current); + } + + JumpHandler jumpHandler = beginLoopHeader(loop); + HLoopInformation loopInfo = current.loopInformation; + HBasicBlock conditionBlock = current; + if (startBlock == null) startBlock = conditionBlock; + + HInstruction conditionInstruction = condition(); + HBasicBlock conditionExitBlock = + close(new HLoopBranch(conditionInstruction)); + SubExpression conditionExpression = + new SubExpression(conditionBlock, conditionExitBlock); + + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + + // The body. + HBasicBlock beginBodyBlock = addNewBlock(); + conditionExitBlock.addSuccessor(beginBodyBlock); + open(beginBodyBlock); + + localsHandler.enterLoopBody(loop); + body(); + + SubGraph bodyGraph = new SubGraph(beginBodyBlock, lastOpenedBlock); + HBasicBlock bodyBlock = current; + if (current != null) close(new HGoto()); + + SubExpression updateGraph; + + // Check that the loop has at least one back-edge. + if (jumpHandler.hasAnyContinue() || bodyBlock != null) { + // Update. + // We create an update block, even when we are in a while loop. There the + // update block is the jump-target for continue statements. We could avoid + // the creation if there is no continue, but for now we always create it. + HBasicBlock updateBlock = addNewBlock(); + + List<LocalsHandler> continueLocals = <LocalsHandler>[]; + jumpHandler.forEachContinue((HContinue instruction, + LocalsHandler locals) { + instruction.block.addSuccessor(updateBlock); + continueLocals.add(locals); + }); + + + if (bodyBlock != null) { + continueLocals.add(localsHandler); + bodyBlock.addSuccessor(updateBlock); + } + + open(updateBlock); + localsHandler = + continueLocals[0].mergeMultiple(continueLocals, updateBlock); + + HLabeledBlockInformation labelInfo; + List<LabelElement> labels = jumpHandler.labels(); + TargetElement target = elements[loop]; + if (!labels.isEmpty) { + beginBodyBlock.setBlockFlow( + new HLabeledBlockInformation( + new HSubGraphBlockInformation(bodyGraph), + jumpHandler.labels(), + isContinue: true), + updateBlock); + } else if (target != null && target.isContinueTarget) { + beginBodyBlock.setBlockFlow( + new HLabeledBlockInformation.implicit( + new HSubGraphBlockInformation(bodyGraph), + target, + isContinue: true), + updateBlock); + } + + localsHandler.enterLoopUpdates(loop); + + update(); + + HBasicBlock updateEndBlock = close(new HGoto()); + // The back-edge completing the cycle. + updateEndBlock.addSuccessor(conditionBlock); + updateGraph = new SubExpression(updateBlock, updateEndBlock); + } + + if (jumpHandler.hasAnyContinue() || bodyBlock != null) { + endLoop(conditionBlock, conditionExitBlock, jumpHandler, savedLocals); + conditionBlock.postProcessLoopHeader(); + HLoopBlockInformation info = + new HLoopBlockInformation( + HLoopBlockInformation.loopType(loop), + wrapExpressionGraph(initializerGraph), + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + wrapExpressionGraph(updateGraph), + conditionBlock.loopInformation.target, + conditionBlock.loopInformation.labels, + sourceFileLocationForBeginToken(loop), + sourceFileLocationForEndToken(loop)); + + startBlock.setBlockFlow(info, current); + loopInfo.loopBlockInformation = info; + } else { + // There is no back edge for the loop, so we turn the code into: + // if (condition) { + // body; + // } else { + // // We always create an empty else block to avoid critical edges. + // } + // + // If there is any break in the body, we attach a synthetic + // label to the if. + HBasicBlock elseBlock = addNewBlock(); + open(elseBlock); + close(new HGoto()); + endLoop(conditionBlock, null, jumpHandler, savedLocals); + + // [endLoop] will not create an exit block if there are no + // breaks. + if (current == null) open(addNewBlock()); + elseBlock.addSuccessor(current); + SubGraph elseGraph = new SubGraph(elseBlock, elseBlock); + // Remove the loop information attached to the header. + conditionBlock.loopInformation = null; + + // Remove the [HLoopBranch] instruction and replace it with + // [HIf]. + HInstruction condition = conditionExitBlock.last.inputs[0]; + conditionExitBlock.addAtExit(new HIf(condition)); + conditionExitBlock.addSuccessor(elseBlock); + conditionExitBlock.remove(conditionExitBlock.last); + HIfBlockInformation info = + new HIfBlockInformation( + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + wrapStatementGraph(elseGraph)); + + conditionBlock.setBlockFlow(info, current); + HIf ifBlock = conditionBlock.last; + ifBlock.blockInformation = conditionBlock.blockFlow; + + // If the body has any break, attach a synthesized label to the + // if block. + if (jumpHandler.hasAnyBreak()) { + TargetElement target = elements[loop]; + LabelElement label = target.addLabel(null, 'loop'); + label.setBreakTarget(); + SubGraph labelGraph = new SubGraph(conditionBlock, current); + HLabeledBlockInformation labelInfo = new HLabeledBlockInformation( + new HSubGraphBlockInformation(labelGraph), + <LabelElement>[label]); + + conditionBlock.setBlockFlow(labelInfo, current); + + jumpHandler.forEachBreak((HBreak breakInstruction, _) { + HBasicBlock block = breakInstruction.block; + block.addAtExit(new HBreak.toLabel(label)); + block.remove(breakInstruction); + }); + } + } + jumpHandler.close(); + } + + visitFor(For node) { + assert(node.body != null); + void buildInitializer() { + if (node.initializer == null) return; + Node initializer = node.initializer; + if (initializer != null) { + visit(initializer); + if (initializer.asExpression() != null) { + pop(); + } + } + } + HInstruction buildCondition() { + if (node.condition == null) { + return graph.addConstantBool(true, constantSystem); + } + visit(node.condition); + return popBoolified(); + } + void buildUpdate() { + for (Expression expression in node.update) { + visit(expression); + assert(!isAborted()); + // The result of the update instruction isn't used, and can just + // be dropped. + HInstruction updateInstruction = pop(); + } + } + void buildBody() { + visit(node.body); + } + handleLoop(node, buildInitializer, buildCondition, buildUpdate, buildBody); + } + + visitWhile(While node) { + HInstruction buildCondition() { + visit(node.condition); + return popBoolified(); + } + handleLoop(node, + () {}, + buildCondition, + () {}, + () { visit(node.body); }); + } + + visitDoWhile(DoWhile node) { + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + localsHandler.startLoop(node); + JumpHandler jumpHandler = beginLoopHeader(node); + HLoopInformation loopInfo = current.loopInformation; + HBasicBlock loopEntryBlock = current; + HBasicBlock bodyEntryBlock = current; + TargetElement target = elements[node]; + bool hasContinues = target != null && target.isContinueTarget; + if (hasContinues) { + // Add extra block to hang labels on. + // It doesn't currently work if they are on the same block as the + // HLoopInfo. The handling of HLabeledBlockInformation will visit a + // SubGraph that starts at the same block again, so the HLoopInfo is + // either handled twice, or it's handled after the labeled block info, + // both of which generate the wrong code. + // Using a separate block is just a simple workaround. + bodyEntryBlock = openNewBlock(); + } + localsHandler.enterLoopBody(node); + visit(node.body); + + // If there are no continues we could avoid the creation of the condition + // block. This could also lead to a block having multiple entries and exits. + HBasicBlock bodyExitBlock; + bool isAbortingBody = false; + if (current != null) { + bodyExitBlock = close(new HGoto()); + } else { + isAbortingBody = true; + bodyExitBlock = lastOpenedBlock; + } + + SubExpression conditionExpression; + HBasicBlock conditionEndBlock; + if (!isAbortingBody || hasContinues) { + HBasicBlock conditionBlock = addNewBlock(); + + List<LocalsHandler> continueLocals = <LocalsHandler>[]; + jumpHandler.forEachContinue((HContinue instruction, + LocalsHandler locals) { + instruction.block.addSuccessor(conditionBlock); + continueLocals.add(locals); + }); + + if (!isAbortingBody) { + bodyExitBlock.addSuccessor(conditionBlock); + } + + if (!continueLocals.isEmpty) { + if (!isAbortingBody) continueLocals.add(localsHandler); + localsHandler = + savedLocals.mergeMultiple(continueLocals, conditionBlock); + SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); + List<LabelElement> labels = jumpHandler.labels(); + HSubGraphBlockInformation bodyInfo = + new HSubGraphBlockInformation(bodyGraph); + HLabeledBlockInformation info; + if (!labels.isEmpty) { + info = new HLabeledBlockInformation(bodyInfo, labels, + isContinue: true); + } else { + info = new HLabeledBlockInformation.implicit(bodyInfo, target, + isContinue: true); + } + bodyEntryBlock.setBlockFlow(info, conditionBlock); + } + open(conditionBlock); + + visit(node.condition); + assert(!isAborted()); + HInstruction conditionInstruction = popBoolified(); + conditionEndBlock = close( + new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP)); + + HBasicBlock avoidCriticalEdge = addNewBlock(); + conditionEndBlock.addSuccessor(avoidCriticalEdge); + open(avoidCriticalEdge); + close(new HGoto()); + avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge. + + conditionExpression = + new SubExpression(conditionBlock, conditionEndBlock); + } + + endLoop(loopEntryBlock, conditionEndBlock, jumpHandler, localsHandler); + if (!isAbortingBody || hasContinues) { + loopEntryBlock.postProcessLoopHeader(); + SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock); + HLoopBlockInformation loopBlockInfo = + new HLoopBlockInformation( + HLoopBlockInformation.DO_WHILE_LOOP, + null, + wrapExpressionGraph(conditionExpression), + wrapStatementGraph(bodyGraph), + null, + loopEntryBlock.loopInformation.target, + loopEntryBlock.loopInformation.labels, + sourceFileLocationForBeginToken(node), + sourceFileLocationForEndToken(node)); + loopEntryBlock.setBlockFlow(loopBlockInfo, current); + loopInfo.loopBlockInformation = loopBlockInfo; + } else { + // If the loop has no back edge, we remove the loop information + // on the header. + loopEntryBlock.loopInformation = null; + + // If the body of the loop has any break, we attach a + // synthesized label to the body. + if (jumpHandler.hasAnyBreak()) { + SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock); + TargetElement target = elements[node]; + LabelElement label = target.addLabel(null, 'loop'); + label.setBreakTarget(); + HLabeledBlockInformation info = new HLabeledBlockInformation( + new HSubGraphBlockInformation(bodyGraph), <LabelElement>[label]); + loopEntryBlock.setBlockFlow(info, current); + jumpHandler.forEachBreak((HBreak breakInstruction, _) { + HBasicBlock block = breakInstruction.block; + block.addAtExit(new HBreak.toLabel(label)); + block.remove(breakInstruction); + }); + } + } + jumpHandler.close(); + } + + visitFunctionExpression(FunctionExpression node) { + ClosureClassMap nestedClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(node); + assert(nestedClosureData != null); + assert(nestedClosureData.closureClassElement != null); + ClassElement closureClassElement = + nestedClosureData.closureClassElement; + FunctionElement callElement = nestedClosureData.callElement; + // TODO(ahe): This should be registered in codegen, not here. + compiler.enqueuer.codegen.addToWorkList(callElement, elements); + // TODO(ahe): This should be registered in codegen, not here. + compiler.enqueuer.codegen.registerInstantiatedClass(closureClassElement); + assert(!closureClassElement.hasLocalScopeMembers); + + List<HInstruction> capturedVariables = <HInstruction>[]; + closureClassElement.forEachBackendMember((Element member) { + // The backendMembers also contains the call method(s). We are only + // interested in the fields. + if (member.isField()) { + Element capturedLocal = nestedClosureData.capturedFieldMapping[member]; + assert(capturedLocal != null); + capturedVariables.add(localsHandler.readLocal(capturedLocal)); + } + }); + + HType type = new HBoundedType.exact( + compiler.functionClass.computeType(compiler)); + push(new HForeignNew(closureClassElement, type, capturedVariables)); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visit(node.function); + localsHandler.updateLocal(elements[node], pop()); + } + + visitIdentifier(Identifier node) { + if (node.isThis()) { + stack.add(localsHandler.readThis()); + } else { + compiler.internalError("SsaBuilder.visitIdentifier on non-this", + node: node); + } + } + + visitIf(If node) { + handleIf(node, + () => visit(node.condition), + () => visit(node.thenPart), + node.elsePart != null ? () => visit(node.elsePart) : null); + } + + void handleIf(Node diagnosticNode, + void visitCondition(), void visitThen(), void visitElse()) { + SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, diagnosticNode); + branchBuilder.handleIf(visitCondition, visitThen, visitElse); + } + + void visitLogicalAndOr(Send node, Operator op) { + SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node); + branchBuilder.handleLogicalAndOrWithLeftNode( + node.receiver, + () { visit(node.argumentsNode); }, + isAnd: (const SourceString("&&") == op.source)); + } + + + void visitLogicalNot(Send node) { + assert(node.argumentsNode is Prefix); + visit(node.receiver); + HNot not = new HNot(popBoolified()); + pushWithPosition(not, node); + } + + void visitUnary(Send node, Operator op) { + if (node.isParameterCheck) { + Element element = elements[node.receiver]; + Node function = element.enclosingElement.parseNode(compiler); + ClosureClassMap parameterClosureData = + compiler.closureToClassMapper.getMappingForNestedFunction(function); + Element fieldCheck = + parameterClosureData.parametersWithSentinel[element]; + stack.add(localsHandler.readLocal(fieldCheck)); + return; + } + assert(node.argumentsNode is Prefix); + visit(node.receiver); + assert(!identical(op.token.kind, PLUS_TOKEN)); + HInstruction operand = pop(); + + // See if we can constant-fold right away. This avoids rewrites later on. + if (operand is HConstant) { + UnaryOperation operation = constantSystem.lookupUnary(op.source); + HConstant constant = operand; + Constant folded = operation.fold(constant.constant); + if (folded != null) { + stack.add(graph.addConstant(folded)); + return; + } + } + + HInvokeDynamicMethod result = + buildInvokeDynamic(node, elements.getSelector(node), operand, []); + pushWithPosition(result, node); + } + + void visitBinary( + HInstruction left, Operator op, HInstruction right, Send send) { + Selector selector = null; + // TODO(ngeoffray): The resolver creates these selectors already + // but does not put them on the [send] instruction. + switch (op.source.stringValue) { + case "+": + case "+=": + case "++": + selector = new Selector.binaryOperator(const SourceString('+')); + break; + case "-": + case "-=": + case "--": + selector = new Selector.binaryOperator(const SourceString('-')); + break; + case "*": + case "*=": + selector = new Selector.binaryOperator(const SourceString('*')); + break; + case "/": + case "/=": + selector = new Selector.binaryOperator(const SourceString('/')); + break; + case "~/": + case "~/=": + selector = new Selector.binaryOperator(const SourceString('~/')); + break; + case "%": + case "%=": + selector = new Selector.binaryOperator(const SourceString('%')); + break; + case "<<": + case "<<=": + selector = new Selector.binaryOperator(const SourceString('<<')); + break; + case ">>": + case ">>=": + selector = new Selector.binaryOperator(const SourceString('>>')); + break; + case "|": + case "|=": + selector = new Selector.binaryOperator(const SourceString('|')); + break; + case "&": + case "&=": + selector = new Selector.binaryOperator(const SourceString('&')); + break; + case "^": + case "^=": + selector = new Selector.binaryOperator(const SourceString('^')); + break; + case "==": + case "!=": + selector = new Selector.binaryOperator(const SourceString('==')); + break; + case "<": + selector = new Selector.binaryOperator(const SourceString('<')); + break; + case "<=": + selector = new Selector.binaryOperator(const SourceString('<=')); + break; + case ">": + selector = new Selector.binaryOperator(const SourceString('>')); + break; + case ">=": + selector = new Selector.binaryOperator(const SourceString('>=')); + break; + case "===": + pushWithPosition(new HIdentity(left, right), op); + return; + case "!==": + HIdentity eq = new HIdentity(left, right); + add(eq); + pushWithPosition(new HNot(eq), op); + return; + default: + compiler.internalError("Unexpected operator $op", node: op); + break; + } + + pushWithPosition( + buildInvokeDynamic(send, selector, left, [right]), + op); + if (op.source.stringValue == '!=') { + HBoolify bl = new HBoolify(pop()); + add(bl); + pushWithPosition(new HNot(bl), op); + } + } + + HInstruction generateInstanceSendReceiver(Send send) { + assert(Elements.isInstanceSend(send, elements)); + if (send.receiver == null) { + return localsHandler.readThis(); + } + visit(send.receiver); + return pop(); + } + + String getTargetName(ErroneousElement error, [String prefix]) { + String result = error.name.slowToString(); + if (?prefix) { + result = '$prefix $result'; + } + return result; + } + + /** + * Returns a set of interceptor classes that contain a member whose + * signature matches the given [selector]. + */ + Set<ClassElement> getInterceptedClassesOn(Selector selector) { + return backend.getInterceptedClassesOn(selector); + } + + void generateInstanceGetterWithCompiledReceiver(Send send, + HInstruction receiver) { + assert(Elements.isInstanceSend(send, elements)); + // TODO(kasperl): This is a convoluted way of checking if we're + // generating code for a compound assignment. If we are, we need + // to get the selector from the mapping for the AST selector node. + Selector selector = (send.asSendSet() == null) + ? elements.getSelector(send) + : elements.getSelector(send.selector); + assert(selector.isGetter()); + SourceString getterName = selector.name; + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + if (interceptedClasses != null) { + // If we're using an interceptor class, emit a call to the + // interceptor method and then the actual dynamic call on the + // interceptor object. + HInstruction instruction = + invokeInterceptor(interceptedClasses, receiver, send); + instruction = new HInvokeDynamicGetter( + selector, null, instruction, !hasGetter); + // Add the receiver as an argument to the getter call on the + // interceptor. + instruction.inputs.add(receiver); + pushWithPosition(instruction, send); + } else { + pushWithPosition( + new HInvokeDynamicGetter(selector, null, receiver, !hasGetter), send); + } + } + + void generateGetter(Send send, Element element) { + if (Elements.isStaticOrTopLevelField(element)) { + Constant value; + if (element.isField() && !element.isAssignable()) { + // A static final or const. Get its constant value and inline it if + // the value can be compiled eagerly. + value = compileVariable(element); + } + if (value != null) { + stack.add(graph.addConstant(value)); + } else if (element.isField() && isLazilyInitialized(element)) { + push(new HLazyStatic(element)); + } else { + if (element.isGetter()) { + Selector selector = elements.getSelector(send); + if (tryInlineMethod(element, selector, const Link<Node>(), send)) { + return; + } + } + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + push(new HStatic(element.declaration)); + if (element.isGetter()) { + push(new HInvokeStatic(<HInstruction>[pop()], HType.UNKNOWN)); + } + } + } else if (Elements.isInstanceSend(send, elements)) { + HInstruction receiver = generateInstanceSendReceiver(send); + generateInstanceGetterWithCompiledReceiver(send, receiver); + } else if (Elements.isStaticOrTopLevelFunction(element)) { + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + push(new HStatic(element.declaration)); + // TODO(ahe): This should be registered in codegen. + compiler.enqueuer.codegen.registerGetOfStaticFunction(element); + } else if (Elements.isErroneousElement(element)) { + // An erroneous element indicates an unresolved static getter. + generateThrowNoSuchMethod(send, + getTargetName(element, 'get'), + argumentNodes: const Link<Node>()); + } else { + stack.add(localsHandler.readLocal(element)); + } + } + + void generateInstanceSetterWithCompiledReceiver(Send send, + HInstruction receiver, + HInstruction value) { + assert(Elements.isInstanceSend(send, elements)); + Selector selector = elements.getSelector(send); + assert(selector.isSetter()); + SourceString setterName = selector.name; + bool hasSetter = compiler.world.hasAnyUserDefinedSetter(selector); + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + if (interceptedClasses != null) { + // If we're using an interceptor class, emit a call to the + // getInterceptor method and then the actual dynamic call on the + // interceptor object. + HInstruction instruction = + invokeInterceptor(interceptedClasses, receiver, send); + instruction = new HInvokeDynamicSetter( + selector, null, instruction, receiver, !hasSetter); + // Add the value as an argument to the setter call on the + // interceptor. + instruction.inputs.add(value); + addWithPosition(instruction, send); + } else { + addWithPosition( + new HInvokeDynamicSetter(selector, null, receiver, value, !hasSetter), + send); + } + stack.add(value); + } + + void generateSetter(SendSet send, Element element, HInstruction value) { + if (Elements.isStaticOrTopLevelField(element)) { + if (element.isSetter()) { + HStatic target = new HStatic(element); + add(target); + addWithPosition( + new HInvokeStatic(<HInstruction>[target, value], HType.UNKNOWN), + send); + } else { + value = potentiallyCheckType(value, element.computeType(compiler)); + addWithPosition(new HStaticStore(element, value), send); + } + stack.add(value); + } else if (element == null || Elements.isInstanceField(element)) { + HInstruction receiver = generateInstanceSendReceiver(send); + generateInstanceSetterWithCompiledReceiver(send, receiver, value); + } else if (Elements.isErroneousElement(element)) { + // An erroneous element indicates an unresolved static setter. + generateThrowNoSuchMethod(send, + getTargetName(element, 'set'), + argumentNodes: send.arguments); + } else { + stack.add(value); + // If the value does not already have a name, give it here. + if (value.sourceElement == null) { + value.sourceElement = element; + } + HInstruction checked = potentiallyCheckType( + value, element.computeType(compiler)); + if (!identical(checked, value)) { + pop(); + stack.add(checked); + } + localsHandler.updateLocal(element, checked); + } + } + + HInstruction invokeInterceptor(Set<ClassElement> intercepted, + HInstruction receiver, + Send send) { + HInterceptor interceptor = new HInterceptor(intercepted, receiver); + add(interceptor); + return interceptor; + } + + void pushInvokeHelper0(Element helper, HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper1(Element helper, HInstruction a0, HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper2(Element helper, + HInstruction a0, + HInstruction a1, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper3(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper4(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HInstruction a3, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + void pushInvokeHelper5(Element helper, + HInstruction a0, + HInstruction a1, + HInstruction a2, + HInstruction a3, + HInstruction a4, + HType type) { + HInstruction reference = new HStatic(helper); + add(reference); + List<HInstruction> inputs = <HInstruction>[reference, a0, a1, a2, a3, a4]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + HForeign createForeign(String code, HType type, List<HInstruction> inputs) { + return new HForeign(new LiteralDartString(code), type, inputs); + } + + HInstruction getRuntimeTypeInfo(HInstruction target) { + pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), target, HType.UNKNOWN); + return pop(); + } + + // TODO(karlklose): change construction of the representations to be GVN'able + // (dartbug.com/7182). + List<HInstruction> buildTypeArgumentRepresentations(DartType type) { + HInstruction createForeignArray(String code, inputs) { + return createForeign(code, HType.READABLE_ARRAY, inputs); + } + HInstruction typeInfo; + + /// Helper to create an instruction that contains the runtime value of + /// the type variable [variable]. + HInstruction getTypeArgument(TypeVariableType variable) { + if (typeInfo == null) { + typeInfo = getRuntimeTypeInfo(localsHandler.readThis()); + } + int intIndex = RuntimeTypeInformation.getTypeVariableIndex(variable); + HInstruction index = graph.addConstantInt(intIndex, constantSystem); + return createForeignArray('#[#]', <HInstruction>[typeInfo, index]); + } + + // Compute the representation of the type arguments, including access + // to the runtime type information for type variables as instructions. + HInstruction representations; + if (type.element.isTypeVariable()) { + return <HInstruction>[getTypeArgument(type)]; + } else { + assert(type.element.isClass()); + List<HInstruction> arguments = <HInstruction>[]; + InterfaceType interface = type; + for (DartType argument in interface.typeArguments) { + List<HInstruction> inputs = <HInstruction>[]; + String template = rti.getTypeRepresentation(argument, (variable) { + HInstruction runtimeType = getTypeArgument(variable); + add(runtimeType); + inputs.add(runtimeType); + }); + HInstruction representation = createForeignArray(template, inputs); + add(representation); + arguments.add(representation); + } + return arguments; + } + } + + visitOperatorSend(node) { + Operator op = node.selector; + if (const SourceString("[]") == op.source) { + visitDynamicSend(node); + } else if (const SourceString("&&") == op.source || + const SourceString("||") == op.source) { + visitLogicalAndOr(node, op); + } else if (const SourceString("!") == op.source) { + visitLogicalNot(node); + } else if (node.argumentsNode is Prefix) { + visitUnary(node, op); + } else if (const SourceString("is") == op.source) { + visit(node.receiver); + HInstruction expression = pop(); + Node argument = node.arguments.head; + TypeAnnotation typeAnnotation = argument.asTypeAnnotation(); + bool isNot = false; + // TODO(ngeoffray): Duplicating pattern in resolver. We should + // add a new kind of node. + if (typeAnnotation == null) { + typeAnnotation = argument.asSend().receiver; + isNot = true; + } + DartType type = elements.getType(typeAnnotation); + if (type.isMalformed) { + String reasons = Types.fetchReasonsFromMalformedType(type); + if (compiler.enableTypeAssertions) { + generateMalformedSubtypeError(node, expression, type, reasons); + } else { + generateRuntimeError(node, '$type is malformed: $reasons'); + } + return; + } + if (type.element.isTypeVariable()) { + // TODO(karlklose): remove this check when the backend can deal with + // checks of the form [:o is T:] where [:T:] is a type variable. + stack.add(graph.addConstantBool(true, constantSystem)); + return; + } + + HInstruction instruction; + if (type.element.isTypeVariable() || + RuntimeTypeInformation.hasTypeArguments(type)) { + HInstruction typeInfo = getRuntimeTypeInfo(expression); + // TODO(karlklose): make isSubtype a HInstruction to enable + // optimizations? + Element helper = compiler.findHelper(const SourceString('isSubtype')); + HInstruction isSubtype = new HStatic(helper); + add(isSubtype); + // Build a list of representations for the type arguments. + List<HInstruction> representations = + buildTypeArgumentRepresentations(type); + // For each type argument, build a call to isSubtype, with the type + // argument as first and the representation of the tested type as + // second argument. + List<HInstruction> checks = <HInstruction>[]; + int index = 0; + representations.forEach((HInstruction representation) { + HInstruction position = graph.addConstantInt(index, constantSystem); + // Get the index'th type argument from the runtime type information. + HInstruction typeArgument = + createForeign('#[#]', HType.UNKNOWN, [typeInfo, position]); + add(typeArgument); + // Create the call to isSubtype. + List<HInstruction> inputs = + <HInstruction>[isSubtype, typeArgument, representation]; + HInstruction call = new HInvokeStatic(inputs, HType.BOOLEAN); + add(call); + checks.add(call); + index++; + }); + instruction = new HIs(type, <HInstruction>[expression]..addAll(checks)); + } else { + instruction = new HIs(type, <HInstruction>[expression]); + } + if (isNot) { + add(instruction); + instruction = new HNot(instruction); + } + push(instruction); + } else if (const SourceString("as") == op.source) { + visit(node.receiver); + HInstruction expression = pop(); + Node argument = node.arguments.head; + TypeAnnotation typeAnnotation = argument.asTypeAnnotation(); + DartType type = elements.getType(typeAnnotation); + HInstruction converted = expression.convertType( + compiler, type, HTypeConversion.CAST_TYPE_CHECK); + if (converted != expression) add(converted); + stack.add(converted); + } else { + visit(node.receiver); + visit(node.argumentsNode); + var right = pop(); + var left = pop(); + visitBinary(left, op, right, node); + } + } + + void addDynamicSendArgumentsToList(Send node, List<HInstruction> list) { + Selector selector = elements.getSelector(node); + if (selector.namedArgumentCount == 0) { + addGenericSendArgumentsToList(node.arguments, list); + } else { + // Visit positional arguments and add them to the list. + Link<Node> arguments = node.arguments; + int positionalArgumentCount = selector.positionalArgumentCount; + for (int i = 0; + i < positionalArgumentCount; + arguments = arguments.tail, i++) { + visit(arguments.head); + list.add(pop()); + } + + // Visit named arguments and add them into a temporary map. + Map<SourceString, HInstruction> instructions = + new Map<SourceString, HInstruction>(); + List<SourceString> namedArguments = selector.namedArguments; + int nameIndex = 0; + for (; !arguments.isEmpty; arguments = arguments.tail) { + visit(arguments.head); + instructions[namedArguments[nameIndex++]] = pop(); + } + + // Iterate through the named arguments to add them to the list + // of instructions, in an order that can be shared with + // selectors with the same named arguments. + List<SourceString> orderedNames = selector.getOrderedNamedArguments(); + for (SourceString name in orderedNames) { + list.add(instructions[name]); + } + } + } + + /** + * Returns true if the arguments were compatible with the function signature. + * + * Invariant: [element] must be an implementation element. + */ + bool addStaticSendArgumentsToList(Selector selector, + Link<Node> arguments, + FunctionElement element, + List<HInstruction> list) { + assert(invariant(element, element.isImplementation)); + + HInstruction compileArgument(Node argument) { + visit(argument); + return pop(); + } + + HInstruction handleConstant(Element parameter) { + Constant constant; + TreeElements calleeElements = + compiler.enqueuer.resolution.getCachedElements(element); + if (calleeElements.isParameterChecked(parameter)) { + constant = SentinelConstant.SENTINEL; + } else { + constant = compileConstant(parameter); + } + return graph.addConstant(constant); + } + + return selector.addArgumentsToList(arguments, + list, + element, + compileArgument, + handleConstant, + compiler); + } + + void addGenericSendArgumentsToList(Link<Node> link, List<HInstruction> list) { + for (; !link.isEmpty; link = link.tail) { + visit(link.head); + list.add(pop()); + } + } + + visitDynamicSend(Send node) { + Selector selector = elements.getSelector(node); + + SourceString dartMethodName; + bool isNotEquals = false; + if (node.isIndex && !node.arguments.tail.isEmpty) { + dartMethodName = Elements.constructOperatorName( + const SourceString('[]='), false); + } else if (node.selector.asOperator() != null) { + SourceString name = node.selector.asIdentifier().source; + isNotEquals = identical(name.stringValue, '!='); + dartMethodName = Elements.constructOperatorName( + name, node.argumentsNode is Prefix); + } else { + dartMethodName = node.selector.asIdentifier().source; + } + + Element element = elements[node]; + bool isClosureCall = false; + if (element != null && compiler.world.hasNoOverridingMember(element)) { + if (tryInlineMethod(element, selector, node.arguments, node)) { + if (element.isGetter()) { + // If the element is a getter, we are doing a closure call + // on what this getter returns. + assert(selector.isCall()); + isClosureCall = true; + } else { + return; + } + } + } + + List<HInstruction> inputs = <HInstruction>[]; + if (isClosureCall) inputs.add(pop()); + + HInstruction receiver; + if (!isClosureCall) { + if (node.receiver == null) { + receiver = localsHandler.readThis(); + } else { + visit(node.receiver); + receiver = pop(); + } + } + + addDynamicSendArgumentsToList(node, inputs); + + HInstruction invoke; + if (isClosureCall) { + Selector closureSelector = new Selector.callClosureFrom(selector); + invoke = new HInvokeClosure(closureSelector, inputs); + } else { + invoke = buildInvokeDynamic(node, selector, receiver, inputs); + } + + pushWithPosition(invoke, node); + + if (isNotEquals) { + HNot not = new HNot(popBoolified()); + push(not); + } + } + + visitClosureSend(Send node) { + Selector selector = elements.getSelector(node); + assert(node.receiver == null); + Element element = elements[node]; + HInstruction closureTarget; + if (element == null) { + visit(node.selector); + closureTarget = pop(); + } else { + assert(Elements.isLocal(element)); + closureTarget = localsHandler.readLocal(element); + } + var inputs = <HInstruction>[]; + inputs.add(closureTarget); + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + pushWithPosition(new HInvokeClosure(closureSelector, inputs), node); + } + + void handleForeignJs(Send node) { + Link<Node> link = node.arguments; + // If the invoke is on foreign code, don't visit the first + // argument, which is the type, and the second argument, + // which is the foreign code. + if (link.isEmpty || link.tail.isEmpty) { + compiler.cancel('At least two arguments expected', + node: node.argumentsNode); + } + List<HInstruction> inputs = <HInstruction>[]; + Node type = link.head; + Node code = link.tail.head; + addGenericSendArgumentsToList(link.tail.tail, inputs); + + native.NativeBehavior nativeBehavior = + compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); + HType ssaType = mapNativeBehaviorType(nativeBehavior); + if (code is StringNode) { + StringNode codeString = code; + if (!codeString.isInterpolation) { + // codeString may not be an interpolation, but may be a juxtaposition. + push(new HForeign(codeString.dartString, ssaType, inputs)); + return; + } + } + compiler.cancel('JS code must be a string literal', node: code); + } + + void handleForeignJsCurrentIsolate(Send node) { + if (!node.arguments.isEmpty) { + compiler.cancel( + 'Too many arguments to JS_CURRENT_ISOLATE', node: node); + } + + if (!compiler.hasIsolateSupport()) { + // If the isolate library is not used, we just generate code + // to fetch the Leg's current isolate. + String name = backend.namer.CURRENT_ISOLATE; + push(new HForeign(new DartString.literal(name), + HType.UNKNOWN, + <HInstruction>[])); + } else { + // Call a helper method from the isolate library. The isolate + // library uses its own isolate structure, that encapsulates + // Leg's isolate. + Element element = compiler.isolateHelperLibrary.find( + const SourceString('_currentIsolate')); + if (element == null) { + compiler.cancel( + 'Isolate library and compiler mismatch', node: node); + } + pushInvokeHelper0(element, HType.UNKNOWN); + } + } + + void handleForeignJsCallInIsolate(Send node) { + Link<Node> link = node.arguments; + if (!compiler.hasIsolateSupport()) { + // If the isolate library is not used, we just invoke the + // closure. + visit(link.tail.head); + Selector selector = new Selector.callClosure(0); + push(new HInvokeClosure(selector, <HInstruction>[pop()])); + } else { + // Call a helper method from the isolate library. + Element element = compiler.isolateHelperLibrary.find( + const SourceString('_callInIsolate')); + if (element == null) { + compiler.cancel( + 'Isolate library and compiler mismatch', node: node); + } + HStatic target = new HStatic(element); + add(target); + List<HInstruction> inputs = <HInstruction>[target]; + addGenericSendArgumentsToList(link, inputs); + push(new HInvokeStatic(inputs, HType.UNKNOWN)); + } + } + + FunctionSignature handleForeignRawFunctionRef(Send node, String name) { + if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { + compiler.cancel('"$name" requires exactly one argument', + node: node.argumentsNode); + } + Node closure = node.arguments.head; + Element element = elements[closure]; + if (!Elements.isStaticOrTopLevelFunction(element)) { + compiler.cancel( + '"$name" requires a static or top-level method', + node: closure); + } + FunctionElement function = element; + // TODO(johnniwinther): Try to eliminate the need to distinguish declaration + // and implementation signatures. Currently it is need because the + // signatures have different elements for parameters. + FunctionElement implementation = function.implementation; + FunctionSignature params = implementation.computeSignature(compiler); + if (params.optionalParameterCount != 0) { + compiler.cancel( + '"$name" does not handle closure with optional parameters', + node: closure); + } + visit(closure); + return params; + } + + void handleForeignDartClosureToJs(Send node, String name) { + FunctionSignature params = handleForeignRawFunctionRef(node, name); + List<HInstruction> inputs = <HInstruction>[pop()]; + String invocationName = backend.namer.invocationName( + new Selector.callClosure(params.requiredParameterCount)); + push(new HForeign(new DartString.literal('#.$invocationName'), + HType.UNKNOWN, + inputs)); + } + + void handleForeignSetCurrentIsolate(Send node) { + if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) { + compiler.cancel('Exactly one argument required', + node: node.argumentsNode); + } + visit(node.arguments.head); + String isolateName = backend.namer.CURRENT_ISOLATE; + push(new HForeign(new DartString.literal("$isolateName = #"), + HType.UNKNOWN, + <HInstruction>[pop()])); + } + + void handleForeignCreateIsolate(Send node) { + if (!node.arguments.isEmpty) { + compiler.cancel('Too many arguments', + node: node.argumentsNode); + } + String constructorName = backend.namer.isolateName; + push(new HForeign(new DartString.literal("new $constructorName"), + HType.UNKNOWN, + <HInstruction>[])); + } + + visitForeignSend(Send node) { + Selector selector = elements.getSelector(node); + SourceString name = selector.name; + if (name == const SourceString('JS')) { + handleForeignJs(node); + } else if (name == const SourceString('JS_CURRENT_ISOLATE')) { + handleForeignJsCurrentIsolate(node); + } else if (name == const SourceString('JS_CALL_IN_ISOLATE')) { + handleForeignJsCallInIsolate(node); + } else if (name == const SourceString('DART_CLOSURE_TO_JS')) { + handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS'); + } else if (name == const SourceString('RAW_DART_FUNCTION_REF')) { + handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF'); + } else if (name == const SourceString('JS_SET_CURRENT_ISOLATE')) { + handleForeignSetCurrentIsolate(node); + } else if (name == const SourceString('JS_CREATE_ISOLATE')) { + handleForeignCreateIsolate(node); + } else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) { + stack.add(addConstantString(node, backend.namer.operatorIsPrefix())); + } else { + throw "Unknown foreign: ${selector}"; + } + } + + generateSuperNoSuchMethodSend(Send node) { + Selector selector = elements.getSelector(node); + SourceString name = selector.name; + + ClassElement cls = currentElement.getEnclosingClass(); + Element element = cls.lookupSuperMember(Compiler.NO_SUCH_METHOD); + if (element.enclosingElement.declaration != compiler.objectClass) { + // Register the call as dynamic if [:noSuchMethod:] on the super class + // is _not_ the default implementation from [:Object:]. + compiler.enqueuer.codegen.registerDynamicInvocation(name, selector); + } + HStatic target = new HStatic(element); + add(target); + HInstruction self = localsHandler.readThis(); + Constant nameConstant = constantSystem.createString( + new DartString.literal(name.slowToString()), node); + + String internalName = backend.namer.invocationName(selector); + Constant internalNameConstant = + constantSystem.createString(new DartString.literal(internalName), node); + + Element createInvocationMirror = + compiler.findHelper(Compiler.CREATE_INVOCATION_MIRROR); + + var arguments = new List<HInstruction>(); + if (node.argumentsNode != null) { + addGenericSendArgumentsToList(node.arguments, arguments); + } + var argumentsInstruction = new HLiteralList(arguments); + add(argumentsInstruction); + + var argumentNames = new List<HInstruction>(); + for (SourceString argumentName in selector.namedArguments) { + Constant argumentNameConstant = + constantSystem.createString(new DartString.literal( + argumentName.slowToString()), node); + argumentNames.add(graph.addConstant(argumentNameConstant)); + } + var argumentNamesInstruction = new HLiteralList(argumentNames); + add(argumentNamesInstruction); + + Constant kindConstant = + constantSystem.createInt(selector.invocationMirrorKind); + + pushInvokeHelper5(createInvocationMirror, + graph.addConstant(nameConstant), + graph.addConstant(internalNameConstant), + graph.addConstant(kindConstant), + argumentsInstruction, + argumentNamesInstruction, + HType.UNKNOWN); + + var inputs = <HInstruction>[ + target, + self, + pop()]; + push(new HInvokeSuper(inputs)); + } + + visitSend(Send node) { + Element element = elements[node]; + if (element != null && identical(element, currentElement)) { + graph.isRecursiveMethod = true; + } + super.visitSend(node); + } + + visitSuperSend(Send node) { + Selector selector = elements.getSelector(node); + Element element = elements[node]; + if (element == null) return generateSuperNoSuchMethodSend(node); + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + HInstruction target = new HStatic(element.declaration); + HInstruction context = localsHandler.readThis(); + add(target); + var inputs = <HInstruction>[target, context]; + if (node.isPropertyAccess) { + push(new HInvokeSuper(inputs)); + } else if (element.isFunction() || element.isGenerativeConstructor()) { + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + FunctionElement function = element.implementation; + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + function, inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, element, node.arguments); + } else { + push(new HInvokeSuper(inputs)); + } + } else { + target = new HInvokeSuper(inputs); + add(target); + inputs = <HInstruction>[target]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + push(new HInvokeClosure(closureSelector, inputs)); + } + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [argument] must not be malformed in checked mode. + */ + HInstruction analyzeTypeArgument(DartType argument, Node currentNode) { + assert(invariant(currentNode, + !compiler.enableTypeAssertions || !argument.isMalformed, + message: '$argument is malformed in checked mode')); + if (argument == compiler.types.dynamicType || argument.isMalformed) { + // Represent [dynamic] as [null]. + return graph.addConstantNull(constantSystem); + } + + // These variables are shared between invocations of the helper. + HInstruction typeInfo; + List<HInstruction> inputs = <HInstruction>[]; + + /** + * Helper to create an instruction that gets the value of a type variable. + */ + void addTypeVariableReference(TypeVariableType type) { + Element member = currentElement; + if (member.enclosingElement.isClosure()) { + ClosureClassElement closureClass = member.enclosingElement; + member = closureClass.methodElement; + member = member.getOutermostEnclosingMemberOrTopLevel(); + } + if (member.isFactoryConstructor()) { + // The type variable is stored in a parameter of the factory. + inputs.add(localsHandler.readLocal(type.element)); + } else if (member.isInstanceMember() + || member.isGenerativeConstructor()) { + // The type variable is stored in [this]. + if (typeInfo == null) { + pushInvokeHelper1(backend.getGetRuntimeTypeInfo(), + localsHandler.readThis(), + HType.UNKNOWN); + typeInfo = pop(); + } + int index = RuntimeTypeInformation.getTypeVariableIndex(type); + HInstruction foreign = createForeign('#[$index]', HType.STRING, + <HInstruction>[typeInfo]); + add(foreign); + inputs.add(foreign); + } else { + // TODO(ngeoffray): Match the VM behavior and throw an + // exception at runtime. + compiler.cancel('Unimplemented unresolved type variable', + node: currentNode); + } + } + + String template = rti.getTypeRepresentation(argument, + addTypeVariableReference); + HInstruction result = createForeign(template, HType.STRING, inputs); + add(result); + return result; + } + + void handleListConstructor(InterfaceType type, + Node currentNode, + HInstruction newObject) { + if (!compiler.world.needsRti(type.element)) return; + List<HInstruction> inputs = <HInstruction>[]; + if (!type.isRaw) { + type.typeArguments.forEach((DartType argument) { + inputs.add(analyzeTypeArgument(argument, currentNode)); + }); + } + callSetRuntimeTypeInfo(type.element, inputs, newObject); + } + + void callSetRuntimeTypeInfo(ClassElement element, + List<HInstruction> rtiInputs, + HInstruction newObject) { + if (!compiler.world.needsRti(element) || element.typeVariables.isEmpty) { + return; + } + + HInstruction typeInfo = new HLiteralList(rtiInputs); + add(typeInfo); + + // Set the runtime type information on the object. + Element typeInfoSetterElement = backend.getSetRuntimeTypeInfo(); + HInstruction typeInfoSetter = new HStatic(typeInfoSetterElement); + add(typeInfoSetter); + add(new HInvokeStatic( + <HInstruction>[typeInfoSetter, newObject, typeInfo], HType.UNKNOWN)); + } + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [type] must not be malformed in checked mode. + */ + visitNewSend(Send node, InterfaceType type) { + assert(invariant(node, + !compiler.enableTypeAssertions || !type.isMalformed, + message: '$type is malformed in checked mode')); + bool isListConstructor = false; + computeType(element) { + Element originalElement = elements[node]; + if (identical(originalElement.getEnclosingClass(), compiler.listClass)) { + isListConstructor = true; + if (node.arguments.isEmpty) { + return HType.EXTENDABLE_ARRAY; + } else { + return HType.MUTABLE_ARRAY; + } + } else if (element.isGenerativeConstructor()) { + ClassElement cls = element.getEnclosingClass(); + return new HBoundedType.exact(cls.thisType); + } else { + return HType.UNKNOWN; + } + } + + Element constructor = elements[node]; + Selector selector = elements.getSelector(node); + if (compiler.enqueuer.resolution.getCachedElements(constructor) == null) { + compiler.internalError("Unresolved element: $constructor", node: node); + } + FunctionElement functionElement = constructor; + constructor = functionElement.redirectionTarget; + // TODO(5346): Try to avoid the need for calling [declaration] before + // creating an [HStatic]. + HInstruction target = new HStatic(constructor.declaration); + add(target); + var inputs = <HInstruction>[]; + inputs.add(target); + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + constructor.implementation, + inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, constructor, node.arguments); + return; + } + + ClassElement cls = constructor.getEnclosingClass(); + if (cls.isAbstract(compiler) && constructor.isGenerativeConstructor()) { + generateAbstractClassInstantiationError(node, cls.name.slowToString()); + return; + } + if (compiler.world.needsRti(cls)) { + Link<DartType> typeVariable = cls.typeVariables; + type.typeArguments.forEach((DartType argument) { + inputs.add(analyzeTypeArgument(argument, node)); + typeVariable = typeVariable.tail; + }); + // Also add null to non-provided type variables to call the + // constructor with the right number of arguments. + while (!typeVariable.isEmpty) { + inputs.add(graph.addConstantNull(constantSystem)); + typeVariable = typeVariable.tail; + } + } + + HType elementType = computeType(constructor); + HInstruction newInstance = new HInvokeStatic(inputs, elementType); + pushWithPosition(newInstance, node); + + // The List constructor forwards to a Dart static method that does + // not know about the type argument. Therefore we special case + // this constructor to have the setRuntimeTypeInfo called where + // the 'new' is done. + if (isListConstructor && compiler.world.needsRti(compiler.listClass)) { + handleListConstructor(type, node, newInstance); + } + } + + visitStaticSend(Send node) { + Selector selector = elements.getSelector(node); + Element element = elements[node]; + if (element.isForeign(compiler)) { + visitForeignSend(node); + return; + } + if (element.isErroneous()) { + generateThrowNoSuchMethod(node, + getTargetName(element), + argumentNodes: node.arguments); + return; + } + if (identical(element, compiler.assertMethod) + && !compiler.enableUserAssertions) { + stack.add(graph.addConstantNull(constantSystem)); + return; + } + compiler.ensure(!element.isGenerativeConstructor()); + if (element.isFunction()) { + bool isIdenticalFunction = element == compiler.identicalFunction; + + if (!isIdenticalFunction + && tryInlineMethod(element, selector, node.arguments, node)) { + return; + } + + HInstruction target = new HStatic(element); + add(target); + var inputs = <HInstruction>[target]; + // TODO(5347): Try to avoid the need for calling [implementation] before + // calling [addStaticSendArgumentsToList]. + bool succeeded = addStaticSendArgumentsToList(selector, node.arguments, + element.implementation, + inputs); + if (!succeeded) { + generateWrongArgumentCountError(node, element, node.arguments); + return; + } + + if (isIdenticalFunction) { + pushWithPosition(new HIdentity(inputs[1], inputs[2]), node); + return; + } + + HInvokeStatic instruction = new HInvokeStatic(inputs, HType.UNKNOWN); + // TODO(ngeoffray): Only do this if knowing the return type is + // useful. + HType returnType = + builder.backend.optimisticReturnTypesWithRecompilationOnTypeChange( + currentElement, element); + if (returnType != null) instruction.guaranteedType = returnType; + pushWithPosition(instruction, node); + } else { + generateGetter(node, element); + List<HInstruction> inputs = <HInstruction>[pop()]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + pushWithPosition(new HInvokeClosure(closureSelector, inputs), node); + } + } + + HConstant addConstantString(Node node, String string) { + DartString dartString = new DartString.literal(string); + Constant constant = constantSystem.createString(dartString, node); + return graph.addConstant(constant); + } + + visitTypeReferenceSend(Send node) { + Element element = elements[node]; + if (element.isClass() || element.isTypedef()) { + // TODO(karlklose): add type representation + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + } else if (element.isTypeVariable()) { + // TODO(6248): implement support for type variables. + compiler.unimplemented('first class type for type variable', node: node); + } else { + internalError('unexpected element kind $element', node: node); + } + if (node.isCall) { + // This send is of the form 'e(...)', where e is resolved to a type + // reference. We create a regular closure call on the result of the type + // reference instead of creating a NoSuchMethodError to avoid pulling it + // in if it is not used (e.g., in a try/catch). + HInstruction target = pop(); + Selector selector = elements.getSelector(node); + List<HInstruction> inputs = <HInstruction>[target]; + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + push(new HInvokeClosure(closureSelector, inputs)); + } + } + + visitGetterSend(Send node) { + generateGetter(node, elements[node]); + } + + // TODO(antonm): migrate rest of SsaBuilder to internalError. + internalError(String reason, {Node node}) { + compiler.internalError(reason, node: node); + } + + void generateError(Node node, String message, Element helper) { + HInstruction errorMessage = addConstantString(node, message); + pushInvokeHelper1(helper, errorMessage, HType.UNKNOWN); + } + + void generateRuntimeError(Node node, String message) { + generateError(node, message, backend.getThrowRuntimeError()); + } + + void generateAbstractClassInstantiationError(Node node, String message) { + generateError(node, + message, + backend.getThrowAbstractClassInstantiationError()); + } + + void generateThrowNoSuchMethod(Node diagnosticNode, + String methodName, + {Link<Node> argumentNodes, + List<HInstruction> argumentValues, + List<String> existingArguments}) { + Element helper = + compiler.findHelper(const SourceString('throwNoSuchMethod')); + Constant receiverConstant = + constantSystem.createString(new DartString.empty(), diagnosticNode); + HInstruction receiver = graph.addConstant(receiverConstant); + DartString dartString = new DartString.literal(methodName); + Constant nameConstant = + constantSystem.createString(dartString, diagnosticNode); + HInstruction name = graph.addConstant(nameConstant); + if (argumentValues == null) { + argumentValues = <HInstruction>[]; + argumentNodes.forEach((argumentNode) { + visit(argumentNode); + HInstruction value = pop(); + argumentValues.add(value); + }); + } + HInstruction arguments = new HLiteralList(argumentValues); + add(arguments); + HInstruction existingNamesList; + if (existingArguments != null) { + List<HInstruction> existingNames = <HInstruction>[]; + for (String name in existingArguments) { + HInstruction nameConstant = + graph.addConstantString(new DartString.literal(name), + diagnosticNode, constantSystem); + existingNames.add(nameConstant); + } + existingNamesList = new HLiteralList(existingNames); + add(existingNamesList); + } else { + existingNamesList = graph.addConstantNull(constantSystem); + } + pushInvokeHelper4( + helper, receiver, name, arguments, existingNamesList, HType.UNKNOWN); + } + + /** + * Generate code to throw a [NoSuchMethodError] exception for calling a + * method with a wrong number of arguments or mismatching named optional + * arguments. + */ + void generateWrongArgumentCountError(Node diagnosticNode, + FunctionElement function, + Link<Node> argumentNodes) { + List<String> existingArguments = <String>[]; + FunctionSignature signature = function.computeSignature(compiler); + signature.forEachParameter((Element parameter) { + existingArguments.add(parameter.name.slowToString()); + }); + generateThrowNoSuchMethod(diagnosticNode, + function.name.slowToString(), + argumentNodes: argumentNodes, + existingArguments: existingArguments); + } + + void generateMalformedSubtypeError(Node node, HInstruction value, + DartType type, String reasons) { + HInstruction typeString = addConstantString(node, type.toString()); + HInstruction reasonsString = addConstantString(node, reasons); + Element helper = backend.getThrowMalformedSubtypeError(); + pushInvokeHelper3(helper, value, typeString, reasonsString, HType.UNKNOWN); + } + + visitNewExpression(NewExpression node) { + Element element = elements[node.send]; + if (!Elements.isErroneousElement(element)) { + FunctionElement function = element; + element = function.redirectionTarget; + } + if (Elements.isErroneousElement(element)) { + ErroneousElement error = element; + if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR) { + generateThrowNoSuchMethod(node.send, + getTargetName(error, 'constructor'), + argumentNodes: node.send.arguments); + } else { + Message message = error.messageKind.message(error.messageArguments); + generateRuntimeError(node.send, message.toString()); + } + } else if (node.isConst()) { + // TODO(karlklose): add type representation + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + } else { + DartType type = elements.getType(node); + if (compiler.enableTypeAssertions && type.isMalformed) { + String reasons = Types.fetchReasonsFromMalformedType(type); + // TODO(johnniwinther): Change to resemble type errors from bounds check + // on type arguments. + generateRuntimeError(node, '$type is malformed: $reasons'); + } else { + // TODO(karlklose): move this type registration to the codegen. + compiler.codegenWorld.instantiatedTypes.add(type); + Send send = node.send; + Element constructor = elements[send]; + Selector selector = elements.getSelector(send); + if (!tryInlineMethod(constructor, selector, send.arguments, node)) { + visitNewSend(send, type); + } + } + } + } + + HInvokeDynamicMethod buildInvokeDynamic(Node node, + Selector selector, + HInstruction receiver, + List<HInstruction> arguments) { + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + List<HInstruction> inputs = <HInstruction>[]; + bool isIntercepted = interceptedClasses != null; + if (isIntercepted) { + assert(!interceptedClasses.isEmpty); + inputs.add(invokeInterceptor(interceptedClasses, receiver, node)); + } + inputs.add(receiver); + inputs.addAll(arguments); + return new HInvokeDynamicMethod(selector, inputs, isIntercepted); + } + + visitSendSet(SendSet node) { + Element element = elements[node]; + if (!Elements.isUnresolved(element) && element.impliesType()) { + Identifier selector = node.selector; + generateThrowNoSuchMethod(node, selector.source.slowToString(), + argumentNodes: node.arguments); + return; + } + Operator op = node.assignmentOperator; + if (node.isSuperCall) { + if (element == null) return generateSuperNoSuchMethodSend(node); + HInstruction target = new HStatic(element); + HInstruction context = localsHandler.readThis(); + add(target); + var inputs = <HInstruction>[target, context]; + addDynamicSendArgumentsToList(node, inputs); + if (!identical(node.assignmentOperator.source.stringValue, '=')) { + compiler.unimplemented('complex super assignment', + node: node.assignmentOperator); + } + push(new HInvokeSuper(inputs, isSetter: true)); + } else if (node.isIndex) { + if (const SourceString("=") == op.source) { + visitDynamicSend(node); + HInvokeDynamicMethod method = pop(); + // Push the value. + stack.add(method.inputs.last); + } else { + visit(node.receiver); + HInstruction receiver = pop(); + visit(node.argumentsNode); + HInstruction value; + HInstruction index; + // Compound assignments are considered as being prefix. + bool isCompoundAssignment = op.source.stringValue.endsWith('='); + bool isPrefix = !node.isPostfix; + Element getter = elements[node.selector]; + if (isCompoundAssignment) { + value = pop(); + index = pop(); + } else { + index = pop(); + value = graph.addConstantInt(1, constantSystem); + } + + HInvokeDynamicMethod left = buildInvokeDynamic( + node, new Selector.index(), receiver, [index]); + add(left); + visitBinary(left, op, value, node); + value = pop(); + HInvokeDynamicMethod assign = buildInvokeDynamic( + node, new Selector.indexSet(), receiver, [index, value]); + add(assign); + if (isPrefix) { + stack.add(value); + } else { + stack.add(left); + } + } + } else if (const SourceString("=") == op.source) { + Element element = elements[node]; + Link<Node> link = node.arguments; + assert(!link.isEmpty && link.tail.isEmpty); + visit(link.head); + HInstruction value = pop(); + generateSetter(node, element, value); + } else if (identical(op.source.stringValue, "is")) { + compiler.internalError("is-operator as SendSet", node: op); + } else { + assert(const SourceString("++") == op.source || + const SourceString("--") == op.source || + node.assignmentOperator.source.stringValue.endsWith("=")); + Element element = elements[node]; + bool isCompoundAssignment = !node.arguments.isEmpty; + bool isPrefix = !node.isPostfix; // Compound assignments are prefix. + + // [receiver] is only used if the node is an instance send. + HInstruction receiver = null; + Element selectorElement = elements[node]; + if (Elements.isInstanceSend(node, elements)) { + receiver = generateInstanceSendReceiver(node); + generateInstanceGetterWithCompiledReceiver(node, receiver); + } else { + generateGetter(node, elements[node.selector]); + } + HInstruction left = pop(); + HInstruction right; + if (isCompoundAssignment) { + visit(node.argumentsNode); + right = pop(); + } else { + right = graph.addConstantInt(1, constantSystem); + } + visitBinary(left, op, right, node); + HInstruction operation = pop(); + assert(operation != null); + if (Elements.isInstanceSend(node, elements)) { + assert(receiver != null); + generateInstanceSetterWithCompiledReceiver(node, receiver, operation); + } else { + assert(receiver == null); + generateSetter(node, element, operation); + } + if (!isPrefix) { + pop(); + stack.add(left); + } + } + } + + void visitLiteralInt(LiteralInt node) { + stack.add(graph.addConstantInt(node.value, constantSystem)); + } + + void visitLiteralDouble(LiteralDouble node) { + stack.add(graph.addConstantDouble(node.value, constantSystem)); + } + + void visitLiteralBool(LiteralBool node) { + stack.add(graph.addConstantBool(node.value, constantSystem)); + } + + void visitLiteralString(LiteralString node) { + stack.add(graph.addConstantString(node.dartString, node, constantSystem)); + } + + void visitStringJuxtaposition(StringJuxtaposition node) { + if (!node.isInterpolation) { + // This is a simple string with no interpolations. + stack.add(graph.addConstantString(node.dartString, node, constantSystem)); + return; + } + StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); + stringBuilder.visit(node); + stack.add(stringBuilder.result); + } + + void visitLiteralNull(LiteralNull node) { + stack.add(graph.addConstantNull(constantSystem)); + } + + visitNodeList(NodeList node) { + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + if (isAborted()) { + compiler.reportWarning(link.head, 'dead code'); + } else { + visit(link.head); + } + } + } + + void visitParenthesizedExpression(ParenthesizedExpression node) { + visit(node.expression); + } + + visitOperator(Operator node) { + // Operators are intercepted in their surrounding Send nodes. + compiler.internalError('visitOperator should not be called', node: node); + } + + visitCascade(Cascade node) { + visit(node.expression); + // Remove the result and reveal the duplicated receiver on the stack. + pop(); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + dup(); + } + + void handleInTryStatement() { + if (!inTryStatement) return; + HBasicBlock block = close(new HExitTry()); + HBasicBlock newBlock = graph.addNewBlock(); + block.addSuccessor(newBlock); + open(newBlock); + } + + visitReturn(Return node) { + if (identical(node.getBeginToken().stringValue, 'native')) { + native.handleSsaNative(this, node.expression); + return; + } + assert(invariant(node, !node.isRedirectingFactoryBody)); + HInstruction value; + if (node.expression == null) { + value = graph.addConstantNull(constantSystem); + } else { + visit(node.expression); + value = pop(); + value = potentiallyCheckType(value, returnType); + } + + handleInTryStatement(); + + if (!inliningStack.isEmpty) { + localsHandler.updateLocal(returnElement, value); + } else { + close(attachPosition(new HReturn(value), node)).addSuccessor(graph.exit); + } + } + + visitThrow(Throw node) { + if (node.expression == null) { + HInstruction exception = rethrowableException; + if (exception == null) { + exception = graph.addConstantNull(constantSystem); + compiler.internalError( + 'rethrowableException should not be null', node: node); + } + close(new HThrow(exception, isRethrow: true)); + } else { + visit(node.expression); + close(new HThrow(pop())); + } + } + + visitTypeAnnotation(TypeAnnotation node) { + compiler.internalError('visiting type annotation in SSA builder', + node: node); + } + + visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; + !link.isEmpty; + link = link.tail) { + Node definition = link.head; + if (definition is Identifier) { + HInstruction initialValue = graph.addConstantNull(constantSystem); + localsHandler.updateLocal(elements[definition], initialValue); + } else { + assert(definition is SendSet); + visitSendSet(definition); + pop(); // Discard value. + } + } + } + + visitLiteralList(LiteralList node) { + if (node.isConst()) { + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + return; + } + + List<HInstruction> inputs = <HInstruction>[]; + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + inputs.add(pop()); + } + push(new HLiteralList(inputs)); + } + + visitConditional(Conditional node) { + SsaBranchBuilder brancher = new SsaBranchBuilder(this, node); + brancher.handleConditional(() => visit(node.condition), + () => visit(node.thenExpression), + () => visit(node.elseExpression)); + } + + visitStringInterpolation(StringInterpolation node) { + StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node); + stringBuilder.visit(node); + stack.add(stringBuilder.result); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + // The parts are iterated in visitStringInterpolation. + compiler.internalError('visitStringInterpolation should not be called', + node: node); + } + + visitEmptyStatement(EmptyStatement node) { + // Do nothing, empty statement. + } + + visitModifiers(Modifiers node) { + compiler.unimplemented('SsaBuilder.visitModifiers', node: node); + } + + visitBreakStatement(BreakStatement node) { + assert(!isAborted()); + handleInTryStatement(); + TargetElement target = elements[node]; + assert(target != null); + JumpHandler handler = jumpTargets[target]; + assert(handler != null); + if (node.target == null) { + handler.generateBreak(); + } else { + LabelElement label = elements[node.target]; + handler.generateBreak(label); + } + } + + visitContinueStatement(ContinueStatement node) { + handleInTryStatement(); + TargetElement target = elements[node]; + assert(target != null); + JumpHandler handler = jumpTargets[target]; + assert(handler != null); + if (node.target == null) { + handler.generateContinue(); + } else { + LabelElement label = elements[node.target]; + assert(label != null); + handler.generateContinue(label); + } + } + + /** + * Creates a [JumpHandler] for a statement. The node must be a jump + * target. If there are no breaks or continues targeting the statement, + * a special "null handler" is returned. + */ + JumpHandler createJumpHandler(Statement node) { + TargetElement element = elements[node]; + if (element == null || !identical(element.statement, node)) { + // No breaks or continues to this node. + return new NullJumpHandler(compiler); + } + return new JumpHandler(this, element); + } + + visitForIn(ForIn node) { + // Generate a structure equivalent to: + // Iterator<E> $iter = <iterable>.iterator; + // while ($iter.moveNext()) { + // E <declaredIdentifier> = $iter.current; + // <body> + // } + + // The iterator is shared between initializer, condition and body. + HInstruction iterator; + void buildInitializer() { + SourceString iteratorName = const SourceString("iterator"); + Selector selector = + new Selector.getter(iteratorName, currentElement.getLibrary()); + Set<ClassElement> interceptedClasses = getInterceptedClassesOn(selector); + visit(node.expression); + HInstruction receiver = pop(); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + if (interceptedClasses == null) { + iterator = + new HInvokeDynamicGetter(selector, null, receiver, hasGetter); + } else { + HInterceptor interceptor = + invokeInterceptor(interceptedClasses, receiver, null); + iterator = + new HInvokeDynamicGetter(selector, null, interceptor, hasGetter); + // Add the receiver as an argument to the getter call on the + // interceptor. + iterator.inputs.add(receiver); + } + add(iterator); + } + HInstruction buildCondition() { + SourceString name = const SourceString('moveNext'); + Selector selector = new Selector.call( + name, currentElement.getLibrary(), 0); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(selector); + push(new HInvokeDynamicMethod(selector, <HInstruction>[iterator])); + return popBoolified(); + } + void buildBody() { + SourceString name = const SourceString('current'); + Selector call = new Selector.getter(name, currentElement.getLibrary()); + bool hasGetter = compiler.world.hasAnyUserDefinedGetter(call); + push(new HInvokeDynamicGetter(call, null, iterator, hasGetter)); + + Element variable; + if (node.declaredIdentifier.asSend() != null) { + variable = elements[node.declaredIdentifier]; + } else { + assert(node.declaredIdentifier.asVariableDefinitions() != null); + VariableDefinitions variableDefinitions = node.declaredIdentifier; + variable = elements[variableDefinitions.definitions.nodes.head]; + } + HInstruction oldVariable = pop(); + if (variable.isErroneous()) { + generateThrowNoSuchMethod(node, + getTargetName(variable, 'set'), + argumentValues: <HInstruction>[oldVariable]); + pop(); + } else { + localsHandler.updateLocal(variable, oldVariable); + } + + visit(node.body); + } + handleLoop(node, buildInitializer, buildCondition, () {}, buildBody); + } + + visitLabel(Label node) { + compiler.internalError('SsaBuilder.visitLabel', node: node); + } + + visitLabeledStatement(LabeledStatement node) { + Statement body = node.statement; + if (body is Loop || body is SwitchStatement) { + // Loops and switches handle their own labels. + visit(body); + return; + } + // Non-loop statements can only be break targets, not continue targets. + TargetElement targetElement = elements[body]; + if (targetElement == null || !identical(targetElement.statement, body)) { + // Labeled statements with no element on the body have no breaks. + // A different target statement only happens if the body is itself + // a break or continue for a different target. In that case, this + // label is also always unused. + visit(body); + return; + } + LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler); + assert(targetElement.isBreakTarget); + JumpHandler handler = new JumpHandler(this, targetElement); + // Introduce a new basic block. + HBasicBlock entryBlock = openNewBlock(); + visit(body); + SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock); + + HBasicBlock joinBlock = graph.addNewBlock(); + List<LocalsHandler> breakLocals = <LocalsHandler>[]; + handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) { + breakInstruction.block.addSuccessor(joinBlock); + breakLocals.add(locals); + }); + bool hasBreak = breakLocals.length > 0; + if (!isAborted()) { + goto(current, joinBlock); + breakLocals.add(localsHandler); + } + open(joinBlock); + localsHandler = beforeLocals.mergeMultiple(breakLocals, joinBlock); + + if (hasBreak) { + // There was at least one reachable break, so the label is needed. + entryBlock.setBlockFlow( + new HLabeledBlockInformation(new HSubGraphBlockInformation(bodyGraph), + handler.labels()), + joinBlock); + } + handler.close(); + } + + visitLiteralMap(LiteralMap node) { + if (node.isConst()) { + ConstantHandler handler = compiler.constantHandler; + Constant constant = handler.compileNodeWithDefinitions(node, elements); + stack.add(graph.addConstant(constant)); + return; + } + List<HInstruction> inputs = <HInstruction>[]; + for (Link<Node> link = node.entries.nodes; + !link.isEmpty; + link = link.tail) { + visit(link.head); + inputs.addLast(pop()); + inputs.addLast(pop()); + } + HLiteralList keyValuePairs = new HLiteralList(inputs); + add(keyValuePairs); + pushInvokeHelper1(backend.getMapMaker(), keyValuePairs, + new HType.fromBoundedType(compiler.mapClass.computeType(compiler), + compiler, + false)); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visit(node.value); + visit(node.key); + } + + visitNamedArgument(NamedArgument node) { + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + if (tryBuildConstantSwitch(node)) return; + + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + HBasicBlock startBlock = openNewBlock(); + visit(node.expression); + HInstruction expression = pop(); + if (node.cases.isEmpty) { + return; + } + + Link<Node> cases = node.cases.nodes; + JumpHandler jumpHandler = createJumpHandler(node); + + buildSwitchCases(cases, expression); + + HBasicBlock lastBlock = lastOpenedBlock; + + // Create merge block for break targets. + HBasicBlock joinBlock = new HBasicBlock(); + List<LocalsHandler> caseLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { + instruction.block.addSuccessor(joinBlock); + caseLocals.add(locals); + }); + if (!isAborted()) { + // The current flow is only aborted if the switch has a default that + // aborts (all previous cases must abort, and if there is no default, + // it's possible to miss all the cases). + caseLocals.add(localsHandler); + goto(current, joinBlock); + } + if (caseLocals.length != 0) { + graph.addBlock(joinBlock); + open(joinBlock); + if (caseLocals.length == 1) { + localsHandler = caseLocals[0]; + } else { + localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock); + } + } else { + // The joinblock is not used. + joinBlock = null; + } + startBlock.setBlockFlow( + new HLabeledBlockInformation.implicit( + new HSubGraphBlockInformation(new SubGraph(startBlock, lastBlock)), + elements[node]), + joinBlock); + jumpHandler.close(); + } + + bool tryBuildConstantSwitch(SwitchStatement node) { + Map<CaseMatch, Constant> constants = new Map<CaseMatch, Constant>(); + // First check whether all case expressions are compile-time constants, + // and all have the same type that doesn't override operator==. + // TODO(lrn): Move the constant resolution to the resolver, so + // we can report an error before reaching the backend. + DartType firstConstantType = null; + bool failure = false; + for (SwitchCase switchCase in node.cases) { + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is CaseMatch) { + CaseMatch match = labelOrCase; + Constant constant = + compiler.constantHandler.tryCompileNodeWithDefinitions( + match.expression, elements); + if (constant == null) { + compiler.reportWarning(match.expression, + MessageKind.NOT_A_COMPILE_TIME_CONSTANT.error()); + failure = true; + continue; + } + if (firstConstantType == null) { + firstConstantType = constant.computeType(compiler); + if (nonPrimitiveTypeOverridesEquals(constant)) { + compiler.reportWarning(match.expression, + MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS.error()); + failure = true; + } + } else { + DartType constantType = + constant.computeType(compiler); + if (constantType != firstConstantType) { + compiler.reportWarning(match.expression, + MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL.error()); + failure = true; + } + } + constants[labelOrCase] = constant; + } else { + compiler.reportWarning(node, "Unsupported: Labels on cases"); + failure = true; + } + } + } + if (failure) { + return false; + } + + // TODO(ngeoffray): Handle switch-instruction in bailout code. + work.allowSpeculativeOptimization = false; + // Then build a switch structure. + HBasicBlock expressionStart = openNewBlock(); + visit(node.expression); + HInstruction expression = pop(); + if (node.cases.isEmpty) { + return true; + } + HBasicBlock expressionEnd = current; + + HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]); + HBasicBlock expressionBlock = close(switchInstruction); + JumpHandler jumpHandler = createJumpHandler(node); + LocalsHandler savedLocals = localsHandler; + + List<List<Constant>> matchExpressions = <List<Constant>>[]; + List<HStatementInformation> statements = <HStatementInformation>[]; + bool hasDefault = false; + Element getFallThroughErrorElement = + compiler.findHelper(const SourceString("getFallThroughError")); + HasNextIterator<Node> caseIterator = + new HasNextIterator<Node>(node.cases.iterator); + while (caseIterator.hasNext) { + SwitchCase switchCase = caseIterator.next(); + List<Constant> caseConstants = <Constant>[]; + HBasicBlock block = graph.addNewBlock(); + for (Node labelOrCase in switchCase.labelsAndCases) { + if (labelOrCase is CaseMatch) { + Constant constant = constants[labelOrCase]; + caseConstants.add(constant); + HConstant hConstant = graph.addConstant(constant); + switchInstruction.inputs.add(hConstant); + hConstant.usedBy.add(switchInstruction); + expressionBlock.addSuccessor(block); + } + } + matchExpressions.add(caseConstants); + + if (switchCase.isDefaultCase) { + // An HSwitch has n inputs and n+1 successors, the last being the + // default case. + expressionBlock.addSuccessor(block); + hasDefault = true; + } + open(block); + localsHandler = new LocalsHandler.from(savedLocals); + visit(switchCase.statements); + if (!isAborted() && caseIterator.hasNext) { + pushInvokeHelper0(getFallThroughErrorElement, HType.UNKNOWN); + HInstruction error = pop(); + close(new HThrow(error)); + } + statements.add( + new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock))); + } + + // Add a join-block if necessary. + // We create [joinBlock] early, and then go through the cases that might + // want to jump to it. In each case, if we add [joinBlock] as a successor + // of another block, we also add an element to [caseLocals] that is used + // to create the phis in [joinBlock]. + // If we never jump to the join block, [caseLocals] will stay empty, and + // the join block is never added to the graph. + HBasicBlock joinBlock = new HBasicBlock(); + List<LocalsHandler> caseLocals = <LocalsHandler>[]; + jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) { + instruction.block.addSuccessor(joinBlock); + caseLocals.add(locals); + }); + if (!isAborted()) { + current.close(new HGoto()); + lastOpenedBlock.addSuccessor(joinBlock); + caseLocals.add(localsHandler); + } + if (!hasDefault) { + // The current flow is only aborted if the switch has a default that + // aborts (all previous cases must abort, and if there is no default, + // it's possible to miss all the cases). + expressionEnd.addSuccessor(joinBlock); + caseLocals.add(savedLocals); + } + assert(caseLocals.length == joinBlock.predecessors.length); + if (caseLocals.length != 0) { + graph.addBlock(joinBlock); + open(joinBlock); + if (caseLocals.length == 1) { + localsHandler = caseLocals[0]; + } else { + localsHandler = savedLocals.mergeMultiple(caseLocals, joinBlock); + } + } else { + // The joinblock is not used. + joinBlock = null; + } + + HSubExpressionBlockInformation expressionInfo = + new HSubExpressionBlockInformation(new SubExpression(expressionStart, + expressionEnd)); + expressionStart.setBlockFlow( + new HSwitchBlockInformation(expressionInfo, + matchExpressions, + statements, + hasDefault, + jumpHandler.target, + jumpHandler.labels()), + joinBlock); + + jumpHandler.close(); + return true; + } + + bool nonPrimitiveTypeOverridesEquals(Constant constant) { + // Function values override equals. Even static ones, since + // they inherit from [Function]. + if (constant.isFunction()) return true; + + // [Map] and [List] do not override equals. + // If constant is primitive, just return false. We know + // about the equals methods of num/String classes. + if (!constant.isConstructedObject()) return false; + + ConstructedConstant constructedConstant = constant; + DartType type = constructedConstant.type; + assert(type != null); + Element element = type.element; + // If the type is not a class, we'll just assume it overrides + // operator==. Typedefs do, since [Function] does. + if (!element.isClass()) return true; + ClassElement classElement = element; + return typeOverridesObjectEquals(classElement); + } + + bool typeOverridesObjectEquals(ClassElement classElement) { + Element operatorEq = + lookupOperator(classElement, const SourceString('==')); + if (operatorEq == null) return false; + // If the operator== declaration is in Object, it's not overridden. + return (operatorEq.getEnclosingClass() != compiler.objectClass); + } + + Element lookupOperator(ClassElement classElement, SourceString operatorName) { + SourceString dartMethodName = + Elements.constructOperatorName(operatorName, false); + return classElement.lookupMember(dartMethodName); + } + + + // Recursively build an if/else structure to match the cases. + void buildSwitchCases(Link<Node> cases, HInstruction expression, + [int encounteredCaseTypes = 0]) { + final int NO_TYPE = 0; + final int INT_TYPE = 1; + final int STRING_TYPE = 2; + final int CONFLICT_TYPE = 3; + int combine(int type1, int type2) => type1 | type2; + + SwitchCase node = cases.head; + // Called for the statements on all but the last case block. + // Ensures that a user expecting a fallthrough gets an error. + void visitStatementsAndAbort() { + visit(node.statements); + if (!isAborted()) { + compiler.reportWarning(node, 'Missing break at end of switch case'); + Element element = + compiler.findHelper(const SourceString("getFallThroughError")); + pushInvokeHelper0(element, HType.UNKNOWN); + HInstruction error = pop(); + close(new HThrow(error)); + } + } + + Link<Node> skipLabels(Link<Node> labelsAndCases) { + while (!labelsAndCases.isEmpty && labelsAndCases.head is Label) { + labelsAndCases = labelsAndCases.tail; + } + return labelsAndCases; + } + + Link<Node> labelsAndCases = skipLabels(node.labelsAndCases.nodes); + if (labelsAndCases.isEmpty) { + // Default case with no expressions. + if (!node.isDefaultCase) { + compiler.internalError("Case with no expression and not default", + node: node); + } + visit(node.statements); + // This must be the final case (otherwise "default" would be invalid), + // so we don't need to check for fallthrough. + return; + } + + // Recursively build the test conditions. Leaves the result on the + // expression stack. + void buildTests(Link<Node> remainingCases) { + // Build comparison for one case expression. + void left() { + CaseMatch match = remainingCases.head; + // TODO(lrn): Move the constant resolution to the resolver, so + // we can report an error before reaching the backend. + Constant constant = + compiler.constantHandler.tryCompileNodeWithDefinitions( + match.expression, elements); + if (constant != null) { + stack.add(graph.addConstant(constant)); + } else { + visit(match.expression); + } + push(new HIdentity(pop(), expression)); + } + + // If this is the last expression, just return it. + Link<Node> tail = skipLabels(remainingCases.tail); + if (tail.isEmpty) { + left(); + return; + } + + void right() { + buildTests(tail); + } + SsaBranchBuilder branchBuilder = + new SsaBranchBuilder(this, remainingCases.head); + branchBuilder.handleLogicalAndOr(left, right, isAnd: false); + } + + if (node.isDefaultCase) { + // Default case must be last. + assert(cases.tail.isEmpty); + // Perform the tests until one of them match, but then always execute the + // statements. + // TODO(lrn): Stop performing tests when all expressions are compile-time + // constant strings or integers. + handleIf(node, () { buildTests(labelsAndCases); }, (){}, null); + visit(node.statements); + } else { + if (cases.tail.isEmpty) { + handleIf(node, + () { buildTests(labelsAndCases); }, + () { visit(node.statements); }, + null); + } else { + handleIf(node, + () { buildTests(labelsAndCases); }, + () { visitStatementsAndAbort(); }, + () { buildSwitchCases(cases.tail, expression, + encounteredCaseTypes); }); + } + } + } + + visitSwitchCase(SwitchCase node) { + compiler.internalError('SsaBuilder.visitSwitchCase'); + } + + visitCaseMatch(CaseMatch node) { + compiler.internalError('SsaBuilder.visitCaseMatch'); + } + + visitTryStatement(TryStatement node) { + work.allowSpeculativeOptimization = false; + // Save the current locals. The catch block and the finally block + // must not reuse the existing locals handler. None of the variables + // that have been defined in the body-block will be used, but for + // loops we will add (unnecessary) phis that will reference the body + // variables. This makes it look as if the variables were used + // in a non-dominated block. + LocalsHandler savedLocals = new LocalsHandler.from(localsHandler); + HBasicBlock enterBlock = openNewBlock(); + HTry tryInstruction = new HTry(); + close(tryInstruction); + bool oldInTryStatement = inTryStatement; + inTryStatement = true; + + HBasicBlock startTryBlock; + HBasicBlock endTryBlock; + HBasicBlock startCatchBlock; + HBasicBlock endCatchBlock; + HBasicBlock startFinallyBlock; + HBasicBlock endFinallyBlock; + + startTryBlock = graph.addNewBlock(); + open(startTryBlock); + visit(node.tryBlock); + if (!isAborted()) endTryBlock = close(new HGoto()); + SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock); + SubGraph catchGraph = null; + HLocalValue exception = null; + + if (!node.catchBlocks.isEmpty) { + localsHandler = new LocalsHandler.from(savedLocals); + startCatchBlock = graph.addNewBlock(); + open(startCatchBlock); + // TODO(kasperl): Bad smell. We shouldn't be constructing elements here. + // Note that the name of this element is irrelevant. + Element element = new ElementX(const SourceString('exception'), + ElementKind.PARAMETER, + currentElement); + exception = new HLocalValue(element); + add(exception); + HInstruction oldRethrowableException = rethrowableException; + rethrowableException = exception; + + pushInvokeHelper1( + backend.getExceptionUnwrapper(), exception, HType.UNKNOWN); + HInvokeStatic unwrappedException = pop(); + tryInstruction.exception = exception; + Link<Node> link = node.catchBlocks.nodes; + + void pushCondition(CatchBlock catchBlock) { + if (catchBlock.onKeyword != null) { + DartType type = elements.getType(catchBlock.type); + if (type == null) { + compiler.cancel('On with unresolved type', + node: catchBlock.type); + } + HInstruction condition = + new HIs(type, <HInstruction>[unwrappedException]); + push(condition); + } + else { + VariableDefinitions declaration = catchBlock.formals.nodes.head; + HInstruction condition = null; + if (declaration.type == null) { + condition = graph.addConstantBool(true, constantSystem); + stack.add(condition); + } else { + // TODO(aprelev@gmail.com): Once old catch syntax is removed + // "if" condition above and this "else" branch should be deleted as + // type of declared variable won't matter for the catch + // condition. + DartType type = elements.getType(declaration.type); + if (type == null) { + compiler.cancel('Catch with unresolved type', node: catchBlock); + } + condition = + new HIs(type, <HInstruction>[unwrappedException], nullOk: true); + push(condition); + } + } + } + + void visitThen() { + CatchBlock catchBlock = link.head; + link = link.tail; + if (catchBlock.exception != null) { + localsHandler.updateLocal(elements[catchBlock.exception], + unwrappedException); + } + Node trace = catchBlock.trace; + if (trace != null) { + pushInvokeHelper1( + backend.getTraceFromException(), exception, HType.UNKNOWN); + HInstruction traceInstruction = pop(); + localsHandler.updateLocal(elements[trace], traceInstruction); + } + visit(catchBlock); + } + + void visitElse() { + if (link.isEmpty) { + close(new HThrow(exception, isRethrow: true)); + } else { + CatchBlock newBlock = link.head; + handleIf(node, + () { pushCondition(newBlock); }, + visitThen, visitElse); + } + } + + CatchBlock firstBlock = link.head; + handleIf(node, () { pushCondition(firstBlock); }, visitThen, visitElse); + if (!isAborted()) endCatchBlock = close(new HGoto()); + + rethrowableException = oldRethrowableException; + tryInstruction.catchBlock = startCatchBlock; + catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock); + } + + SubGraph finallyGraph = null; + if (node.finallyBlock != null) { + localsHandler = new LocalsHandler.from(savedLocals); + startFinallyBlock = graph.addNewBlock(); + open(startFinallyBlock); + visit(node.finallyBlock); + if (!isAborted()) endFinallyBlock = close(new HGoto()); + tryInstruction.finallyBlock = startFinallyBlock; + finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock); + } + + HBasicBlock exitBlock = graph.addNewBlock(); + + addOptionalSuccessor(b1, b2) { if (b2 != null) b1.addSuccessor(b2); } + addExitTrySuccessor(successor) { + if (successor == null) return; + // Iterate over all blocks created inside this try/catch, and + // attach successor information to blocks that end with + // [HExitTry]. + for (int i = startTryBlock.id; i < successor.id; i++) { + HBasicBlock block = graph.blocks[i]; + var last = block.last; + if (last is HExitTry) { + block.addSuccessor(successor); + } else if (last is HTry) { + // Skip all blocks inside this nested try/catch. + i = last.joinBlock.id; + } + } + } + + // Setup all successors. The entry block that contains the [HTry] + // has 1) the body, 2) the catch, 3) the finally, and 4) the exit + // blocks as successors. + enterBlock.addSuccessor(startTryBlock); + addOptionalSuccessor(enterBlock, startCatchBlock); + addOptionalSuccessor(enterBlock, startFinallyBlock); + enterBlock.addSuccessor(exitBlock); + + // The body has either the catch or the finally block as successor. + if (endTryBlock != null) { + assert(startCatchBlock != null || startFinallyBlock != null); + endTryBlock.addSuccessor( + startCatchBlock != null ? startCatchBlock : startFinallyBlock); + } + + // The catch block has either the finally or the exit block as + // successor. + if (endCatchBlock != null) { + endCatchBlock.addSuccessor( + startFinallyBlock != null ? startFinallyBlock : exitBlock); + } + + // The finally block has the exit block as successor. + if (endFinallyBlock != null) { + endFinallyBlock.addSuccessor(exitBlock); + } + + // If a block inside try/catch aborts (eg with a return statement), + // we explicitely mark this block a predecessor of the catch + // block and the finally block. + addExitTrySuccessor(startCatchBlock); + addExitTrySuccessor(startFinallyBlock); + + // Use the locals handler not altered by the catch and finally + // blocks. + localsHandler = savedLocals; + open(exitBlock); + enterBlock.setBlockFlow( + new HTryBlockInformation( + wrapStatementGraph(bodyGraph), + exception, + wrapStatementGraph(catchGraph), + wrapStatementGraph(finallyGraph)), + exitBlock); + inTryStatement = oldInTryStatement; + } + + visitScriptTag(ScriptTag node) { + compiler.unimplemented('SsaBuilder.visitScriptTag', node: node); + } + + visitCatchBlock(CatchBlock node) { + visit(node.block); + } + + visitTypedef(Typedef node) { + compiler.unimplemented('SsaBuilder.visitTypedef', node: node); + } + + visitTypeVariable(TypeVariable node) { + compiler.internalError('SsaBuilder.visitTypeVariable'); + } + + HType mapBaseType(BaseType baseType) { + if (!baseType.isClass()) return HType.UNKNOWN; + ClassBaseType classBaseType = baseType; + return new HType.fromBoundedType( + classBaseType.element.computeType(compiler), compiler, false); + } + + HType mapInferredType(ConcreteType concreteType) { + if (concreteType == null) return HType.UNKNOWN; + HType ssaType = HType.CONFLICTING; + for (BaseType baseType in concreteType.baseTypes) { + ssaType = ssaType.union(mapBaseType(baseType), compiler); + } + assert(!ssaType.isConflicting()); + return ssaType; + } + + HType mapNativeType(type) { + if (type == native.SpecialType.JsObject) { + return new HBoundedType.exact( + compiler.objectClass.computeType(compiler)); + } else if (type == native.SpecialType.JsArray) { + return HType.READABLE_ARRAY; + } else { + return new HType.fromBoundedType(type, compiler, false); + } + } + + HType mapNativeBehaviorType(native.NativeBehavior nativeBehavior) { + if (nativeBehavior.typesInstantiated.isEmpty) return HType.UNKNOWN; + + HType ssaType = HType.CONFLICTING; + for (final type in nativeBehavior.typesInstantiated) { + ssaType = ssaType.union(mapNativeType(type), compiler); + } + assert(!ssaType.isConflicting()); + return ssaType; + } +} + +/** + * Visitor that handles generation of string literals (LiteralString, + * StringInterpolation), and otherwise delegates to the given visitor for + * non-literal subexpressions. + * TODO(lrn): Consider whether to handle compile time constant int/boolean + * expressions as well. + */ +class StringBuilderVisitor extends Visitor { + final SsaBuilder builder; + final Node diagnosticNode; + + /** + * The string value generated so far. + */ + HInstruction result = null; + + StringBuilderVisitor(this.builder, this.diagnosticNode); + + void visit(Node node) { + node.accept(this); + } + + visitNode(Node node) { + builder.compiler.internalError('unexpected node', node: node); + } + + void visitExpression(Node node) { + node.accept(builder); + HInstruction expression = builder.pop(); + result = (result == null) ? expression : concat(result, expression); + } + + void visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + } + + void visitStringInterpolationPart(StringInterpolationPart node) { + visit(node.expression); + visit(node.string); + } + + void visitStringJuxtaposition(StringJuxtaposition node) { + node.visitChildren(this); + } + + void visitNodeList(NodeList node) { + node.visitChildren(this); + } + + HInstruction concat(HInstruction left, HInstruction right) { + HInstruction instruction = new HStringConcat(left, right, diagnosticNode); + builder.add(instruction); + return instruction; + } +} + +/** + * This class visits the method that is a candidate for inlining and + * finds whether it is too difficult to inline. + */ +class InlineWeeder extends Visitor { + final TreeElements elements; + bool seenReturn = false; + bool tooDifficult = false; + + InlineWeeder(this.elements); + + static bool canBeInlined(FunctionExpression functionExpression, + TreeElements elements) { + InlineWeeder weeder = new InlineWeeder(elements); + weeder.visit(functionExpression.body); + if (weeder.tooDifficult) return false; + return true; + } + + void visit(Node node) { + node.accept(this); + } + + void visitNode(Node node) { + if (seenReturn) { + tooDifficult = true; + } else { + node.visitChildren(this); + } + } + + void visitFunctionExpression(Node node) { + tooDifficult = true; + } + + void visitFunctionDeclaration(Node node) { + tooDifficult = true; + } + + void visitSend(Send node) { + if (node.isParameterCheck) { + tooDifficult = true; + return; + } + node.visitChildren(this); + } + + visitLoop(Node node) { + node.visitChildren(this); + if (seenReturn) tooDifficult = true; + } + + void visitReturn(Return node) { + if (seenReturn + || identical(node.getBeginToken().stringValue, 'native') + || node.isRedirectingFactoryBody) { + tooDifficult = true; + return; + } + node.visitChildren(this); + seenReturn = true; + } + + void visitTryStatement(Node node) { + tooDifficult = true; + } + + void visitThrow(Node node) { + tooDifficult = true; + } +} + +class InliningState { + /** + * Documentation wanted -- johnniwinther + * + * Invariant: [function] must be an implementation element. + */ + final PartialFunctionElement function; + final Element oldReturnElement; + final DartType oldReturnType; + final TreeElements oldElements; + final List<HInstruction> oldStack; + + InliningState(this.function, + this.oldReturnElement, + this.oldReturnType, + this.oldElements, + this.oldStack) { + assert(function.isImplementation); + } +} + +class SsaBranch { + final SsaBranchBuilder branchBuilder; + final HBasicBlock block; + LocalsHandler startLocals; + LocalsHandler exitLocals; + SubGraph graph; + + SsaBranch(this.branchBuilder) : block = new HBasicBlock(); +} + +class SsaBranchBuilder { + final SsaBuilder builder; + final Node diagnosticNode; + + SsaBranchBuilder(this.builder, [this.diagnosticNode]); + + Compiler get compiler => builder.compiler; + + void checkNotAborted() { + if (builder.isAborted()) { + compiler.unimplemented("aborted control flow", node: diagnosticNode); + } + } + + void buildCondition(void visitCondition(), + SsaBranch conditionBranch, + SsaBranch thenBranch, + SsaBranch elseBranch) { + startBranch(conditionBranch); + visitCondition(); + checkNotAborted(); + assert(identical(builder.current, builder.lastOpenedBlock)); + HInstruction conditionValue = builder.popBoolified(); + HIf branch = new HIf(conditionValue); + HBasicBlock conditionExitBlock = builder.current; + builder.close(branch); + conditionBranch.exitLocals = builder.localsHandler; + conditionExitBlock.addSuccessor(thenBranch.block); + conditionExitBlock.addSuccessor(elseBranch.block); + bool conditionBranchLocalsCanBeReused = + mergeLocals(conditionBranch, thenBranch, mayReuseFromLocals: true); + mergeLocals(conditionBranch, elseBranch, + mayReuseFromLocals: conditionBranchLocalsCanBeReused); + + conditionBranch.graph = + new SubExpression(conditionBranch.block, conditionExitBlock); + } + + /** + * Returns true if the locals of the [fromBranch] may be reused. A [:true:] + * return value implies that [mayReuseFromLocals] was set to [:true:]. + */ + bool mergeLocals(SsaBranch fromBranch, SsaBranch toBranch, + {bool mayReuseFromLocals}) { + LocalsHandler fromLocals = fromBranch.exitLocals; + if (toBranch.startLocals == null) { + if (mayReuseFromLocals) { + toBranch.startLocals = fromLocals; + return false; + } else { + toBranch.startLocals = new LocalsHandler.from(fromLocals); + return true; + } + } else { + toBranch.startLocals.mergeWith(fromLocals, toBranch.block); + return true; + } + } + + void startBranch(SsaBranch branch) { + builder.graph.addBlock(branch.block); + builder.localsHandler = branch.startLocals; + builder.open(branch.block); + } + + HInstruction buildBranch(SsaBranch branch, + void visitBranch(), + SsaBranch joinBranch, + bool isExpression) { + startBranch(branch); + visitBranch(); + branch.graph = new SubGraph(branch.block, builder.lastOpenedBlock); + branch.exitLocals = builder.localsHandler; + if (!builder.isAborted()) { + builder.goto(builder.current, joinBranch.block); + mergeLocals(branch, joinBranch, mayReuseFromLocals: true); + } + if (isExpression) { + checkNotAborted(); + return builder.pop(); + } + return null; + } + + handleIf(void visitCondition(), void visitThen(), void visitElse()) { + if (visitElse == null) { + // Make sure to have an else part to avoid a critical edge. A + // critical edge is an edge that connects a block with multiple + // successors to a block with multiple predecessors. We avoid + // such edges because they prevent inserting copies during code + // generation of phi instructions. + visitElse = () {}; + } + + _handleDiamondBranch(visitCondition, visitThen, visitElse, false); + } + + handleConditional(void visitCondition(), void visitThen(), void visitElse()) { + assert(visitElse != null); + _handleDiamondBranch(visitCondition, visitThen, visitElse, true); + } + + void handleLogicalAndOr(void left(), void right(), {bool isAnd}) { + // x && y is transformed into: + // t0 = boolify(x); + // if (t0) { + // t1 = boolify(y); + // } + // result = phi(t1, false); + // + // x || y is transformed into: + // t0 = boolify(x); + // if (not(t0)) { + // t1 = boolify(y); + // } + // result = phi(t1, true); + HInstruction boolifiedLeft; + HInstruction boolifiedRight; + + void visitCondition() { + left(); + boolifiedLeft = builder.popBoolified(); + builder.stack.add(boolifiedLeft); + if (!isAnd) { + builder.push(new HNot(builder.pop())); + } + } + + void visitThen() { + right(); + boolifiedRight = builder.popBoolified(); + } + + handleIf(visitCondition, visitThen, null); + HConstant notIsAnd = + builder.graph.addConstantBool(!isAnd, builder.constantSystem); + HPhi result = new HPhi.manyInputs(null, + <HInstruction>[boolifiedRight, notIsAnd]); + builder.current.addPhi(result); + builder.stack.add(result); + } + + void handleLogicalAndOrWithLeftNode(Node left, + void visitRight(), + {bool isAnd}) { + // This method is similar to [handleLogicalAndOr] but optimizes the case + // where left is a logical "and" or logical "or". + // + // For example (x && y) && z is transformed into x && (y && z): + // t0 = boolify(x); + // if (t0) { + // t1 = boolify(y); + // if (t1) { + // t2 = boolify(z); + // } + // t3 = phi(t2, false); + // } + // result = phi(t3, false); + + Send send = left.asSend(); + if (send != null && + (isAnd ? send.isLogicalAnd : send.isLogicalOr)) { + Node newLeft = send.receiver; + Link<Node> link = send.argumentsNode.nodes; + assert(link.tail.isEmpty); + Node middle = link.head; + handleLogicalAndOrWithLeftNode( + newLeft, + () => handleLogicalAndOrWithLeftNode(middle, visitRight, + isAnd: isAnd), + isAnd: isAnd); + } else { + handleLogicalAndOr(() => builder.visit(left), visitRight, isAnd: isAnd); + } + } + + void _handleDiamondBranch(void visitCondition(), + void visitThen(), + void visitElse(), + bool isExpression) { + SsaBranch conditionBranch = new SsaBranch(this); + SsaBranch thenBranch = new SsaBranch(this); + SsaBranch elseBranch = new SsaBranch(this); + SsaBranch joinBranch = new SsaBranch(this); + + conditionBranch.startLocals = builder.localsHandler; + builder.goto(builder.current, conditionBranch.block); + + buildCondition(visitCondition, conditionBranch, thenBranch, elseBranch); + HInstruction thenValue = + buildBranch(thenBranch, visitThen, joinBranch, isExpression); + HInstruction elseValue = + buildBranch(elseBranch, visitElse, joinBranch, isExpression); + + if (isExpression) { + assert(thenValue != null && elseValue != null); + HPhi phi = + new HPhi.manyInputs(null, <HInstruction>[thenValue, elseValue]); + joinBranch.block.addPhi(phi); + builder.stack.add(phi); + } + + HBasicBlock thenBlock = thenBranch.block; + HBasicBlock elseBlock = elseBranch.block; + HBasicBlock joinBlock; + // If at least one branch did not abort, open the joinBranch. + if (!joinBranch.block.predecessors.isEmpty) { + startBranch(joinBranch); + joinBlock = joinBranch.block; + } + + HIfBlockInformation info = + new HIfBlockInformation( + new HSubExpressionBlockInformation(conditionBranch.graph), + new HSubGraphBlockInformation(thenBranch.graph), + new HSubGraphBlockInformation(elseBranch.graph)); + + HBasicBlock conditionStartBlock = conditionBranch.block; + conditionStartBlock.setBlockFlow(info, joinBlock); + SubGraph conditionGraph = conditionBranch.graph; + HIf branch = conditionGraph.end.last; + assert(branch is HIf); + branch.blockInformation = conditionStartBlock.blockFlow; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart new file mode 100644 index 0000000..e5caac1 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart
@@ -0,0 +1,3006 @@ +// 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. + +part of ssa; + +class SsaCodeGeneratorTask extends CompilerTask { + + final JavaScriptBackend backend; + + SsaCodeGeneratorTask(JavaScriptBackend backend) + : this.backend = backend, + super(backend.compiler); + String get name => 'SSA code generator'; + NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter; + + + js.Fun buildJavaScriptFunction(FunctionElement element, + List<js.Parameter> parameters, + js.Block body) { + FunctionExpression expression = + element.implementation.parseNode(backend.compiler); + js.Fun result = new js.Fun(parameters, body); + // TODO(johnniwinther): remove the 'element.patch' hack. + Element sourceElement = element.patch == null ? element : element.patch; + SourceFile sourceFile = sourceElement.getCompilationUnit().script.file; + // TODO(podivilov): find the right sourceFile here and remove offset checks + // below. + if (expression.getBeginToken().charOffset < sourceFile.text.length) { + result.sourcePosition = new SourceFileLocation( + sourceFile, expression.getBeginToken()); + } + if (expression.getEndToken().charOffset < sourceFile.text.length) { + result.endSourcePosition = new SourceFileLocation( + sourceFile, expression.getEndToken()); + } + return result; + } + + CodeBuffer prettyPrint(js.Node node) { + var code = js.prettyPrint(node, compiler, allowVariableMinification: true); + return code; + } + + js.Expression generateCode(CodegenWorkItem work, HGraph graph) { + if (work.element.isField()) { + return generateLazyInitializer(work, graph); + } else { + return generateMethod(work, graph); + } + } + + js.Expression generateLazyInitializer(work, graph) { + return measure(() { + compiler.tracer.traceGraph("codegen", graph); + SsaOptimizedCodeGenerator codegen = + new SsaOptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + return new js.Fun(codegen.parameters, codegen.body); + }); + } + + js.Expression generateMethod(CodegenWorkItem work, HGraph graph) { + return measure(() { + compiler.tracer.traceGraph("codegen", graph); + SsaOptimizedCodeGenerator codegen = + new SsaOptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + + FunctionElement element = work.element; + js.Block body; + ClassElement enclosingClass = element.getEnclosingClass(); + + if (element.isInstanceMember() + && enclosingClass.isNative() + && native.isOverriddenMethod( + element, enclosingClass, nativeEmitter)) { + // Record that this method is overridden. In case of optional + // arguments, the emitter will generate stubs to handle them, + // and needs to know if the method is overridden. + nativeEmitter.overriddenMethods.add(element); + StringBuffer buffer = new StringBuffer(); + body = + nativeEmitter.generateMethodBodyWithPrototypeCheckForElement( + element, codegen.body, codegen.parameters); + } else { + body = codegen.body; + } + + return buildJavaScriptFunction(element, codegen.parameters, body); + }); + } + + js.Expression generateBailoutMethod(CodegenWorkItem work, HGraph graph) { + return measure(() { + compiler.tracer.traceGraph("codegen-bailout", graph); + + SsaUnoptimizedCodeGenerator codegen = + new SsaUnoptimizedCodeGenerator(backend, work); + codegen.visitGraph(graph); + + js.Block body = new js.Block(<js.Statement>[]); + body.statements.add(codegen.body); + js.Fun fun = + buildJavaScriptFunction(work.element, codegen.newParameters, body); + return fun; + }); + } +} + +// Stop-gap until the core classes have such a class. +class OrderedSet<T> { + final LinkedHashMap<T, bool> map = new LinkedHashMap<T, bool>(); + + void add(T x) { + if (!map.containsKey(x)) { + map[x] = true; + } + } + + bool contains(T x) => map.containsKey(x); + + bool remove(T x) => map.remove(x) != null; + + bool get isEmpty => map.isEmpty; + + void forEach(f) => map.keys.forEach(f); + + T get first { + var iterator = map.keys.iterator; + if (!iterator.moveNext()) throw new StateError("No elements"); + return iterator.current; + } + + get length => map.length; +} + +typedef void ElementAction(Element element); + +abstract class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor { + /** + * Returned by [expressionType] to tell how code can be generated for + * a subgraph. + * - [TYPE_STATEMENT] means that the graph must be generated as a statement, + * which is always possible. + * - [TYPE_EXPRESSION] means that the graph can be generated as an expression, + * or possibly several comma-separated expressions. + * - [TYPE_DECLARATION] means that the graph can be generated as an + * expression, and that it only generates expressions of the form + * variable = expression + * which are also valid as parts of a "var" declaration. + */ + static const int TYPE_STATEMENT = 0; + static const int TYPE_EXPRESSION = 1; + static const int TYPE_DECLARATION = 2; + + /** + * Whether we are currently generating expressions instead of statements. + * This includes declarations, which are generated as expressions. + */ + bool isGeneratingExpression = false; + + final JavaScriptBackend backend; + final CodegenWorkItem work; + final HTypeMap types; + + final Set<HInstruction> generateAtUseSite; + final Set<HInstruction> controlFlowOperators; + final Map<Element, ElementAction> breakAction; + final Map<Element, ElementAction> continueAction; + final List<js.Parameter> parameters; + + js.Block currentContainer; + js.Block get body => currentContainer; + List<js.Expression> expressionStack; + List<js.Block> oldContainerStack; + + /** + * Contains the names of the instructions, as well as the parallel + * copies to perform on block transitioning. + */ + VariableNames variableNames; + bool shouldGroupVarDeclarations = false; + + /** + * While generating expressions, we can't insert variable declarations. + * Instead we declare them at the start of the function. When minifying + * we do this most of the time, because it reduces the size unless there + * is only one variable. + */ + final OrderedSet<String> collectedVariableDeclarations; + + /** + * Set of variables and parameters that have already been declared. + */ + final Set<String> declaredLocals; + + int indent = 0; + HGraph currentGraph; + + // Records a block-information that is being handled specially. + // Used to break bad recursion. + HBlockInformation currentBlockInformation; + // The subgraph is used to delimit traversal for some constructions, e.g., + // if branches. + SubGraph subGraph; + + SsaCodeGenerator(this.backend, CodegenWorkItem work) + : this.work = work, + this.types = + (work.compilationContext as JavaScriptItemCompilationContext).types, + declaredLocals = new Set<String>(), + collectedVariableDeclarations = new OrderedSet<String>(), + currentContainer = new js.Block.empty(), + parameters = <js.Parameter>[], + expressionStack = <js.Expression>[], + oldContainerStack = <js.Block>[], + generateAtUseSite = new Set<HInstruction>(), + controlFlowOperators = new Set<HInstruction>(), + breakAction = new Map<Element, ElementAction>(), + continueAction = new Map<Element, ElementAction>(); + + Compiler get compiler => backend.compiler; + NativeEmitter get nativeEmitter => backend.emitter.nativeEmitter; + CodegenEnqueuer get world => backend.compiler.enqueuer.codegen; + + bool isGenerateAtUseSite(HInstruction instruction) { + return generateAtUseSite.contains(instruction); + } + + bool isNonNegativeInt32Constant(HInstruction instruction) { + if (instruction.isConstantInteger()) { + HConstant constantInstruction = instruction; + PrimitiveConstant primitiveConstant = constantInstruction.constant; + int value = primitiveConstant.value; + if (value >= 0 && value < (1 << 31)) { + return true; + } + } + return false; + } + + bool hasNonBitOpUser(HInstruction instruction, Set<HPhi> phiSet) { + for (HInstruction user in instruction.usedBy) { + if (user is HPhi) { + if (!phiSet.contains(user)) { + phiSet.add(user); + if (hasNonBitOpUser(user, phiSet)) return true; + } + } else if (user is! HBitNot && user is! HBinaryBitOp) { + return true; + } + } + return false; + } + + // We want the outcome of bit-operations to be positive. However, if + // the result of a bit-operation is only used by other bit + // operations we do not have to convert to an unsigned + // integer. Also, if we are using & with a positive constant we know + // that the result is positive already and need no conversion. + bool requiresUintConversion(HInstruction instruction) { + if (instruction is HBitAnd) { + HBitAnd bitAnd = instruction; + if (isNonNegativeInt32Constant(bitAnd.left) || + isNonNegativeInt32Constant(bitAnd.right)) { + return false; + } + } + return hasNonBitOpUser(instruction, new Set<HPhi>()); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [statement]. + */ + void pushStatement(js.Statement statement, [HInstruction instruction]) { + assert(expressionStack.isEmpty); + if (instruction != null) { + attachLocation(statement, instruction); + } + currentContainer.statements.add(statement); + } + + void insertStatementAtStart(js.Statement statement) { + currentContainer.statements.insertRange(0, 1, statement); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [expression]. + */ + pushExpressionAsStatement(js.Expression expression, + [HInstruction instruction]) { + pushStatement(new js.ExpressionStatement(expression), instruction); + } + + /** + * If the [instruction] is not `null` it will be used to attach the position + * to the [expression]. + */ + push(js.Expression expression, [HInstruction instruction]) { + if (instruction != null) { + attachLocation(expression, instruction); + } + expressionStack.add(expression); + } + + js.Expression pop() { + return expressionStack.removeLast(); + } + + attachLocationToLast(HInstruction instruction) { + attachLocation(expressionStack.last, instruction); + } + + js.Node attachLocation(js.Node jsNode, HInstruction instruction) { + jsNode.sourcePosition = instruction.sourcePosition; + return jsNode; + } + + js.Node attachLocationRange(js.Node jsNode, + SourceFileLocation sourcePosition, + SourceFileLocation endSourcePosition) { + jsNode.sourcePosition = sourcePosition; + jsNode.endSourcePosition = endSourcePosition; + return jsNode; + } + + visitTypeGuard(HTypeGuard node); + visitBailoutTarget(HBailoutTarget node); + + beginGraph(HGraph graph); + endGraph(HGraph graph); + + preLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + startLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + endLabeledBlock(HLabeledBlockInformation labeledBlockInfo); + + void preGenerateMethod(HGraph graph) { + new SsaInstructionMerger(types, generateAtUseSite).visitGraph(graph); + new SsaConditionMerger( + types, generateAtUseSite, controlFlowOperators).visitGraph(graph); + SsaLiveIntervalBuilder intervalBuilder = + new SsaLiveIntervalBuilder(compiler, generateAtUseSite); + intervalBuilder.visitGraph(graph); + SsaVariableAllocator allocator = new SsaVariableAllocator( + compiler, + intervalBuilder.liveInstructions, + intervalBuilder.liveIntervals, + generateAtUseSite); + allocator.visitGraph(graph); + variableNames = allocator.names; + shouldGroupVarDeclarations = allocator.names.numberOfVariables > 1; + + // Don't register a return type for lazily initialized variables. + if (work.element is! FunctionElement) return; + + // Register return types to the backend. + graph.exit.predecessors.forEach((HBasicBlock block) { + HInstruction last = block.last; + assert(last is HGoto || last is HReturn); + if (last is HReturn) { + backend.registerReturnType(work.element, types[last.inputs[0]]); + } else { + backend.registerReturnType(work.element, HType.NULL); + } + }); + } + + void handleDelayedVariableDeclarations() { + // If we have only one variable declaration and the first statement is an + // assignment to that variable then we can merge the two. We count the + // number of variables in the variable allocator to try to avoid this issue, + // but it sometimes happens that the variable allocator introduces a + // temporary variable that it later eliminates. + if (!collectedVariableDeclarations.isEmpty) { + if (collectedVariableDeclarations.length == 1 && + currentContainer.statements.length >= 1 && + currentContainer.statements[0] is js.ExpressionStatement) { + String name = collectedVariableDeclarations.first; + js.ExpressionStatement statement = currentContainer.statements[0]; + if (statement.expression is js.Assignment) { + js.Assignment assignment = statement.expression; + if (!assignment.isCompound && + assignment.leftHandSide is js.VariableReference) { + js.VariableReference variableReference = assignment.leftHandSide; + if (variableReference.name == name) { + js.VariableDeclaration decl = new js.VariableDeclaration(name); + js.VariableInitialization initialization = + new js.VariableInitialization(decl, assignment.value); + currentContainer.statements[0] = new js.ExpressionStatement( + new js.VariableDeclarationList([initialization])); + return; + } + } + } + } + // If we can't merge the declaration with the first assignment then we + // just do it with a new var z,y,x; statement. + List<js.VariableInitialization> declarations = + <js.VariableInitialization>[]; + collectedVariableDeclarations.forEach((String name) { + declarations.add(new js.VariableInitialization( + new js.VariableDeclaration(name), null)); + }); + var declarationList = new js.VariableDeclarationList(declarations); + insertStatementAtStart(new js.ExpressionStatement(declarationList)); + } + } + + visitGraph(HGraph graph) { + preGenerateMethod(graph); + currentGraph = graph; + indent++; // We are already inside a function. + subGraph = new SubGraph(graph.entry, graph.exit); + HBasicBlock start = beginGraph(graph); + visitBasicBlock(start); + handleDelayedVariableDeclarations(); + endGraph(graph); + } + + void visitSubGraph(SubGraph newSubGraph) { + SubGraph oldSubGraph = subGraph; + subGraph = newSubGraph; + visitBasicBlock(subGraph.start); + subGraph = oldSubGraph; + } + + /** + * Check whether a sub-graph can be generated as an expression, or even + * as a declaration, or if it has to fall back to being generated as + * a statement. + * Expressions are anything that doesn't generate control flow constructs. + * Declarations must only generate assignments on the form "id = expression", + * and not, e.g., expressions where the value isn't assigned, or where it's + * assigned to something that's not a simple variable. + */ + int expressionType(HExpressionInformation info) { + // The only HExpressionInformation used as part of a HBlockInformation is + // current HSubExpressionBlockInformation, so it's the only one reaching + // here. If we start using the other HExpressionInformation types too, + // this code should be generalized. + assert(info is HSubExpressionBlockInformation); + HSubExpressionBlockInformation expressionInfo = info; + SubGraph limits = expressionInfo.subExpression; + + // Start assuming that we can generate declarations. If we find a + // counter-example, we degrade our assumption to either expression or + // statement, and in the latter case, we can return immediately since + // it can't get any worse. E.g., a function call where the return value + // isn't used can't be in a declaration. A bailout can't be in an + // expression. + int result = TYPE_DECLARATION; + HBasicBlock basicBlock = limits.start; + do { + HInstruction current = basicBlock.first; + while (current != basicBlock.last) { + // E.g, type guards. + if (current.isControlFlow()) { + return TYPE_STATEMENT; + } + // HFieldSet generates code on the form x.y = ..., which isn't + // valid in a declaration, but it also always have no uses, so + // it's caught by that test too. + assert(current is! HFieldSet || current.usedBy.isEmpty); + if (current.usedBy.isEmpty) { + result = TYPE_EXPRESSION; + } + current = current.next; + } + if (current is HGoto) { + basicBlock = basicBlock.successors[0]; + } else if (current is HConditionalBranch) { + if (generateAtUseSite.contains(current)) { + // Short-circuit control flow operator trickery. + // Check the second half, which will continue into the join. + // (The first half is [inputs[0]], the second half is [successors[0]], + // and [successors[1]] is the join-block). + basicBlock = basicBlock.successors[0]; + } else { + // We allow an expression to end on an HIf (a condition expression). + return identical(basicBlock, limits.end) ? result : TYPE_STATEMENT; + } + } else { + // Expression-incompatible control flow. + return TYPE_STATEMENT; + } + } while (limits.contains(basicBlock)); + return result; + } + + bool isJSExpression(HExpressionInformation info) { + return !identical(expressionType(info), TYPE_STATEMENT); + } + + bool isJSDeclaration(HExpressionInformation info) { + return identical(expressionType(info), TYPE_DECLARATION); + } + + bool isJSCondition(HExpressionInformation info) { + HSubExpressionBlockInformation graph = info; + SubExpression limits = graph.subExpression; + return !identical(expressionType(info), TYPE_STATEMENT) && + (limits.end.last is HConditionalBranch); + } + + /** + * Generate statements from block information. + * If the block information contains expressions, generate only + * assignments, and if it ends in a conditional branch, don't generate + * the condition. + */ + void generateStatements(HBlockInformation block) { + if (block is HStatementInformation) { + block.accept(this); + } else { + HSubExpressionBlockInformation expression = block; + visitSubGraph(expression.subExpression); + } + } + + js.Block generateStatementsInNewBlock(HBlockInformation block) { + js.Block result = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = result; + generateStatements(block); + currentContainer = oldContainer; + return result; + } + + /** + * If the [block] only contains one statement returns that statement. If the + * that statement itself is a block, recursively calls this method. + * + * If the block is empty, returns a new instance of [js.NOP]. + */ + js.Statement unwrapStatement(js.Block block) { + int len = block.statements.length; + if (len == 0) return new js.EmptyStatement(); + if (len == 1) { + js.Statement result = block.statements[0]; + if (result is Block) return unwrapStatement(result); + return result; + } + return block; + } + + /** + * Generate expressions from block information. + */ + js.Expression generateExpression(HExpressionInformation expression) { + // Currently we only handle sub-expression graphs. + assert(expression is HSubExpressionBlockInformation); + + bool oldIsGeneratingExpression = isGeneratingExpression; + isGeneratingExpression = true; + List<js.Expression> oldExpressionStack = expressionStack; + List<js.Expression> sequenceElements = <js.Expression>[]; + expressionStack = sequenceElements; + HSubExpressionBlockInformation expressionSubGraph = expression; + visitSubGraph(expressionSubGraph.subExpression); + expressionStack = oldExpressionStack; + isGeneratingExpression = oldIsGeneratingExpression; + if (sequenceElements.isEmpty) { + // Happens when the initializer, condition or update of a loop is empty. + return null; + } else if (sequenceElements.length == 1) { + return sequenceElements[0]; + } else { + return new js.Sequence(sequenceElements); + } + } + + /** + * Only visits the arguments starting at inputs[HInvoke.ARGUMENTS_OFFSET]. + */ + List<js.Expression> visitArguments(List<HInstruction> inputs) { + assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET); + List<js.Expression> result = <js.Expression>[]; + for (int i = HInvoke.ARGUMENTS_OFFSET; i < inputs.length; i++) { + use(inputs[i]); + result.add(pop()); + } + return result; + } + + bool isVariableDeclared(String variableName) { + return declaredLocals.contains(variableName) || + collectedVariableDeclarations.contains(variableName); + } + + js.Expression generateExpressionAssignment(String variableName, + js.Expression value) { + if (value is js.Binary) { + js.Binary binary = value; + String op = binary.op; + if (op == '+' || op == '-' || op == '/' || op == '*' || op == '%' || + op == '^' || op == '&' || op == '|') { + if (binary.left is js.VariableUse && + (binary.left as js.VariableUse).name == variableName) { + // We know now, that we can shorten x = x + y into x += y. + // Also check for the shortcut where y equals 1: x++ and x--. + if ((op == '+' || op == '-') && + binary.right is js.LiteralNumber && + (binary.right as js.LiteralNumber).value == "1") { + return new js.Prefix(op == '+' ? '++' : '--', binary.left); + } + return new js.Assignment.compound(binary.left, op, binary.right); + } + } + } + return new js.Assignment(new js.VariableUse(variableName), value); + } + + void assignVariable(String variableName, js.Expression value) { + if (isGeneratingExpression) { + // If we are in an expression then we can't declare the variable here. + // We have no choice, but to use it and then declare it separately. + if (!isVariableDeclared(variableName)) { + collectedVariableDeclarations.add(variableName); + } + push(generateExpressionAssignment(variableName, value)); + // Otherwise if we are trying to declare inline and we are in a statement + // then we declare (unless it was already declared). + } else if (!shouldGroupVarDeclarations && + !declaredLocals.contains(variableName)) { + // It may be necessary to remove it from the ones to be declared later. + collectedVariableDeclarations.remove(variableName); + declaredLocals.add(variableName); + js.VariableDeclaration decl = new js.VariableDeclaration(variableName); + js.VariableInitialization initialization = + new js.VariableInitialization(decl, value); + + pushExpressionAsStatement(new js.VariableDeclarationList( + <js.VariableInitialization>[initialization])); + } else { + // Otherwise we are just going to use it. If we have not already declared + // it then we make sure we will declare it later. + if (!declaredLocals.contains(variableName)) { + collectedVariableDeclarations.add(variableName); + } + pushExpressionAsStatement( + generateExpressionAssignment(variableName, value)); + } + } + + void define(HInstruction instruction) { + // For simple type checks like i = intTypeCheck(i), we don't have to + // emit an assignment, because the intTypeCheck just returns its + // argument. + bool needsAssignment = true; + if (instruction is HTypeConversion) { + String inputName = variableNames.getName(instruction.checkedInput); + if (variableNames.getName(instruction) == inputName) { + needsAssignment = false; + } + } + if (instruction is HLocalValue) { + needsAssignment = false; + } + + if (needsAssignment && + !instruction.isControlFlow() && variableNames.hasName(instruction)) { + visitExpression(instruction); + assignVariable(variableNames.getName(instruction), pop()); + return; + } + + if (isGeneratingExpression) { + visitExpression(instruction); + } else { + visitStatement(instruction); + } + } + + void use(HInstruction argument) { + if (isGenerateAtUseSite(argument)) { + visitExpression(argument); + } else if (argument is HCheck && argument.isControlFlow()) { + // A [HCheck] that has control flow can never be used as an + // expression and may not have a name. Therefore we just use the + // checked instruction. + HCheck check = argument; + use(check.checkedInput); + } else { + push(new js.VariableUse(variableNames.getName(argument))); + } + } + + visit(HInstruction node) { + node.accept(this); + } + + visitExpression(HInstruction node) { + bool oldIsGeneratingExpression = isGeneratingExpression; + isGeneratingExpression = true; + visit(node); + isGeneratingExpression = oldIsGeneratingExpression; + } + + visitStatement(HInstruction node) { + assert(!isGeneratingExpression); + visit(node); + if (!expressionStack.isEmpty) { + assert(expressionStack.length == 1); + pushExpressionAsStatement(pop()); + } + } + + void continueAsBreak(LabelElement target) { + pushStatement(new js.Break(backend.namer.continueLabelName(target))); + } + + void implicitContinueAsBreak(TargetElement target) { + pushStatement(new js.Break( + backend.namer.implicitContinueLabelName(target))); + } + + void implicitBreakWithLabel(TargetElement target) { + pushStatement(new js.Break(backend.namer.implicitBreakLabelName(target))); + } + + js.Statement wrapIntoLabels(js.Statement result, List<LabelElement> labels) { + for (LabelElement label in labels) { + if (label.isTarget) { + String breakLabelString = backend.namer.breakLabelName(label); + result = new js.LabeledStatement(breakLabelString, result); + } + } + return result; + } + + + // The regular [visitIf] method implements the needed logic. + bool visitIfInfo(HIfBlockInformation info) => false; + + bool visitSwitchInfo(HSwitchBlockInformation info) { + bool isExpression = isJSExpression(info.expression); + if (!isExpression) { + generateStatements(info.expression); + } + + if (isExpression) { + push(generateExpression(info.expression)); + } else { + use(info.expression.conditionExpression); + } + js.Expression key = pop(); + List<js.SwitchClause> cases = <js.SwitchClause>[]; + + js.Block oldContainer = currentContainer; + for (int i = 0; i < info.matchExpressions.length; i++) { + for (Constant constant in info.matchExpressions[i]) { + generateConstant(constant); + currentContainer = new js.Block.empty(); + cases.add(new js.Case(pop(), currentContainer)); + } + if (i == info.matchExpressions.length - 1 && info.hasDefault) { + currentContainer = new js.Block.empty(); + cases.add(new js.Default(currentContainer)); + } + generateStatements(info.statements[i]); + } + currentContainer = oldContainer; + + js.Statement result = new js.Switch(key, cases); + pushStatement(wrapIntoLabels(result, info.labels)); + return true; + } + + bool visitSequenceInfo(HStatementSequenceInformation info) { + return false; + } + + bool visitSubGraphInfo(HSubGraphBlockInformation info) { + visitSubGraph(info.subGraph); + return true; + } + + bool visitSubExpressionInfo(HSubExpressionBlockInformation info) { + return false; + } + + bool visitAndOrInfo(HAndOrBlockInformation info) { + return false; + } + + bool visitTryInfo(HTryBlockInformation info) { + js.Block body = generateStatementsInNewBlock(info.body); + js.Catch catchPart = null; + js.Block finallyPart = null; + if (info.catchBlock != null) { + HLocalValue exception = info.catchVariable; + String name = variableNames.getName(exception); + js.VariableDeclaration decl = new js.VariableDeclaration(name); + js.Block catchBlock = generateStatementsInNewBlock(info.catchBlock); + catchPart = new js.Catch(decl, catchBlock); + } + if (info.finallyBlock != null) { + finallyPart = generateStatementsInNewBlock(info.finallyBlock); + } + pushStatement(new js.Try(body, catchPart, finallyPart)); + return true; + } + + void visitBodyIgnoreLabels(HLoopBlockInformation info) { + if (info.body.start.isLabeledBlock()) { + HBlockInformation oldInfo = currentBlockInformation; + currentBlockInformation = info.body.start.blockFlow.body; + generateStatements(info.body); + currentBlockInformation = oldInfo; + } else { + generateStatements(info.body); + } + } + + bool visitLoopInfo(HLoopBlockInformation info) { + HExpressionInformation condition = info.condition; + bool isConditionExpression = isJSCondition(condition); + + js.Loop loop; + + switch (info.kind) { + // Treate all three "test-first" loops the same way. + case HLoopBlockInformation.FOR_LOOP: + case HLoopBlockInformation.WHILE_LOOP: + case HLoopBlockInformation.FOR_IN_LOOP: + HBlockInformation initialization = info.initializer; + int initializationType = TYPE_STATEMENT; + if (initialization != null) { + initializationType = expressionType(initialization); + if (initializationType == TYPE_STATEMENT) { + generateStatements(initialization); + initialization = null; + } + } + if (isConditionExpression && + info.updates != null && isJSExpression(info.updates)) { + // If we have an updates graph, and it's expressible as an + // expression, generate a for-loop. + js.Expression jsInitialization = null; + if (initialization != null) { + int delayedVariablesCount = collectedVariableDeclarations.length; + jsInitialization = generateExpression(initialization); + if (!shouldGroupVarDeclarations && + delayedVariablesCount < collectedVariableDeclarations.length) { + // We just added a new delayed variable-declaration. See if we + // can put in a 'var' in front of the initialization to make it + // go away. + List<js.Expression> expressions; + if (jsInitialization is js.Sequence) { + expressions = jsInitialization.expressions; + } else { + expressions = <js.Expression>[jsInitialization]; + } + bool canTransformToVariableDeclaration = true; + for (js.Expression expression in expressions) { + bool expressionIsVariableAssignment = false; + if (expression is js.Assignment) { + js.Assignment assignment = expression; + if (assignment.leftHandSide is js.VariableUse && + assignment.compoundTarget == null) { + expressionIsVariableAssignment = true; + } + } + if (!expressionIsVariableAssignment) { + canTransformToVariableDeclaration = false; + break; + } + } + if (canTransformToVariableDeclaration) { + List<js.VariableInitialization> inits = + <js.VariableInitialization>[]; + for (js.Assignment assignment in expressions) { + String id = (assignment.leftHandSide as js.VariableUse).name; + js.Node declaration = new js.VariableDeclaration(id); + inits.add(new js.VariableInitialization(declaration, + assignment.value)); + collectedVariableDeclarations.remove(id); + } + jsInitialization = new js.VariableDeclarationList(inits); + } + } + } + js.Expression jsCondition = generateExpression(condition); + js.Expression jsUpdates = generateExpression(info.updates); + // The body might be labeled. Ignore this when recursing on the + // subgraph. + // TODO(lrn): Remove this extra labeling when handling all loops + // using subgraphs. + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + currentContainer = body; + visitBodyIgnoreLabels(info); + currentContainer = oldContainer; + body = unwrapStatement(body); + loop = new js.For(jsInitialization, jsCondition, jsUpdates, body); + } else { + // We have either no update graph, or it's too complex to + // put in an expression. + if (initialization != null) { + generateStatements(initialization); + } + js.Expression jsCondition; + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + if (isConditionExpression) { + jsCondition = generateExpression(condition); + currentContainer = body; + } else { + jsCondition = newLiteralBool(true); + currentContainer = body; + generateStatements(condition); + use(condition.conditionExpression); + js.Expression ifTest = new js.Prefix("!", pop()); + js.Break jsBreak = new js.Break(null); + pushStatement(new js.If.noElse(ifTest, jsBreak)); + } + if (info.updates != null) { + wrapLoopBodyForContinue(info); + generateStatements(info.updates); + } else { + visitBodyIgnoreLabels(info); + } + currentContainer = oldContainer; + body = unwrapStatement(body); + loop = new js.While(jsCondition, body); + } + break; + case HLoopBlockInformation.DO_WHILE_LOOP: + if (info.initializer != null) { + generateStatements(info.initializer); + } + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + // If there are phi copies in the block that jumps to the + // loop entry, we must emit the condition like this: + // do { + // body; + // if (condition) { + // phi updates; + // continue; + // } else { + // break; + // } + // } while (true); + HBasicBlock avoidEdge = info.end.successors[0]; + js.Block updateBody = new js.Block.empty(); + currentContainer = updateBody; + assignPhisOfSuccessors(avoidEdge); + bool hasPhiUpdates = !updateBody.statements.isEmpty; + currentContainer = body; + visitBodyIgnoreLabels(info); + if (info.updates != null) { + generateStatements(info.updates); + } + if (isConditionExpression) { + push(generateExpression(condition)); + } else { + generateStatements(condition); + use(condition.conditionExpression); + } + js.Expression jsCondition = pop(); + if (hasPhiUpdates) { + updateBody.statements.add(new js.Continue(null)); + body.statements.add( + new js.If(jsCondition, updateBody, new js.Break(null))); + jsCondition = newLiteralBool(true); + } + loop = new js.Do(unwrapStatement(body), jsCondition); + currentContainer = oldContainer; + break; + default: + compiler.internalError( + 'Unexpected loop kind: ${info.kind}', + instruction: condition.conditionExpression); + } + attachLocationRange(loop, info.sourcePosition, info.endSourcePosition); + pushStatement(wrapIntoLabels(loop, info.labels)); + return true; + } + + bool visitLabeledBlockInfo(HLabeledBlockInformation labeledBlockInfo) { + preLabeledBlock(labeledBlockInfo); + Link<Element> continueOverrides = const Link<Element>(); + + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + js.Statement result = body; + + currentContainer = body; + + // If [labeledBlockInfo.isContinue], the block is an artificial + // block around the body of a loop with an update block, so that + // continues of the loop can be written as breaks of the body + // block. + if (labeledBlockInfo.isContinue) { + for (LabelElement label in labeledBlockInfo.labels) { + if (label.isContinueTarget) { + String labelName = backend.namer.continueLabelName(label); + result = new js.LabeledStatement(labelName, result); + continueAction[label] = continueAsBreak; + continueOverrides = continueOverrides.prepend(label); + } + } + // For handling unlabeled continues from the body of a loop. + // TODO(lrn): Consider recording whether the target is in fact + // a target of an unlabeled continue, and not generate this if it isn't. + TargetElement target = labeledBlockInfo.target; + String labelName = backend.namer.implicitContinueLabelName(target); + result = new js.LabeledStatement(labelName, result); + continueAction[target] = implicitContinueAsBreak; + continueOverrides = continueOverrides.prepend(target); + } else { + for (LabelElement label in labeledBlockInfo.labels) { + if (label.isBreakTarget) { + String labelName = backend.namer.breakLabelName(label); + result = new js.LabeledStatement(labelName, result); + } + } + TargetElement target = labeledBlockInfo.target; + if (target.isSwitch) { + // This is an extra block around a switch that is generated + // as a nested if/else chain. We add an extra break target + // so that case code can break. + String labelName = backend.namer.implicitBreakLabelName(target); + result = new js.LabeledStatement(labelName, result); + breakAction[target] = implicitBreakWithLabel; + } + } + + currentContainer = body; + startLabeledBlock(labeledBlockInfo); + generateStatements(labeledBlockInfo.body); + endLabeledBlock(labeledBlockInfo); + + if (labeledBlockInfo.isContinue) { + while (!continueOverrides.isEmpty) { + continueAction.remove(continueOverrides.head); + continueOverrides = continueOverrides.tail; + } + } else { + breakAction.remove(labeledBlockInfo.target); + } + + currentContainer = oldContainer; + pushStatement(result); + return true; + } + + // Wraps a loop body in a block to make continues have a target to break + // to (if necessary). + void wrapLoopBodyForContinue(HLoopBlockInformation info) { + TargetElement target = info.target; + if (target != null && target.isContinueTarget) { + js.Block oldContainer = currentContainer; + js.Block body = new js.Block.empty(); + currentContainer = body; + js.Statement result = body; + for (LabelElement label in info.labels) { + if (label.isContinueTarget) { + String labelName = backend.namer.continueLabelName(label); + result = new js.LabeledStatement(labelName, result); + continueAction[label] = continueAsBreak; + } + } + String labelName = backend.namer.implicitContinueLabelName(target); + result = new js.LabeledStatement(labelName, result); + continueAction[info.target] = implicitContinueAsBreak; + visitBodyIgnoreLabels(info); + continueAction.remove(info.target); + for (LabelElement label in info.labels) { + if (label.isContinueTarget) { + continueAction.remove(label); + } + } + currentContainer = oldContainer; + pushStatement(result); + } else { + // Loop body contains no continues, so we don't need a break target. + generateStatements(info.body); + } + } + + bool handleBlockFlow(HBlockFlow block) { + HBlockInformation info = block.body; + // If we reach here again while handling the attached information, + // e.g., because we call visitSubGraph on a subgraph starting on + // the same block, don't handle it again. + // When the structure graph is complete, we will be able to have + // different structures starting on the same basic block (e.g., an + // "if" and its condition). + if (identical(info, currentBlockInformation)) return false; + + HBlockInformation oldBlockInformation = currentBlockInformation; + currentBlockInformation = info; + bool success = info.accept(this); + currentBlockInformation = oldBlockInformation; + if (success) { + HBasicBlock continuation = block.continuation; + if (continuation != null) { + visitBasicBlock(continuation); + } + } + return success; + } + + void visitBasicBlock(HBasicBlock node) { + // Abort traversal if we are leaving the currently active sub-graph. + if (!subGraph.contains(node)) return; + + // If this node has block-structure based information attached, + // try using that to traverse from here. + if (node.blockFlow != null && handleBlockFlow(node.blockFlow)) { + return; + } + iterateBasicBlock(node); + } + + void emitAssignment(String destination, String source) { + assignVariable(destination, new js.VariableUse(source)); + } + + /** + * Sequentialize a list of conceptually parallel copies. Parallel + * copies may contain cycles, that this method breaks. + */ + void sequentializeCopies(Iterable<Copy> copies, + String tempName, + void doAssignment(String target, String source)) { + // Map to keep track of the current location (ie the variable that + // holds the initial value) of a variable. + Map<String, String> currentLocation = new Map<String, String>(); + + // Map to keep track of the initial value of a variable. + Map<String, String> initialValue = new Map<String, String>(); + + // List of variables to assign a value. + List<String> worklist = <String>[]; + + // List of variables that we can assign a value to (ie are not + // being used anymore). + List<String> ready = <String>[]; + + // Prune [copies] by removing self-copies. + List<Copy> prunedCopies = <Copy>[]; + for (Copy copy in copies) { + if (copy.source != copy.destination) { + prunedCopies.add(copy); + } + } + copies = prunedCopies; + + + // For each copy, set the current location of the source to + // itself, and the initial value of the destination to the source. + // Add the destination to the list of copies to make. + for (Copy copy in copies) { + currentLocation[copy.source] = copy.source; + initialValue[copy.destination] = copy.source; + worklist.add(copy.destination); + } + + // For each copy, if the destination does not have a current + // location, then we can safely assign to it. + for (Copy copy in copies) { + if (currentLocation[copy.destination] == null) { + ready.add(copy.destination); + } + } + + while (!worklist.isEmpty) { + while (!ready.isEmpty) { + String destination = ready.removeLast(); + String source = initialValue[destination]; + // Since [source] might have been updated, use the current + // location of [source] + String copy = currentLocation[source]; + doAssignment(destination, copy); + // Now [destination] is the current location of [source]. + currentLocation[source] = destination; + // If [source] hasn't been updated and needs to have a value, + // add it to the list of variables that can be updated. Copies + // of [source] will now use [destination]. + if (source == copy && initialValue[source] != null) { + ready.add(source); + } + } + + // Check if we have a cycle. + String current = worklist.removeLast(); + // If [current] is used as a source, and the assignment has been + // done, we are done with this variable. Otherwise there is a + // cycle that we break by using a temporary name. + if (currentLocation[current] != null + && current != currentLocation[initialValue[current]]) { + doAssignment(tempName, current); + currentLocation[current] = tempName; + // [current] can now be safely updated. Copies of [current] + // will now use [tempName]. + ready.add(current); + } + } + } + + void assignPhisOfSuccessors(HBasicBlock node) { + CopyHandler handler = variableNames.getCopyHandler(node); + if (handler == null) return; + + // Map the instructions to strings. + Iterable<Copy> copies = handler.copies.map((Copy copy) { + return new Copy(variableNames.getName(copy.source), + variableNames.getName(copy.destination)); + }); + + sequentializeCopies(copies, variableNames.getSwapTemp(), emitAssignment); + + for (Copy copy in handler.assignments) { + String name = variableNames.getName(copy.destination); + use(copy.source); + assignVariable(name, pop()); + } + } + + void iterateBasicBlock(HBasicBlock node) { + HInstruction instruction = node.first; + while (!identical(instruction, node.last)) { + if (instruction is HTypeGuard || instruction is HBailoutTarget) { + visit(instruction); + } else if (!isGenerateAtUseSite(instruction)) { + define(instruction); + } + instruction = instruction.next; + } + assignPhisOfSuccessors(node); + visit(instruction); + } + + visitInvokeBinary(HInvokeBinary node, String op) { + use(node.left); + js.Expression jsLeft = pop(); + use(node.right); + push(new js.Binary(op, jsLeft, pop()), node); + } + + visitRelational(HRelational node, String op) => visitInvokeBinary(node, op); + + // We want the outcome of bit-operations to be positive. We use the unsigned + // shift operator to achieve this. + visitBitInvokeBinary(HBinaryBitOp node, String op) { + visitInvokeBinary(node, op); + if (requiresUintConversion(node)) { + push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node); + } + } + + visitInvokeUnary(HInvokeUnary node, String op) { + use(node.operand); + push(new js.Prefix(op, pop()), node); + } + + // We want the outcome of bit-operations to be positive. We use the unsigned + // shift operator to achieve this. + visitBitInvokeUnary(HInvokeUnary node, String op) { + visitInvokeUnary(node, op); + if (requiresUintConversion(node)) { + push(new js.Binary(">>>", pop(), new js.LiteralNumber("0")), node); + } + } + + void emitIdentityComparison(HInstruction left, HInstruction right) { + String op = singleIdentityComparison(left, right, types); + if (op != null) { + use(left); + js.Expression jsLeft = pop(); + use(right); + push(new js.Binary(op, jsLeft, pop())); + } else { + assert(NullConstant.JsNull == 'null'); + use(left); + js.Binary leftEqualsNull = + new js.Binary("==", pop(), new js.LiteralNull()); + use(right); + js.Binary rightEqualsNull = + new js.Binary("==", pop(), new js.LiteralNull()); + use(right); + use(left); + js.Binary tripleEq = new js.Binary("===", pop(), pop()); + + push(new js.Conditional(leftEqualsNull, rightEqualsNull, tripleEq)); + } + } + + visitIdentity(HIdentity node) { + emitIdentityComparison(node.left, node.right); + } + + visitAdd(HAdd node) => visitInvokeBinary(node, '+'); + visitDivide(HDivide node) => visitInvokeBinary(node, '/'); + visitMultiply(HMultiply node) => visitInvokeBinary(node, '*'); + visitSubtract(HSubtract node) => visitInvokeBinary(node, '-'); + visitBitAnd(HBitAnd node) => visitBitInvokeBinary(node, '&'); + visitBitNot(HBitNot node) => visitBitInvokeUnary(node, '~'); + visitBitOr(HBitOr node) => visitBitInvokeBinary(node, '|'); + visitBitXor(HBitXor node) => visitBitInvokeBinary(node, '^'); + visitShiftLeft(HShiftLeft node) => visitBitInvokeBinary(node, '<<'); + + visitNegate(HNegate node) => visitInvokeUnary(node, '-'); + + visitLess(HLess node) => visitRelational(node, '<'); + visitLessEqual(HLessEqual node) => visitRelational(node, '<='); + visitGreater(HGreater node) => visitRelational(node, '>'); + visitGreaterEqual(HGreaterEqual node) => visitRelational(node, '>='); + + visitBoolify(HBoolify node) { + assert(node.inputs.length == 1); + use(node.inputs[0]); + push(new js.Binary('===', pop(), newLiteralBool(true)), node); + } + + visitExit(HExit node) { + // Don't do anything. + } + + visitGoto(HGoto node) { + HBasicBlock block = node.block; + assert(block.successors.length == 1); + List<HBasicBlock> dominated = block.dominatedBlocks; + // With the exception of the entry-node which dominates its successor + // and the exit node, no block finishing with a 'goto' can have more than + // one dominated block (since it has only one successor). + // If the successor is dominated by another block, then the other block + // is responsible for visiting the successor. + if (dominated.isEmpty) return; + if (dominated.length > 2) { + compiler.internalError('dominated.length = ${dominated.length}', + instruction: node); + } + if (dominated.length == 2 && block != currentGraph.entry) { + compiler.internalError('node.block != currentGraph.entry', + instruction: node); + } + assert(dominated[0] == block.successors[0]); + visitBasicBlock(dominated[0]); + } + + visitLoopBranch(HLoopBranch node) { + assert(node.block == subGraph.end); + // We are generating code for a loop condition. + // If we are generating the subgraph as an expression, the + // condition will be generated as the expression. + // Otherwise, we don't generate the expression, and leave that + // to the code that called [visitSubGraph]. + if (isGeneratingExpression) { + use(node.inputs[0]); + } + } + + /** + * Checks if [map] contains an [ElementAction] for [element], and + * if so calls that action and returns true. + * Otherwise returns false. + */ + bool tryCallAction(Map<Element, ElementAction> map, Element element) { + ElementAction action = map[element]; + if (action == null) return false; + action(element); + return true; + } + + visitBreak(HBreak node) { + assert(node.block.successors.length == 1); + if (node.label != null) { + LabelElement label = node.label; + if (!tryCallAction(breakAction, label)) { + pushStatement(new js.Break(backend.namer.breakLabelName(label)), node); + } + } else { + TargetElement target = node.target; + if (!tryCallAction(breakAction, target)) { + pushStatement(new js.Break(null), node); + } + } + } + + visitContinue(HContinue node) { + assert(node.block.successors.length == 1); + if (node.label != null) { + LabelElement label = node.label; + if (!tryCallAction(continueAction, label)) { + // TODO(floitsch): should this really be the breakLabelName? + pushStatement(new js.Continue(backend.namer.breakLabelName(label)), + node); + } + } else { + TargetElement target = node.target; + if (!tryCallAction(continueAction, target)) { + pushStatement(new js.Continue(null), node); + } + } + } + + visitExitTry(HExitTry node) { + // An [HExitTry] is used to represent the control flow graph of a + // try/catch block, ie the try body is always a predecessor + // of the catch and finally. Here, we continue visiting the try + // body by visiting the block that contains the user-level control + // flow instruction. + visitBasicBlock(node.bodyTrySuccessor); + } + + visitTry(HTry node) { + // We should never get here. Try/catch/finally is always handled using block + // information in [visitTryInfo], or not at all, in the case of the bailout + // generator. + compiler.internalError('visitTry should not be called', instruction: node); + } + + bool tryControlFlowOperation(HIf node) { + if (!controlFlowOperators.contains(node)) return false; + HPhi phi = node.joinBlock.phis.first; + bool atUseSite = isGenerateAtUseSite(phi); + // Don't generate a conditional operator in this situation: + // i = condition ? bar() : i; + // But generate this instead: + // if (condition) i = bar(); + // Usually, the variable name is longer than 'if' and it takes up + // more space to duplicate the name. + if (!atUseSite + && variableNames.getName(phi) == variableNames.getName(phi.inputs[1])) { + return false; + } + if (!atUseSite) define(phi); + visitBasicBlock(node.joinBlock); + return true; + } + + void generateIf(HIf node, HIfBlockInformation info) { + use(node.inputs[0]); + js.Expression test = pop(); + + HStatementInformation thenGraph = info.thenGraph; + HStatementInformation elseGraph = info.elseGraph; + js.Statement thenPart = + unwrapStatement(generateStatementsInNewBlock(thenGraph)); + js.Statement elsePart = + unwrapStatement(generateStatementsInNewBlock(elseGraph)); + + pushStatement(new js.If(test, thenPart, elsePart), node); + } + + visitIf(HIf node) { + if (tryControlFlowOperation(node)) return; + + HInstruction condition = node.inputs[0]; + HIfBlockInformation info = node.blockInformation.body; + + if (condition.isConstant()) { + HConstant constant = condition; + if (constant.constant.isTrue()) { + generateStatements(info.thenGraph); + } else { + generateStatements(info.elseGraph); + } + } else { + generateIf(node, info); + } + + HBasicBlock joinBlock = node.joinBlock; + if (joinBlock != null && !identical(joinBlock.dominator, node.block)) { + // The join block is dominated by a block in one of the branches. + // The subgraph traversal never reached it, so we visit it here + // instead. + visitBasicBlock(joinBlock); + } + + // Visit all the dominated blocks that are not part of the then or else + // branches, and is not the join block. + // Depending on how the then/else branches terminate + // (e.g., return/throw/break) there can be any number of these. + List<HBasicBlock> dominated = node.block.dominatedBlocks; + for (int i = 2; i < dominated.length; i++) { + visitBasicBlock(dominated[i]); + } + } + + js.Call jsPropertyCall(js.Expression receiver, + String fieldName, + List<js.Expression> arguments) { + return new js.Call(new js.PropertyAccess.field(receiver, fieldName), + arguments); + } + + void visitInterceptor(HInterceptor node) { + backend.registerSpecializedGetInterceptor(node.interceptedClasses); + String name = backend.namer.getInterceptorName( + backend.getInterceptorMethod, node.interceptedClasses); + var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE); + use(node.receiver); + List<js.Expression> arguments = <js.Expression>[pop()]; + push(jsPropertyCall(isolate, name, arguments), node); + } + + visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + use(node.receiver); + js.Expression object = pop(); + SourceString name = node.selector.name; + String methodName; + List<js.Expression> arguments = visitArguments(node.inputs); + Element target = node.element; + + if (target != null) { + // Avoid adding the generative constructor name to the list of + // seen selectors. + if (target.isGenerativeConstructorBody()) { + methodName = name.slowToString(); + } else if (!node.isInterceptorCall) { + if (target == backend.jsArrayAdd) { + methodName = 'push'; + } else if (target == backend.jsArrayRemoveLast) { + methodName = 'pop'; + } else if (target == backend.jsStringSplit) { + methodName = 'split'; + // Split returns a List, so we make sure the backend knows the + // list class is instantiated. + world.registerInstantiatedClass(compiler.listClass); + } else if (target == backend.jsStringConcat) { + push(new js.Binary('+', object, arguments[0]), node); + return; + } + } + } + + if (methodName == null) { + methodName = backend.namer.invocationName(node.selector); + registerMethodInvoke(node); + } + push(jsPropertyCall(object, methodName, arguments), node); + } + + void visitOneShotInterceptor(HOneShotInterceptor node) { + List<js.Expression> arguments = visitArguments(node.inputs); + var isolate = new js.VariableUse(backend.namer.CURRENT_ISOLATE); + Selector selector = node.selector; + String methodName = backend.namer.oneShotInterceptorName(selector); + push(jsPropertyCall(isolate, methodName, arguments), node); + backend.registerSpecializedGetInterceptor(node.interceptedClasses); + backend.addOneShotInterceptor(selector); + if (selector.isGetter()) { + registerGetter(node); + } else if (selector.isSetter()) { + registerSetter(node); + } else { + registerMethodInvoke(node); + } + } + + Selector getOptimizedSelectorFor(HInvokeDynamic node, + Selector defaultSelector) { + // If [JSInvocationMirror.invokeOn] has been called, we must not create a + // typed selector based on the receiver type. + if (node.element == null && // Invocation is not exact. + backend.compiler.enabledInvokeOn) { + return defaultSelector; + } + int receiverIndex = node.isInterceptorCall ? 1 : 0; + HType receiverHType = types[node.inputs[receiverIndex]]; + DartType receiverType = receiverHType.computeType(compiler); + if (receiverType != null && + !identical(receiverType.kind, TypeKind.MALFORMED_TYPE)) { + return new TypedSelector(receiverType, defaultSelector); + } else { + return defaultSelector; + } + } + + void registerInvoke(HInvokeDynamic node) { + bool inLoop = node.block.enclosingLoopHeader != null; + SourceString name = node.selector.name; + if (inLoop) { + Element target = node.element; + if (target != null) { + backend.builder.functionsCalledInLoop.add(target); + } else { + backend.builder.selectorsCalledInLoop[name] = node.selector; + } + } + + if (node.isInterceptorCall) { + backend.addInterceptedSelector(node.selector); + } + } + + void registerMethodInvoke(HInvokeDynamic node) { + Selector selector = getOptimizedSelectorFor(node, node.selector); + // Register this invocation to collect the types used at all call sites. + backend.registerDynamicInvocation(node, selector, types); + + // If we don't know what we're calling or if we are calling a getter, + // we need to register that fact that we may be calling a closure + // with the same arguments. + Element target = node.element; + if (target == null || target.isGetter()) { + // TODO(kasperl): If we have a typed selector for the call, we + // may know something about the types of closures that need + // the specific closure call method. + Selector call = new Selector.callClosureFrom(selector); + world.registerDynamicInvocation(call.name, call); + } + + if (target != null) { + // If we know we're calling a specific method, register that + // method only. + world.registerDynamicInvocationOf(target, selector); + } else { + SourceString name = node.selector.name; + world.registerDynamicInvocation(name, selector); + } + registerInvoke(node); + } + + void registerSetter(HInvokeDynamic node) { + Selector selector = getOptimizedSelectorFor(node, node.selector); + world.registerDynamicSetter(selector.name, selector); + HType valueType = node.isInterceptorCall + ? types[node.inputs[2]] + : types[node.inputs[1]]; + backend.addedDynamicSetter(selector, valueType); + registerInvoke(node); + } + + void registerGetter(HInvokeDynamic node) { + Selector getter = node.selector; + world.registerDynamicGetter( + getter.name, getOptimizedSelectorFor(node, getter)); + world.registerInstantiatedClass(compiler.functionClass); + registerInvoke(node); + } + + visitInvokeDynamicSetter(HInvokeDynamicSetter node) { + use(node.receiver); + Selector setter = node.selector; + String name = backend.namer.invocationName(setter); + push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node); + registerSetter(node); + } + + visitInvokeDynamicGetter(HInvokeDynamicGetter node) { + use(node.receiver); + Selector getter = node.selector; + String name = backend.namer.invocationName(getter); + push(jsPropertyCall(pop(), name, visitArguments(node.inputs)), node); + registerGetter(node); + } + + visitInvokeClosure(HInvokeClosure node) { + Selector call = new Selector.callClosureFrom(node.selector); + use(node.receiver); + push(jsPropertyCall(pop(), + backend.namer.invocationName(call), + visitArguments(node.inputs)), + node); + world.registerDynamicInvocation(call.name, call); + } + + visitInvokeStatic(HInvokeStatic node) { + if (node.typeCode() == HInstruction.INVOKE_STATIC_TYPECODE) { + // Register this invocation to collect the types used at all call sites. + backend.registerStaticInvocation(node, types); + } + use(node.target); + push(new js.Call(pop(), visitArguments(node.inputs)), node); + } + + visitInvokeSuper(HInvokeSuper node) { + Element superMethod = node.element; + Element superClass = superMethod.getEnclosingClass(); + if (superMethod.kind == ElementKind.FIELD) { + ClassElement currentClass = work.element.getEnclosingClass(); + if (currentClass.isClosure()) { + ClosureClassElement closure = currentClass; + currentClass = closure.methodElement.getEnclosingClass(); + } + String fieldName = currentClass.isShadowedByField(superMethod) + ? backend.namer.shadowedFieldName(superMethod) + : backend.namer.instanceFieldName(superMethod); + use(node.inputs[1]); + js.PropertyAccess access = + new js.PropertyAccess.field(pop(), fieldName); + if (node.isSetter) { + use(node.value); + push(new js.Assignment(access, pop()), node); + } else { + push(access, node); + } + } else { + String methodName = backend.namer.getName(superMethod); + String className = backend.namer.isolateAccess(superClass); + js.VariableUse classReference = new js.VariableUse(className); + js.PropertyAccess prototype = + new js.PropertyAccess.field(classReference, "prototype"); + js.PropertyAccess method = + new js.PropertyAccess.field(prototype, methodName); + push(jsPropertyCall(method, "call", visitArguments(node.inputs)), node); + } + world.registerStaticUse(superMethod); + } + + visitFieldGet(HFieldGet node) { + use(node.receiver); + if (node.element == backend.jsArrayLength + || node.element == backend.jsStringLength) { + // We're accessing a native JavaScript property called 'length' + // on a JS String or a JS array. Therefore, the name of that + // property should not be mangled. + push(new js.PropertyAccess.field(pop(), 'length'), node); + } else { + String name = _fieldPropertyName(node.element); + push(new js.PropertyAccess.field(pop(), name), node); + HType receiverHType = types[node.receiver]; + DartType type = receiverHType.computeType(compiler); + if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) { + world.registerFieldGetter( + node.element.name, node.element.getLibrary(), type); + } + } + } + + visitFieldSet(HFieldSet node) { + String name = _fieldPropertyName(node.element); + DartType type = types[node.receiver].computeType(compiler); + if (type != null && !identical(type.kind, TypeKind.MALFORMED_TYPE)) { + // Field setters in the generative constructor body are handled in a + // step "SsaConstructionFieldTypes" in the ssa optimizer. + if (!work.element.isGenerativeConstructorBody()) { + world.registerFieldSetter( + node.element.name, node.element.getLibrary(), type); + backend.registerFieldSetter( + work.element, node.element, types[node.value]); + } + } + use(node.receiver); + js.Expression receiver = pop(); + use(node.value); + push(new js.Assignment(new js.PropertyAccess.field(receiver, name), pop()), + node); + } + + String _fieldPropertyName(Element element) => element.hasFixedBackendName() + ? element.fixedBackendName() + : backend.namer.getName(element); + + visitLocalGet(HLocalGet node) { + use(node.receiver); + } + + visitLocalSet(HLocalSet node) { + use(node.value); + assignVariable(variableNames.getName(node.receiver), pop()); + } + + void registerForeignType(HType type) { + DartType dartType = type.computeType(compiler); + if (dartType == null) { + assert(type == HType.UNKNOWN); + return; + } + world.registerInstantiatedClass(dartType.element); + } + + visitForeign(HForeign node) { + String code = node.code.slowToString(); + List<HInstruction> inputs = node.inputs; + if (node.isJsStatement()) { + if (!inputs.isEmpty) { + compiler.internalError("foreign statement with inputs: $code", + instruction: node); + } + pushStatement(new js.LiteralStatement(code), node); + } else { + List<js.Expression> data = <js.Expression>[]; + for (int i = 0; i < inputs.length; i++) { + use(inputs[i]); + data.add(pop()); + } + push(new js.LiteralExpression.withData(code, data), node); + } + registerForeignType(types[node]); + // TODO(sra): Tell world.nativeEnqueuer about the types created here. + } + + visitForeignNew(HForeignNew node) { + String jsClassReference = backend.namer.isolateAccess(node.element); + List<HInstruction> inputs = node.inputs; + // We can't use 'visitArguments', since our arguments start at input[0]. + List<js.Expression> arguments = <js.Expression>[]; + for (int i = 0; i < inputs.length; i++) { + use(inputs[i]); + arguments.add(pop()); + } + // TODO(floitsch): jsClassReference is an Access. We shouldn't treat it + // as if it was a string. + push(new js.New(new js.VariableUse(jsClassReference), arguments), node); + registerForeignType(types[node]); + } + + js.Expression newLiteralBool(bool value) { + if (compiler.enableMinification) { + // Use !0 for true, !1 for false. + return new js.Prefix("!", new js.LiteralNumber(value ? "0" : "1")); + } else { + return new js.LiteralBool(value); + } + } + + void generateConstant(Constant constant) { + if (constant.isFunction()) { + FunctionConstant function = constant; + world.registerStaticUse(function.element); + } + push(backend.emitter.constantReference(constant)); + } + + visitConstant(HConstant node) { + assert(isGenerateAtUseSite(node)); + generateConstant(node.constant); + DartType type = node.constant.computeType(compiler); + if (node.constant is ConstructedConstant) { + ConstantHandler handler = compiler.constantHandler; + handler.registerCompileTimeConstant(node.constant); + } + world.registerInstantiatedClass(type.element); + } + + visitNot(HNot node) { + assert(node.inputs.length == 1); + generateNot(node.inputs[0]); + attachLocationToLast(node); + } + + void generateNot(HInstruction input) { + bool canGenerateOptimizedComparison(HInstruction instruction) { + if (instruction is !HRelational) return false; + HRelational relational = instruction; + HInstruction left = relational.left; + HInstruction right = relational.right; + // This optimization doesn't work for NaN, so we only do it if the + // type is known to be an integer. + return types[left].isUseful() && left.isInteger(types) + && types[right].isUseful() && right.isInteger(types); + } + + if (input is HBoolify && isGenerateAtUseSite(input)) { + use(input.inputs[0]); + push(new js.Binary("!==", pop(), newLiteralBool(true)), input); + } else if (canGenerateOptimizedComparison(input) && + isGenerateAtUseSite(input)) { + Map<String, String> inverseOperator = const <String, String>{ + "==" : "!=", + "!=" : "==", + "===": "!==", + "!==": "===", + "<" : ">=", + "<=" : ">", + ">" : "<=", + ">=" : "<" + }; + HRelational relational = input; + BinaryOperation operation = relational.operation(backend.constantSystem); + visitRelational(input, inverseOperator[operation.name.stringValue]); + } else { + use(input); + push(new js.Prefix("!", pop())); + } + } + + visitParameterValue(HParameterValue node) { + assert(!isGenerateAtUseSite(node)); + String name = variableNames.getName(node); + parameters.add(new js.Parameter(name)); + declaredLocals.add(name); + } + + visitLocalValue(HLocalValue node) { + assert(!isGenerateAtUseSite(node)); + String name = variableNames.getName(node); + collectedVariableDeclarations.add(name); + } + + visitPhi(HPhi node) { + // This method is only called for phis that are generated at use + // site. A phi can be generated at use site only if it is the + // result of a control flow operation. + HBasicBlock ifBlock = node.block.dominator; + assert(controlFlowOperators.contains(ifBlock.last)); + HInstruction input = ifBlock.last.inputs[0]; + if (input.isConstantFalse()) { + use(node.inputs[1]); + } else if (input.isConstantTrue()) { + use(node.inputs[0]); + } else if (node.inputs[1].isConstantBoolean()) { + String operation = node.inputs[1].isConstantFalse() ? '&&' : '||'; + if (operation == '||') { + if (input is HNot) { + use(input.inputs[0]); + } else { + generateNot(input); + } + } else { + use(input); + } + js.Expression left = pop(); + use(node.inputs[0]); + push(new js.Binary(operation, left, pop())); + } else { + use(input); + js.Expression test = pop(); + use(node.inputs[0]); + js.Expression then = pop(); + use(node.inputs[1]); + push(new js.Conditional(test, then, pop())); + } + } + + visitReturn(HReturn node) { + assert(node.inputs.length == 1); + HInstruction input = node.inputs[0]; + if (input.isConstantNull()) { + pushStatement(new js.Return(null), node); + } else { + use(node.inputs[0]); + pushStatement(new js.Return(pop()), node); + } + } + + visitThis(HThis node) { + push(new js.This()); + } + + visitThrow(HThrow node) { + if (node.isRethrow) { + use(node.inputs[0]); + pushStatement(new js.Throw(pop()), node); + } else { + generateThrowWithHelper(r'$throw', node.inputs[0]); + } + } + + visitRangeConversion(HRangeConversion node) { + // Range conversion instructions are removed by the value range + // analyzer. + assert(false); + } + + visitBoundsCheck(HBoundsCheck node) { + // TODO(ngeoffray): Separate the two checks of the bounds check, so, + // e.g., the zero checks can be shared if possible. + + // If the checks always succeeds, we would have removed the bounds check + // completely. + assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE); + if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) { + js.Expression under; + js.Expression over; + if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) { + use(node.index); + under = new js.Binary("<", pop(), new js.LiteralNumber("0")); + } + if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) { + var index = node.index; + use(index); + js.Expression jsIndex = pop(); + use(node.length); + over = new js.Binary(">=", jsIndex, pop()); + } + assert(over != null || under != null); + js.Expression underOver = under == null + ? over + : over == null + ? under + : new js.Binary("||", under, over); + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + generateThrowWithHelper('ioore', node.index); + currentContainer = oldContainer; + thenBody = unwrapStatement(thenBody); + pushStatement(new js.If.noElse(underOver, thenBody), node); + } else { + generateThrowWithHelper('ioore', node.index); + } + } + + visitIntegerCheck(HIntegerCheck node) { + if (!node.alwaysFalse) { + checkInt(node.value, '!=='); + js.Expression test = pop(); + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + generateThrowWithHelper('iae', node.value); + currentContainer = oldContainer; + thenBody = unwrapStatement(thenBody); + pushStatement(new js.If.noElse(test, thenBody), node); + } else { + generateThrowWithHelper('iae', node.value); + } + } + + void generateThrowWithHelper(String helperName, HInstruction argument) { + Element helper = compiler.findHelper(new SourceString(helperName)); + world.registerStaticUse(helper); + js.VariableUse jsHelper = + new js.VariableUse(backend.namer.isolateAccess(helper)); + js.Call value = new js.Call(jsHelper, visitArguments([null, argument])); + attachLocation(value, argument); + // BUG(4906): Using throw here adds to the size of the generated code + // but it has the advantage of explicitly telling the JS engine that + // this code path will terminate abruptly. Needs more work. + pushStatement(new js.Throw(value)); + } + + void visitSwitch(HSwitch node) { + // Switches are handled using [visitSwitchInfo]. + } + + void visitStatic(HStatic node) { + // Check whether this static is used for anything else than as a target in + // a static call. + node.usedBy.forEach((HInstruction instr) { + if (instr is !HInvokeStatic) { + backend.registerNonCallStaticUse(node); + if (node.element.isFunction()) { + world.registerInstantiatedClass(compiler.functionClass); + } + } else if (instr.target != node) { + backend.registerNonCallStaticUse(node); + } + }); + Element element = node.element; + world.registerStaticUse(element); + ClassElement cls = element.getEnclosingClass(); + if (element.isGenerativeConstructor() + || (element.isFactoryConstructor() && cls == compiler.listClass)) { + world.registerInstantiatedClass(cls); + } + push(new js.VariableUse(backend.namer.isolateAccess(node.element))); + } + + void visitLazyStatic(HLazyStatic node) { + Element element = node.element; + world.registerStaticUse(element); + String lazyGetter = backend.namer.isolateLazyInitializerAccess(element); + js.VariableUse target = new js.VariableUse(lazyGetter); + js.Call call = new js.Call(target, <js.Expression>[]); + push(call, node); + } + + void visitStaticStore(HStaticStore node) { + world.registerStaticUse(node.element); + js.VariableUse variableUse = + new js.VariableUse(backend.namer.isolateAccess(node.element)); + use(node.inputs[0]); + push(new js.Assignment(variableUse, pop()), node); + } + + void visitStringConcat(HStringConcat node) { + if (isEmptyString(node.left)) { + useStringified(node.right); + } else if (isEmptyString(node.right)) { + useStringified(node.left); + } else { + useStringified(node.left); + js.Expression left = pop(); + useStringified(node.right); + push(new js.Binary("+", left, pop()), node); + } + } + + bool isEmptyString(HInstruction node) { + if (!node.isConstantString()) return false; + HConstant constant = node; + StringConstant string = constant.constant; + return string.value.length == 0; + } + + void useStringified(HInstruction node) { + if (node.isString(types)) { + use(node); + } else { + Element convertToString = compiler.findHelper(const SourceString("S")); + world.registerStaticUse(convertToString); + js.VariableUse variableUse = + new js.VariableUse(backend.namer.isolateAccess(convertToString)); + use(node); + push(new js.Call(variableUse, <js.Expression>[pop()]), node); + } + } + + void visitLiteralList(HLiteralList node) { + world.registerInstantiatedClass(compiler.listClass); + generateArrayLiteral(node); + } + + void generateArrayLiteral(HLiteralList node) { + int len = node.inputs.length; + List<js.ArrayElement> elements = <js.ArrayElement>[]; + for (int i = 0; i < len; i++) { + use(node.inputs[i]); + elements.add(new js.ArrayElement(i, pop())); + } + push(new js.ArrayInitializer(len, elements), node); + } + + void visitIndex(HIndex node) { + use(node.receiver); + js.Expression receiver = pop(); + use(node.index); + push(new js.PropertyAccess(receiver, pop()), node); + } + + void visitIndexAssign(HIndexAssign node) { + use(node.receiver); + js.Expression receiver = pop(); + use(node.index); + js.Expression index = pop(); + use(node.value); + push(new js.Assignment(new js.PropertyAccess(receiver, index), pop()), + node); + } + + void checkInt(HInstruction input, String cmp) { + use(input); + js.Expression left = pop(); + use(input); + js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0")); + push(new js.Binary(cmp, left, or0)); + } + + void checkBigInt(HInstruction input, String cmp) { + use(input); + js.Expression left = pop(); + use(input); + js.Expression right = pop(); + // TODO(4984): Deal with infinity and -0.0. + push(new js.LiteralExpression.withData('Math.floor(#) === #', + <js.Expression>[left, right])); + } + + void checkTypeOf(HInstruction input, String cmp, String typeName) { + use(input); + js.Expression typeOf = new js.Prefix("typeof", pop()); + push(new js.Binary(cmp, typeOf, js.string(typeName))); + } + + void checkNum(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'number'); + + void checkDouble(HInstruction input, String cmp) => checkNum(input, cmp); + + void checkString(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'string'); + + void checkBool(HInstruction input, String cmp) + => checkTypeOf(input, cmp, 'boolean'); + + void checkObject(HInstruction input, String cmp) { + assert(NullConstant.JsNull == 'null'); + if (cmp == "===") { + checkTypeOf(input, '===', 'object'); + js.Expression left = pop(); + use(input); + js.Expression notNull = new js.Binary("!==", pop(), new js.LiteralNull()); + push(new js.Binary("&&", left, notNull)); + } else { + assert(cmp == "!=="); + checkTypeOf(input, '!==', 'object'); + js.Expression left = pop(); + use(input); + js.Expression eqNull = new js.Binary("===", pop(), new js.LiteralNull()); + push(new js.Binary("||", left, eqNull)); + } + } + + void checkArray(HInstruction input, String cmp) { + use(input); + js.PropertyAccess constructor = + new js.PropertyAccess.field(pop(), 'constructor'); + push(new js.Binary(cmp, constructor, new js.VariableUse('Array'))); + } + + void checkFieldExists(HInstruction input, String fieldName) { + use(input); + js.PropertyAccess field = new js.PropertyAccess.field(pop(), fieldName); + // Double negate to boolify the result. + push(new js.Prefix('!', new js.Prefix('!', field))); + } + + void checkImmutableArray(HInstruction input) { + checkFieldExists(input, 'immutable\$list'); + } + + void checkExtendableArray(HInstruction input) { + checkFieldExists(input, 'fixed\$length'); + } + + void checkFixedArray(HInstruction input) { + checkFieldExists(input, 'fixed\$length'); + } + + void checkNull(HInstruction input) { + use(input); + push(new js.Binary('==', pop(), new js.LiteralNull())); + } + + void checkNonNull(HInstruction input) { + use(input); + push(new js.Binary('!=', pop(), new js.LiteralNull())); + } + + void checkFunction(HInstruction input, DartType type) { + checkTypeOf(input, '===', 'function'); + js.Expression functionTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + functionTest, + new js.Binary('&&', objectTest, pop()))); + } + + void checkType(HInstruction input, DartType type, {bool negative: false}) { + assert(invariant(input, !type.isMalformed, + message: 'Attempt to check malformed type $type')); + world.registerIsCheck(type); + Element element = type.element; + use(input); + js.PropertyAccess field = + new js.PropertyAccess.field(pop(), backend.namer.operatorIs(element)); + if (backend.emitter.nativeEmitter.requiresNativeIsCheck(element)) { + push(new js.Call(field, <js.Expression>[])); + if (negative) push(new js.Prefix('!', pop())); + } else { + // We always negate at least once so that the result is boolified. + push(new js.Prefix('!', field)); + // If the result is not negated, put another '!' in front. + if (!negative) push(new js.Prefix('!', pop())); + } + } + + void handleNumberOrStringSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.listClass) + && !Elements.isListSupertype(type.element, compiler) + && !Elements.isStringOnlySupertype(type.element, compiler)); + checkNum(input, '==='); + js.Expression numberTest = pop(); + checkString(input, '==='); + js.Expression stringTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + new js.Binary('||', numberTest, stringTest), + new js.Binary('&&', objectTest, pop()))); + } + + void handleStringSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.listClass) + && !Elements.isListSupertype(type.element, compiler) + && !Elements.isNumberOrStringSupertype(type.element, compiler)); + checkString(input, '==='); + js.Expression stringTest = pop(); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('||', + stringTest, + new js.Binary('&&', objectTest, pop()))); + } + + void handleListOrSupertypeCheck(HInstruction input, DartType type) { + assert(!identical(type.element, compiler.stringClass) + && !Elements.isStringOnlySupertype(type.element, compiler) + && !Elements.isNumberOrStringSupertype(type.element, compiler)); + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkArray(input, '==='); + js.Expression arrayTest = pop(); + checkType(input, type); + push(new js.Binary('&&', + objectTest, + new js.Binary('||', arrayTest, pop()))); + } + + void visitIs(HIs node) { + DartType type = node.typeExpression; + world.registerIsCheck(type); + Element element = type.element; + if (identical(element.kind, ElementKind.TYPE_VARIABLE)) { + compiler.unimplemented("visitIs for type variables", + instruction: node.expression); + } + LibraryElement coreLibrary = compiler.coreLibrary; + ClassElement objectClass = compiler.objectClass; + HInstruction input = node.expression; + + if (identical(element, objectClass) || + identical(element, compiler.dynamicClass)) { + // The constant folder also does this optimization, but we make + // it safe by assuming it may have not run. + push(newLiteralBool(true), node); + } else if (element == compiler.stringClass) { + checkString(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.doubleClass) { + checkDouble(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.numClass) { + checkNum(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.boolClass) { + checkBool(input, '==='); + attachLocationToLast(node); + } else if (element == compiler.functionClass) { + checkFunction(input, type); + attachLocationToLast(node); + } else if (element == compiler.intClass) { + // The is check in the code tells us that it might not be an + // int. So we do a typeof first to avoid possible + // deoptimizations on the JS engine due to the Math.floor check. + checkNum(input, '==='); + js.Expression numTest = pop(); + checkBigInt(input, '==='); + push(new js.Binary('&&', numTest, pop()), node); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + handleNumberOrStringSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + handleStringSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (identical(element, compiler.listClass) + || Elements.isListSupertype(element, compiler)) { + handleListOrSupertypeCheck(input, type); + attachLocationToLast(node); + } else if (element.isTypedef()) { + checkNonNull(input); + js.Expression nullTest = pop(); + checkType(input, type); + push(new js.Binary('&&', nullTest, pop())); + attachLocationToLast(node); + } else if (types[input].canBePrimitive() || types[input].canBeNull()) { + checkObject(input, '==='); + js.Expression objectTest = pop(); + checkType(input, type); + push(new js.Binary('&&', objectTest, pop()), node); + } else { + checkType(input, type); + attachLocationToLast(node); + } + if (node.hasArgumentChecks()) { + InterfaceType interfaceType = type; + ClassElement cls = type.element; + Link<DartType> arguments = interfaceType.typeArguments; + js.Expression result = pop(); + for (int i = 0; i < node.checkCount; i++) { + use(node.getCheck(i)); + result = new js.Binary('&&', result, pop()); + } + push(result, node); + } + if (node.nullOk) { + checkNull(input); + push(new js.Binary('||', pop(), pop()), node); + } + } + + // TODO(johnniwinther): Refactor this method. + void visitTypeConversion(HTypeConversion node) { + Map<String, SourceString> castNames = const <String, SourceString> { + "stringTypeCheck": + const SourceString("stringTypeCast"), + "doubleTypeCheck": + const SourceString("doubleTypeCast"), + "numTypeCheck": + const SourceString("numTypeCast"), + "boolTypeCheck": + const SourceString("boolTypeCast"), + "functionTypeCheck": + const SourceString("functionTypeCast"), + "intTypeCheck": + const SourceString("intTypeCast"), + "numberOrStringSuperNativeTypeCheck": + const SourceString("numberOrStringSuperNativeTypeCast"), + "numberOrStringSuperTypeCheck": + const SourceString("numberOrStringSuperTypeCast"), + "stringSuperNativeTypeCheck": + const SourceString("stringSuperNativeTypeCast"), + "stringSuperTypeCheck": + const SourceString("stringSuperTypeCast"), + "listTypeCheck": + const SourceString("listTypeCast"), + "listSuperNativeTypeCheck": + const SourceString("listSuperNativeTypeCast"), + "listSuperTypeCheck": + const SourceString("listSuperTypeCast"), + "callTypeCheck": + const SourceString("callTypeCast"), + "propertyTypeCheck": + const SourceString("propertyTypeCast"), + // TODO(johnniwinther): Add a malformedTypeCast which produces a TypeError + // with another message. + "malformedTypeCheck": + const SourceString("malformedTypeCheck") + }; + + if (node.isChecked) { + DartType type = node.type.computeType(compiler); + Element element = type.element; + world.registerIsCheck(type); + + if (node.isArgumentTypeCheck) { + if (element == backend.jsIntClass) { + checkInt(node.checkedInput, '!=='); + } else { + assert(element == backend.jsNumberClass); + checkNum(node.checkedInput, '!=='); + } + js.Expression test = pop(); + js.Block oldContainer = currentContainer; + js.Statement body = new js.Block.empty(); + currentContainer = body; + generateThrowWithHelper('iae', node.checkedInput); + currentContainer = oldContainer; + body = unwrapStatement(body); + pushStatement(new js.If.noElse(test, body), node); + return; + } + assert(node.isCheckedModeCheck || node.isCastTypeCheck); + + SourceString helper; + if (node.isBooleanConversionCheck) { + helper = const SourceString('boolConversionCheck'); + } else { + helper = backend.getCheckedModeHelper(type); + if (node.isCastTypeCheck) { + helper = castNames[helper.stringValue]; + } + } + FunctionElement helperElement = compiler.findHelper(helper); + world.registerStaticUse(helperElement); + List<js.Expression> arguments = <js.Expression>[]; + use(node.checkedInput); + arguments.add(pop()); + int parameterCount = + helperElement.computeSignature(compiler).parameterCount; + if (parameterCount == 2) { + // 2 arguments implies that the method is either [propertyTypeCheck] + // or [propertyTypeCast]. + assert(!type.isMalformed); + String additionalArgument = backend.namer.operatorIs(element); + arguments.add(js.string(additionalArgument)); + } else if (parameterCount == 3) { + // 3 arguments implies that the method is [malformedTypeCheck]. + assert(type.isMalformed); + String reasons = Types.fetchReasonsFromMalformedType(type); + arguments.add(js.string('$type')); + // TODO(johnniwinther): Handle escaping correctly. + arguments.add(js.string(reasons)); + } else { + assert(!type.isMalformed); + } + String helperName = backend.namer.isolateAccess(helperElement); + push(new js.Call(new js.VariableUse(helperName), arguments)); + } else { + use(node.checkedInput); + } + } +} + +class SsaOptimizedCodeGenerator extends SsaCodeGenerator { + SsaOptimizedCodeGenerator(backend, work) : super(backend, work); + + HBasicBlock beginGraph(HGraph graph) { + return graph.entry; + } + + void endGraph(HGraph graph) {} + + // Called by visitTypeGuard to generate the actual bailout call, something + // like "return $.foo$bailout(t0, t1);" + js.Statement bailout(HTypeGuard guard, String reason) { + HBailoutTarget target = guard.bailoutTarget; + List<js.Expression> arguments = <js.Expression>[]; + arguments.add(new js.LiteralNumber("${guard.state}")); + + for (int i = 0; i < target.inputs.length; i++) { + HInstruction parameter = target.inputs[i]; + for (int pad = target.padding[i]; pad != 0; pad--) { + // This argument will not be used by the bailout function, because + // of the control flow (controlled by the state argument passed + // above). We need to pass it to get later arguments in the right + // position. + arguments.add(new js.LiteralNumber('0')); + } + use(parameter); + arguments.add(pop()); + } + // Don't bother emitting the rest of the pending nulls. Doing so might make + // the function invocation a little faster by having the call site and + // function defintion have the same number of arguments, but it would be + // more verbose and we don't expect the calls to bailout functions to be + // hot. + + Element method = work.element; + js.Expression bailoutTarget; // Receiver of the bailout call. + Namer namer = backend.namer; + if (method.isInstanceMember()) { + String bailoutName = namer.getBailoutName(method); + bailoutTarget = new js.PropertyAccess.field(new js.This(), bailoutName); + } else { + assert(!method.isField()); + bailoutTarget = new js.VariableUse(namer.isolateBailoutAccess(method)); + } + js.Call call = new js.Call(bailoutTarget, arguments); + attachLocation(call, guard); + return new js.Return(call); + } + + // Generate a type guard, something like "if (typeof t0 == 'number')" and the + // corresponding bailout call, something like "return $.foo$bailout(t0, t1);" + void visitTypeGuard(HTypeGuard node) { + HInstruction input = node.guarded; + DartType indexingBehavior = + backend.jsIndexingBehaviorInterface.computeType(compiler); + if (node.isInteger(types)) { + // if (input is !int) bailout + checkInt(input, '!=='); + js.Statement then = bailout(node, 'Not an integer'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isNumber(types)) { + // if (input is !num) bailout + checkNum(input, '!=='); + js.Statement then = bailout(node, 'Not a number'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isBoolean(types)) { + // if (input is !bool) bailout + checkBool(input, '!=='); + js.Statement then = bailout(node, 'Not a boolean'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isString(types)) { + // if (input is !string) bailout + checkString(input, '!=='); + js.Statement then = bailout(node, 'Not a string'); + pushStatement(new js.If.noElse(pop(), then), node); + } else if (node.isExtendableArray(types)) { + // if (input is !Object || input is !Array || input.isFixed) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkFixedArray(input); + js.Binary test = new js.Binary('||', objectTest, arrayTest); + test = new js.Binary('||', test, pop()); + js.Statement then = bailout(node, 'Not an extendable array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isMutableArray(types)) { + // if (input is !Object + // || ((input is !Array || input.isImmutable) + // && input is !JsIndexingBehavior)) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkImmutableArray(input); + js.Binary notArrayOrImmutable = new js.Binary('||', arrayTest, pop()); + checkType(input, indexingBehavior, negative: true); + js.Binary notIndexing = new js.Binary('&&', notArrayOrImmutable, pop()); + js.Binary test = new js.Binary('||', objectTest, notIndexing); + js.Statement then = bailout(node, 'Not a mutable array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isReadableArray(types)) { + // if (input is !Object + // || (input is !Array && input is !JsIndexingBehavior)) bailout + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkType(input, indexingBehavior, negative: true); + js.Expression notIndexing = new js.Binary('&&', arrayTest, pop()); + js.Binary test = new js.Binary('||', objectTest, notIndexing); + js.Statement then = bailout(node, 'Not an array'); + pushStatement(new js.If.noElse(test, then), node); + } else if (node.isIndexablePrimitive(types)) { + // if (input is !String + // && (input is !Object + // || (input is !Array && input is !JsIndexingBehavior))) bailout + checkString(input, '!=='); + js.Expression stringTest = pop(); + checkObject(input, '!=='); + js.Expression objectTest = pop(); + checkArray(input, '!=='); + js.Expression arrayTest = pop(); + checkType(input, indexingBehavior, negative: true); + js.Binary notIndexingTest = new js.Binary('&&', arrayTest, pop()); + js.Binary notObjectOrIndexingTest = + new js.Binary('||', objectTest, notIndexingTest); + js.Binary test = + new js.Binary('&&', stringTest, notObjectOrIndexingTest); + js.Statement then = bailout(node, 'Not a string or array'); + pushStatement(new js.If.noElse(test, then), node); + } else { + compiler.internalError('Unexpected type guard', instruction: input); + } + } + + void visitBailoutTarget(HBailoutTarget target) { + // Do nothing. Bailout targets are only used in the non-optimized version. + } + + void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } + + void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } + + void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + } +} + +class SsaUnoptimizedCodeGenerator extends SsaCodeGenerator { + + js.Switch currentBailoutSwitch; + final List<js.Switch> oldBailoutSwitches; + final List<js.Parameter> newParameters; + final List<String> labels; + int labelId = 0; + /** + * Keeps track if a bailout switch already used its [:default::] clause. New + * bailout-switches just push [:false:] on the stack and replace it when + * they used the [:default::] clause. + */ + final List<bool> defaultClauseUsedInBailoutStack; + + SsaBailoutPropagator propagator; + HInstruction savedFirstInstruction; + + SsaUnoptimizedCodeGenerator(backend, work) + : super(backend, work), + oldBailoutSwitches = <js.Switch>[], + newParameters = <js.Parameter>[], + labels = <String>[], + defaultClauseUsedInBailoutStack = <bool>[]; + + String pushLabel() { + String label = 'L${labelId++}'; + labels.addLast(label); + return label; + } + + String popLabel() { + return labels.removeLast(); + } + + String currentLabel() { + return labels.last; + } + + js.VariableUse generateStateUse() + => new js.VariableUse(variableNames.stateName); + + HBasicBlock beginGraph(HGraph graph) { + propagator = new SsaBailoutPropagator(compiler, variableNames); + propagator.visitGraph(graph); + // TODO(ngeoffray): We could avoid generating the state at the + // call site for non-complex bailout methods. + newParameters.add(new js.Parameter(variableNames.stateName)); + + List<String> names = new List<String>(propagator.bailoutArity); + for (String variable in propagator.parameterNames.keys) { + int index = propagator.parameterNames[variable]; + assert(names[index] == null); + names[index] = variable; + } + for (int i = 0; i < names.length; i++) { + declaredLocals.add(names[i]); + newParameters.add(new js.Parameter(names[i])); + } + + if (propagator.hasComplexBailoutTargets) { + startBailoutSwitch(); + + return graph.entry; + } else { + // We change the first instruction of the first guard to be the + // bailout target. We will change it back in the call to [endGraph]. + HBasicBlock block = propagator.firstBailoutTarget.block; + savedFirstInstruction = block.first; + block.first = propagator.firstBailoutTarget; + return block; + } + } + + // If argument is a [HCheck] and it does not have a name, we try to + // find the name of its checked input. Note that there must be a + // name, otherwise the instruction would not be in the live + // environment. + HInstruction unwrap(HInstruction argument) { + while (argument is HCheck && !variableNames.hasName(argument)) { + argument = argument.checkedInput; + } + assert(variableNames.hasName(argument)); + return argument; + } + + void endGraph(HGraph graph) { + if (propagator.hasComplexBailoutTargets) { + endBailoutSwitch(); + } else { + // Put back the original first instruction of the block. + propagator.firstBailoutTarget.block.first = savedFirstInstruction; + } + } + + visitParameterValue(HParameterValue node) { + // Nothing to do, parameters are dealt with specially in a bailout + // method. + } + + bool visitAndOrInfo(HAndOrBlockInformation info) => false; + + visitLoopBranch(HLoopBranch node) { + if (node.computeLoopHeader().hasBailoutTargets()) { + // The graph visitor in [visitLoopInfo] does not handle the + // condition. We must instead manually emit it here. + handleLoopCondition(node); + // We must also visit the body from here. + // For a do while loop, the body has already been visited. + if (!node.isDoWhile()) { + visitBasicBlock(node.block.dominatedBlocks[0]); + } + } else { + super.visitLoopBranch(node); + } + } + + + bool visitIfInfo(HIfBlockInformation info) { + if (info.thenGraph.start.hasBailoutTargets()) return false; + if (info.elseGraph.start.hasBailoutTargets()) return false; + return super.visitIfInfo(info); + } + + bool visitLoopInfo(HLoopBlockInformation info) { + // Always emit with block flow traversal. + if (info.loopHeader.hasBailoutTargets()) { + // If there are any bailout targets in the loop, we cannot use + // the pretty [SsaCodeGenerator.visitLoopInfo] printer. + if (info.initializer != null) { + generateStatements(info.initializer); + } + beginLoop(info.loopHeader); + if (!info.isDoWhile()) { + generateStatements(info.condition); + } + generateStatements(info.body); + if (info.isDoWhile()) { + generateStatements(info.condition); + } + if (info.updates != null) { + generateStatements(info.updates); + } + endLoop(info.end); + return true; + } + return super.visitLoopInfo(info); + } + + bool visitTryInfo(HTryBlockInformation info) => false; + bool visitSequenceInfo(HStatementSequenceInformation info) => false; + + void visitTypeGuard(HTypeGuard node) { + // Do nothing. Type guards are only used in the optimized version. + } + + void visitBailoutTarget(HBailoutTarget node) { + if (propagator.hasComplexBailoutTargets) { + js.Block nextBlock = new js.Block.empty(); + js.Case clause = new js.Case(new js.LiteralNumber('${node.state}'), + nextBlock); + currentBailoutSwitch.cases.add(clause); + currentContainer = nextBlock; + pushExpressionAsStatement(new js.Assignment(generateStateUse(), + new js.LiteralNumber('0'))); + } + // Here we need to rearrange the inputs of the bailout target, so that they + // are output in the correct order, perhaps with interspersed nulls, to + // match the order in the bailout function, which is of course common to all + // the bailout points. + var newInputs = new List<HInstruction>(propagator.bailoutArity); + for (HInstruction input in node.inputs) { + int index = propagator.parameterNames[variableNames.getName(input)]; + newInputs[index] = input; + } + // We record the count of unused arguments instead of just filling in the + // inputs list with dummy arguments because it is useful to be able easily + // to distinguish between a dummy argument (eg 0 or null) and a real + // argument that happens to have the same value. The dummy arguments are + // not going to be accessed by the bailout function due to the control flow + // implied by the state argument, so we can put anything there, including + // just not emitting enough arguments and letting the JS engine insert + // undefined for the trailing arguments. + node.padding = new List<int>(node.inputs.length); + int j = 0; + int pendingUnusedArguments = 0; + for (int i = 0; i < newInputs.length; i++) { + HInstruction input = newInputs[i]; + if (input == null) { + pendingUnusedArguments++; + } else { + node.padding[j] = pendingUnusedArguments; + pendingUnusedArguments = 0; + node.updateInput(j, input); + j++; + } + } + assert(j == node.inputs.length); + } + + void startBailoutCase(List<HBailoutTarget> bailouts1, + [List<HBailoutTarget> bailouts2 = const []]) { + if (!defaultClauseUsedInBailoutStack.last && + bailouts1.length + bailouts2.length >= 2) { + currentContainer = new js.Block.empty(); + currentBailoutSwitch.cases.add(new js.Default(currentContainer)); + int len = defaultClauseUsedInBailoutStack.length; + defaultClauseUsedInBailoutStack[len - 1] = true; + } else { + _handleBailoutCase(bailouts1); + _handleBailoutCase(bailouts2); + currentContainer = currentBailoutSwitch.cases.last.body; + } + } + + void _handleBailoutCase(List<HBailoutTarget> targets) { + for (int i = 0, len = targets.length; i < len; i++) { + js.LiteralNumber expr = new js.LiteralNumber('${targets[i].state}'); + currentBailoutSwitch.cases.add(new js.Case(expr, new js.Block.empty())); + } + } + + void startBailoutSwitch() { + defaultClauseUsedInBailoutStack.add(false); + oldBailoutSwitches.add(currentBailoutSwitch); + List<js.SwitchClause> cases = <js.SwitchClause>[]; + js.Block firstBlock = new js.Block.empty(); + cases.add(new js.Case(new js.LiteralNumber("0"), firstBlock)); + currentBailoutSwitch = new js.Switch(generateStateUse(), cases); + pushStatement(currentBailoutSwitch); + oldContainerStack.add(currentContainer); + currentContainer = firstBlock; + } + + js.Switch endBailoutSwitch() { + js.Switch result = currentBailoutSwitch; + currentBailoutSwitch = oldBailoutSwitches.removeLast(); + defaultClauseUsedInBailoutStack.removeLast(); + currentContainer = oldContainerStack.removeLast(); + return result; + } + + void beginLoop(HBasicBlock block) { + String loopLabel = pushLabel(); + if (block.hasBailoutTargets()) { + startBailoutCase(block.bailoutTargets); + } + oldContainerStack.add(currentContainer); + currentContainer = new js.Block.empty(); + if (block.hasBailoutTargets()) { + startBailoutSwitch(); + HLoopInformation loopInformation = block.loopInformation; + if (loopInformation.target != null) { + breakAction[loopInformation.target] = (TargetElement target) { + pushStatement(new js.Break(loopLabel)); + }; + } + } + } + + void endLoop(HBasicBlock block) { + String loopLabel = popLabel(); + + HBasicBlock header = block.isLoopHeader() ? block : block.parentLoopHeader; + HLoopInformation info = header.loopInformation; + if (header.hasBailoutTargets()) { + endBailoutSwitch(); + if (info.target != null) breakAction.remove(info.target); + } + + js.Statement body = unwrapStatement(currentContainer); + currentContainer = oldContainerStack.removeLast(); + + js.Statement result = new js.While(newLiteralBool(true), body); + attachLocationRange(result, + info.loopBlockInformation.sourcePosition, + info.loopBlockInformation.endSourcePosition); + result = new js.LabeledStatement(loopLabel, result); + result = wrapIntoLabels(result, info.labels); + pushStatement(result); + } + + void handleLoopCondition(HLoopBranch node) { + use(node.inputs[0]); + js.Expression test = new js.Prefix('!', pop()); + js.Statement then = new js.Break(currentLabel()); + pushStatement(new js.If.noElse(test, then), node); + } + + void generateIf(HIf node, HIfBlockInformation info) { + HStatementInformation thenGraph = info.thenGraph; + HStatementInformation elseGraph = info.elseGraph; + bool thenHasGuards = thenGraph.start.hasBailoutTargets(); + bool elseHasGuards = elseGraph.start.hasBailoutTargets(); + bool hasGuards = thenHasGuards || elseHasGuards; + if (!hasGuards) { + super.generateIf(node, info); + return; + } + + startBailoutCase(thenGraph.start.bailoutTargets, + elseGraph.start.bailoutTargets); + + use(node.inputs[0]); + js.Binary stateEquals0 = + new js.Binary('===', generateStateUse(), new js.LiteralNumber('0')); + js.Expression condition = new js.Binary('&&', stateEquals0, pop()); + // TODO(ngeoffray): Put the condition initialization in the + // arguments? + List<HBailoutTarget> targets = node.thenBlock.bailoutTargets; + for (int i = 0, len = targets.length; i < len; i++) { + js.VariableUse stateRef = generateStateUse(); + js.Expression targetState = new js.LiteralNumber('${targets[i].state}'); + js.Binary stateTest = new js.Binary('===', stateRef, targetState); + condition = new js.Binary('||', stateTest, condition); + } + + js.Statement thenBody = new js.Block.empty(); + js.Block oldContainer = currentContainer; + currentContainer = thenBody; + if (thenHasGuards) startBailoutSwitch(); + generateStatements(thenGraph); + if (thenHasGuards) endBailoutSwitch(); + thenBody = unwrapStatement(thenBody); + + js.Statement elseBody = null; + elseBody = new js.Block.empty(); + currentContainer = elseBody; + if (elseHasGuards) startBailoutSwitch(); + generateStatements(elseGraph); + if (elseHasGuards) endBailoutSwitch(); + elseBody = unwrapStatement(elseBody); + + currentContainer = oldContainer; + pushStatement(new js.If(condition, thenBody, elseBody), node); + } + + void preLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + indent--; + startBailoutCase(labeledBlockInfo.body.start.bailoutTargets); + indent++; + } + } + + void startLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + startBailoutSwitch(); + } + } + + void endLabeledBlock(HLabeledBlockInformation labeledBlockInfo) { + if (labeledBlockInfo.body.start.hasBailoutTargets()) { + endBailoutSwitch(); + } + } +} + +String singleIdentityComparison(HInstruction left, + HInstruction right, + HTypeMap propagatedTypes) { + // Returns the single identity comparison (== or ===) or null if a more + // complex expression is required. + if ((left.isConstant() && left.isConstantSentinel()) || + (right.isConstant() && right.isConstantSentinel())) return '==='; + HType leftType = propagatedTypes[left]; + HType rightType = propagatedTypes[right]; + if (leftType.canBeNull() && rightType.canBeNull()) { + if (left.isConstantNull() || right.isConstantNull() || + (leftType.isPrimitive() && leftType == rightType)) { + return '=='; + } + return null; + } else { + return '==='; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart new file mode 100644 index 0000000..625eafb --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart
@@ -0,0 +1,380 @@ +// 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. + +part of ssa; + +/** + * Instead of emitting each SSA instruction with a temporary variable + * mark instructions that can be emitted at their use-site. + * For example, in: + * t0 = 4; + * t1 = 3; + * t2 = add(t0, t1); + * t0 and t1 would be marked and the resulting code would then be: + * t2 = add(4, 3); + */ +class SsaInstructionMerger extends HBaseVisitor { + HTypeMap types; + /** + * List of [HInstruction] that the instruction merger expects in + * order when visiting the inputs of an instruction. + */ + List<HInstruction> expectedInputs; + /** + * Set of pure [HInstruction] that the instruction merger expects to + * find. The order of pure instructions do not matter, as they will + * not be affected by side effects. + */ + Set<HInstruction> pureInputs; + Set<HInstruction> generateAtUseSite; + + void markAsGenerateAtUseSite(HInstruction instruction) { + assert(!instruction.isJsStatement()); + generateAtUseSite.add(instruction); + } + + SsaInstructionMerger(this.types, this.generateAtUseSite); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void analyzeInputs(HInstruction user, int start) { + List<HInstruction> inputs = user.inputs; + for (int i = start; i < inputs.length; i++) { + HInstruction input = inputs[i]; + if (!generateAtUseSite.contains(input) + && !input.isCodeMotionInvariant() + && input.usedBy.length == 1 + && input is !HPhi + && input is !HLocalValue + && !input.isJsStatement()) { + if (input.isPure()) { + // Only consider a pure input if it is in the same loop. + // Otherwise, we might move GVN'ed instruction back into the + // loop. + if (user.hasSameLoopHeaderAs(input)) { + // Move it closer to [user], so that instructions in + // between do not prevent making it generate at use site. + input.moveBefore(user); + pureInputs.add(input); + // Visit the pure input now so that the expected inputs + // are after the expected inputs of [user]. + input.accept(this); + } + } else { + expectedInputs.add(input); + } + } + } + } + + void visitInstruction(HInstruction instruction) { + // A code motion invariant instruction is dealt before visiting it. + assert(!instruction.isCodeMotionInvariant()); + analyzeInputs(instruction, 0); + } + + // The codegen might use the input multiple times, so it must not be + // set generate at use site. + void visitIs(HIs instruction) {} + + // A bounds check method must not have its first input generated at use site, + // because it's using it twice. + void visitBoundsCheck(HBoundsCheck instruction) { + analyzeInputs(instruction, 1); + } + + // An integer check method must not have its input generated at use site, + // because it's using it twice. + void visitIntegerCheck(HIntegerCheck instruction) {} + + // A type guard should not generate its input at use site, otherwise + // they would not be alive. + void visitTypeGuard(HTypeGuard instruction) {} + + // An identity operation must only have its inputs generated at use site if + // does not require an expression with multiple uses (because of null / + // undefined). + void visitIdentity(HIdentity instruction) { + HInstruction left = instruction.left; + HInstruction right = instruction.right; + if (singleIdentityComparison(left, right, types) != null) { + super.visitIdentity(instruction); + } + // Do nothing. + } + + void visitTypeConversion(HTypeConversion instruction) { + if (!instruction.isChecked) { + markAsGenerateAtUseSite(instruction); + } else if (!instruction.isArgumentTypeCheck) { + assert(instruction.isCheckedModeCheck || instruction.isCastTypeCheck); + // Checked mode checks and cast checks compile to code that + // only use their input once, so we can safely visit them + // and try to merge the input. + visitInstruction(instruction); + } + } + + void tryGenerateAtUseSite(HInstruction instruction) { + if (instruction.isControlFlow()) return; + markAsGenerateAtUseSite(instruction); + } + + bool isBlockSinglePredecessor(HBasicBlock block) { + return block.successors.length == 1 + && block.successors[0].predecessors.length == 1; + } + + void visitBasicBlock(HBasicBlock block) { + // Compensate from not merging blocks: if the block is the + // single predecessor of its single successor, let the successor + // visit it. + if (isBlockSinglePredecessor(block)) return; + + tryMergingExpressions(block); + } + + void tryMergingExpressions(HBasicBlock block) { + // Visit each instruction of the basic block in last-to-first order. + // Keep a list of expected inputs of the current "expression" being + // merged. If instructions occur in the expected order, they are + // included in the expression. + + // The expectedInputs list holds non-trivial instructions that may + // be generated at their use site, if they occur in the correct order. + if (expectedInputs == null) expectedInputs = new List<HInstruction>(); + if (pureInputs == null) pureInputs = new Set<HInstruction>(); + + // Pop instructions from expectedInputs until instruction is found. + // Return true if it is found, or false if not. + bool findInInputsAndPopNonMatching(HInstruction instruction) { + assert(!instruction.isPure()); + while (!expectedInputs.isEmpty) { + HInstruction nextInput = expectedInputs.removeLast(); + assert(!generateAtUseSite.contains(nextInput)); + assert(nextInput.usedBy.length == 1); + if (identical(nextInput, instruction)) { + return true; + } + } + return false; + } + + block.last.accept(this); + bool dontVisitPure = false; + for (HInstruction instruction = block.last.previous; + instruction != null; + instruction = instruction.previous) { + if (generateAtUseSite.contains(instruction)) { + continue; + } + if (instruction.isCodeMotionInvariant()) { + markAsGenerateAtUseSite(instruction); + continue; + } + if (instruction.isJsStatement()) { + expectedInputs.clear(); + } + if (instruction.isPure()) { + if (pureInputs.contains(instruction)) { + tryGenerateAtUseSite(instruction); + } else { + // If the input is not in the [pureInputs] set, it has not + // been visited. + instruction.accept(this); + } + } else { + if (findInInputsAndPopNonMatching(instruction)) { + // The current instruction is the next non-trivial + // expected input. + tryGenerateAtUseSite(instruction); + } else { + assert(expectedInputs.isEmpty); + } + instruction.accept(this); + } + } + + if (block.predecessors.length == 1 + && isBlockSinglePredecessor(block.predecessors[0])) { + assert(block.phis.isEmpty); + tryMergingExpressions(block.predecessors[0]); + } else { + expectedInputs = null; + pureInputs = null; + } + } +} + +/** + * Detect control flow arising from short-circuit logical and + * conditional operators, and prepare the program to be generated + * using these operators instead of nested ifs and boolean variables. + */ +class SsaConditionMerger extends HGraphVisitor { + final HTypeMap types; + Set<HInstruction> generateAtUseSite; + Set<HInstruction> controlFlowOperators; + + void markAsGenerateAtUseSite(HInstruction instruction) { + assert(!instruction.isJsStatement()); + generateAtUseSite.add(instruction); + } + + SsaConditionMerger(this.types, + this.generateAtUseSite, + this.controlFlowOperators); + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + } + + /** + * Check if a block has at least one statement other than + * [instruction]. + */ + bool hasAnyStatement(HBasicBlock block, HInstruction instruction) { + // If [instruction] is not in [block], then if the block is not + // empty, we know there will be a statement to emit. + if (!identical(instruction.block, block)) return !identical(block.last, block.first); + + // If [instruction] is not the last instruction of the block + // before the control flow instruction, or the last instruction, + // then we will have to emit a statement for that last instruction. + if (instruction != block.last + && !identical(instruction, block.last.previous)) return true; + + // If one of the instructions in the block until [instruction] is + // not generated at use site, then we will have to emit a + // statement for it. + // TODO(ngeoffray): we could generate a comma separated + // list of expressions. + for (HInstruction temp = block.first; + !identical(temp, instruction); + temp = temp.next) { + if (!generateAtUseSite.contains(temp)) return true; + } + + return false; + } + + bool isSafeToGenerateAtUseSite(HInstruction user, HInstruction input) { + // A [HForeign] instruction uses operators and if we generate + // [input] at use site, the precedence might be wrong. + if (user is HForeign) return false; + // A [HCheck] instruction with control flow uses its input + // multiple times, so we avoid generating it at use site. + if (user is HCheck && user.isControlFlow()) return false; + // A [HIs] instruction uses its input multiple times, so we + // avoid generating it at use site. + if (user is HIs) return false; + return true; + } + + void visitBasicBlock(HBasicBlock block) { + if (block.last is !HIf) return; + HIf startIf = block.last; + HBasicBlock end = startIf.joinBlock; + + // We check that the structure is the following: + // If + // / \ + // / \ + // 1 expr goto + // goto / + // \ / + // \ / + // phi(expr, true|false) + // + // and the same for nested nodes: + // + // If + // / \ + // / \ + // 1 expr1 \ + // If \ + // / \ \ + // / \ goto + // 1 expr2 | + // goto goto | + // \ / | + // \ / | + // phi1(expr2, true|false) + // \ | + // \ | + // phi(phi1, true|false) + + if (end == null) return; + if (end.phis.isEmpty) return; + if (!identical(end.phis.first, end.phis.last)) return; + HBasicBlock elseBlock = startIf.elseBlock; + + if (!identical(end.predecessors[1], elseBlock)) return; + HPhi phi = end.phis.first; + HInstruction thenInput = phi.inputs[0]; + HInstruction elseInput = phi.inputs[1]; + if (thenInput.isJsStatement() || elseInput.isJsStatement()) return; + + if (hasAnyStatement(elseBlock, elseInput)) return; + assert(elseBlock.successors.length == 1); + assert(end.predecessors.length == 2); + + HBasicBlock thenBlock = startIf.thenBlock; + // Skip trivial goto blocks. + while (thenBlock.successors[0] != end && thenBlock.first is HGoto) { + thenBlock = thenBlock.successors[0]; + } + + // If the [thenBlock] is already a control flow operation, and does not + // have any statement and its join block is [end], we can emit a + // sequence of control flow operation. + if (controlFlowOperators.contains(thenBlock.last)) { + HIf otherIf = thenBlock.last; + if (!identical(otherIf.joinBlock, end)) { + // This could be a join block that just feeds into our join block. + HBasicBlock otherJoin = otherIf.joinBlock; + if (otherJoin.first != otherJoin.last) return; + if (otherJoin.successors.length != 1) return; + if (otherJoin.successors[0] != end) return; + if (otherJoin.phis.isEmpty) return; + if (!identical(otherJoin.phis.first, otherJoin.phis.last)) return; + HPhi otherPhi = otherJoin.phis.first; + if (thenInput != otherPhi) return; + if (elseInput != otherPhi.inputs[1]) return; + } + if (hasAnyStatement(thenBlock, otherIf)) return; + } else { + if (!identical(end.predecessors[0], thenBlock)) return; + if (hasAnyStatement(thenBlock, thenInput)) return; + assert(thenBlock.successors.length == 1); + } + + // From now on, we have recognized a control flow operation built from + // the builder. Mark the if instruction as such. + controlFlowOperators.add(startIf); + + // If the operation is only used by the first instruction + // of its block and is safe to be generated at use site, mark it + // so. + if (phi.usedBy.length == 1 + && identical(phi.usedBy[0], phi.block.first) + && isSafeToGenerateAtUseSite(phi.usedBy[0], phi)) { + markAsGenerateAtUseSite(phi); + } + + if (identical(elseInput.block, elseBlock)) { + assert(elseInput.usedBy.length == 1); + markAsGenerateAtUseSite(elseInput); + } + + // If [thenInput] is defined in the first predecessor, then it is only used + // by [phi] and can be generated at use site. + if (identical(thenInput.block, end.predecessors[0])) { + assert(thenInput.usedBy.length == 1); + markAsGenerateAtUseSite(thenInput); + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart new file mode 100644 index 0000000..375f617 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart
@@ -0,0 +1,620 @@ +// Copyright (c) 2013, 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. + +part of ssa; + +/** + * [InvokeDynamicSpecializer] and its subclasses are helpers to + * optimize intercepted dynamic calls. It knows what input types + * would be beneficial for performance, and how to change a invoke + * dynamic to a builtin instruction (e.g. HIndex, HBitNot). + */ +class InvokeDynamicSpecializer { + const InvokeDynamicSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + return null; + } + + Operation operation(ConstantSystem constantSystem) => null; + + static InvokeDynamicSpecializer lookupSpecializer(Selector selector) { + if (selector.kind == SelectorKind.INDEX) { + return selector.name == const SourceString('[]') + ? const IndexSpecializer() + : const IndexAssignSpecializer(); + } else if (selector.kind == SelectorKind.OPERATOR) { + if (selector.name == const SourceString('unary-')) { + return const UnaryNegateSpecializer(); + } else if (selector.name == const SourceString('~')) { + return const BitNotSpecializer(); + } else if (selector.name == const SourceString('+')) { + return const AddSpecializer(); + } else if (selector.name == const SourceString('-')) { + return const SubtractSpecializer(); + } else if (selector.name == const SourceString('*')) { + return const MultiplySpecializer(); + } else if (selector.name == const SourceString('/')) { + return const DivideSpecializer(); + } else if (selector.name == const SourceString('~/')) { + return const TruncatingDivideSpecializer(); + } else if (selector.name == const SourceString('%')) { + return const ModuloSpecializer(); + } else if (selector.name == const SourceString('>>')) { + return const ShiftRightSpecializer(); + } else if (selector.name == const SourceString('<<')) { + return const ShiftLeftSpecializer(); + } else if (selector.name == const SourceString('&')) { + return const BitAndSpecializer(); + } else if (selector.name == const SourceString('|')) { + return const BitOrSpecializer(); + } else if (selector.name == const SourceString('^')) { + return const BitXorSpecializer(); + } else if (selector.name == const SourceString('==')) { + return const EqualsSpecializer(); + } else if (selector.name == const SourceString('<')) { + return const LessSpecializer(); + } else if (selector.name == const SourceString('<=')) { + return const LessEqualSpecializer(); + } else if (selector.name == const SourceString('>')) { + return const GreaterSpecializer(); + } else if (selector.name == const SourceString('>=')) { + return const GreaterEqualSpecializer(); + } + } + return const InvokeDynamicSpecializer(); + } +} + +class IndexAssignSpecializer extends InvokeDynamicSpecializer { + const IndexAssignSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction index = instruction.inputs[2]; + if (input == instruction.inputs[1] && + (index.isTypeUnknown(types) || index.isNumber(types))) { + return HType.MUTABLE_ARRAY; + } + // The index should be an int when the receiver is a string or array. + // However it turns out that inserting an integer check in the optimized + // version is cheaper than having another bailout case. This is true, + // because the integer check will simply throw if it fails. + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (instruction.inputs[1].isMutableArray(types)) { + return new HIndexAssign(instruction.inputs[1], + instruction.inputs[2], + instruction.inputs[3]); + } + return null; + } +} + +class IndexSpecializer extends InvokeDynamicSpecializer { + const IndexSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction index = instruction.inputs[2]; + if (input == instruction.inputs[1] && + (index.isTypeUnknown(types) || index.isNumber(types))) { + return HType.INDEXABLE_PRIMITIVE; + } + // The index should be an int when the receiver is a string or array. + // However it turns out that inserting an integer check in the optimized + // version is cheaper than having another bailout case. This is true, + // because the integer check will simply throw if it fails. + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (instruction.inputs[1].isIndexablePrimitive(types)) { + return new HIndex(instruction.inputs[1], instruction.inputs[2]); + } + return null; + } +} + +class BitNotSpecializer extends InvokeDynamicSpecializer { + const BitNotSpecializer(); + + UnaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitNot; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[1]) { + HType propagatedType = types[instruction]; + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.INTEGER; + } + } + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + // All bitwise operations on primitive types either produce an + // integer or throw an error. + if (instruction.inputs[1].isPrimitive(types)) return HType.INTEGER; + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction input = instruction.inputs[1]; + if (input.isNumber(types)) return new HBitNot(input); + return null; + } +} + +class UnaryNegateSpecializer extends InvokeDynamicSpecializer { + const UnaryNegateSpecializer(); + + UnaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.negate; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[1]) { + HType propagatedType = types[instruction]; + // If the outgoing type should be a number (integer, double or both) we + // want the outgoing type to be the input too. + // If we don't know the outgoing type we try to make it a number. + if (propagatedType.isNumber()) return propagatedType; + if (propagatedType.isUnknown()) return HType.NUMBER; + } + return HType.UNKNOWN; + } + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + HType operandType = types[instruction.inputs[1]]; + if (operandType.isNumber()) return operandType; + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction input = instruction.inputs[1]; + if (input.isNumber(types)) return new HNegate(input); + return null; + } +} + +abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer { + const BinaryArithmeticSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER; + if (left.isNumber(types)) { + if (left.isDouble(types) || right.isDouble(types)) return HType.DOUBLE; + return HType.NUMBER; + } + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + + HType propagatedType = types[instruction]; + // If the desired output type should be an integer we want to get two + // integers as arguments. + if (propagatedType.isInteger()) return HType.INTEGER; + // If the outgoing type should be a number we can get that if both inputs + // are numbers. If we don't know the outgoing type we try to make it a + // number. + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.NUMBER; + } + // Even if the desired outgoing type is not a number we still want the + // second argument to be a number if the first one is a number. This will + // not help for the outgoing type, but at least the binary arithmetic + // operation will not have type problems. + // TODO(floitsch): normally we shouldn't request a number, but simply + // throw an ArgumentError if it isn't. This would be similar + // to the array case. + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (input == right && left.isNumber(types)) return HType.NUMBER; + return HType.UNKNOWN; + } + + bool isBuiltin(HInvokeDynamic instruction, HTypeMap types) { + return instruction.inputs[1].isNumber(types) + && instruction.inputs[2].isNumber(types); + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + if (isBuiltin(instruction, types)) { + HInstruction builtin = + newBuiltinVariant(instruction.inputs[1], instruction.inputs[2]); + if (builtin != null) return builtin; + // Even if there is no builtin equivalent instruction, we know + // the instruction does not have any side effect, and that it + // can be GVN'ed. + instruction.clearAllSideEffects(); + instruction.clearAllDependencies(); + instruction.setUseGvn(); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right); +} + +class AddSpecializer extends BinaryArithmeticSpecializer { + const AddSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.add; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HAdd(left, right); + } +} + +class DivideSpecializer extends BinaryArithmeticSpecializer { + const DivideSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.divide; + } + + HType computeTypeFromInputTypes(HInstruction instruction, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + if (left.isNumber(types)) return HType.DOUBLE; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInstruction instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + // A division can never return an integer. So don't ask for integer inputs. + if (instruction.isInteger(types)) return HType.UNKNOWN; + return super.computeDesiredTypeForInput( + instruction, input, types, compiler); + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HDivide(left, right); + } +} + +class ModuloSpecializer extends BinaryArithmeticSpecializer { + const ModuloSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.modulo; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Modulo cannot be mapped to the native operator (different semantics). + return null; + } +} + +class MultiplySpecializer extends BinaryArithmeticSpecializer { + const MultiplySpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.multiply; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HMultiply(left, right); + } +} + +class SubtractSpecializer extends BinaryArithmeticSpecializer { + const SubtractSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.subtract; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HSubtract(left, right); + } +} + +class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer { + const TruncatingDivideSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.truncatingDivide; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Truncating divide does not have a JS equivalent. + return null; + } +} + +abstract class BinaryBitOpSpecializer extends BinaryArithmeticSpecializer { + const BinaryBitOpSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + // All bitwise operations on primitive types either produce an + // integer or throw an error. + HInstruction left = instruction.inputs[1]; + if (left.isPrimitive(types)) return HType.INTEGER; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + HType propagatedType = types[instruction]; + // If the outgoing type should be a number we can get that only if both + // inputs are integers. If we don't know the outgoing type we try to make + // it an integer. + if (propagatedType.isUnknown() || propagatedType.isNumber()) { + return HType.INTEGER; + } + return HType.UNKNOWN; + } +} + +class ShiftLeftSpecializer extends BinaryBitOpSpecializer { + const ShiftLeftSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.shiftLeft; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (!left.isNumber(types) || !right.isConstantInteger()) return null; + HConstant rightConstant = right; + IntConstant intConstant = rightConstant.constant; + int count = intConstant.value; + if (count >= 0 && count <= 31) { + return newBuiltinVariant(left, right); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HShiftLeft(left, right); + } +} + +class ShiftRightSpecializer extends BinaryBitOpSpecializer { + const ShiftRightSpecializer(); + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + // Shift right cannot be mapped to the native operator easily. + return null; + } + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.shiftRight; + } +} + +class BitOrSpecializer extends BinaryBitOpSpecializer { + const BitOrSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitOr; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitOr(left, right); + } +} + +class BitAndSpecializer extends BinaryBitOpSpecializer { + const BitAndSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitAnd; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitAnd(left, right); + } +} + +class BitXorSpecializer extends BinaryBitOpSpecializer { + const BitXorSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.bitXor; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HBitXor(left, right); + } +} + +abstract class RelationalSpecializer extends InvokeDynamicSpecializer { + const RelationalSpecializer(); + + HType computeTypeFromInputTypes(HInvokeDynamic instruction, + HTypeMap types, + Compiler compiler) { + if (types[instruction.inputs[1]].isPrimitiveOrNull()) return HType.BOOLEAN; + return HType.UNKNOWN; + } + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + if (input == instruction.inputs[0]) return HType.UNKNOWN; + HType propagatedType = types[instruction]; + // For all relational operations except HIdentity, we expect to get numbers + // only. With numbers the outgoing type is a boolean. If something else + // is desired, then numbers are incorrect, though. + if (propagatedType.isUnknown() || propagatedType.isBoolean()) { + HInstruction left = instruction.inputs[1]; + if (left.isTypeUnknown(types) || left.isNumber(types)) { + return HType.NUMBER; + } + } + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isNumber(types) && right.isNumber(types)) { + return newBuiltinVariant(left, right); + } + return null; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right); +} + +class EqualsSpecializer extends RelationalSpecializer { + const EqualsSpecializer(); + + HType computeDesiredTypeForInput(HInvokeDynamic instruction, + HInstruction input, + HTypeMap types, + Compiler compiler) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + HType propagatedType = types[instruction]; + if (input == left && types[right].isUseful()) { + // All our useful types have 'identical' semantics. But we don't want to + // speculatively test for all possible types. Therefore we try to match + // the two types. That is, if we see x == 3, then we speculatively test + // if x is a number and bailout if it isn't. + // If right is a number we don't need more than a number (no need to match + // the exact type of right). + if (right.isNumber(types)) return HType.NUMBER; + return types[right]; + } + // String equality testing is much more common than array equality testing. + if (input == left && left.isIndexablePrimitive(types)) { + return HType.READABLE_ARRAY; + } + // String equality testing is much more common than array equality testing. + if (input == right && right.isIndexablePrimitive(types)) { + return HType.STRING; + } + return HType.UNKNOWN; + } + + HInstruction tryConvertToBuiltin(HInvokeDynamic instruction, + HTypeMap types) { + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (types[left].isPrimitiveOrNull() || right.isConstantNull()) { + return newBuiltinVariant(left, right); + } + return null; + } + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.equal; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HIdentity(left, right); + } +} + +class LessSpecializer extends RelationalSpecializer { + const LessSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.less; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HLess(left, right); + } +} + +class GreaterSpecializer extends RelationalSpecializer { + const GreaterSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.greater; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HGreater(left, right); + } +} + +class GreaterEqualSpecializer extends RelationalSpecializer { + const GreaterEqualSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.greaterEqual; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HGreaterEqual(left, right); + } +} + +class LessEqualSpecializer extends RelationalSpecializer { + const LessEqualSpecializer(); + + BinaryOperation operation(ConstantSystem constantSystem) { + return constantSystem.lessEqual; + } + + HInstruction newBuiltinVariant(HInstruction left, HInstruction right) { + return new HLessEqual(left, right); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart new file mode 100644 index 0000000..afbd2e1 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart
@@ -0,0 +1,2711 @@ +// 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. + +part of ssa; + +abstract class HVisitor<R> { + R visitAdd(HAdd node); + R visitBailoutTarget(HBailoutTarget node); + R visitBitAnd(HBitAnd node); + R visitBitNot(HBitNot node); + R visitBitOr(HBitOr node); + R visitBitXor(HBitXor node); + R visitBoolify(HBoolify node); + R visitBoundsCheck(HBoundsCheck node); + R visitBreak(HBreak node); + R visitConstant(HConstant node); + R visitContinue(HContinue node); + R visitDivide(HDivide node); + R visitExit(HExit node); + R visitExitTry(HExitTry node); + R visitFieldGet(HFieldGet node); + R visitFieldSet(HFieldSet node); + R visitForeign(HForeign node); + R visitForeignNew(HForeignNew node); + R visitGoto(HGoto node); + R visitGreater(HGreater node); + R visitGreaterEqual(HGreaterEqual node); + R visitIdentity(HIdentity node); + R visitIf(HIf node); + R visitIndex(HIndex node); + R visitIndexAssign(HIndexAssign node); + R visitIntegerCheck(HIntegerCheck node); + R visitInterceptor(HInterceptor node); + R visitInvokeClosure(HInvokeClosure node); + R visitInvokeDynamicGetter(HInvokeDynamicGetter node); + R visitInvokeDynamicMethod(HInvokeDynamicMethod node); + R visitInvokeDynamicSetter(HInvokeDynamicSetter node); + R visitInvokeStatic(HInvokeStatic node); + R visitInvokeSuper(HInvokeSuper node); + R visitIs(HIs node); + R visitLazyStatic(HLazyStatic node); + R visitLess(HLess node); + R visitLessEqual(HLessEqual node); + R visitLiteralList(HLiteralList node); + R visitLocalGet(HLocalGet node); + R visitLocalSet(HLocalSet node); + R visitLocalValue(HLocalValue node); + R visitLoopBranch(HLoopBranch node); + R visitMultiply(HMultiply node); + R visitNegate(HNegate node); + R visitNot(HNot node); + R visitOneShotInterceptor(HOneShotInterceptor); + R visitParameterValue(HParameterValue node); + R visitPhi(HPhi node); + R visitRangeConversion(HRangeConversion node); + R visitReturn(HReturn node); + R visitShiftLeft(HShiftLeft node); + R visitStatic(HStatic node); + R visitStaticStore(HStaticStore node); + R visitStringConcat(HStringConcat node); + R visitSubtract(HSubtract node); + R visitSwitch(HSwitch node); + R visitThis(HThis node); + R visitThrow(HThrow node); + R visitTry(HTry node); + R visitTypeGuard(HTypeGuard node); + R visitTypeConversion(HTypeConversion node); +} + +abstract class HGraphVisitor { + visitDominatorTree(HGraph graph) { + void visitBasicBlockAndSuccessors(HBasicBlock block) { + visitBasicBlock(block); + List dominated = block.dominatedBlocks; + for (int i = 0; i < dominated.length; i++) { + visitBasicBlockAndSuccessors(dominated[i]); + } + } + + visitBasicBlockAndSuccessors(graph.entry); + } + + visitPostDominatorTree(HGraph graph) { + void visitBasicBlockAndSuccessors(HBasicBlock block) { + List dominated = block.dominatedBlocks; + for (int i = dominated.length - 1; i >= 0; i--) { + visitBasicBlockAndSuccessors(dominated[i]); + } + visitBasicBlock(block); + } + + visitBasicBlockAndSuccessors(graph.entry); + } + + visitBasicBlock(HBasicBlock block); +} + +abstract class HInstructionVisitor extends HGraphVisitor { + HBasicBlock currentBlock; + + visitInstruction(HInstruction node); + + visitBasicBlock(HBasicBlock node) { + void visitInstructionList(HInstructionList list) { + HInstruction instruction = list.first; + while (instruction != null) { + visitInstruction(instruction); + instruction = instruction.next; + assert(instruction != list.first); + } + } + + currentBlock = node; + visitInstructionList(node); + } +} + +class HGraph { + HBasicBlock entry; + HBasicBlock exit; + HThis thisInstruction; + bool isRecursiveMethod = false; + bool calledInLoop = false; + final List<HBasicBlock> blocks; + + // We canonicalize all constants used within a graph so we do not + // have to worry about them for global value numbering. + Map<Constant, HConstant> constants; + + HGraph() + : blocks = new List<HBasicBlock>(), + constants = new Map<Constant, HConstant>() { + entry = addNewBlock(); + // The exit block will be added later, so it has an id that is + // after all others in the system. + exit = new HBasicBlock(); + } + + void addBlock(HBasicBlock block) { + int id = blocks.length; + block.id = id; + blocks.add(block); + assert(identical(blocks[id], block)); + } + + HBasicBlock addNewBlock() { + HBasicBlock result = new HBasicBlock(); + addBlock(result); + return result; + } + + HBasicBlock addNewLoopHeaderBlock(TargetElement target, + List<LabelElement> labels) { + HBasicBlock result = addNewBlock(); + result.loopInformation = + new HLoopInformation(result, target, labels); + return result; + } + + static HType mapConstantTypeToSsaType(Constant constant) { + if (constant.isNull()) return HType.NULL; + if (constant.isBool()) return HType.BOOLEAN; + if (constant.isInt()) return HType.INTEGER; + if (constant.isDouble()) return HType.DOUBLE; + if (constant.isString()) return HType.STRING; + if (constant.isList()) return HType.READABLE_ARRAY; + if (constant.isFunction()) return HType.UNKNOWN; + if (constant.isSentinel()) return HType.UNKNOWN; + ObjectConstant objectConstant = constant; + return new HBoundedType.exact(objectConstant.type); + } + + HConstant addConstant(Constant constant) { + HConstant result = constants[constant]; + if (result == null) { + HType type = mapConstantTypeToSsaType(constant); + result = new HConstant.internal(constant, type); + entry.addAtExit(result); + constants[constant] = result; + } else if (result.block == null) { + // The constant was not used anymore. + entry.addAtExit(result); + } + return result; + } + + HConstant addConstantInt(int i, ConstantSystem constantSystem) { + return addConstant(constantSystem.createInt(i)); + } + + HConstant addConstantDouble(double d, ConstantSystem constantSystem) { + return addConstant(constantSystem.createDouble(d)); + } + + HConstant addConstantString(DartString str, + Node diagnosticNode, + ConstantSystem constantSystem) { + return addConstant(constantSystem.createString(str, diagnosticNode)); + } + + HConstant addConstantBool(bool value, ConstantSystem constantSystem) { + return addConstant(constantSystem.createBool(value)); + } + + HConstant addConstantNull(ConstantSystem constantSystem) { + return addConstant(constantSystem.createNull()); + } + + void finalize() { + addBlock(exit); + exit.open(); + exit.close(new HExit()); + assignDominators(); + } + + void assignDominators() { + // Run through the blocks in order of increasing ids so we are + // guaranteed that we have computed dominators for all blocks + // higher up in the dominator tree. + for (int i = 0, length = blocks.length; i < length; i++) { + HBasicBlock block = blocks[i]; + List<HBasicBlock> predecessors = block.predecessors; + if (block.isLoopHeader()) { + block.assignCommonDominator(predecessors[0]); + } else { + for (int j = predecessors.length - 1; j >= 0; j--) { + block.assignCommonDominator(predecessors[j]); + } + } + } + } + + bool isValid() { + HValidator validator = new HValidator(); + validator.visitGraph(this); + return validator.isValid; + } +} + +class HBaseVisitor extends HGraphVisitor implements HVisitor { + HBasicBlock currentBlock; + + visitBasicBlock(HBasicBlock node) { + currentBlock = node; + + HInstruction instruction = node.first; + while (instruction != null) { + instruction.accept(this); + instruction = instruction.next; + } + } + + visitInstruction(HInstruction instruction) {} + + visitBinaryArithmetic(HBinaryArithmetic node) => visitInvokeBinary(node); + visitBinaryBitOp(HBinaryBitOp node) => visitBinaryArithmetic(node); + visitInvoke(HInvoke node) => visitInstruction(node); + visitInvokeBinary(HInvokeBinary node) => visitInstruction(node); + visitInvokeDynamic(HInvokeDynamic node) => visitInvoke(node); + visitInvokeDynamicField(HInvokeDynamicField node) => visitInvokeDynamic(node); + visitInvokeUnary(HInvokeUnary node) => visitInstruction(node); + visitConditionalBranch(HConditionalBranch node) => visitControlFlow(node); + visitControlFlow(HControlFlow node) => visitInstruction(node); + visitFieldAccess(HFieldAccess node) => visitInstruction(node); + visitRelational(HRelational node) => visitInvokeBinary(node); + + visitAdd(HAdd node) => visitBinaryArithmetic(node); + visitBailoutTarget(HBailoutTarget node) => visitInstruction(node); + visitBitAnd(HBitAnd node) => visitBinaryBitOp(node); + visitBitNot(HBitNot node) => visitInvokeUnary(node); + visitBitOr(HBitOr node) => visitBinaryBitOp(node); + visitBitXor(HBitXor node) => visitBinaryBitOp(node); + visitBoolify(HBoolify node) => visitInstruction(node); + visitBoundsCheck(HBoundsCheck node) => visitCheck(node); + visitBreak(HBreak node) => visitJump(node); + visitContinue(HContinue node) => visitJump(node); + visitCheck(HCheck node) => visitInstruction(node); + visitConstant(HConstant node) => visitInstruction(node); + visitDivide(HDivide node) => visitBinaryArithmetic(node); + visitExit(HExit node) => visitControlFlow(node); + visitExitTry(HExitTry node) => visitControlFlow(node); + visitFieldGet(HFieldGet node) => visitFieldAccess(node); + visitFieldSet(HFieldSet node) => visitFieldAccess(node); + visitForeign(HForeign node) => visitInstruction(node); + visitForeignNew(HForeignNew node) => visitForeign(node); + visitGoto(HGoto node) => visitControlFlow(node); + visitGreater(HGreater node) => visitRelational(node); + visitGreaterEqual(HGreaterEqual node) => visitRelational(node); + visitIdentity(HIdentity node) => visitRelational(node); + visitIf(HIf node) => visitConditionalBranch(node); + visitIndex(HIndex node) => visitInstruction(node); + visitIndexAssign(HIndexAssign node) => visitInstruction(node); + visitIntegerCheck(HIntegerCheck node) => visitCheck(node); + visitInterceptor(HInterceptor node) => visitInstruction(node); + visitInvokeClosure(HInvokeClosure node) + => visitInvokeDynamic(node); + visitInvokeDynamicMethod(HInvokeDynamicMethod node) + => visitInvokeDynamic(node); + visitInvokeDynamicGetter(HInvokeDynamicGetter node) + => visitInvokeDynamicField(node); + visitInvokeDynamicSetter(HInvokeDynamicSetter node) + => visitInvokeDynamicField(node); + visitInvokeStatic(HInvokeStatic node) => visitInvoke(node); + visitInvokeSuper(HInvokeSuper node) => visitInvoke(node); + visitJump(HJump node) => visitControlFlow(node); + visitLazyStatic(HLazyStatic node) => visitInstruction(node); + visitLess(HLess node) => visitRelational(node); + visitLessEqual(HLessEqual node) => visitRelational(node); + visitLiteralList(HLiteralList node) => visitInstruction(node); + visitLocalGet(HLocalGet node) => visitFieldAccess(node); + visitLocalSet(HLocalSet node) => visitFieldAccess(node); + visitLocalValue(HLocalValue node) => visitInstruction(node); + visitLoopBranch(HLoopBranch node) => visitConditionalBranch(node); + visitNegate(HNegate node) => visitInvokeUnary(node); + visitNot(HNot node) => visitInstruction(node); + visitOneShotInterceptor(HOneShotInterceptor node) + => visitInvokeDynamic(node); + visitPhi(HPhi node) => visitInstruction(node); + visitMultiply(HMultiply node) => visitBinaryArithmetic(node); + visitParameterValue(HParameterValue node) => visitLocalValue(node); + visitRangeConversion(HRangeConversion node) => visitCheck(node); + visitReturn(HReturn node) => visitControlFlow(node); + visitShiftLeft(HShiftLeft node) => visitBinaryBitOp(node); + visitSubtract(HSubtract node) => visitBinaryArithmetic(node); + visitSwitch(HSwitch node) => visitControlFlow(node); + visitStatic(HStatic node) => visitInstruction(node); + visitStaticStore(HStaticStore node) => visitInstruction(node); + visitStringConcat(HStringConcat node) => visitInstruction(node); + visitThis(HThis node) => visitParameterValue(node); + visitThrow(HThrow node) => visitControlFlow(node); + visitTry(HTry node) => visitControlFlow(node); + visitTypeGuard(HTypeGuard node) => visitCheck(node); + visitIs(HIs node) => visitInstruction(node); + visitTypeConversion(HTypeConversion node) => visitCheck(node); +} + +class SubGraph { + // The first and last block of the sub-graph. + final HBasicBlock start; + final HBasicBlock end; + + const SubGraph(this.start, this.end); + + bool contains(HBasicBlock block) { + assert(start != null); + assert(end != null); + assert(block != null); + return start.id <= block.id && block.id <= end.id; + } +} + +class SubExpression extends SubGraph { + const SubExpression(HBasicBlock start, HBasicBlock end) + : super(start, end); + + /** Find the condition expression if this sub-expression is a condition. */ + HInstruction get conditionExpression { + HInstruction last = end.last; + if (last is HConditionalBranch || last is HSwitch) return last.inputs[0]; + return null; + } +} + +class HInstructionList { + HInstruction first = null; + HInstruction last = null; + + bool get isEmpty { + return first == null; + } + + void internalAddAfter(HInstruction cursor, HInstruction instruction) { + if (cursor == null) { + assert(isEmpty); + first = last = instruction; + } else if (identical(cursor, last)) { + last.next = instruction; + instruction.previous = last; + last = instruction; + } else { + instruction.previous = cursor; + instruction.next = cursor.next; + cursor.next.previous = instruction; + cursor.next = instruction; + } + } + + void internalAddBefore(HInstruction cursor, HInstruction instruction) { + if (cursor == null) { + assert(isEmpty); + first = last = instruction; + } else if (identical(cursor, first)) { + first.previous = instruction; + instruction.next = first; + first = instruction; + } else { + instruction.next = cursor; + instruction.previous = cursor.previous; + cursor.previous.next = instruction; + cursor.previous = instruction; + } + } + + void detach(HInstruction instruction) { + assert(contains(instruction)); + assert(instruction.isInBasicBlock()); + if (instruction.previous == null) { + first = instruction.next; + } else { + instruction.previous.next = instruction.next; + } + if (instruction.next == null) { + last = instruction.previous; + } else { + instruction.next.previous = instruction.previous; + } + instruction.previous = null; + instruction.next = null; + } + + void remove(HInstruction instruction) { + assert(instruction.usedBy.isEmpty); + detach(instruction); + } + + /** Linear search for [instruction]. */ + bool contains(HInstruction instruction) { + HInstruction cursor = first; + while (cursor != null) { + if (identical(cursor, instruction)) return true; + cursor = cursor.next; + } + return false; + } +} + +class HBasicBlock extends HInstructionList { + // The [id] must be such that any successor's id is greater than + // this [id]. The exception are back-edges. + int id; + + static const int STATUS_NEW = 0; + static const int STATUS_OPEN = 1; + static const int STATUS_CLOSED = 2; + int status = STATUS_NEW; + + HInstructionList phis; + + HLoopInformation loopInformation = null; + HBlockFlow blockFlow = null; + HBasicBlock parentLoopHeader = null; + List<HBailoutTarget> bailoutTargets; + + final List<HBasicBlock> predecessors; + List<HBasicBlock> successors; + + HBasicBlock dominator = null; + final List<HBasicBlock> dominatedBlocks; + + HBasicBlock() : this.withId(null); + HBasicBlock.withId(this.id) + : phis = new HInstructionList(), + predecessors = <HBasicBlock>[], + successors = const <HBasicBlock>[], + dominatedBlocks = <HBasicBlock>[], + bailoutTargets = <HBailoutTarget>[]; + + int get hashCode => id; + + bool isNew() => status == STATUS_NEW; + bool isOpen() => status == STATUS_OPEN; + bool isClosed() => status == STATUS_CLOSED; + + bool isLoopHeader() { + return loopInformation != null; + } + + void setBlockFlow(HBlockInformation blockInfo, HBasicBlock continuation) { + blockFlow = new HBlockFlow(blockInfo, continuation); + } + + bool isLabeledBlock() => + blockFlow != null && + blockFlow.body is HLabeledBlockInformation; + + HBasicBlock get enclosingLoopHeader { + if (isLoopHeader()) return this; + return parentLoopHeader; + } + + bool hasBailoutTargets() => !bailoutTargets.isEmpty; + + void open() { + assert(isNew()); + status = STATUS_OPEN; + } + + void close(HControlFlow end) { + assert(isOpen()); + addAfter(last, end); + status = STATUS_CLOSED; + } + + void addAtEntry(HInstruction instruction) { + assert(instruction is !HPhi); + internalAddBefore(first, instruction); + instruction.notifyAddedToBlock(this); + } + + void addAtExit(HInstruction instruction) { + assert(isClosed()); + assert(last is HControlFlow); + assert(instruction is !HPhi); + internalAddBefore(last, instruction); + instruction.notifyAddedToBlock(this); + } + + void moveAtExit(HInstruction instruction) { + assert(instruction is !HPhi); + assert(instruction.isInBasicBlock()); + assert(isClosed()); + assert(last is HControlFlow); + internalAddBefore(last, instruction); + instruction.block = this; + assert(isValid()); + } + + void add(HInstruction instruction) { + assert(instruction is !HControlFlow); + assert(instruction is !HPhi); + internalAddAfter(last, instruction); + instruction.notifyAddedToBlock(this); + } + + void addPhi(HPhi phi) { + phis.internalAddAfter(phis.last, phi); + phi.notifyAddedToBlock(this); + } + + void removePhi(HPhi phi) { + phis.remove(phi); + assert(phi.block == this); + phi.notifyRemovedFromBlock(); + } + + void addAfter(HInstruction cursor, HInstruction instruction) { + assert(cursor is !HPhi); + assert(instruction is !HPhi); + assert(isOpen() || isClosed()); + internalAddAfter(cursor, instruction); + instruction.notifyAddedToBlock(this); + } + + void addBefore(HInstruction cursor, HInstruction instruction) { + assert(cursor is !HPhi); + assert(instruction is !HPhi); + assert(isOpen() || isClosed()); + internalAddBefore(cursor, instruction); + instruction.notifyAddedToBlock(this); + } + + void remove(HInstruction instruction) { + assert(isOpen() || isClosed()); + assert(instruction is !HPhi); + super.remove(instruction); + assert(instruction.block == this); + instruction.notifyRemovedFromBlock(); + } + + void addSuccessor(HBasicBlock block) { + if (successors.isEmpty) { + successors = [block]; + } else { + successors.add(block); + } + block.predecessors.add(this); + } + + void postProcessLoopHeader() { + assert(isLoopHeader()); + // Only the first entry into the loop is from outside the + // loop. All other entries must be back edges. + for (int i = 1, length = predecessors.length; i < length; i++) { + loopInformation.addBackEdge(predecessors[i]); + } + } + + /** + * Rewrites all uses of the [from] instruction to using the [to] + * instruction instead. + */ + void rewrite(HInstruction from, HInstruction to) { + for (HInstruction use in from.usedBy) { + use.rewriteInput(from, to); + } + to.usedBy.addAll(from.usedBy); + from.usedBy.clear(); + } + + /** + * Rewrites all uses of the [from] instruction to using either the + * [to] instruction, or a [HCheck] instruction that has better type + * information on [to], and that dominates the user. + */ + void rewriteWithBetterUser(HInstruction from, HInstruction to) { + Link<HCheck> better = const Link<HCheck>(); + for (HInstruction user in to.usedBy) { + if (user is HCheck && identical((user as HCheck).checkedInput, to)) { + better = better.prepend(user); + } + } + + if (better.isEmpty) return rewrite(from, to); + + L1: for (HInstruction user in from.usedBy) { + for (HCheck check in better) { + if (check.dominates(user)) { + user.rewriteInput(from, check); + check.usedBy.add(user); + continue L1; + } + } + user.rewriteInput(from, to); + to.usedBy.add(user); + } + from.usedBy.clear(); + } + + bool isExitBlock() { + return identical(first, last) && first is HExit; + } + + void addDominatedBlock(HBasicBlock block) { + assert(isClosed()); + assert(id != null && block.id != null); + assert(dominatedBlocks.indexOf(block) < 0); + // Keep the list of dominated blocks sorted such that if there are two + // succeeding blocks in the list, the predecessor is before the successor. + // Assume that we add the dominated blocks in the right order. + int index = dominatedBlocks.length; + while (index > 0 && dominatedBlocks[index - 1].id > block.id) { + index--; + } + if (index == dominatedBlocks.length) { + dominatedBlocks.add(block); + } else { + dominatedBlocks.insertRange(index, 1, block); + } + assert(block.dominator == null); + block.dominator = this; + } + + void removeDominatedBlock(HBasicBlock block) { + assert(isClosed()); + assert(id != null && block.id != null); + int index = dominatedBlocks.indexOf(block); + assert(index >= 0); + if (index == dominatedBlocks.length - 1) { + dominatedBlocks.removeLast(); + } else { + dominatedBlocks.removeRange(index, 1); + } + assert(identical(block.dominator, this)); + block.dominator = null; + } + + void assignCommonDominator(HBasicBlock predecessor) { + assert(isClosed()); + if (dominator == null) { + // If this basic block doesn't have a dominator yet we use the + // given predecessor as the dominator. + predecessor.addDominatedBlock(this); + } else if (predecessor.dominator != null) { + // If the predecessor has a dominator and this basic block has a + // dominator, we find a common parent in the dominator tree and + // use that as the dominator. + HBasicBlock block0 = dominator; + HBasicBlock block1 = predecessor; + while (!identical(block0, block1)) { + if (block0.id > block1.id) { + block0 = block0.dominator; + } else { + block1 = block1.dominator; + } + assert(block0 != null && block1 != null); + } + if (!identical(dominator, block0)) { + dominator.removeDominatedBlock(this); + block0.addDominatedBlock(this); + } + } + } + + void forEachPhi(void f(HPhi phi)) { + HPhi current = phis.first; + while (current != null) { + HInstruction saved = current.next; + f(current); + current = saved; + } + } + + void forEachInstruction(void f(HInstruction instruction)) { + HInstruction current = first; + while (current != null) { + HInstruction saved = current.next; + f(current); + current = saved; + } + } + + bool isValid() { + assert(isClosed()); + HValidator validator = new HValidator(); + validator.visitBasicBlock(this); + return validator.isValid; + } + + // TODO(ngeoffray): Cache the information if this method ends up + // being hot. + bool dominates(HBasicBlock other) { + do { + if (identical(this, other)) return true; + other = other.dominator; + } while (other != null && other.id >= id); + return false; + } +} + + +abstract class HInstruction implements Spannable { + Element sourceElement; + SourceFileLocation sourcePosition; + + final int id; + static int idCounter; + + final List<HInstruction> inputs; + final List<HInstruction> usedBy; + + HBasicBlock block; + HInstruction previous = null; + HInstruction next = null; + int flags = 0; + + // Changes flags. + static const int FLAG_CHANGES_INDEX = 0; + static const int FLAG_CHANGES_INSTANCE_PROPERTY = FLAG_CHANGES_INDEX + 1; + static const int FLAG_CHANGES_STATIC_PROPERTY + = FLAG_CHANGES_INSTANCE_PROPERTY + 1; + static const int FLAG_CHANGES_COUNT = FLAG_CHANGES_STATIC_PROPERTY + 1; + + // Depends flags (one for each changes flag). + static const int FLAG_DEPENDS_ON_INDEX_STORE = FLAG_CHANGES_COUNT; + static const int FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE = + FLAG_DEPENDS_ON_INDEX_STORE + 1; + static const int FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE = + FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE + 1; + static const int FLAG_DEPENDS_ON_COUNT = + FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE + 1; + + // Other flags. + static const int FLAG_USE_GVN = FLAG_DEPENDS_ON_COUNT; + + // Type codes. + static const int UNDEFINED_TYPECODE = -1; + static const int BOOLIFY_TYPECODE = 0; + static const int TYPE_GUARD_TYPECODE = 1; + static const int BOUNDS_CHECK_TYPECODE = 2; + static const int INTEGER_CHECK_TYPECODE = 3; + static const int INTERCEPTOR_TYPECODE = 4; + static const int ADD_TYPECODE = 5; + static const int DIVIDE_TYPECODE = 6; + static const int MULTIPLY_TYPECODE = 7; + static const int SUBTRACT_TYPECODE = 8; + static const int SHIFT_LEFT_TYPECODE = 9; + static const int BIT_OR_TYPECODE = 10; + static const int BIT_AND_TYPECODE = 11; + static const int BIT_XOR_TYPECODE = 12; + static const int NEGATE_TYPECODE = 13; + static const int BIT_NOT_TYPECODE = 14; + static const int NOT_TYPECODE = 15; + static const int IDENTITY_TYPECODE = 16; + static const int GREATER_TYPECODE = 17; + static const int GREATER_EQUAL_TYPECODE = 18; + static const int LESS_TYPECODE = 19; + static const int LESS_EQUAL_TYPECODE = 20; + static const int STATIC_TYPECODE = 21; + static const int STATIC_STORE_TYPECODE = 22; + static const int FIELD_GET_TYPECODE = 23; + static const int TYPE_CONVERSION_TYPECODE = 24; + static const int BAILOUT_TARGET_TYPECODE = 25; + static const int INVOKE_STATIC_TYPECODE = 26; + static const int INDEX_TYPECODE = 27; + static const int IS_TYPECODE = 28; + static const int INVOKE_DYNAMIC_TYPECODE = 29; + + HInstruction(this.inputs) : id = idCounter++, usedBy = <HInstruction>[]; + + int get hashCode => id; + + bool getFlag(int position) => (flags & (1 << position)) != 0; + void setFlag(int position) { flags |= (1 << position); } + void clearFlag(int position) { flags &= ~(1 << position); } + + static int computeDependsOnFlags(int flags) => flags << FLAG_CHANGES_COUNT; + + int getChangesFlags() => flags & ((1 << FLAG_CHANGES_COUNT) - 1); + int getDependsOnFlags() { + return (flags & ((1 << FLAG_DEPENDS_ON_COUNT) - 1)) >> FLAG_CHANGES_COUNT; + } + + bool hasSideEffects() => getChangesFlags() != 0; + bool dependsOnSomething() => getDependsOnFlags() != 0; + + void setAllSideEffects() { flags |= ((1 << FLAG_CHANGES_COUNT) - 1); } + void clearAllSideEffects() { flags &= ~((1 << FLAG_CHANGES_COUNT) - 1); } + + void setDependsOnSomething() { + int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT; + flags |= (((1 << count) - 1) << FLAG_CHANGES_COUNT); + } + void clearAllDependencies() { + int count = FLAG_DEPENDS_ON_COUNT - FLAG_CHANGES_COUNT; + flags &= ~(((1 << count) - 1) << FLAG_CHANGES_COUNT); + } + + bool dependsOnStaticPropertyStore() { + return getFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE); + } + void setDependsOnStaticPropertyStore() { + setFlag(FLAG_DEPENDS_ON_STATIC_PROPERTY_STORE); + } + void setChangesStaticProperty() { setFlag(FLAG_CHANGES_STATIC_PROPERTY); } + + bool dependsOnIndexStore() => getFlag(FLAG_DEPENDS_ON_INDEX_STORE); + void setDependsOnIndexStore() { setFlag(FLAG_DEPENDS_ON_INDEX_STORE); } + void setChangesIndex() { setFlag(FLAG_CHANGES_INDEX); } + + bool dependsOnInstancePropertyStore() { + return getFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE); + } + void setDependsOnInstancePropertyStore() { + setFlag(FLAG_DEPENDS_ON_INSTANCE_PROPERTY_STORE); + } + void setChangesInstanceProperty() { setFlag(FLAG_CHANGES_INSTANCE_PROPERTY); } + + bool useGvn() => getFlag(FLAG_USE_GVN); + void setUseGvn() { setFlag(FLAG_USE_GVN); } + + void updateInput(int i, HInstruction insn) { + inputs[i] = insn; + } + + /** + * A pure instruction is an instruction that does not have any side + * effect, nor any dependency. They can be moved anywhere in the + * graph. + */ + bool isPure() => !hasSideEffects() && !dependsOnSomething() && !canThrow(); + + // Can this node throw an exception? + bool canThrow() => false; + + // Does this node potentially affect control flow. + bool isControlFlow() => false; + + // All isFunctions work on the propagated types. + bool isArray(HTypeMap types) => types[this].isArray(); + bool isReadableArray(HTypeMap types) => types[this].isReadableArray(); + bool isMutableArray(HTypeMap types) => types[this].isMutableArray(); + bool isExtendableArray(HTypeMap types) => types[this].isExtendableArray(); + bool isFixedArray(HTypeMap types) => types[this].isFixedArray(); + bool isBoolean(HTypeMap types) => types[this].isBoolean(); + bool isInteger(HTypeMap types) => types[this].isInteger(); + bool isDouble(HTypeMap types) => types[this].isDouble(); + bool isNumber(HTypeMap types) => types[this].isNumber(); + bool isNumberOrNull(HTypeMap types) => types[this].isNumberOrNull(); + bool isString(HTypeMap types) => types[this].isString(); + bool isTypeUnknown(HTypeMap types) => types[this].isUnknown(); + bool isIndexablePrimitive(HTypeMap types) + => types[this].isIndexablePrimitive(); + bool isPrimitive(HTypeMap types) => types[this].isPrimitive(); + bool canBePrimitive(HTypeMap types) => types[this].canBePrimitive(); + bool canBeNull(HTypeMap types) => types[this].canBeNull(); + + /** + * This is the type the instruction is guaranteed to have. It does not + * take any propagation into account. + */ + HType guaranteedType = HType.UNKNOWN; + bool hasGuaranteedType() => !guaranteedType.isUnknown(); + + /** + * Some instructions have a good idea of their return type, but cannot + * guarantee the type. The computed does not need to be more specialized + * than the provided type for [this]. + * + * Examples: the likely type of [:x == y:] is a boolean. In most cases this + * cannot be guaranteed, but when merging types we still want to use this + * information. + * + * Similarily the [HAdd] instruction is likely a number. Note that, even if + * the incoming type is already set to integer, the likely type might still + * just return the number type. + */ + HType computeLikelyType(HTypeMap types, Compiler compiler) => types[this]; + + /** + * Compute the type of the instruction by propagating the input types through + * the instruction. + * + * By default just copy the guaranteed type. + */ + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return guaranteedType; + } + + /** + * Compute the desired type for the the given [input]. Aside from using + * other inputs to compute the desired type one should also use + * the given [types] which, during the invocation of this method, + * represents the desired type of [this]. + */ + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.UNKNOWN; + } + + bool isInBasicBlock() => block != null; + + String inputsToString() { + void addAsCommaSeparated(StringBuffer buffer, List<HInstruction> list) { + for (int i = 0; i < list.length; i++) { + if (i != 0) buffer.add(', '); + buffer.add("@${list[i].id}"); + } + } + + StringBuffer buffer = new StringBuffer(); + buffer.add('('); + addAsCommaSeparated(buffer, inputs); + buffer.add(') - used at ['); + addAsCommaSeparated(buffer, usedBy); + buffer.add(']'); + return buffer.toString(); + } + + bool gvnEquals(HInstruction other) { + assert(useGvn() && other.useGvn()); + // Check that the type and the flags match. + bool hasSameType = typeEquals(other); + assert(hasSameType == (typeCode() == other.typeCode())); + if (!hasSameType) return false; + if (flags != other.flags) return false; + // Check that the inputs match. + final int inputsLength = inputs.length; + final List<HInstruction> otherInputs = other.inputs; + if (inputsLength != otherInputs.length) return false; + for (int i = 0; i < inputsLength; i++) { + if (!identical(inputs[i], otherInputs[i])) return false; + } + // Check that the data in the instruction matches. + return dataEquals(other); + } + + int gvnHashCode() { + int result = typeCode(); + int length = inputs.length; + for (int i = 0; i < length; i++) { + result = (result * 19) + (inputs[i].id) + (result >> 7); + } + return result; + } + + // These methods should be overwritten by instructions that + // participate in global value numbering. + int typeCode() => HInstruction.UNDEFINED_TYPECODE; + bool typeEquals(HInstruction other) => false; + bool dataEquals(HInstruction other) => false; + + accept(HVisitor visitor); + + void notifyAddedToBlock(HBasicBlock targetBlock) { + assert(!isInBasicBlock()); + assert(block == null); + // Add [this] to the inputs' uses. + for (int i = 0; i < inputs.length; i++) { + assert(inputs[i].isInBasicBlock()); + inputs[i].usedBy.add(this); + } + block = targetBlock; + assert(isValid()); + } + + void notifyRemovedFromBlock() { + assert(isInBasicBlock()); + assert(usedBy.isEmpty); + + // Remove [this] from the inputs' uses. + for (int i = 0; i < inputs.length; i++) { + inputs[i].removeUser(this); + } + this.block = null; + assert(isValid()); + } + + void rewriteInput(HInstruction from, HInstruction to) { + for (int i = 0; i < inputs.length; i++) { + if (identical(inputs[i], from)) inputs[i] = to; + } + } + + /** Removes all occurrences of [user] from [usedBy]. */ + void removeUser(HInstruction user) { + List<HInstruction> users = usedBy; + int length = users.length; + for (int i = 0; i < length; i++) { + if (identical(users[i], user)) { + users[i] = users[length - 1]; + length--; + } + } + users.length = length; + } + + // Change all uses of [oldInput] by [this] to [newInput]. Also + // updates the [usedBy] of [oldInput] and [newInput]. + void changeUse(HInstruction oldInput, HInstruction newInput) { + for (int i = 0; i < inputs.length; i++) { + if (identical(inputs[i], oldInput)) { + inputs[i] = newInput; + newInput.usedBy.add(this); + } + } + List<HInstruction> oldInputUsers = oldInput.usedBy; + int i = 0; + while (i < oldInputUsers.length) { + if (oldInputUsers[i] == this) { + oldInputUsers[i] = oldInputUsers[oldInput.usedBy.length - 1]; + oldInputUsers.length--; + } else { + i++; + } + } + } + + // Compute the set of users of this instruction that is dominated by + // [other]. If [other] is a user of [this], it is included in the + // returned set. + Set<HInstruction> dominatedUsers(HInstruction other) { + // Keep track of all instructions that we have to deal with later + // and count the number of them that are in the current block. + Set<HInstruction> users = new Set<HInstruction>(); + int usersInCurrentBlock = 0; + + // Run through all the users and see if they are dominated or + // potentially dominated by [other]. + HBasicBlock otherBlock = other.block; + for (int i = 0, length = usedBy.length; i < length; i++) { + HInstruction current = usedBy[i]; + if (otherBlock.dominates(current.block)) { + if (identical(current.block, otherBlock)) usersInCurrentBlock++; + users.add(current); + } + } + + // Run through all the phis in the same block as [other] and remove them + // from the users set. + if (usersInCurrentBlock > 0) { + for (HPhi phi = otherBlock.phis.first; phi != null; phi = phi.next) { + if (users.contains(phi)) { + users.remove(phi); + if (--usersInCurrentBlock == 0) break; + } + } + } + + // Run through all the instructions before [other] and remove them + // from the users set. + if (usersInCurrentBlock > 0) { + HInstruction current = otherBlock.first; + while (!identical(current, other)) { + if (users.contains(current)) { + users.remove(current); + if (--usersInCurrentBlock == 0) break; + } + current = current.next; + } + } + + return users; + } + + void moveBefore(HInstruction other) { + assert(this is !HControlFlow); + assert(this is !HPhi); + assert(other is !HPhi); + block.detach(this); + other.block.internalAddBefore(other, this); + block = other.block; + } + + bool isConstant() => false; + bool isConstantBoolean() => false; + bool isConstantNull() => false; + bool isConstantNumber() => false; + bool isConstantInteger() => false; + bool isConstantString() => false; + bool isConstantList() => false; + bool isConstantMap() => false; + bool isConstantFalse() => false; + bool isConstantTrue() => false; + bool isConstantSentinel() => false; + + bool isValid() { + HValidator validator = new HValidator(); + validator.currentBlock = block; + validator.visitInstruction(this); + return validator.isValid; + } + + /** + * The code for computing a bailout environment, and the code + * generation must agree on what does not need to be captured, + * so should always be generated at use site. + */ + bool isCodeMotionInvariant() => false; + + bool isJsStatement() => false; + + bool dominates(HInstruction other) { + // An instruction does not dominates itself. + if (this == other) return false; + if (block != other.block) return block.dominates(other.block); + + HInstruction current = this.next; + while (current != null) { + if (current == other) return true; + current = current.next; + } + return false; + } + + + HInstruction convertType(Compiler compiler, DartType type, int kind) { + if (type == null) return this; + if (identical(type.element, compiler.dynamicClass)) return this; + if (identical(type.element, compiler.objectClass)) return this; + + // If the original can't be null, type conversion also can't produce null. + bool canBeNull = this.guaranteedType.canBeNull(); + HType convertedType = + new HType.fromBoundedType(type, compiler, canBeNull); + + // No need to convert if we know the instruction has + // [convertedType] as a bound. + if (this.guaranteedType == convertedType) { + return this; + } + + return new HTypeConversion(convertedType, this, kind); + } + + /** + * Return whether the instructions do not belong to a loop or + * belong to the same loop. + */ + bool hasSameLoopHeaderAs(HInstruction other) { + return block.enclosingLoopHeader == other.block.enclosingLoopHeader; + } +} + +class HBoolify extends HInstruction { + HBoolify(HInstruction value) : super(<HInstruction>[value]) { + assert(!hasSideEffects()); + setUseGvn(); + } + + HType get guaranteedType => HType.BOOLEAN; + + accept(HVisitor visitor) => visitor.visitBoolify(this); + int typeCode() => HInstruction.BOOLIFY_TYPECODE; + bool typeEquals(other) => other is HBoolify; + bool dataEquals(HInstruction other) => true; +} + +/** + * A [HCheck] instruction is an instruction that might do a dynamic + * check at runtime on another instruction. To have proper instruction + * dependencies in the graph, instructions that depend on the check + * being done reference the [HCheck] instruction instead of the + * instruction itself. + */ +abstract class HCheck extends HInstruction { + HCheck(inputs) : super(inputs) { + assert(!hasSideEffects()); + setUseGvn(); + } + HInstruction get checkedInput => inputs[0]; + bool isJsStatement() => true; + bool canThrow() => true; +} + +class HBailoutTarget extends HInstruction { + final int state; + bool isEnabled = true; + // For each argument we record how many dummy (unused) arguments should + // precede it, to make sure it lands in the correctly named parameter in the + // bailout function. + List<int> padding; + HBailoutTarget(this.state) : super(<HInstruction>[]) { + assert(!hasSideEffects()); + setUseGvn(); + } + + bool isControlFlow() => isEnabled; + bool isJsStatement() => isEnabled; + + accept(HVisitor visitor) => visitor.visitBailoutTarget(this); + int typeCode() => HInstruction.BAILOUT_TARGET_TYPECODE; + bool typeEquals(other) => other is HBailoutTarget; + bool dataEquals(HBailoutTarget other) => other.state == state; +} + +class HTypeGuard extends HCheck { + final HType guardedType; + bool isEnabled = false; + + HTypeGuard(this.guardedType, HInstruction guarded, HInstruction bailoutTarget) + : super(<HInstruction>[guarded, bailoutTarget]); + + HInstruction get guarded => inputs[0]; + HInstruction get checkedInput => guarded; + HBailoutTarget get bailoutTarget => inputs[1]; + int get state => bailoutTarget.state; + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return isEnabled ? guardedType : types[guarded]; + } + + HType get guaranteedType => isEnabled ? guardedType : HType.UNKNOWN; + + bool isControlFlow() => true; + bool isJsStatement() => isEnabled; + bool canThrow() => isEnabled; + + accept(HVisitor visitor) => visitor.visitTypeGuard(this); + int typeCode() => HInstruction.TYPE_GUARD_TYPECODE; + bool typeEquals(other) => other is HTypeGuard; + bool dataEquals(HTypeGuard other) => guardedType == other.guardedType; +} + +class HBoundsCheck extends HCheck { + static const int ALWAYS_FALSE = 0; + static const int FULL_CHECK = 1; + static const int ALWAYS_ABOVE_ZERO = 2; + static const int ALWAYS_BELOW_LENGTH = 3; + static const int ALWAYS_TRUE = 4; + /** + * Details which tests have been done statically during compilation. + * Default is that all checks must be performed dynamically. + */ + int staticChecks = FULL_CHECK; + + HBoundsCheck(length, index) : super(<HInstruction>[length, index]); + + HInstruction get length => inputs[1]; + HInstruction get index => inputs[0]; + bool isControlFlow() => true; + + HType get guaranteedType => HType.INTEGER; + + accept(HVisitor visitor) => visitor.visitBoundsCheck(this); + int typeCode() => HInstruction.BOUNDS_CHECK_TYPECODE; + bool typeEquals(other) => other is HBoundsCheck; + bool dataEquals(HInstruction other) => true; +} + +class HIntegerCheck extends HCheck { + bool alwaysFalse = false; + + HIntegerCheck(value) : super(<HInstruction>[value]); + + HInstruction get value => inputs[0]; + bool isControlFlow() => true; + + HType get guaranteedType => HType.INTEGER; + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + // If the desired type of the input is already a number, we want + // to specialize it to an integer. + return input.isNumber(types) + ? HType.INTEGER + : super.computeDesiredTypeForInput(input, types, compiler); + } + + accept(HVisitor visitor) => visitor.visitIntegerCheck(this); + int typeCode() => HInstruction.INTEGER_CHECK_TYPECODE; + bool typeEquals(other) => other is HIntegerCheck; + bool dataEquals(HInstruction other) => true; +} + +abstract class HConditionalBranch extends HControlFlow { + HConditionalBranch(inputs) : super(inputs); + HInstruction get condition => inputs[0]; + HBasicBlock get trueBranch => block.successors[0]; + HBasicBlock get falseBranch => block.successors[1]; +} + +abstract class HControlFlow extends HInstruction { + HControlFlow(inputs) : super(inputs); + bool isControlFlow() => true; + bool isJsStatement() => true; +} + +abstract class HInvoke extends HInstruction { + /** + * The first argument must be the target: either an [HStatic] node, or + * the receiver of a method-call. The remaining inputs are the arguments + * to the invocation. + */ + HInvoke(List<HInstruction> inputs) : super(inputs) { + setAllSideEffects(); + setDependsOnSomething(); + } + static const int ARGUMENTS_OFFSET = 1; + bool canThrow() => true; +} + +abstract class HInvokeDynamic extends HInvoke { + final InvokeDynamicSpecializer specializer; + final Selector selector; + Element element; + + HInvokeDynamic(Selector selector, + this.element, + List<HInstruction> inputs, + [bool isIntercepted = false]) + : super(inputs), + this.selector = selector, + specializer = isIntercepted + ? InvokeDynamicSpecializer.lookupSpecializer(selector) + : const InvokeDynamicSpecializer(); + toString() => 'invoke dynamic: $selector'; + HInstruction get receiver => inputs[0]; + + bool get isInterceptorCall { + // We know it's a selector call if it follows the interceptor + // calling convention, which adds the actual receiver as a + // parameter to the call. + return inputs.length - 2 == selector.argumentCount; + } + + int typeCode() => HInstruction.INVOKE_DYNAMIC_TYPECODE; + bool typeEquals(other) => other is HInvokeDynamic; + bool dataEquals(HInvokeDynamic other) { + return selector == other.selector + && element == other.element; + } + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return specializer.computeDesiredTypeForInput(this, input, types, compiler); + } + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return specializer.computeTypeFromInputTypes(this, types, compiler); + } +} + +class HInvokeClosure extends HInvokeDynamic { + HInvokeClosure(Selector selector, List<HInstruction> inputs) + : super(selector, null, inputs) { + assert(selector.isClosureCall()); + } + accept(HVisitor visitor) => visitor.visitInvokeClosure(this); +} + +class HInvokeDynamicMethod extends HInvokeDynamic { + HInvokeDynamicMethod(Selector selector, + List<HInstruction> inputs, + [bool isIntercepted = false]) + : super(selector, null, inputs, isIntercepted); + + String toString() => 'invoke dynamic method: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicMethod(this); + + bool isIndexOperatorOnIndexablePrimitive(HTypeMap types) { + return isInterceptorCall + && selector.kind == SelectorKind.INDEX + && selector.name == const SourceString('[]') + && inputs[1].isIndexablePrimitive(types); + } +} + +abstract class HInvokeDynamicField extends HInvokeDynamic { + final bool isSideEffectFree; + HInvokeDynamicField( + Selector selector, Element element, List<HInstruction> inputs, + this.isSideEffectFree) + : super(selector, element, inputs); + toString() => 'invoke dynamic field: $selector'; +} + +class HInvokeDynamicGetter extends HInvokeDynamicField { + HInvokeDynamicGetter(selector, element, receiver, isSideEffectFree) + : super(selector, element, [receiver], isSideEffectFree) { + clearAllSideEffects(); + if (isSideEffectFree) { + setUseGvn(); + setDependsOnInstancePropertyStore(); + } else { + setDependsOnSomething(); + setAllSideEffects(); + } + } + toString() => 'invoke dynamic getter: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicGetter(this); +} + +class HInvokeDynamicSetter extends HInvokeDynamicField { + HInvokeDynamicSetter(selector, element, receiver, value, isSideEffectFree) + : super(selector, element, [receiver, value], isSideEffectFree) { + clearAllSideEffects(); + if (isSideEffectFree) { + setChangesInstanceProperty(); + } else { + setAllSideEffects(); + setDependsOnSomething(); + } + } + toString() => 'invoke dynamic setter: $selector'; + accept(HVisitor visitor) => visitor.visitInvokeDynamicSetter(this); +} + +class HInvokeStatic extends HInvoke { + /** The first input must be the target. */ + HInvokeStatic(inputs, HType type) : super(inputs) { + guaranteedType = type; + } + + toString() => 'invoke static: ${element.name}'; + accept(HVisitor visitor) => visitor.visitInvokeStatic(this); + int typeCode() => HInstruction.INVOKE_STATIC_TYPECODE; + Element get element => target.element; + HStatic get target => inputs[0]; +} + +class HInvokeSuper extends HInvokeStatic { + final bool isSetter; + HInvokeSuper(inputs, {this.isSetter: false}) : super(inputs, HType.UNKNOWN); + toString() => 'invoke super: ${element.name}'; + accept(HVisitor visitor) => visitor.visitInvokeSuper(this); + + HInstruction get value { + assert(isSetter); + // Index 0: the element, index 1: 'this'. + return inputs[2]; + } +} + +abstract class HFieldAccess extends HInstruction { + final Element element; + + HFieldAccess(Element element, List<HInstruction> inputs) + : this.element = element, super(inputs); + + HInstruction get receiver => inputs[0]; +} + +class HFieldGet extends HFieldAccess { + final bool isAssignable; + + HFieldGet(Element element, HInstruction receiver, {bool isAssignable}) + : this.isAssignable = (isAssignable != null) + ? isAssignable + : element.isAssignable(), + super(element, <HInstruction>[receiver]) { + clearAllSideEffects(); + setUseGvn(); + if (this.isAssignable) { + setDependsOnInstancePropertyStore(); + } + } + + // TODO(ngeoffray): Only if input can be null. + bool canThrow() => true; + + accept(HVisitor visitor) => visitor.visitFieldGet(this); + + int typeCode() => HInstruction.FIELD_GET_TYPECODE; + bool typeEquals(other) => other is HFieldGet; + bool dataEquals(HFieldGet other) => element == other.element; + String toString() => "FieldGet $element"; +} + +class HFieldSet extends HFieldAccess { + HFieldSet(Element element, + HInstruction receiver, + HInstruction value) + : super(element, <HInstruction>[receiver, value]) { + clearAllSideEffects(); + setChangesInstanceProperty(); + } + + // TODO(ngeoffray): Only if input can be null. + bool canThrow() => true; + + HInstruction get value => inputs[1]; + accept(HVisitor visitor) => visitor.visitFieldSet(this); + + bool isJsStatement() => true; + String toString() => "FieldSet $element"; +} + +class HLocalGet extends HFieldAccess { + // No need to use GVN for a [HLocalGet], it is just a local + // access. + HLocalGet(Element element, HLocalValue local) + : super(element, <HInstruction>[local]); + + accept(HVisitor visitor) => visitor.visitLocalGet(this); + + HLocalValue get local => inputs[0]; +} + +class HLocalSet extends HFieldAccess { + HLocalSet(Element element, HLocalValue local, HInstruction value) + : super(element, <HInstruction>[local, value]); + + accept(HVisitor visitor) => visitor.visitLocalSet(this); + + HLocalValue get local => inputs[0]; + HInstruction get value => inputs[1]; + bool isJsStatement() => true; +} + +class HForeign extends HInstruction { + final DartString code; + final HType type; + final bool isStatement; + + HForeign(this.code, + this.type, + List<HInstruction> inputs, + {this.isStatement: false}) + : super(inputs) { + setAllSideEffects(); + setDependsOnSomething(); + } + + HForeign.statement(code, List<HInstruction> inputs) + : this(code, HType.UNKNOWN, inputs, isStatement: true); + + accept(HVisitor visitor) => visitor.visitForeign(this); + + HType get guaranteedType => type; + + bool isJsStatement() => isStatement; + bool canThrow() => true; +} + +class HForeignNew extends HForeign { + ClassElement element; + HForeignNew(this.element, HType type, List<HInstruction> inputs) + : super(const LiteralDartString("new"), type, inputs); + accept(HVisitor visitor) => visitor.visitForeignNew(this); +} + +abstract class HInvokeBinary extends HInstruction { + HInvokeBinary(HInstruction left, HInstruction right) + : super(<HInstruction>[left, right]) { + clearAllSideEffects(); + setUseGvn(); + } + + HInstruction get left => inputs[0]; + HInstruction get right => inputs[1]; + + BinaryOperation operation(ConstantSystem constantSystem); +} + +abstract class HBinaryArithmetic extends HInvokeBinary { + HBinaryArithmetic(HInstruction left, HInstruction right) : super(left, right); + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + if (left.isInteger(types) && right.isInteger(types)) return HType.INTEGER; + if (left.isDouble(types)) return HType.DOUBLE; + return HType.NUMBER; + } + + BinaryOperation operation(ConstantSystem constantSystem); +} + +class HAdd extends HBinaryArithmetic { + HAdd(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitAdd(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.add; + int typeCode() => HInstruction.ADD_TYPECODE; + bool typeEquals(other) => other is HAdd; + bool dataEquals(HInstruction other) => true; +} + +class HDivide extends HBinaryArithmetic { + HDivide(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitDivide(this); + + HType get guaranteedType => HType.DOUBLE; + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.divide; + int typeCode() => HInstruction.DIVIDE_TYPECODE; + bool typeEquals(other) => other is HDivide; + bool dataEquals(HInstruction other) => true; +} + +class HMultiply extends HBinaryArithmetic { + HMultiply(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitMultiply(this); + + BinaryOperation operation(ConstantSystem operations) + => operations.multiply; + int typeCode() => HInstruction.MULTIPLY_TYPECODE; + bool typeEquals(other) => other is HMultiply; + bool dataEquals(HInstruction other) => true; +} + +class HSubtract extends HBinaryArithmetic { + HSubtract(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitSubtract(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.subtract; + int typeCode() => HInstruction.SUBTRACT_TYPECODE; + bool typeEquals(other) => other is HSubtract; + bool dataEquals(HInstruction other) => true; +} + +/** + * An [HSwitch] instruction has one input for the incoming + * value, and one input per constant that it can switch on. + * Its block has one successor per constant, and one for the default. + */ +class HSwitch extends HControlFlow { + HSwitch(List<HInstruction> inputs) : super(inputs); + + HConstant constant(int index) => inputs[index + 1]; + HInstruction get expression => inputs[0]; + + /** + * Provides the target to jump to if none of the constants match + * the expression. If the switch had no default case, this is the + * following join-block. + */ + HBasicBlock get defaultTarget => block.successors.last; + + accept(HVisitor visitor) => visitor.visitSwitch(this); + + String toString() => "HSwitch cases = $inputs"; +} + +// TODO(floitsch): Should HBinaryArithmetic really be the super class of +// HBinaryBitOp? +abstract class HBinaryBitOp extends HBinaryArithmetic { + HBinaryBitOp(HInstruction left, HInstruction right) : super(left, right); + HType get guaranteedType => HType.INTEGER; +} + +class HShiftLeft extends HBinaryBitOp { + HShiftLeft(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitShiftLeft(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.shiftLeft; + int typeCode() => HInstruction.SHIFT_LEFT_TYPECODE; + bool typeEquals(other) => other is HShiftLeft; + bool dataEquals(HInstruction other) => true; +} + +class HBitOr extends HBinaryBitOp { + HBitOr(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitOr(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitOr; + int typeCode() => HInstruction.BIT_OR_TYPECODE; + bool typeEquals(other) => other is HBitOr; + bool dataEquals(HInstruction other) => true; +} + +class HBitAnd extends HBinaryBitOp { + HBitAnd(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitAnd(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitAnd; + int typeCode() => HInstruction.BIT_AND_TYPECODE; + bool typeEquals(other) => other is HBitAnd; + bool dataEquals(HInstruction other) => true; +} + +class HBitXor extends HBinaryBitOp { + HBitXor(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitBitXor(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitXor; + int typeCode() => HInstruction.BIT_XOR_TYPECODE; + bool typeEquals(other) => other is HBitXor; + bool dataEquals(HInstruction other) => true; +} + +abstract class HInvokeUnary extends HInstruction { + HInvokeUnary(HInstruction input) : super(<HInstruction>[input]) { + clearAllSideEffects(); + setUseGvn(); + } + + HInstruction get operand => inputs[0]; + + UnaryOperation operation(ConstantSystem constantSystem); +} + +class HNegate extends HInvokeUnary { + HNegate(HInstruction input) : super(input); + accept(HVisitor visitor) => visitor.visitNegate(this); + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + return types[operand]; + } + + UnaryOperation operation(ConstantSystem constantSystem) + => constantSystem.negate; + int typeCode() => HInstruction.NEGATE_TYPECODE; + bool typeEquals(other) => other is HNegate; + bool dataEquals(HInstruction other) => true; +} + +class HBitNot extends HInvokeUnary { + HBitNot(HInstruction input) : super(input); + accept(HVisitor visitor) => visitor.visitBitNot(this); + + HType get guaranteedType => HType.INTEGER; + UnaryOperation operation(ConstantSystem constantSystem) + => constantSystem.bitNot; + int typeCode() => HInstruction.BIT_NOT_TYPECODE; + bool typeEquals(other) => other is HBitNot; + bool dataEquals(HInstruction other) => true; +} + +class HExit extends HControlFlow { + HExit() : super(const <HInstruction>[]); + toString() => 'exit'; + accept(HVisitor visitor) => visitor.visitExit(this); +} + +class HGoto extends HControlFlow { + HGoto() : super(const <HInstruction>[]); + toString() => 'goto'; + accept(HVisitor visitor) => visitor.visitGoto(this); +} + +abstract class HJump extends HControlFlow { + final TargetElement target; + final LabelElement label; + HJump(this.target) : label = null, super(const <HInstruction>[]); + HJump.toLabel(LabelElement label) + : label = label, target = label.target, super(const <HInstruction>[]); +} + +class HBreak extends HJump { + HBreak(TargetElement target) : super(target); + HBreak.toLabel(LabelElement label) : super.toLabel(label); + toString() => (label != null) ? 'break ${label.labelName}' : 'break'; + accept(HVisitor visitor) => visitor.visitBreak(this); +} + +class HContinue extends HJump { + HContinue(TargetElement target) : super(target); + HContinue.toLabel(LabelElement label) : super.toLabel(label); + toString() => (label != null) ? 'continue ${label.labelName}' : 'continue'; + accept(HVisitor visitor) => visitor.visitContinue(this); +} + +class HTry extends HControlFlow { + HLocalValue exception; + HBasicBlock catchBlock; + HBasicBlock finallyBlock; + HTry() : super(const <HInstruction>[]); + toString() => 'try'; + accept(HVisitor visitor) => visitor.visitTry(this); + HBasicBlock get joinBlock => this.block.successors.last; +} + +// An [HExitTry] control flow node is used when the body of a try or +// the body of a catch contains a return, break or continue. To build +// the control flow graph, we explicitly mark the body that +// leads to one of this instruction a predecessor of catch and +// finally. +class HExitTry extends HControlFlow { + HExitTry() : super(const <HInstruction>[]); + toString() => 'exit try'; + accept(HVisitor visitor) => visitor.visitExitTry(this); + HBasicBlock get bodyTrySuccessor => block.successors[0]; +} + +class HIf extends HConditionalBranch { + HBlockFlow blockInformation = null; + HIf(HInstruction condition) : super(<HInstruction>[condition]); + toString() => 'if'; + accept(HVisitor visitor) => visitor.visitIf(this); + + HBasicBlock get thenBlock { + assert(identical(block.dominatedBlocks[0], block.successors[0])); + return block.successors[0]; + } + + HBasicBlock get elseBlock { + assert(identical(block.dominatedBlocks[1], block.successors[1])); + return block.successors[1]; + } + + HBasicBlock get joinBlock => blockInformation.continuation; +} + +class HLoopBranch extends HConditionalBranch { + static const int CONDITION_FIRST_LOOP = 0; + static const int DO_WHILE_LOOP = 1; + + final int kind; + HLoopBranch(HInstruction condition, [this.kind = CONDITION_FIRST_LOOP]) + : super(<HInstruction>[condition]); + toString() => 'loop-branch'; + accept(HVisitor visitor) => visitor.visitLoopBranch(this); + + bool isDoWhile() { + return identical(kind, DO_WHILE_LOOP); + } + + HBasicBlock computeLoopHeader() { + HBasicBlock result; + if (isDoWhile()) { + // In case of a do/while, the successor is a block that avoids + // a critical edge and branchs to the loop header. + result = block.successors[0].successors[0]; + } else { + // For other loops, the loop header might be up the dominator + // tree if the loop condition has control flow. + result = block; + while (!result.isLoopHeader()) result = result.dominator; + } + + assert(result.isLoopHeader()); + return result; + } +} + +class HConstant extends HInstruction { + final Constant constant; + final HType constantType; + HConstant.internal(this.constant, HType this.constantType) + : super(<HInstruction>[]); + + toString() => 'literal: $constant'; + accept(HVisitor visitor) => visitor.visitConstant(this); + + HType get guaranteedType => constantType; + + bool isConstant() => true; + bool isConstantBoolean() => constant.isBool(); + bool isConstantNull() => constant.isNull(); + bool isConstantNumber() => constant.isNum(); + bool isConstantInteger() => constant.isInt(); + bool isConstantString() => constant.isString(); + bool isConstantList() => constant.isList(); + bool isConstantMap() => constant.isMap(); + bool isConstantFalse() => constant.isFalse(); + bool isConstantTrue() => constant.isTrue(); + bool isConstantSentinel() => constant.isSentinel(); + + // Maybe avoid this if the literal is big? + bool isCodeMotionInvariant() => true; +} + +class HNot extends HInstruction { + HNot(HInstruction value) : super(<HInstruction>[value]) { + setUseGvn(); + } + + HType get guaranteedType => HType.BOOLEAN; + + // 'Not' only works on booleans. That's what we want as input. + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + return HType.BOOLEAN; + } + + accept(HVisitor visitor) => visitor.visitNot(this); + int typeCode() => HInstruction.NOT_TYPECODE; + bool typeEquals(other) => other is HNot; + bool dataEquals(HInstruction other) => true; +} + +/** + * An [HLocalValue] represents a local. Unlike [HParameterValue]s its + * first use must be in an HLocalSet. That is, [HParameterValue]s have a + * value from the start, whereas [HLocalValue]s need to be initialized first. + */ +class HLocalValue extends HInstruction { + HLocalValue(Element element) : super(<HInstruction>[]) { + sourceElement = element; + } + + toString() => 'local ${sourceElement.name}'; + accept(HVisitor visitor) => visitor.visitLocalValue(this); +} + +class HParameterValue extends HLocalValue { + HParameterValue(Element element) : super(element); + + toString() => 'parameter ${sourceElement.name.slowToString()}'; + accept(HVisitor visitor) => visitor.visitParameterValue(this); +} + +class HThis extends HParameterValue { + HThis(Element element, [HType type = HType.UNKNOWN]) : super(element) { + guaranteedType = type; + } + toString() => 'this'; + accept(HVisitor visitor) => visitor.visitThis(this); + bool isCodeMotionInvariant() => true; +} + +class HPhi extends HInstruction { + static const IS_NOT_LOGICAL_OPERATOR = 0; + static const IS_AND = 1; + static const IS_OR = 2; + + int logicalOperatorType = IS_NOT_LOGICAL_OPERATOR; + + // The order of the [inputs] must correspond to the order of the + // predecessor-edges. That is if an input comes from the first predecessor + // of the surrounding block, then the input must be the first in the [HPhi]. + HPhi(Element element, List<HInstruction> inputs) : super(inputs) { + sourceElement = element; + } + HPhi.noInputs(Element element) : this(element, <HInstruction>[]); + HPhi.singleInput(Element element, HInstruction input) + : this(element, <HInstruction>[input]); + HPhi.manyInputs(Element element, List<HInstruction> inputs) + : this(element, inputs); + + void addInput(HInstruction input) { + assert(isInBasicBlock()); + inputs.add(input); + input.usedBy.add(this); + } + + // Compute the (shared) type of the inputs if any. If all inputs + // have the same known type return it. If any two inputs have + // different known types, we'll return a conflict -- otherwise we'll + // simply return an unknown type. + HType computeInputsType(bool ignoreUnknowns, + HTypeMap types, + Compiler compiler) { + HType candidateType = HType.CONFLICTING; + for (int i = 0, length = inputs.length; i < length; i++) { + HType inputType = types[inputs[i]]; + if (ignoreUnknowns && inputType.isUnknown()) continue; + // Phis need to combine the incoming types using the union operation. + // For example, if one incoming edge has type integer and the other has + // type double, then the phi is either an integer or double and thus has + // type number. + candidateType = candidateType.union(inputType, compiler); + if (candidateType.isUnknown()) return HType.UNKNOWN; + } + return candidateType; + } + + HType computeTypeFromInputTypes(HTypeMap types, Compiler compiler) { + HType inputsType = computeInputsType(false, types, compiler); + if (inputsType.isConflicting()) return HType.UNKNOWN; + return inputsType; + } + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + HType propagatedType = types[this]; + // Best case scenario for a phi is, when all inputs have the same type. If + // there is no desired outgoing type we therefore try to unify the input + // types (which is basically the [likelyType]). + if (propagatedType.isUnknown()) return computeLikelyType(types, compiler); + // When the desired outgoing type is conflicting we don't need to give any + // requirements on the inputs. + if (propagatedType.isConflicting()) return HType.UNKNOWN; + // Otherwise the input type must match the desired outgoing type. + return propagatedType; + } + + HType computeLikelyType(HTypeMap types, Compiler compiler) { + HType agreedType = computeInputsType(true, types, compiler); + if (agreedType.isConflicting()) return HType.UNKNOWN; + // Don't be too restrictive. If the agreed type is integer or double just + // say that the likely type is number. If more is expected the type will be + // propagated back. + if (agreedType.isNumber()) return HType.NUMBER; + return agreedType; + } + + bool isLogicalOperator() => logicalOperatorType != IS_NOT_LOGICAL_OPERATOR; + + String logicalOperator() { + assert(isLogicalOperator()); + if (logicalOperatorType == IS_AND) return "&&"; + assert(logicalOperatorType == IS_OR); + return "||"; + } + + toString() => 'phi'; + accept(HVisitor visitor) => visitor.visitPhi(this); +} + +abstract class HRelational extends HInvokeBinary { + bool usesBoolifiedInterceptor = false; + HRelational(HInstruction left, HInstruction right) : super(left, right); + HType get guaranteedType => HType.BOOLEAN; +} + +class HIdentity extends HRelational { + HIdentity(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitIdentity(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.identity; + int typeCode() => HInstruction.IDENTITY_TYPECODE; + bool typeEquals(other) => other is HIdentity; + bool dataEquals(HInstruction other) => true; +} + +class HGreater extends HRelational { + HGreater(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitGreater(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.greater; + int typeCode() => HInstruction.GREATER_TYPECODE; + bool typeEquals(other) => other is HGreater; + bool dataEquals(HInstruction other) => true; +} + +class HGreaterEqual extends HRelational { + HGreaterEqual(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitGreaterEqual(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.greaterEqual; + int typeCode() => HInstruction.GREATER_EQUAL_TYPECODE; + bool typeEquals(other) => other is HGreaterEqual; + bool dataEquals(HInstruction other) => true; +} + +class HLess extends HRelational { + HLess(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitLess(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.less; + int typeCode() => HInstruction.LESS_TYPECODE; + bool typeEquals(other) => other is HLess; + bool dataEquals(HInstruction other) => true; +} + +class HLessEqual extends HRelational { + HLessEqual(HInstruction left, HInstruction right) : super(left, right); + accept(HVisitor visitor) => visitor.visitLessEqual(this); + + BinaryOperation operation(ConstantSystem constantSystem) + => constantSystem.lessEqual; + int typeCode() => HInstruction.LESS_EQUAL_TYPECODE; + bool typeEquals(other) => other is HLessEqual; + bool dataEquals(HInstruction other) => true; +} + +class HReturn extends HControlFlow { + HReturn(value) : super(<HInstruction>[value]); + toString() => 'return'; + accept(HVisitor visitor) => visitor.visitReturn(this); +} + +class HThrow extends HControlFlow { + final bool isRethrow; + HThrow(value, {this.isRethrow: false}) : super(<HInstruction>[value]); + toString() => 'throw'; + accept(HVisitor visitor) => visitor.visitThrow(this); +} + +class HStatic extends HInstruction { + final Element element; + HStatic(this.element) : super(<HInstruction>[]) { + assert(element != null); + assert(invariant(this, element.isDeclaration)); + clearAllSideEffects(); + if (element.isAssignable()) { + setDependsOnStaticPropertyStore(); + } + setUseGvn(); + } + toString() => 'static ${element.name}'; + accept(HVisitor visitor) => visitor.visitStatic(this); + + int gvnHashCode() => super.gvnHashCode() ^ element.hashCode; + int typeCode() => HInstruction.STATIC_TYPECODE; + bool typeEquals(other) => other is HStatic; + bool dataEquals(HStatic other) => element == other.element; + bool isCodeMotionInvariant() => !element.isAssignable(); +} + +class HInterceptor extends HInstruction { + Set<ClassElement> interceptedClasses; + HInterceptor(this.interceptedClasses, HInstruction receiver) + : super(<HInstruction>[receiver]) { + clearAllSideEffects(); + setUseGvn(); + } + String toString() => 'interceptor on $interceptedClasses'; + accept(HVisitor visitor) => visitor.visitInterceptor(this); + HInstruction get receiver => inputs[0]; + + HType computeDesiredTypeForInput(HInstruction input, + HTypeMap types, + Compiler compiler) { + if (interceptedClasses.length != 1) return HType.UNKNOWN; + // If the only class being intercepted is of type number, we + // make this interceptor call say it wants that class as input. + Element interceptor = interceptedClasses.toList()[0]; + JavaScriptBackend backend = compiler.backend; + if (interceptor == backend.jsNumberClass) { + return HType.NUMBER; + } else if (interceptor == backend.jsIntClass) { + return HType.INTEGER; + } else if (interceptor == backend.jsDoubleClass) { + return HType.DOUBLE; + } + return HType.UNKNOWN; + } + + int typeCode() => HInstruction.INTERCEPTOR_TYPECODE; + bool typeEquals(other) => other is HInterceptor; + bool dataEquals(HInterceptor other) { + return interceptedClasses == other.interceptedClasses + || (interceptedClasses.length == other.interceptedClasses.length + && interceptedClasses.containsAll(other.interceptedClasses)); + } +} + +/** + * A "one-shot" interceptor is a call to a synthetized method that + * will fetch the interceptor of its first parameter, and make a call + * on a given selector with the remaining parameters. + * + * In order to share the same optimizations with regular interceptor + * calls, this class extends [HInvokeDynamic] and also has the null + * constant as the first input. + */ +class HOneShotInterceptor extends HInvokeDynamic { + Set<ClassElement> interceptedClasses; + HOneShotInterceptor(Selector selector, + List<HInstruction> inputs, + this.interceptedClasses) + : super(selector, null, inputs, true) { + assert(inputs[0] is HConstant); + assert(inputs[0].guaranteedType == HType.NULL); + } + + String toString() => 'one shot interceptor on $selector'; + accept(HVisitor visitor) => visitor.visitOneShotInterceptor(this); +} + +/** An [HLazyStatic] is a static that is initialized lazily at first read. */ +class HLazyStatic extends HInstruction { + final Element element; + HLazyStatic(this.element) : super(<HInstruction>[]) { + // TODO(4931): The first access has side-effects, but we afterwards we + // should be able to GVN. + setAllSideEffects(); + setDependsOnSomething(); + } + + toString() => 'lazy static ${element.name}'; + accept(HVisitor visitor) => visitor.visitLazyStatic(this); + + int typeCode() => 30; + // TODO(4931): can we do better here? + bool isCodeMotionInvariant() => false; + bool canThrow() => true; +} + +class HStaticStore extends HInstruction { + Element element; + HStaticStore(this.element, HInstruction value) + : super(<HInstruction>[value]) { + clearAllSideEffects(); + setChangesStaticProperty(); + } + toString() => 'static store ${element.name}'; + accept(HVisitor visitor) => visitor.visitStaticStore(this); + + int typeCode() => HInstruction.STATIC_STORE_TYPECODE; + bool typeEquals(other) => other is HStaticStore; + bool dataEquals(HStaticStore other) => element == other.element; + bool isJsStatement() => true; +} + +class HLiteralList extends HInstruction { + HLiteralList(inputs) : super(inputs); + toString() => 'literal list'; + accept(HVisitor visitor) => visitor.visitLiteralList(this); + + HType get guaranteedType => HType.EXTENDABLE_ARRAY; +} + +/** + * The primitive array indexing operation. Note that this instruction + * does not throw because we generate the checks explicitly. + */ +class HIndex extends HInstruction { + HIndex(HInstruction receiver, HInstruction index) + : super(<HInstruction>[receiver, index]) { + clearAllSideEffects(); + setDependsOnIndexStore(); + setUseGvn(); + } + + String toString() => 'index operator'; + accept(HVisitor visitor) => visitor.visitIndex(this); + + HInstruction get receiver => inputs[0]; + HInstruction get index => inputs[1]; + + int typeCode() => HInstruction.INDEX_TYPECODE; + bool typeEquals(HInstruction other) => other is HIndex; + bool dataEquals(HIndex other) => true; +} + +/** + * The primitive array assignment operation. Note that this instruction + * does not throw because we generate the checks explicitly. + */ +class HIndexAssign extends HInstruction { + HIndexAssign(HInstruction receiver, + HInstruction index, + HInstruction value) + : super(<HInstruction>[receiver, index, value]) { + clearAllSideEffects(); + setChangesIndex(); + } + String toString() => 'index assign operator'; + accept(HVisitor visitor) => visitor.visitIndexAssign(this); + + HInstruction get receiver => inputs[0]; + HInstruction get index => inputs[1]; + HInstruction get value => inputs[2]; +} + +class HIs extends HInstruction { + final DartType typeExpression; + final bool nullOk; + + HIs(this.typeExpression, List<HInstruction> inputs, {this.nullOk: false}) + : super(inputs) { + setUseGvn(); + } + + HInstruction get expression => inputs[0]; + HInstruction getCheck(int index) => inputs[index + 1]; + int get checkCount => inputs.length - 1; + + bool hasArgumentChecks() => inputs.length > 1; + + HType get guaranteedType => HType.BOOLEAN; + + accept(HVisitor visitor) => visitor.visitIs(this); + + toString() => "$expression is $typeExpression"; + + int typeCode() => HInstruction.IS_TYPECODE; + bool typeEquals(HInstruction other) => other is HIs; + bool dataEquals(HIs other) { + return typeExpression == other.typeExpression + && nullOk == other.nullOk; + } +} + +class HTypeConversion extends HCheck { + HType type; + final int kind; + + static const int NO_CHECK = 0; + static const int CHECKED_MODE_CHECK = 1; + static const int ARGUMENT_TYPE_CHECK = 2; + static const int CAST_TYPE_CHECK = 3; + static const int BOOLEAN_CONVERSION_CHECK = 4; + + HTypeConversion(this.type, HInstruction input, [this.kind = NO_CHECK]) + : super(<HInstruction>[input]) { + sourceElement = input.sourceElement; + } + HTypeConversion.checkedModeCheck(HType type, HInstruction input) + : this(type, input, CHECKED_MODE_CHECK); + HTypeConversion.argumentTypeCheck(HType type, HInstruction input) + : this(type, input, ARGUMENT_TYPE_CHECK); + HTypeConversion.castCheck(HType type, HInstruction input) + : this(type, input, CAST_TYPE_CHECK); + + + bool get isChecked => kind != NO_CHECK; + bool get isCheckedModeCheck { + return kind == CHECKED_MODE_CHECK || kind == BOOLEAN_CONVERSION_CHECK; + } + bool get isArgumentTypeCheck => kind == ARGUMENT_TYPE_CHECK; + bool get isCastTypeCheck => kind == CAST_TYPE_CHECK; + bool get isBooleanConversionCheck => kind == BOOLEAN_CONVERSION_CHECK; + + HType get guaranteedType => type; + + accept(HVisitor visitor) => visitor.visitTypeConversion(this); + + bool isJsStatement() => kind == ARGUMENT_TYPE_CHECK; + bool isControlFlow() => kind == ARGUMENT_TYPE_CHECK; + bool canThrow() => isChecked; + + int typeCode() => HInstruction.TYPE_CONVERSION_TYPECODE; + bool typeEquals(HInstruction other) => other is HTypeConversion; + bool dataEquals(HTypeConversion other) { + return type == other.type && kind == other.kind; + } +} + +class HRangeConversion extends HCheck { + HRangeConversion(HInstruction input) : super(<HInstruction>[input]) { + sourceElement = input.sourceElement; + } + accept(HVisitor visitor) => visitor.visitRangeConversion(this); + + // We currently only do range analysis for integers. + HType get guaranteedType => HType.INTEGER; +} + +class HStringConcat extends HInstruction { + final Node node; + HStringConcat(HInstruction left, HInstruction right, this.node) + : super(<HInstruction>[left, right]) { + setAllSideEffects(); + setDependsOnSomething(); + } + HType get guaranteedType => HType.STRING; + + HInstruction get left => inputs[0]; + HInstruction get right => inputs[1]; + + accept(HVisitor visitor) => visitor.visitStringConcat(this); + toString() => "string concat"; +} + +/** Non-block-based (aka. traditional) loop information. */ +class HLoopInformation { + final HBasicBlock header; + final List<HBasicBlock> blocks; + final List<HBasicBlock> backEdges; + final List<LabelElement> labels; + final TargetElement target; + + /** Corresponding block information for the loop. */ + HLoopBlockInformation loopBlockInformation; + + HLoopInformation(this.header, this.target, this.labels) + : blocks = new List<HBasicBlock>(), + backEdges = new List<HBasicBlock>(); + + void addBackEdge(HBasicBlock predecessor) { + backEdges.add(predecessor); + addBlock(predecessor); + } + + // Adds a block and transitively all its predecessors in the loop as + // loop blocks. + void addBlock(HBasicBlock block) { + if (identical(block, header)) return; + HBasicBlock parentHeader = block.parentLoopHeader; + if (identical(parentHeader, header)) { + // Nothing to do in this case. + } else if (parentHeader != null) { + addBlock(parentHeader); + } else { + block.parentLoopHeader = header; + blocks.add(block); + for (int i = 0, length = block.predecessors.length; i < length; i++) { + addBlock(block.predecessors[i]); + } + } + } + + HBasicBlock getLastBackEdge() { + int maxId = -1; + HBasicBlock result = null; + for (int i = 0, length = backEdges.length; i < length; i++) { + HBasicBlock current = backEdges[i]; + if (current.id > maxId) { + maxId = current.id; + result = current; + } + } + return result; + } +} + + +/** + * Embedding of a [HBlockInformation] for block-structure based traversal + * in a dominator based flow traversal by attaching it to a basic block. + * To go back to dominator-based traversal, a [HSubGraphBlockInformation] + * structure can be added in the block structure. + */ +class HBlockFlow { + final HBlockInformation body; + final HBasicBlock continuation; + HBlockFlow(this.body, this.continuation); +} + + +/** + * Information about a syntactic-like structure. + */ +abstract class HBlockInformation { + HBasicBlock get start; + HBasicBlock get end; + bool accept(HBlockInformationVisitor visitor); +} + + +/** + * Information about a statement-like structure. + */ +abstract class HStatementInformation extends HBlockInformation { + bool accept(HStatementInformationVisitor visitor); +} + + +/** + * Information about an expression-like structure. + */ +abstract class HExpressionInformation extends HBlockInformation { + bool accept(HExpressionInformationVisitor visitor); + HInstruction get conditionExpression; +} + + +abstract class HStatementInformationVisitor { + bool visitLabeledBlockInfo(HLabeledBlockInformation info); + bool visitLoopInfo(HLoopBlockInformation info); + bool visitIfInfo(HIfBlockInformation info); + bool visitTryInfo(HTryBlockInformation info); + bool visitSwitchInfo(HSwitchBlockInformation info); + bool visitSequenceInfo(HStatementSequenceInformation info); + // Pseudo-structure embedding a dominator-based traversal into + // the block-structure traversal. This will eventually go away. + bool visitSubGraphInfo(HSubGraphBlockInformation info); +} + + +abstract class HExpressionInformationVisitor { + bool visitAndOrInfo(HAndOrBlockInformation info); + bool visitSubExpressionInfo(HSubExpressionBlockInformation info); +} + + +abstract class HBlockInformationVisitor + implements HStatementInformationVisitor, HExpressionInformationVisitor { +} + + +/** + * Generic class wrapping a [SubGraph] as a block-information until + * all structures are handled properly. + */ +class HSubGraphBlockInformation implements HStatementInformation { + final SubGraph subGraph; + HSubGraphBlockInformation(this.subGraph); + + HBasicBlock get start => subGraph.start; + HBasicBlock get end => subGraph.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSubGraphInfo(this); +} + + +/** + * Generic class wrapping a [SubExpression] as a block-information until + * expressions structures are handled properly. + */ +class HSubExpressionBlockInformation implements HExpressionInformation { + final SubExpression subExpression; + HSubExpressionBlockInformation(this.subExpression); + + HBasicBlock get start => subExpression.start; + HBasicBlock get end => subExpression.end; + + HInstruction get conditionExpression => subExpression.conditionExpression; + + bool accept(HExpressionInformationVisitor visitor) => + visitor.visitSubExpressionInfo(this); +} + + +/** A sequence of separate statements. */ +class HStatementSequenceInformation implements HStatementInformation { + final List<HStatementInformation> statements; + HStatementSequenceInformation(this.statements); + + HBasicBlock get start => statements[0].start; + HBasicBlock get end => statements.last.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSequenceInfo(this); +} + + +class HLabeledBlockInformation implements HStatementInformation { + final HStatementInformation body; + final List<LabelElement> labels; + final TargetElement target; + final bool isContinue; + + HLabeledBlockInformation(this.body, + List<LabelElement> labels, + {this.isContinue: false}) : + this.labels = labels, this.target = labels[0].target; + + HLabeledBlockInformation.implicit(this.body, + this.target, + {this.isContinue: false}) + : this.labels = const<LabelElement>[]; + + HBasicBlock get start => body.start; + HBasicBlock get end => body.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitLabeledBlockInfo(this); +} + +class LoopTypeVisitor extends Visitor { + const LoopTypeVisitor(); + int visitNode(Node node) => HLoopBlockInformation.NOT_A_LOOP; + int visitWhile(While node) => HLoopBlockInformation.WHILE_LOOP; + int visitFor(For node) => HLoopBlockInformation.FOR_LOOP; + int visitDoWhile(DoWhile node) => HLoopBlockInformation.DO_WHILE_LOOP; + int visitForIn(ForIn node) => HLoopBlockInformation.FOR_IN_LOOP; +} + +class HLoopBlockInformation implements HStatementInformation { + static const int WHILE_LOOP = 0; + static const int FOR_LOOP = 1; + static const int DO_WHILE_LOOP = 2; + static const int FOR_IN_LOOP = 3; + static const int NOT_A_LOOP = -1; + + final int kind; + final HExpressionInformation initializer; + final HExpressionInformation condition; + final HStatementInformation body; + final HExpressionInformation updates; + final TargetElement target; + final List<LabelElement> labels; + final SourceFileLocation sourcePosition; + final SourceFileLocation endSourcePosition; + + HLoopBlockInformation(this.kind, + this.initializer, + this.condition, + this.body, + this.updates, + this.target, + this.labels, + this.sourcePosition, + this.endSourcePosition) { + assert( + (kind == DO_WHILE_LOOP ? body.start : condition.start).isLoopHeader()); + } + + HBasicBlock get start { + if (initializer != null) return initializer.start; + if (kind == DO_WHILE_LOOP) { + return body.start; + } + return condition.start; + } + + HBasicBlock get loopHeader { + return kind == DO_WHILE_LOOP ? body.start : condition.start; + } + + HBasicBlock get end { + if (updates != null) return updates.end; + if (kind == DO_WHILE_LOOP && condition != null) { + return condition.end; + } + return body.end; + } + + static int loopType(Node node) { + return node.accept(const LoopTypeVisitor()); + } + + bool isDoWhile() => kind == DO_WHILE_LOOP; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitLoopInfo(this); +} + +class HIfBlockInformation implements HStatementInformation { + final HExpressionInformation condition; + final HStatementInformation thenGraph; + final HStatementInformation elseGraph; + HIfBlockInformation(this.condition, + this.thenGraph, + this.elseGraph); + + HBasicBlock get start => condition.start; + HBasicBlock get end => elseGraph == null ? thenGraph.end : elseGraph.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitIfInfo(this); +} + +class HAndOrBlockInformation implements HExpressionInformation { + final bool isAnd; + final HExpressionInformation left; + final HExpressionInformation right; + HAndOrBlockInformation(this.isAnd, + this.left, + this.right); + + HBasicBlock get start => left.start; + HBasicBlock get end => right.end; + + // We don't currently use HAndOrBlockInformation. + HInstruction get conditionExpression { + return null; + } + bool accept(HExpressionInformationVisitor visitor) => + visitor.visitAndOrInfo(this); +} + +class HTryBlockInformation implements HStatementInformation { + final HStatementInformation body; + final HLocalValue catchVariable; + final HStatementInformation catchBlock; + final HStatementInformation finallyBlock; + HTryBlockInformation(this.body, + this.catchVariable, + this.catchBlock, + this.finallyBlock); + + HBasicBlock get start => body.start; + HBasicBlock get end => + finallyBlock == null ? catchBlock.end : finallyBlock.end; + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitTryInfo(this); +} + + + +class HSwitchBlockInformation implements HStatementInformation { + final HExpressionInformation expression; + final List<List<Constant>> matchExpressions; + final List<HStatementInformation> statements; + // If the switch has a default, it's the last statement block, which + // may or may not have other expresions. + final bool hasDefault; + final TargetElement target; + final List<LabelElement> labels; + + HSwitchBlockInformation(this.expression, + this.matchExpressions, + this.statements, + this.hasDefault, + this.target, + this.labels); + + HBasicBlock get start => expression.start; + HBasicBlock get end { + // We don't create a switch block if there are no cases. + assert(!statements.isEmpty); + return statements.last.end; + } + + bool accept(HStatementInformationVisitor visitor) => + visitor.visitSwitchInfo(this); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart new file mode 100644 index 0000000..35ba2cb --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart
@@ -0,0 +1,1545 @@ +// 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. + +part of ssa; + +abstract class OptimizationPhase { + String get name; + void visitGraph(HGraph graph); +} + +class SsaOptimizerTask extends CompilerTask { + final JavaScriptBackend backend; + SsaOptimizerTask(JavaScriptBackend backend) + : this.backend = backend, + super(backend.compiler); + String get name => 'SSA optimizer'; + Compiler get compiler => backend.compiler; + + void runPhases(HGraph graph, List<OptimizationPhase> phases) { + for (OptimizationPhase phase in phases) { + runPhase(graph, phase); + } + } + + void runPhase(HGraph graph, OptimizationPhase phase) { + phase.visitGraph(graph); + compiler.tracer.traceGraph(phase.name, graph); + assert(graph.isValid()); + } + + void optimize(CodegenWorkItem work, HGraph graph, bool speculative) { + ConstantSystem constantSystem = compiler.backend.constantSystem; + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + measure(() { + List<OptimizationPhase> phases = <OptimizationPhase>[ + // Run trivial constant folding first to optimize + // some patterns useful for type conversion. + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaTypeConversionInserter(compiler), + new SsaTypePropagator(compiler, types), + new SsaConstantFolder(constantSystem, backend, work, types), + // The constant folder affects the types of instructions, so + // we run the type propagator again. Note that this would + // not be necessary if types were directly stored on + // instructions. + new SsaTypePropagator(compiler, types), + new SsaCheckInserter(backend, work, types, context.boundsChecked), + new SsaRedundantPhiEliminator(), + new SsaDeadPhiEliminator(), + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaTypePropagator(compiler, types), + new SsaReceiverSpecialization(compiler), + new SsaGlobalValueNumberer(compiler, types), + new SsaCodeMotion(), + new SsaValueRangeAnalyzer(constantSystem, types, work), + // Previous optimizations may have generated new + // opportunities for constant folding. + new SsaConstantFolder(constantSystem, backend, work, types), + new SsaSimplifyInterceptors(constantSystem), + new SsaDeadCodeEliminator(types)]; + runPhases(graph, phases); + if (!speculative) { + runPhase(graph, new SsaConstructionFieldTypes(backend, work, types)); + } + }); + } + + bool trySpeculativeOptimizations(CodegenWorkItem work, HGraph graph) { + if (work.element.isField()) { + // Lazy initializers may not have bailout methods. + return false; + } + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + return measure(() { + // Run the phases that will generate type guards. + List<OptimizationPhase> phases = <OptimizationPhase>[ + new SsaSpeculativeTypePropagator(compiler, types), + new SsaTypeGuardInserter(compiler, work, types), + new SsaEnvironmentBuilder(compiler), + // Change the propagated types back to what they were before we + // speculatively propagated, so that we can generate the bailout + // version. + // Note that we do this even if there were no guards inserted. If a + // guard is not beneficial enough we don't emit one, but there might + // still be speculative types on the instructions. + new SsaTypePropagator(compiler, types), + // Then run the [SsaCheckInserter] because the type propagator also + // propagated types non-speculatively. For example, it might have + // propagated the type array for a call to the List constructor. + new SsaCheckInserter(backend, work, types, context.boundsChecked)]; + runPhases(graph, phases); + return !work.guards.isEmpty; + }); + } + + void prepareForSpeculativeOptimizations(CodegenWorkItem work, HGraph graph) { + JavaScriptItemCompilationContext context = work.compilationContext; + HTypeMap types = context.types; + measure(() { + // In order to generate correct code for the bailout version, we did not + // propagate types from the instruction to the type guard. We do it + // now to be able to optimize further. + work.guards.forEach((HTypeGuard guard) { + guard.bailoutTarget.isEnabled = false; + guard.isEnabled = true; + }); + // We also need to insert range and integer checks for the type + // guards. Now that they claim to have a certain type, some + // depending instructions might become builtin (like native array + // accesses) and need to be checked. + // Also run the type propagator, to please the codegen in case + // no other optimization is run. + runPhases(graph, <OptimizationPhase>[ + new SsaCheckInserter(backend, work, types, context.boundsChecked), + new SsaTypePropagator(compiler, types)]); + }); + } +} + +/** + * If both inputs to known operations are available execute the operation at + * compile-time. + */ +class SsaConstantFolder extends HBaseVisitor implements OptimizationPhase { + final String name = "SsaConstantFolder"; + final JavaScriptBackend backend; + final CodegenWorkItem work; + final ConstantSystem constantSystem; + final HTypeMap types; + HGraph graph; + Compiler get compiler => backend.compiler; + + SsaConstantFolder(this.constantSystem, this.backend, this.work, this.types); + + void visitGraph(HGraph visitee) { + graph = visitee; + visitDominatorTree(visitee); + } + + visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + HInstruction replacement = instruction.accept(this); + if (replacement != instruction) { + block.rewrite(instruction, replacement); + + // If we can replace [instruction] with [replacement], then + // [replacement]'s type can be narrowed. + types[replacement] = + types[replacement].intersection(types[instruction], compiler); + + // If the replacement instruction does not know its + // source element, use the source element of the + // instruction. + if (replacement.sourceElement == null) { + replacement.sourceElement = instruction.sourceElement; + } + if (replacement.sourcePosition == null) { + replacement.sourcePosition = instruction.sourcePosition; + } + if (!replacement.isInBasicBlock()) { + // The constant folding can return an instruction that is already + // part of the graph (like an input), so we only add the replacement + // if necessary. + block.addAfter(instruction, replacement); + // Visit the replacement as the next instruction in case it + // can also be constant folded away. + next = replacement; + } + block.remove(instruction); + } + instruction = next; + } + } + + HInstruction visitInstruction(HInstruction node) { + return node; + } + + HInstruction visitBoolify(HBoolify node) { + List<HInstruction> inputs = node.inputs; + assert(inputs.length == 1); + HInstruction input = inputs[0]; + HType type = types[input]; + if (type.isBoolean()) return input; + // All values !== true are boolified to false. + if (!type.isBooleanOrNull() && !type.isUnknown()) { + return graph.addConstantBool(false, constantSystem); + } + return node; + } + + HInstruction visitNot(HNot node) { + List<HInstruction> inputs = node.inputs; + assert(inputs.length == 1); + HInstruction input = inputs[0]; + if (input is HConstant) { + HConstant constant = input; + bool isTrue = constant.constant.isTrue(); + return graph.addConstantBool(!isTrue, constantSystem); + } else if (input is HNot) { + return input.inputs[0]; + } + return node; + } + + HInstruction visitInvokeUnary(HInvokeUnary node) { + HInstruction folded = + foldUnary(node.operation(constantSystem), node.operand); + return folded != null ? folded : node; + } + + HInstruction foldUnary(UnaryOperation operation, HInstruction operand) { + if (operand is HConstant) { + HConstant receiver = operand; + Constant folded = operation.fold(receiver.constant); + if (folded != null) return graph.addConstant(folded); + } + return null; + } + + HInstruction optimizeLengthInterceptedGetter(HInvokeDynamic node) { + HInstruction actualReceiver = node.inputs[1]; + if (actualReceiver.isIndexablePrimitive(types)) { + if (actualReceiver.isConstantString()) { + HConstant constantInput = actualReceiver; + StringConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } else if (actualReceiver.isConstantList()) { + HConstant constantInput = actualReceiver; + ListConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } + Element element; + bool isAssignable; + if (actualReceiver.isString(types)) { + element = backend.jsStringLength; + isAssignable = false; + } else { + element = backend.jsArrayLength; + isAssignable = !actualReceiver.isFixedArray(types); + } + HFieldGet result = new HFieldGet( + element, actualReceiver, isAssignable: isAssignable); + result.guaranteedType = HType.INTEGER; + types[result] = HType.INTEGER; + return result; + } else if (actualReceiver.isConstantMap()) { + HConstant constantInput = actualReceiver; + MapConstant constant = constantInput.constant; + return graph.addConstantInt(constant.length, constantSystem); + } + return node; + } + + HInstruction handleInterceptorCall(HInvokeDynamic node) { + // We only optimize for intercepted method calls in this method. + Selector selector = node.selector; + + // Try constant folding the instruction. + Operation operation = node.specializer.operation(constantSystem); + if (operation != null) { + HInstruction instruction = node.inputs.length == 2 + ? foldUnary(operation, node.inputs[1]) + : foldBinary(operation, node.inputs[1], node.inputs[2]); + if (instruction != null) return instruction; + } + + // Try converting the instruction to a builtin instruction. + HInstruction instruction = + node.specializer.tryConvertToBuiltin(node, types); + if (instruction != null) return instruction; + + // Check if this call does not need to be intercepted. + HInstruction input = node.inputs[1]; + HType type = types[input]; + var interceptor = node.inputs[0]; + + if (interceptor.isConstant() && selector.isCall()) { + DartType type = types[interceptor].computeType(compiler); + ClassElement cls = type.element; + node.element = cls.lookupSelector(selector); + } + + if (interceptor is !HThis && !type.canBePrimitive()) { + // If the type can be null, and the intercepted method can be in + // the object class, keep the interceptor. + if (type.canBeNull()) { + Set<ClassElement> interceptedClasses; + if (interceptor is HInterceptor) { + interceptedClasses = interceptor.interceptedClasses; + } else if (node is HOneShotInterceptor) { + var oneShotInterceptor = node; + interceptedClasses = oneShotInterceptor.interceptedClasses; + } + if (interceptedClasses.contains(compiler.objectClass)) return node; + } + if (selector.isGetter()) { + // Change the call to a regular invoke dynamic call. + return new HInvokeDynamicGetter(selector, null, input, false); + } else if (selector.isSetter()) { + return new HInvokeDynamicSetter( + selector, null, input, node.inputs[2], false); + } else { + // Change the call to a regular invoke dynamic call. + return new HInvokeDynamicMethod( + selector, node.inputs.getRange(1, node.inputs.length - 1)); + } + } + + if (selector.isCall()) { + Element target; + if (input.isExtendableArray(types)) { + if (selector.applies(backend.jsArrayRemoveLast, compiler)) { + target = backend.jsArrayRemoveLast; + } else if (selector.applies(backend.jsArrayAdd, compiler)) { + // The codegen special cases array calls, but does not + // inline argument type checks. + if (!compiler.enableTypeAssertions) { + target = backend.jsArrayAdd; + } + } + } else if (input.isString(types)) { + if (selector.applies(backend.jsStringSplit, compiler)) { + if (node.inputs[2].isString(types)) { + target = backend.jsStringSplit; + } + } else if (selector.applies(backend.jsStringConcat, compiler)) { + if (node.inputs[2].isString(types)) { + target = backend.jsStringConcat; + } + } else if (selector.applies(backend.jsStringToString, compiler)) { + return input; + } + } + if (target != null) { + // TODO(ngeoffray): There is a strong dependency between codegen + // and this optimization that the dynamic invoke does not need an + // interceptor. We currently need to keep a + // HInvokeDynamicMethod and not create a HForeign because + // HForeign is too opaque for the SssaCheckInserter (that adds a + // bounds check on removeLast). Once we start inlining, the + // bounds check will become explicit, so we won't need this + // optimization. + HInvokeDynamicMethod result = new HInvokeDynamicMethod( + node.selector, node.inputs.getRange(1, node.inputs.length - 1)); + result.element = target; + return result; + } + } else if (selector.isGetter()) { + if (selector.applies(backend.jsArrayLength, compiler)) { + return optimizeLengthInterceptedGetter(node); + } + } + return node; + } + + bool isFixedSizeListConstructor(HInvokeStatic node) { + Element element = node.target.element; + if (backend.fixedLengthListConstructor == null) { + backend.fixedLengthListConstructor = + compiler.listClass.lookupConstructor( + new Selector.callConstructor(const SourceString("fixedLength"), + compiler.listClass.getLibrary())); + } + // TODO(ngeoffray): checking if the second input is an integer + // should not be necessary but it currently makes it easier for + // other optimizations to reason on a fixed length constructor + // that we know takes an int. + return element == backend.fixedLengthListConstructor + && node.inputs[1].isInteger(types); + } + + HInstruction visitInvokeStatic(HInvokeStatic node) { + if (isFixedSizeListConstructor(node)) { + node.guaranteedType = HType.FIXED_ARRAY; + } + return node; + } + + HInstruction visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + HType receiverType = types[node.receiver]; + if (receiverType.isExact()) { + HBoundedType type = receiverType; + Element element = type.lookupMember(node.selector.name); + // TODO(ngeoffray): Also fold if it's a getter or variable. + if (element != null && element.isFunction()) { + if (node.selector.applies(element, compiler)) { + FunctionElement method = element; + FunctionSignature parameters = method.computeSignature(compiler); + if (parameters.optionalParameterCount == 0) { + node.element = element; + } + // TODO(ngeoffray): If the method has optional parameters, + // we should pass the default values here. + } + } + } + return node; + } + + HInstruction visitIntegerCheck(HIntegerCheck node) { + HInstruction value = node.value; + if (value.isInteger(types)) return value; + if (value.isConstant()) { + HConstant constantInstruction = value; + assert(!constantInstruction.constant.isInt()); + if (!constantSystem.isInt(constantInstruction.constant)) { + // -0.0 is a double but will pass the runtime integer check. + node.alwaysFalse = true; + } + } + return node; + } + + HInstruction foldBinary(BinaryOperation operation, + HInstruction left, + HInstruction right) { + if (left is HConstant && right is HConstant) { + HConstant op1 = left; + HConstant op2 = right; + Constant folded = operation.fold(op1.constant, op2.constant); + if (folded != null) return graph.addConstant(folded); + } + return null; + } + + HInstruction visitInvokeBinary(HInvokeBinary node) { + HInstruction left = node.left; + HInstruction right = node.right; + BinaryOperation operation = node.operation(constantSystem); + HConstant folded = foldBinary(operation, left, right); + if (folded != null) return folded; + return node; + } + + bool allUsersAreBoolifies(HInstruction instruction) { + List<HInstruction> users = instruction.usedBy; + int length = users.length; + for (int i = 0; i < length; i++) { + if (users[i] is! HBoolify) return false; + } + return true; + } + + HInstruction visitRelational(HRelational node) { + if (allUsersAreBoolifies(node)) { + // TODO(ngeoffray): Call a boolified selector. + // This node stays the same, but the Boolify node will go away. + } + // Note that we still have to call [super] to make sure that we end up + // in the remaining optimizations. + return super.visitRelational(node); + } + + HInstruction handleIdentityCheck(HRelational node) { + HInstruction left = node.left; + HInstruction right = node.right; + HType leftType = types[left]; + HType rightType = types[right]; + + // We don't optimize on numbers to preserve the runtime semantics. + if (!(left.isNumberOrNull(types) && right.isNumberOrNull(types)) && + leftType.intersection(rightType, compiler).isConflicting()) { + return graph.addConstantBool(false, constantSystem); + } + + if (left.isConstantBoolean() && right.isBoolean(types)) { + HConstant constant = left; + if (constant.constant.isTrue()) { + return right; + } else { + return new HNot(right); + } + } + + if (right.isConstantBoolean() && left.isBoolean(types)) { + HConstant constant = right; + if (constant.constant.isTrue()) { + return left; + } else { + return new HNot(left); + } + } + + return null; + } + + HInstruction visitIdentity(HIdentity node) { + HInstruction newInstruction = handleIdentityCheck(node); + return newInstruction == null ? super.visitIdentity(node) : newInstruction; + } + + HInstruction visitTypeGuard(HTypeGuard node) { + HInstruction value = node.guarded; + // If the intersection of the types is still the incoming type then + // the incoming type was a subtype of the guarded type, and no check + // is required. + HType combinedType = types[value].intersection(node.guardedType, compiler); + return (combinedType == types[value]) ? value : node; + } + + HInstruction visitIs(HIs node) { + DartType type = node.typeExpression; + Element element = type.element; + if (element.isTypeVariable()) { + compiler.unimplemented("visitIs for type variables"); + } if (element.isTypedef()) { + return node; + } + + HType expressionType = types[node.expression]; + if (identical(element, compiler.objectClass) + || identical(element, compiler.dynamicClass)) { + return graph.addConstantBool(true, constantSystem); + } else if (expressionType.isInteger()) { + if (identical(element, compiler.intClass) + || identical(element, compiler.numClass) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else if (identical(element, compiler.doubleClass)) { + // We let the JS semantics decide for that check. Currently + // the code we emit will always return true. + return node; + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isDouble()) { + if (identical(element, compiler.doubleClass) + || identical(element, compiler.numClass) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else if (identical(element, compiler.intClass)) { + // We let the JS semantics decide for that check. Currently + // the code we emit will return true for a double that can be + // represented as a 31-bit integer and for -0.0. + return node; + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isNumber()) { + if (identical(element, compiler.numClass)) { + return graph.addConstantBool(true, constantSystem); + } + // We cannot just return false, because the expression may be of + // type int or double. + } else if (expressionType.isString()) { + if (identical(element, compiler.stringClass) + || Elements.isStringOnlySupertype(element, compiler) + || Elements.isNumberOrStringSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else { + return graph.addConstantBool(false, constantSystem); + } + } else if (expressionType.isArray()) { + if (identical(element, compiler.listClass) + || Elements.isListSupertype(element, compiler)) { + return graph.addConstantBool(true, constantSystem); + } else { + return graph.addConstantBool(false, constantSystem); + } + // TODO(karlklose): remove the hasTypeArguments check. + } else if (expressionType.isUseful() + && !expressionType.canBeNull() + && !RuntimeTypeInformation.hasTypeArguments(type)) { + DartType receiverType = expressionType.computeType(compiler); + if (receiverType != null) { + if (!receiverType.isMalformed && + !type.isMalformed && + compiler.types.isSubtype(receiverType, type)) { + return graph.addConstantBool(true, constantSystem); + } else if (expressionType.isExact()) { + return graph.addConstantBool(false, constantSystem); + } + } + } + return node; + } + + HInstruction visitTypeConversion(HTypeConversion node) { + HInstruction value = node.inputs[0]; + DartType type = types[node].computeType(compiler); + if (identical(type.element, compiler.dynamicClass) + || identical(type.element, compiler.objectClass)) { + return value; + } + if (types[value].canBeNull() && node.isBooleanConversionCheck) { + return node; + } + HType combinedType = types[value].intersection(types[node], compiler); + return (combinedType == types[value]) ? value : node; + } + + Element findConcreteFieldForDynamicAccess(HInstruction receiver, + Selector selector) { + HType receiverType = types[receiver]; + if (!receiverType.isUseful()) return null; + if (receiverType.canBeNull()) return null; + DartType type = receiverType.computeType(compiler); + if (type == null) return null; + return compiler.world.locateSingleField(type, selector); + } + + HInstruction visitFieldGet(HFieldGet node) { + if (node.element == backend.jsArrayLength) { + if (node.receiver is HInvokeStatic) { + // Try to recognize the length getter with input + // [:new List.fixedLength(int):]. + HInvokeStatic call = node.receiver; + if (isFixedSizeListConstructor(call)) { + return call.inputs[1]; + } + } + } + return node; + } + + HInstruction visitInvokeDynamicGetter(HInvokeDynamicGetter node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + + Element field = + findConcreteFieldForDynamicAccess(node.receiver, node.selector); + if (field == null) return node; + + Modifiers modifiers = field.modifiers; + bool isFinalOrConst = modifiers.isFinal() || modifiers.isConst(); + if (!compiler.resolverWorld.hasInvokedSetter(field, compiler)) { + // If no setter is ever used for this field it is only initialized in the + // initializer list. + isFinalOrConst = true; + } + HFieldGet result = new HFieldGet( + field, node.inputs[0], isAssignable: !isFinalOrConst); + HType type = backend.optimisticFieldType(field); + if (type != null) { + result.guaranteedType = type; + backend.registerFieldTypesOptimization( + work.element, field, result.guaranteedType); + } + return result; + } + + HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) { + if (node.isInterceptorCall) return handleInterceptorCall(node); + + Element field = + findConcreteFieldForDynamicAccess(node.receiver, node.selector); + if (field == null || !field.isAssignable()) return node; + HInstruction value = node.inputs[1]; + if (compiler.enableTypeAssertions) { + HInstruction other = value.convertType( + compiler, + field.computeType(compiler), + HTypeConversion.CHECKED_MODE_CHECK); + if (other != value) { + node.block.addBefore(node, other); + value = other; + } + } + return new HFieldSet(field, node.inputs[0], value); + } + + HInstruction visitStringConcat(HStringConcat node) { + DartString folded = const LiteralDartString(""); + for (int i = 0; i < node.inputs.length; i++) { + HInstruction part = node.inputs[i]; + if (!part.isConstant()) return node; + HConstant constant = part; + if (!constant.constant.isPrimitive()) return node; + PrimitiveConstant primitive = constant.constant; + folded = new DartString.concat(folded, primitive.toDartString()); + } + return graph.addConstant(constantSystem.createString(folded, node.node)); + } + + HInstruction visitInterceptor(HInterceptor node) { + if (node.isConstant()) return node; + HInstruction constant = tryComputeConstantInterceptor( + node.inputs[0], node.interceptedClasses); + if (constant == null) return node; + return constant; + } + + HInstruction tryComputeConstantInterceptor(HInstruction input, + Set<ClassElement> intercepted) { + HType type = types[input]; + ClassElement constantInterceptor; + if (type.isInteger()) { + constantInterceptor = backend.jsIntClass; + } else if (type.isDouble()) { + constantInterceptor = backend.jsDoubleClass; + } else if (type.isBoolean()) { + constantInterceptor = backend.jsBoolClass; + } else if (type.isString()) { + constantInterceptor = backend.jsStringClass; + } else if (type.isArray()) { + constantInterceptor = backend.jsArrayClass; + } else if (type.isNull()) { + constantInterceptor = backend.jsNullClass; + } else if (type.isNumber()) { + // If the method being intercepted is not defined in [int] or + // [double] we can safely use the number interceptor. + if (!intercepted.contains(compiler.intClass) + && !intercepted.contains(compiler.doubleClass)) { + constantInterceptor = backend.jsNumberClass; + } + } + + if (constantInterceptor == null) return null; + if (constantInterceptor == work.element.getEnclosingClass()) { + return graph.thisInstruction; + } + + Constant constant = new ConstructedConstant( + constantInterceptor.computeType(compiler), <Constant>[]); + return graph.addConstant(constant); + } + + HInstruction visitOneShotInterceptor(HOneShotInterceptor node) { + HInstruction newInstruction = handleInterceptorCall(node); + if (newInstruction != node) return newInstruction; + + HInstruction constant = tryComputeConstantInterceptor( + node.inputs[1], node.interceptedClasses); + + if (constant == null) return node; + + Selector selector = node.selector; + // TODO(ngeoffray): make one shot interceptors know whether + // they have side effects. + if (selector.isGetter()) { + HInstruction res = new HInvokeDynamicGetter( + selector, node.element, constant, false); + res.inputs.add(node.inputs[1]); + return res; + } else if (node.selector.isSetter()) { + HInstruction res = new HInvokeDynamicSetter( + selector, node.element, constant, node.inputs[1], false); + res.inputs.add(node.inputs[2]); + return res; + } else { + List<HInstruction> inputs = new List<HInstruction>.from(node.inputs); + inputs[0] = constant; + return new HInvokeDynamicMethod(selector, inputs, true); + } + } +} + +class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase { + final HTypeMap types; + final Set<HInstruction> boundsChecked; + final CodegenWorkItem work; + final JavaScriptBackend backend; + final String name = "SsaCheckInserter"; + HGraph graph; + + SsaCheckInserter(this.backend, + this.work, + this.types, + this.boundsChecked); + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + instruction = instruction.accept(this); + instruction = next; + } + } + + HBoundsCheck insertBoundsCheck(HInstruction node, + HInstruction receiver, + HInstruction index) { + bool isAssignable = !receiver.isFixedArray(types); + HFieldGet length = new HFieldGet( + backend.jsArrayLength, receiver, isAssignable: isAssignable); + length.guaranteedType = HType.INTEGER; + types[length] = HType.INTEGER; + node.block.addBefore(node, length); + + HBoundsCheck check = new HBoundsCheck(index, length); + node.block.addBefore(node, check); + boundsChecked.add(node); + return check; + } + + HIntegerCheck insertIntegerCheck(HInstruction node, HInstruction value) { + HIntegerCheck check = new HIntegerCheck(value); + node.block.addBefore(node, check); + Set<HInstruction> dominatedUsers = value.dominatedUsers(node); + for (HInstruction user in dominatedUsers) { + user.changeUse(value, check); + } + return check; + } + + void visitIndex(HIndex node) { + if (boundsChecked.contains(node)) return; + HInstruction index = node.index; + if (!node.index.isInteger(types)) { + index = insertIntegerCheck(node, index); + } + index = insertBoundsCheck(node, node.receiver, index); + node.changeUse(node.index, index); + } + + void visitIndexAssign(HIndexAssign node) { + if (!node.receiver.isMutableArray(types)) return; + if (boundsChecked.contains(node)) return; + HInstruction index = node.index; + if (!node.index.isInteger(types)) { + index = insertIntegerCheck(node, index); + } + index = insertBoundsCheck(node, node.receiver, index); + node.changeUse(node.index, index); + } + + void visitInvokeDynamicMethod(HInvokeDynamicMethod node) { + Element element = node.element; + if (node.isInterceptorCall) return; + if (element != backend.jsArrayRemoveLast) return; + if (boundsChecked.contains(node)) return; + insertBoundsCheck( + node, node.receiver, graph.addConstantInt(0, backend.constantSystem)); + } +} + +class SsaDeadCodeEliminator extends HGraphVisitor implements OptimizationPhase { + final HTypeMap types; + final String name = "SsaDeadCodeEliminator"; + + SsaDeadCodeEliminator(this.types); + + bool isDeadCode(HInstruction instruction) { + return !instruction.hasSideEffects() + && !instruction.canThrow() + && instruction.usedBy.isEmpty + && instruction is !HTypeGuard + && instruction is !HParameterValue + && instruction is !HLocalSet + && !instruction.isControlFlow(); + } + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + HInstruction instruction = block.last; + while (instruction != null) { + var previous = instruction.previous; + if (isDeadCode(instruction)) block.remove(instruction); + instruction = previous; + } + } +} + +class SsaDeadPhiEliminator implements OptimizationPhase { + final String name = "SsaDeadPhiEliminator"; + + void visitGraph(HGraph graph) { + final List<HPhi> worklist = <HPhi>[]; + // A set to keep track of the live phis that we found. + final Set<HPhi> livePhis = new Set<HPhi>(); + + // Add to the worklist all live phis: phis referenced by non-phi + // instructions. + for (final block in graph.blocks) { + block.forEachPhi((HPhi phi) { + for (final user in phi.usedBy) { + if (user is !HPhi) { + worklist.add(phi); + livePhis.add(phi); + break; + } + } + }); + } + + // Process the worklist by propagating liveness to phi inputs. + while (!worklist.isEmpty) { + HPhi phi = worklist.removeLast(); + for (final input in phi.inputs) { + if (input is HPhi && !livePhis.contains(input)) { + worklist.add(input); + livePhis.add(input); + } + } + } + + // Remove phis that are not live. + // Traverse in reverse order to remove phis with no uses before the + // phis that they might use. + // NOTICE: Doesn't handle circular references, but we don't currently + // create any. + List<HBasicBlock> blocks = graph.blocks; + for (int i = blocks.length - 1; i >= 0; i--) { + HBasicBlock block = blocks[i]; + HPhi current = block.phis.first; + HPhi next = null; + while (current != null) { + next = current.next; + if (!livePhis.contains(current) + // TODO(ahe): Not sure the following is correct. + && current.usedBy.isEmpty) { + block.removePhi(current); + } + current = next; + } + } + } +} + +class SsaRedundantPhiEliminator implements OptimizationPhase { + final String name = "SsaRedundantPhiEliminator"; + + void visitGraph(HGraph graph) { + final List<HPhi> worklist = <HPhi>[]; + + // Add all phis in the worklist. + for (final block in graph.blocks) { + block.forEachPhi((HPhi phi) => worklist.add(phi)); + } + + while (!worklist.isEmpty) { + HPhi phi = worklist.removeLast(); + + // If the phi has already been processed, continue. + if (!phi.isInBasicBlock()) continue; + + // Find if the inputs of the phi are the same instruction. + // The builder ensures that phi.inputs[0] cannot be the phi + // itself. + assert(!identical(phi.inputs[0], phi)); + HInstruction candidate = phi.inputs[0]; + for (int i = 1; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + // If the input is the phi, the phi is still candidate for + // elimination. + if (!identical(input, candidate) && !identical(input, phi)) { + candidate = null; + break; + } + } + + // If the inputs are not the same, continue. + if (candidate == null) continue; + + // Because we're updating the users of this phi, we may have new + // phis candidate for elimination. Add phis that used this phi + // to the worklist. + for (final user in phi.usedBy) { + if (user is HPhi) worklist.add(user); + } + phi.block.rewrite(phi, candidate); + phi.block.removePhi(phi); + } + } +} + +class SsaGlobalValueNumberer implements OptimizationPhase { + final String name = "SsaGlobalValueNumberer"; + final Compiler compiler; + final HTypeMap types; + final Set<int> visited; + + List<int> blockChangesFlags; + List<int> loopChangesFlags; + + SsaGlobalValueNumberer(this.compiler, this.types) : visited = new Set<int>(); + + void visitGraph(HGraph graph) { + computeChangesFlags(graph); + moveLoopInvariantCode(graph); + visitBasicBlock(graph.entry, new ValueSet()); + } + + void moveLoopInvariantCode(HGraph graph) { + for (int i = graph.blocks.length - 1; i >= 0; i--) { + HBasicBlock block = graph.blocks[i]; + if (block.isLoopHeader()) { + int changesFlags = loopChangesFlags[block.id]; + HLoopInformation info = block.loopInformation; + // Iterate over all blocks of this loop. Note that blocks in + // inner loops are not visited here, but we know they + // were visited before because we are iterating in post-order. + // So instructions that are GVN'ed in an inner loop are in their + // loop entry, and [info.blocks] contains this loop entry. + for (HBasicBlock other in info.blocks) { + moveLoopInvariantCodeFromBlock(other, block, changesFlags); + } + } + } + } + + void moveLoopInvariantCodeFromBlock(HBasicBlock block, + HBasicBlock loopHeader, + int changesFlags) { + assert(block.parentLoopHeader == loopHeader); + HBasicBlock preheader = loopHeader.predecessors[0]; + int dependsFlags = HInstruction.computeDependsOnFlags(changesFlags); + HInstruction instruction = block.first; + while (instruction != null) { + HInstruction next = instruction.next; + if (instruction.useGvn() + && (instruction is !HCheck) + && (instruction.flags & dependsFlags) == 0) { + bool loopInvariantInputs = true; + List<HInstruction> inputs = instruction.inputs; + for (int i = 0, length = inputs.length; i < length; i++) { + if (isInputDefinedAfterDominator(inputs[i], preheader)) { + loopInvariantInputs = false; + break; + } + } + + // If the inputs are loop invariant, we can move the + // instruction from the current block to the pre-header block. + if (loopInvariantInputs) { + block.detach(instruction); + preheader.moveAtExit(instruction); + } + } + int oldChangesFlags = changesFlags; + changesFlags |= instruction.getChangesFlags(); + if (oldChangesFlags != changesFlags) { + dependsFlags = HInstruction.computeDependsOnFlags(changesFlags); + } + instruction = next; + } + } + + bool isInputDefinedAfterDominator(HInstruction input, + HBasicBlock dominator) { + return input.block.id > dominator.id; + } + + void visitBasicBlock(HBasicBlock block, ValueSet values) { + HInstruction instruction = block.first; + if (block.isLoopHeader()) { + int flags = loopChangesFlags[block.id]; + values.kill(flags); + } + while (instruction != null) { + HInstruction next = instruction.next; + int flags = instruction.getChangesFlags(); + assert(flags == 0 || !instruction.useGvn()); + values.kill(flags); + if (instruction.useGvn()) { + HInstruction other = values.lookup(instruction); + if (other != null) { + assert(other.gvnEquals(instruction) && instruction.gvnEquals(other)); + block.rewriteWithBetterUser(instruction, other); + block.remove(instruction); + } else { + values.add(instruction); + } + } + instruction = next; + } + + List<HBasicBlock> dominatedBlocks = block.dominatedBlocks; + for (int i = 0, length = dominatedBlocks.length; i < length; i++) { + HBasicBlock dominated = dominatedBlocks[i]; + // No need to copy the value set for the last child. + ValueSet successorValues = (i == length - 1) ? values : values.copy(); + // If we have no values in our set, we do not have to kill + // anything. Also, if the range of block ids from the current + // block to the dominated block is empty, there is no blocks on + // any path from the current block to the dominated block so we + // don't have to do anything either. + assert(block.id < dominated.id); + if (!successorValues.isEmpty && block.id + 1 < dominated.id) { + visited.clear(); + int changesFlags = getChangesFlagsForDominatedBlock(block, dominated); + successorValues.kill(changesFlags); + } + visitBasicBlock(dominated, successorValues); + } + } + + void computeChangesFlags(HGraph graph) { + // Create the changes flags lists. Make sure to initialize the + // loop changes flags list to zero so we can use bitwise or when + // propagating loop changes upwards. + final int length = graph.blocks.length; + blockChangesFlags = new List<int>.fixedLength(length); + loopChangesFlags = new List<int>.fixedLength(length); + for (int i = 0; i < length; i++) loopChangesFlags[i] = 0; + + // Run through all the basic blocks in the graph and fill in the + // changes flags lists. + for (int i = length - 1; i >= 0; i--) { + final HBasicBlock block = graph.blocks[i]; + final int id = block.id; + + // Compute block changes flags for the block. + int changesFlags = 0; + HInstruction instruction = block.first; + while (instruction != null) { + changesFlags |= instruction.getChangesFlags(); + instruction = instruction.next; + } + assert(blockChangesFlags[id] == null); + blockChangesFlags[id] = changesFlags; + + // Loop headers are part of their loop, so update the loop + // changes flags accordingly. + if (block.isLoopHeader()) { + loopChangesFlags[id] |= changesFlags; + } + + // Propagate loop changes flags upwards. + HBasicBlock parentLoopHeader = block.parentLoopHeader; + if (parentLoopHeader != null) { + loopChangesFlags[parentLoopHeader.id] |= (block.isLoopHeader()) + ? loopChangesFlags[id] + : changesFlags; + } + } + } + + int getChangesFlagsForDominatedBlock(HBasicBlock dominator, + HBasicBlock dominated) { + int changesFlags = 0; + List<HBasicBlock> predecessors = dominated.predecessors; + for (int i = 0, length = predecessors.length; i < length; i++) { + HBasicBlock block = predecessors[i]; + int id = block.id; + // If the current predecessor block is on the path from the + // dominator to the dominated, it must have an id that is in the + // range from the dominator to the dominated. + if (dominator.id < id && id < dominated.id && !visited.contains(id)) { + visited.add(id); + changesFlags |= blockChangesFlags[id]; + // Loop bodies might not be on the path from dominator to dominated, + // but they can invalidate values. + changesFlags |= loopChangesFlags[id]; + changesFlags |= getChangesFlagsForDominatedBlock(dominator, block); + } + } + return changesFlags; + } +} + +// This phase merges equivalent instructions on different paths into +// one instruction in a dominator block. It runs through the graph +// post dominator order and computes a ValueSet for each block of +// instructions that can be moved to a dominator block. These +// instructions are the ones that: +// 1) can be used for GVN, and +// 2) do not use definitions of their own block. +// +// A basic block looks at its sucessors and finds the intersection of +// these computed ValueSet. It moves all instructions of the +// intersection into its own list of instructions. +class SsaCodeMotion extends HBaseVisitor implements OptimizationPhase { + final String name = "SsaCodeMotion"; + + List<ValueSet> values; + + void visitGraph(HGraph graph) { + values = new List<ValueSet>.fixedLength(graph.blocks.length); + for (int i = 0; i < graph.blocks.length; i++) { + values[graph.blocks[i].id] = new ValueSet(); + } + visitPostDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + List<HBasicBlock> successors = block.successors; + + // Phase 1: get the ValueSet of all successors (if there are more than one), + // compute the intersection and move the instructions of the intersection + // into this block. + if (successors.length > 1) { + ValueSet instructions = values[successors[0].id]; + for (int i = 1; i < successors.length; i++) { + ValueSet other = values[successors[i].id]; + instructions = instructions.intersection(other); + } + + if (!instructions.isEmpty) { + List<HInstruction> list = instructions.toList(); + for (HInstruction instruction in list) { + // Move the instruction to the current block. + instruction.block.detach(instruction); + block.moveAtExit(instruction); + // Go through all successors and rewrite their instruction + // to the shared one. + for (final successor in successors) { + HInstruction toRewrite = values[successor.id].lookup(instruction); + if (toRewrite != instruction) { + successor.rewriteWithBetterUser(toRewrite, instruction); + successor.remove(toRewrite); + } + } + } + } + } + + // Don't try to merge instructions to a dominator if we have + // multiple predecessors. + if (block.predecessors.length != 1) return; + + // Phase 2: Go through all instructions of this block and find + // which instructions can be moved to a dominator block. + ValueSet set_ = values[block.id]; + HInstruction instruction = block.first; + int flags = 0; + while (instruction != null) { + int dependsFlags = HInstruction.computeDependsOnFlags(flags); + flags |= instruction.getChangesFlags(); + + HInstruction current = instruction; + instruction = instruction.next; + + // TODO(ngeoffray): this check is needed because we currently do + // not have flags to express 'Gvn'able', but not movable. + if (current is HCheck) continue; + if (!current.useGvn()) continue; + if ((current.flags & dependsFlags) != 0) continue; + + bool canBeMoved = true; + for (final HInstruction input in current.inputs) { + if (input.block == block) { + canBeMoved = false; + break; + } + } + if (!canBeMoved) continue; + + // This is safe because we are running after GVN. + // TODO(ngeoffray): ensure GVN has been run. + set_.add(current); + } + } +} + +class SsaTypeConversionInserter extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaTypeconversionInserter"; + final Compiler compiler; + + SsaTypeConversionInserter(this.compiler); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + + // Update users of [input] that are dominated by [:dominator.first:] + // to use [newInput] instead. + void changeUsesDominatedBy(HBasicBlock dominator, + HInstruction input, + HType convertedType) { + Set<HInstruction> dominatedUsers = input.dominatedUsers(dominator.first); + if (dominatedUsers.isEmpty) return; + + HTypeConversion newInput = new HTypeConversion(convertedType, input); + dominator.addBefore(dominator.first, newInput); + dominatedUsers.forEach((HInstruction user) { + user.changeUse(input, newInput); + }); + } + + void visitIs(HIs instruction) { + HInstruction input = instruction.expression; + HType convertedType = + new HType.fromBoundedType(instruction.typeExpression, compiler); + + List<HInstruction> ifUsers = <HInstruction>[]; + List<HInstruction> notIfUsers = <HInstruction>[]; + + for (HInstruction user in instruction.usedBy) { + if (user is HIf) { + ifUsers.add(user); + } else if (user is HNot) { + for (HInstruction notUser in user.usedBy) { + if (notUser is HIf) notIfUsers.add(notUser); + } + } + } + + if (ifUsers.isEmpty && notIfUsers.isEmpty) return; + + for (HIf ifUser in ifUsers) { + changeUsesDominatedBy(ifUser.thenBlock, input, convertedType); + // TODO(ngeoffray): Also change uses for the else block on a HType + // that knows it is not of a specific Type. + } + + for (HIf ifUser in notIfUsers) { + changeUsesDominatedBy(ifUser.elseBlock, input, convertedType); + // TODO(ngeoffray): Also change uses for the then block on a HType + // that knows it is not of a specific Type. + } + } +} + + +// Analyze the constructors to see if some fields will always have a specific +// type after construction. If this is the case we can ignore the type given +// by the field initializer. This is especially useful when the field +// initializer is initializing the field to null. +class SsaConstructionFieldTypes + extends HBaseVisitor implements OptimizationPhase { + final JavaScriptBackend backend; + final CodegenWorkItem work; + final HTypeMap types; + final String name = "SsaConstructionFieldTypes"; + final Set<HInstruction> thisUsers; + final Set<Element> allSetters; + final Map<HBasicBlock, Map<Element, HType>> blockFieldSetters; + bool thisExposed = false; + HGraph currentGraph; + Map<Element, HType> currentFieldSetters; + + SsaConstructionFieldTypes(JavaScriptBackend this.backend, + CodegenWorkItem this.work, + HTypeMap this.types) + : thisUsers = new Set<HInstruction>(), + allSetters = new Set<Element>(), + blockFieldSetters = new Map<HBasicBlock, Map<Element, HType>>(); + + void visitGraph(HGraph graph) { + currentGraph = graph; + if (!work.element.isGenerativeConstructorBody() && + !work.element.isGenerativeConstructor()) return; + visitDominatorTree(graph); + if (work.element.isGenerativeConstructor()) { + backend.registerConstructor(work.element); + } + } + + visitBasicBlock(HBasicBlock block) { + if (block.predecessors.length == 0) { + // Create a new empty map for the first block. + currentFieldSetters = new Map<Element, HType>(); + } else { + // Build a map which intersects the fields from all predecessors. For + // each field in this intersection it unions the types. + currentFieldSetters = + new Map.from(blockFieldSetters[block.predecessors[0]]); + // Loop headers are the only nodes with back edges. + if (!block.isLoopHeader()) { + for (int i = 1; i < block.predecessors.length; i++) { + Map<Element, HType> predecessorsFieldSetters = + blockFieldSetters[block.predecessors[i]]; + Map<Element, HType> newFieldSetters = new Map<Element, HType>(); + predecessorsFieldSetters.forEach((Element element, HType type) { + HType currentType = currentFieldSetters[element]; + if (currentType != null) { + newFieldSetters[element] = + currentType.union(type, backend.compiler); + } + }); + currentFieldSetters = newFieldSetters; + } + } else { + assert(block.predecessors.length <= 2); + } + } + block.forEachPhi((HPhi phi) => phi.accept(this)); + block.forEachInstruction( + (HInstruction instruction) => instruction.accept(this)); + assert(currentFieldSetters != null); + blockFieldSetters[block] = currentFieldSetters; + } + + visitInstruction(HInstruction instruction) { + // All instructions not explicitly handled below will flag the this + // exposure if using this. + thisExposed = thisExposed || thisUsers.contains(instruction); + } + + visitPhi(HPhi phi) { + if (thisUsers.contains(phi)) { + thisUsers.addAll(phi.usedBy); + } + } + + visitThis(HThis instruction) { + // Collect all users of this in a set to make the this exposed check simple + // and cheap. + thisUsers.addAll(instruction.usedBy); + } + + visitFieldGet(HInstruction _) { + // The field get instruction is allowed to use this. + } + + visitForeignNew(HForeignNew node) { + // The HForeignNew instruction is used in the generative constructor to + // initialize all fields in newly created objects. The fields are + // initialized to the value present in the initializer list or set to null + // if not otherwise initialized. + // Here we handle members in superclasses as well, as the handling of + // the generative constructor bodies will ensure, that the initializer + // type will not be used if the field is in any of these. + int j = 0; + node.element.forEachInstanceField( + (ClassElement enclosingClass, Element element) { + backend.registerFieldInitializer(element, types[node.inputs[j]]); + j++; + }, + includeBackendMembers: false, + includeSuperMembers: true); + } + + visitFieldSet(HFieldSet node) { + Element field = node.element; + HInstruction value = node.value; + HType type = types[value]; + // [HFieldSet] is also used for variables in try/catch. + if (field.isField()) allSetters.add(field); + // Don't handle fields defined in superclasses. Given that the field is + // always added to the [allSetters] set, setting a field defined in a + // superclass will get an inferred type of UNKNOWN. + if (identical(work.element.getEnclosingClass(), field.getEnclosingClass()) && + value.hasGuaranteedType()) { + currentFieldSetters[field] = type; + } + } + + visitExit(HExit node) { + // If this has been exposed then we cannot say anything about types after + // construction. + if (!thisExposed) { + // Register the known field types. + currentFieldSetters.forEach((Element element, HType type) { + backend.registerFieldConstructor(element, type); + allSetters.remove(element); + }); + } + + // For other fields having setters in the generative constructor body, set + // the type to UNKNOWN to avoid relying on the type set in the initializer + // list. + allSetters.forEach((Element element) { + backend.registerFieldConstructor(element, HType.UNKNOWN); + }); + } +} + +/** + * This phase specializes dominated uses of a call, where the call + * can give us some type information of what the receiver might be. + * For example, after a call to [:a.foo():], if [:foo:] is only + * in class [:A:], a can be of type [:A:]. + */ +class SsaReceiverSpecialization extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaReceiverSpecialization"; + final Compiler compiler; + + SsaReceiverSpecialization(this.compiler); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void visitInterceptor(HInterceptor interceptor) { + HInstruction receiver = interceptor.receiver; + JavaScriptBackend backend = compiler.backend; + for (var user in receiver.usedBy) { + if (user is HInterceptor && interceptor.dominates(user)) { + Set<ClassElement> otherIntercepted = user.interceptedClasses; + // If the dominated interceptor intercepts the int class or + // the double class, we make sure these classes are also being + // intercepted by the dominating interceptor. Otherwise, the + // dominating interceptor could just intercept the number + // class and therefore not implement the methods in the int or + // double class. + if (otherIntercepted.contains(backend.jsIntClass) + || otherIntercepted.contains(backend.jsDoubleClass)) { + interceptor.interceptedClasses.addAll(user.interceptedClasses); + } + user.interceptedClasses = interceptor.interceptedClasses; + } + } + } + + // TODO(ngeoffray): Also implement it for non-intercepted calls. +} + +/** + * This phase replaces all interceptors that are used only once with + * one-shot interceptors. It saves code size and makes the receiver of + * an intercepted call a candidate for being generated at use site. + */ +class SsaSimplifyInterceptors extends HBaseVisitor + implements OptimizationPhase { + final String name = "SsaSimplifyInterceptors"; + final ConstantSystem constantSystem; + HGraph graph; + + SsaSimplifyInterceptors(this.constantSystem); + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + } + + void visitInterceptor(HInterceptor node) { + if (node.usedBy.length != 1) return; + // [HBailoutTarget] instructions might have the interceptor as + // input. In such situation we let the dead code analyzer find out + // the interceptor is not needed. + if (node.usedBy[0] is !HInvokeDynamic) return; + + HInvokeDynamic user = node.usedBy[0]; + + // If [node] was loop hoisted, we keep the interceptor. + if (!user.hasSameLoopHeaderAs(node)) return; + + // Replace the user with a [HOneShotInterceptor]. + HConstant nullConstant = graph.addConstantNull(constantSystem); + List<HInstruction> inputs = new List<HInstruction>.from(user.inputs); + inputs[0] = nullConstant; + HOneShotInterceptor interceptor = new HOneShotInterceptor( + user.selector, inputs, node.interceptedClasses); + interceptor.sourcePosition = user.sourcePosition; + interceptor.sourceElement = user.sourceElement; + + HBasicBlock block = user.block; + block.addAfter(user, interceptor); + block.rewrite(user, interceptor); + block.remove(user); + + // The interceptor will be removed in the dead code elimination + // phase. Note that removing it here would not work because of how + // the [visitBasicBlock] is implemented. + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart new file mode 100644 index 0000000..13de8b6 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart
@@ -0,0 +1,43 @@ +// 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. + +library ssa; + +import 'dart:collection'; + +import '../closure.dart'; +import '../js/js.dart' as js; +import '../dart2jslib.dart' hide Selector; +import '../dart_types.dart'; +import '../source_file.dart'; +import '../source_map_builder.dart'; +import '../elements/elements.dart'; +import '../js_backend/js_backend.dart'; +import '../native_handler.dart' as native; +import '../tree/tree.dart'; +import '../types/types.dart'; +import '../universe/universe.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +import '../scanner/scannerlib.dart' + show PartialFunctionElement, Token, PLUS_TOKEN; + +import '../elements/modelx.dart' + show ElementX, + ConstructorBodyElementX; + +part 'bailout.dart'; +part 'builder.dart'; +part 'codegen.dart'; +part 'codegen_helpers.dart'; +part 'invoke_dynamic_specializers.dart'; +part 'nodes.dart'; +part 'optimize.dart'; +part 'types.dart'; +part 'types_propagation.dart'; +part 'validate.dart'; +part 'variable_allocator.dart'; +part 'value_range_analyzer.dart'; +part 'value_set.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart new file mode 100644 index 0000000..7a8f6bc --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart
@@ -0,0 +1,563 @@ +// 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. + +library tracer; + +import 'dart:io'; +import 'ssa.dart'; +import '../js_backend/js_backend.dart'; +import '../dart2jslib.dart'; + +const bool GENERATE_SSA_TRACE = false; +const String SSA_TRACE_FILTER = null; + +class HTracer extends HGraphVisitor implements Tracer { + JavaScriptItemCompilationContext context; + int indent = 0; + final RandomAccessFile output; + final bool enabled = GENERATE_SSA_TRACE; + bool traceActive = false; + + HTracer([String path = "dart.cfg"]) + : output = GENERATE_SSA_TRACE ? new File(path).openSync(FileMode.WRITE) + : null; + + void close() { + if (enabled) output.closeSync(); + } + + void traceCompilation(String methodName, + JavaScriptItemCompilationContext compilationContext) { + if (!enabled) return; + this.context = compilationContext; + traceActive = + SSA_TRACE_FILTER == null || methodName.contains(SSA_TRACE_FILTER); + if (!traceActive) return; + tag("compilation", () { + printProperty("name", methodName); + printProperty("method", methodName); + printProperty("date", new DateTime.now().millisecondsSinceEpoch); + }); + } + + void traceGraph(String name, HGraph graph) { + if (!traceActive) return; + tag("cfg", () { + printProperty("name", name); + visitDominatorTree(graph); + }); + } + + void addPredecessors(HBasicBlock block) { + if (block.predecessors.isEmpty) { + printEmptyProperty("predecessors"); + } else { + addIndent(); + add("predecessors"); + for (HBasicBlock predecessor in block.predecessors) { + add(' "B${predecessor.id}"'); + } + add("\n"); + } + } + + void addSuccessors(HBasicBlock block) { + if (block.successors.isEmpty) { + printEmptyProperty("successors"); + } else { + addIndent(); + add("successors"); + for (HBasicBlock successor in block.successors) { + add(' "B${successor.id}"'); + } + add("\n"); + } + } + + void addInstructions(HInstructionStringifier stringifier, + HInstructionList list) { + HTypeMap types = context.types; + for (HInstruction instruction = list.first; + instruction != null; + instruction = instruction.next) { + int bci = 0; + int uses = instruction.usedBy.length; + String changes = instruction.hasSideEffects() ? '!' : ' '; + String depends = instruction.dependsOnSomething() ? '?' : ''; + addIndent(); + String temporaryId = stringifier.temporaryId(instruction); + String instructionString = stringifier.visit(instruction); + add("$bci $uses $temporaryId $instructionString $changes $depends <|@\n"); + } + } + + void visitBasicBlock(HBasicBlock block) { + HInstructionStringifier stringifier = + new HInstructionStringifier(context, block); + assert(block.id != null); + tag("block", () { + printProperty("name", "B${block.id}"); + printProperty("from_bci", -1); + printProperty("to_bci", -1); + addPredecessors(block); + addSuccessors(block); + printEmptyProperty("xhandlers"); + printEmptyProperty("flags"); + if (block.dominator != null) { + printProperty("dominator", "B${block.dominator.id}"); + } + tag("states", () { + tag("locals", () { + printProperty("size", 0); + printProperty("method", "None"); + block.forEachPhi((phi) { + String phiId = stringifier.temporaryId(phi); + StringBuffer inputIds = new StringBuffer(); + for (int i = 0; i < phi.inputs.length; i++) { + inputIds.add(stringifier.temporaryId(phi.inputs[i])); + inputIds.add(" "); + } + println("${phi.id} $phiId [ $inputIds]"); + }); + }); + }); + tag("HIR", () { + addInstructions(stringifier, block.phis); + addInstructions(stringifier, block); + }); + }); + } + + void tag(String tagName, Function f) { + println("begin_$tagName"); + indent++; + f(); + indent--; + println("end_$tagName"); + } + + void println(String string) { + addIndent(); + add(string); + add("\n"); + } + + void printEmptyProperty(String propertyName) { + println(propertyName); + } + + void printProperty(String propertyName, var value) { + if (value is num) { + println("$propertyName $value"); + } else { + println('$propertyName "$value"'); + } + } + + void add(String string) { + output.writeStringSync(string); + } + + void addIndent() { + for (int i = 0; i < indent; i++) { + add(" "); + } + } +} + +class HInstructionStringifier implements HVisitor<String> { + JavaScriptItemCompilationContext context; + HBasicBlock currentBlock; + + HInstructionStringifier(this.context, this.currentBlock); + + visit(HInstruction node) => node.accept(this); + + String temporaryId(HInstruction instruction) { + String prefix; + HType type = context.types[instruction]; + if (!type.isPrimitive()) { + prefix = 'U'; + } else { + if (type == HType.MUTABLE_ARRAY) { + prefix = 'm'; + } else if (type == HType.READABLE_ARRAY) { + prefix = 'a'; + } else if (type == HType.EXTENDABLE_ARRAY) { + prefix = 'e'; + } else if (type == HType.BOOLEAN) { + prefix = 'b'; + } else if (type == HType.INTEGER) { + prefix = 'i'; + } else if (type == HType.DOUBLE) { + prefix = 'd'; + } else if (type == HType.NUMBER) { + prefix = 'n'; + } else if (type == HType.STRING) { + prefix = 's'; + } else if (type == HType.UNKNOWN) { + prefix = 'v'; + } else if (type == HType.CONFLICTING) { + prefix = 'c'; + } else if (type == HType.INDEXABLE_PRIMITIVE) { + prefix = 'r'; + } else if (type == HType.NULL) { + prefix = 'u'; + } else { + prefix = 'x'; + } + } + return "$prefix${instruction.id}"; + } + + String visitBailoutTarget(HBailoutTarget node) { + StringBuffer envBuffer = new StringBuffer(); + List<HInstruction> inputs = node.inputs; + for (int i = 0; i < inputs.length; i++) { + envBuffer.add(" ${temporaryId(inputs[i])}"); + } + String on = node.isEnabled ? "enabled" : "disabled"; + return "BailoutTarget($on): id: ${node.state} env: $envBuffer"; + } + + String visitBoolify(HBoolify node) { + return "Boolify: ${temporaryId(node.inputs[0])}"; + } + + String handleInvokeBinary(HInvokeBinary node, String op) { + String left = temporaryId(node.left); + String right= temporaryId(node.right); + return '$left $op $right'; + } + + String visitAdd(HAdd node) => handleInvokeBinary(node, '+'); + + String visitBitAnd(HBitAnd node) => handleInvokeBinary(node, '&'); + + String visitBitNot(HBitNot node) { + String operand = temporaryId(node.operand); + return "~$operand"; + } + + String visitBitOr(HBitOr node) => handleInvokeBinary(node, '|'); + + String visitBitXor(HBitXor node) => handleInvokeBinary(node, '^'); + + String visitBoundsCheck(HBoundsCheck node) { + String lengthId = temporaryId(node.length); + String indexId = temporaryId(node.index); + return "Bounds check: length = $lengthId, index = $indexId"; + } + + String visitBreak(HBreak node) { + HBasicBlock target = currentBlock.successors[0]; + if (node.label != null) { + return "Break ${node.label.labelName}: (B${target.id})"; + } + return "Break: (B${target.id})"; + } + + String visitConstant(HConstant constant) => "Constant ${constant.constant}"; + + String visitContinue(HContinue node) { + HBasicBlock target = currentBlock.successors[0]; + if (node.label != null) { + return "Continue ${node.label.labelName}: (B${target.id})"; + } + return "Continue: (B${target.id})"; + } + + String visitDivide(HDivide node) => handleInvokeBinary(node, '/'); + + String visitExit(HExit node) => "exit"; + + String visitFieldGet(HFieldGet node) { + String fieldName = node.element.name.slowToString(); + return 'field get ${temporaryId(node.receiver)}.$fieldName'; + } + + String visitFieldSet(HFieldSet node) { + String valueId = temporaryId(node.value); + String fieldName = node.element.name.slowToString(); + return 'field set ${temporaryId(node.receiver)}.$fieldName to $valueId'; + } + + String visitLocalGet(HLocalGet node) { + String localName = node.element.name.slowToString(); + return 'local get ${temporaryId(node.local)}.$localName'; + } + + String visitLocalSet(HLocalSet node) { + String valueId = temporaryId(node.value); + String localName = node.element.name.slowToString(); + return 'local set ${temporaryId(node.local)}.$localName to $valueId'; + } + + String visitGoto(HGoto node) { + HBasicBlock target = currentBlock.successors[0]; + return "Goto: (B${target.id})"; + } + + String visitGreater(HGreater node) => handleInvokeBinary(node, '>'); + String visitGreaterEqual(HGreaterEqual node) { + handleInvokeBinary(node, '>='); + } + String visitIdentity(HIdentity node) => handleInvokeBinary(node, '==='); + + String visitIf(HIf node) { + HBasicBlock thenBlock = currentBlock.successors[0]; + HBasicBlock elseBlock = currentBlock.successors[1]; + String conditionId = temporaryId(node.inputs[0]); + return "If ($conditionId): (B${thenBlock.id}) else (B${elseBlock.id})"; + } + + String visitGenericInvoke(String invokeType, String functionName, + List<HInstruction> arguments) { + StringBuffer argumentsString = new StringBuffer(); + for (int i = 0; i < arguments.length; i++) { + if (i != 0) argumentsString.add(", "); + argumentsString.add(temporaryId(arguments[i])); + } + return "$invokeType: $functionName($argumentsString)"; + } + + String visitIndex(HIndex node) { + String receiver = temporaryId(node.receiver); + String index = temporaryId(node.index); + return "Index: $receiver[$index]"; + } + + String visitIndexAssign(HIndexAssign node) { + String receiver = temporaryId(node.receiver); + String index = temporaryId(node.index); + String value = temporaryId(node.value); + return "IndexAssign: $receiver[$index] = $value"; + } + + String visitIntegerCheck(HIntegerCheck node) { + String value = temporaryId(node.value); + return "Integer check: $value"; + } + + String visitInterceptor(HInterceptor node) { + String value = temporaryId(node.inputs[0]); + return "Intercept: $value"; + } + + String visitInvokeClosure(HInvokeClosure node) + => visitInvokeDynamic(node, "closure"); + + String visitInvokeDynamic(HInvokeDynamic invoke, String kind) { + String receiver = temporaryId(invoke.receiver); + String name = invoke.selector.name.slowToString(); + String target = "($kind) $receiver.$name"; + int offset = HInvoke.ARGUMENTS_OFFSET; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke", target, arguments); + } + + String visitInvokeDynamicMethod(HInvokeDynamicMethod node) + => visitInvokeDynamic(node, "method"); + String visitInvokeDynamicGetter(HInvokeDynamicGetter node) + => visitInvokeDynamic(node, "get"); + String visitInvokeDynamicSetter(HInvokeDynamicSetter node) + => visitInvokeDynamic(node, "set"); + + String visitInvokeStatic(HInvokeStatic invoke) { + String target = temporaryId(invoke.target); + int offset = HInvoke.ARGUMENTS_OFFSET; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke", target, arguments); + } + + String visitInvokeSuper(HInvokeSuper invoke) { + String target = temporaryId(invoke.target); + int offset = HInvoke.ARGUMENTS_OFFSET + 1; + List arguments = + invoke.inputs.getRange(offset, invoke.inputs.length - offset); + return visitGenericInvoke("Invoke super", target, arguments); + } + + String visitForeign(HForeign foreign) { + return visitGenericInvoke("Foreign", "${foreign.code}", foreign.inputs); + } + + String visitForeignNew(HForeignNew node) { + return visitGenericInvoke("New", + "${node.element.name.slowToString()}", + node.inputs); + } + + String visitLess(HLess node) => handleInvokeBinary(node, '<'); + String visitLessEqual(HLessEqual node) => handleInvokeBinary(node, '<='); + + String visitLiteralList(HLiteralList node) { + StringBuffer elementsString = new StringBuffer(); + for (int i = 0; i < node.inputs.length; i++) { + if (i != 0) elementsString.add(", "); + elementsString.add(temporaryId(node.inputs[i])); + } + return "Literal list: [$elementsString]"; + } + + String visitLoopBranch(HLoopBranch branch) { + HBasicBlock bodyBlock = currentBlock.successors[0]; + HBasicBlock exitBlock = currentBlock.successors[1]; + String conditionId = temporaryId(branch.inputs[0]); + return "While ($conditionId): (B${bodyBlock.id}) then (B${exitBlock.id})"; + } + + String visitMultiply(HMultiply node) => handleInvokeBinary(node, '*'); + + String visitNegate(HNegate node) { + String operand = temporaryId(node.operand); + return "-$operand"; + } + + String visitNot(HNot node) => "Not: ${temporaryId(node.inputs[0])}"; + + String visitParameterValue(HParameterValue node) { + return "p${node.sourceElement.name.slowToString()}"; + } + + String visitLocalValue(HLocalValue node) { + return "l${node.sourceElement.name.slowToString()}"; + } + + String visitPhi(HPhi phi) { + StringBuffer buffer = new StringBuffer(); + buffer.add("Phi("); + for (int i = 0; i < phi.inputs.length; i++) { + if (i > 0) buffer.add(", "); + buffer.add(temporaryId(phi.inputs[i])); + } + buffer.add(")"); + return buffer.toString(); + } + + String visitReturn(HReturn node) => "Return ${temporaryId(node.inputs[0])}"; + + String visitShiftLeft(HShiftLeft node) => handleInvokeBinary(node, '<<'); + + String visitStatic(HStatic node) + => "Static ${node.element.name.slowToString()}"; + + String visitLazyStatic(HLazyStatic node) + => "LazyStatic ${node.element.name.slowToString()}"; + + String visitOneShotInterceptor(HOneShotInterceptor node) + => visitInvokeDynamic(node, "one shot interceptor"); + + String visitStaticStore(HStaticStore node) { + String lhs = node.element.name.slowToString(); + return "Static $lhs = ${temporaryId(node.inputs[0])}"; + } + + String visitStringConcat(HStringConcat node) { + var leftId = temporaryId(node.left); + var rightId = temporaryId(node.right); + return "StringConcat: $leftId + $rightId"; + } + + String visitSubtract(HSubtract node) => handleInvokeBinary(node, '-'); + + String visitSwitch(HSwitch node) { + StringBuffer buf = new StringBuffer(); + buf.add("Switch: ("); + buf.add(temporaryId(node.inputs[0])); + buf.add(") "); + for (int i = 1; i < node.inputs.length; i++) { + buf.add(temporaryId(node.inputs[i])); + buf.add(": B"); + buf.add(node.block.successors[i - 1].id); + buf.add(", "); + } + buf.add("default: B"); + buf.add(node.block.successors.last.id); + return buf.toString(); + } + + String visitThis(HThis node) => "this"; + + String visitThrow(HThrow node) => "Throw ${temporaryId(node.inputs[0])}"; + + String visitExitTry(HExitTry node) { + return "Exit try"; + } + + String visitTry(HTry node) { + List<HBasicBlock> successors = currentBlock.successors; + String tryBlock = 'B${successors[0].id}'; + String catchBlock = 'none'; + if (node.catchBlock != null) { + catchBlock = 'B${successors[1].id}'; + } + + String finallyBlock = 'none'; + if (node.finallyBlock != null) { + finallyBlock = 'B${node.finallyBlock.id}'; + } + + return "Try: $tryBlock, Catch: $catchBlock, Finally: $finallyBlock, " + "Join: B${successors.last.id}"; + } + + String visitTypeGuard(HTypeGuard node) { + String type; + HType guardedType = node.guardedType; + if (guardedType == HType.MUTABLE_ARRAY) { + type = "mutable_array"; + } else if (guardedType == HType.READABLE_ARRAY) { + type = "readable_array"; + } else if (guardedType == HType.EXTENDABLE_ARRAY) { + type = "extendable_array"; + } else if (guardedType == HType.BOOLEAN) { + type = "bool"; + } else if (guardedType == HType.INTEGER) { + type = "integer"; + } else if (guardedType == HType.DOUBLE) { + type = "double"; + } else if (guardedType == HType.NUMBER) { + type = "number"; + } else if (guardedType == HType.STRING) { + type = "string"; + } else if (guardedType == HType.INDEXABLE_PRIMITIVE) { + type = "string_or_array"; + } else if (guardedType == HType.UNKNOWN) { + type = 'unknown'; + } else { + throw new CompilerCancelledException('Unexpected type guard: $type'); + } + HInstruction guarded = node.guarded; + HInstruction bailoutTarget = node.bailoutTarget; + StringBuffer envBuffer = new StringBuffer(); + List<HInstruction> inputs = node.inputs; + assert(inputs.length >= 2); + assert(inputs[0] == guarded); + assert(inputs[1] == bailoutTarget); + for (int i = 2; i < inputs.length; i++) { + envBuffer.add(" ${temporaryId(inputs[i])}"); + } + String on = node.isEnabled ? "enabled" : "disabled"; + String guardedId = temporaryId(node.guarded); + String bailoutId = temporaryId(node.bailoutTarget); + return "TypeGuard($on): $guardedId is $type bailout: $bailoutId " + "env: $envBuffer"; + } + + String visitIs(HIs node) { + String type = node.typeExpression.toString(); + return "TypeTest: ${temporaryId(node.expression)} is $type"; + } + + String visitTypeConversion(HTypeConversion node) { + return "TypeConversion: ${temporaryId(node.checkedInput)} to ${node.type}"; + } + + String visitRangeConversion(HRangeConversion node) { + return "RangeConversion: ${node.checkedInput}"; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart new file mode 100644 index 0000000..b6d4746 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart
@@ -0,0 +1,997 @@ +// 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. + +part of ssa; + +abstract class HType { + const HType(); + + /** + * Returns an [HType] that represents [type] and all types that have + * [type] as supertype. + */ + factory HType.fromBoundedType(DartType type, + Compiler compiler, + [bool canBeNull = false]) { + Element element = type.element; + if (element.kind == ElementKind.TYPE_VARIABLE) { + // TODO(ngeoffray): Replace object type with [type]. + return new HBoundedPotentialPrimitiveType( + compiler.objectClass.computeType(compiler), canBeNull, true); + } + + if (element == compiler.intClass) { + return canBeNull ? HType.INTEGER_OR_NULL : HType.INTEGER; + } else if (element == compiler.numClass) { + return canBeNull ? HType.NUMBER_OR_NULL : HType.NUMBER; + } else if (element == compiler.doubleClass) { + return canBeNull ? HType.DOUBLE_OR_NULL : HType.DOUBLE; + } else if (element == compiler.stringClass) { + return canBeNull ? HType.STRING_OR_NULL : HType.STRING; + } else if (element == compiler.boolClass) { + return canBeNull ? HType.BOOLEAN_OR_NULL : HType.BOOLEAN; + } else if (element == compiler.nullClass) { + return HType.NULL; + } else if (element == compiler.listClass + || Elements.isListSupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveArray(type, canBeNull); + } else if (Elements.isNumberOrStringSupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveNumberOrString(type, canBeNull); + } else if (Elements.isStringOnlySupertype(element, compiler)) { + return new HBoundedPotentialPrimitiveString(type, canBeNull); + } else if (element == compiler.objectClass) { + return new HBoundedPotentialPrimitiveType( + compiler.objectClass.computeType(compiler), canBeNull, true); + } else { + return canBeNull ? new HBoundedType.withNull(type) + : new HBoundedType.nonNull(type); + } + } + + static const HType CONFLICTING = const HConflictingType(); + static const HType UNKNOWN = const HUnknownType(); + static const HType BOOLEAN = const HBooleanType(); + static const HType NUMBER = const HNumberType(); + static const HType INTEGER = const HIntegerType(); + static const HType DOUBLE = const HDoubleType(); + static const HType INDEXABLE_PRIMITIVE = const HIndexablePrimitiveType(); + static const HType STRING = const HStringType(); + static const HType READABLE_ARRAY = const HReadableArrayType(); + static const HType MUTABLE_ARRAY = const HMutableArrayType(); + static const HType FIXED_ARRAY = const HFixedArrayType(); + static const HType EXTENDABLE_ARRAY = const HExtendableArrayType(); + static const HType NULL = const HNullType(); + + static const HType BOOLEAN_OR_NULL = const HBooleanOrNullType(); + static const HType NUMBER_OR_NULL = const HNumberOrNullType(); + static const HType INTEGER_OR_NULL = const HIntegerOrNullType(); + static const HType DOUBLE_OR_NULL = const HDoubleOrNullType(); + static const HType STRING_OR_NULL = const HStringOrNullType(); + + bool isConflicting() => identical(this, CONFLICTING); + bool isUnknown() => identical(this, UNKNOWN); + bool isNull() => false; + bool isBoolean() => false; + bool isNumber() => false; + bool isInteger() => false; + bool isDouble() => false; + bool isString() => false; + bool isBooleanOrNull() => false; + bool isNumberOrNull() => false; + bool isIntegerOrNull() => false; + bool isDoubleOrNull() => false; + bool isStringOrNull() => false; + bool isIndexablePrimitive() => false; + bool isFixedArray() => false; + bool isReadableArray() => false; + bool isMutableArray() => false; + bool isExtendableArray() => false; + bool isPrimitive() => false; + bool isExact() => false; + bool isPrimitiveOrNull() => false; + bool isTop() => false; + + bool canBePrimitive() => false; + bool canBeNull() => false; + + /** A type is useful it is not unknown, not conflicting, and not null. */ + bool isUseful() => !isUnknown() && !isConflicting() && !isNull(); + /** Alias for isReadableArray. */ + bool isArray() => isReadableArray(); + + DartType computeType(Compiler compiler); + + /** + * The intersection of two types is the intersection of its values. For + * example: + * * INTEGER.intersect(NUMBER) => INTEGER. + * * DOUBLE.intersect(INTEGER) => CONFLICTING. + * * MUTABLE_ARRAY.intersect(READABLE_ARRAY) => MUTABLE_ARRAY. + * + * When there is no predefined type to represent the intersection returns + * [CONFLICTING]. + * + * An intersection with [UNKNOWN] returns the non-UNKNOWN type. An + * intersection with [CONFLICTING] returns [CONFLICTING]. + */ + HType intersection(HType other, Compiler compiler); + + /** + * The union of two types is the union of its values. For example: + * * INTEGER.union(NUMBER) => NUMBER. + * * DOUBLE.union(INTEGER) => NUMBER. + * * MUTABLE_ARRAY.union(READABLE_ARRAY) => READABLE_ARRAY. + * + * When there is no predefined type to represent the union returns + * [UNKNOWN]. + * + * A union with [UNKNOWN] returns [UNKNOWN]. + * A union of [CONFLICTING] with any other types returns the other type. + */ + HType union(HType other, Compiler compiler); +} + +/** Used to represent [HType.UNKNOWN] and [HType.CONFLICTING]. */ +abstract class HAnalysisType extends HType { + final String name; + const HAnalysisType(this.name); + String toString() => name; + + DartType computeType(Compiler compiler) => null; +} + +class HUnknownType extends HAnalysisType { + const HUnknownType() : super("unknown"); + bool canBePrimitive() => true; + bool canBeNull() => true; + + HType union(HType other, Compiler compiler) => this; + HType intersection(HType other, Compiler compiler) => other; +} + +class HConflictingType extends HAnalysisType { + const HConflictingType() : super("conflicting"); + bool canBePrimitive() => true; + bool canBeNull() => true; + + HType union(HType other, Compiler compiler) => other; + HType intersection(HType other, Compiler compiler) => this; +} + +abstract class HPrimitiveType extends HType { + const HPrimitiveType(); + bool isPrimitive() => true; + bool canBePrimitive() => true; + bool isPrimitiveOrNull() => true; +} + +class HNullType extends HPrimitiveType { + const HNullType(); + bool canBeNull() => true; + bool isNull() => true; + String toString() => 'null'; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNullClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING_OR_NULL; + if (other.isInteger()) return HType.INTEGER_OR_NULL; + if (other.isDouble()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN_OR_NULL; + // TODO(ngeoffray): Deal with the type of null more generally. + if (other.isReadableArray()) return other.union(this, compiler); + if (!other.canBeNull()) return HType.UNKNOWN; + return other; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isUnknown()) return HType.NULL; + if (other.isConflicting()) return HType.CONFLICTING; + if (!other.canBeNull()) return HType.CONFLICTING; + return HType.NULL; + } +} + +abstract class HPrimitiveOrNullType extends HType { + const HPrimitiveOrNullType(); + bool canBePrimitive() => true; + bool canBeNull() => true; + bool isPrimitiveOrNull() => true; +} + +class HBooleanOrNullType extends HPrimitiveOrNullType { + const HBooleanOrNullType(); + String toString() => "boolean or null"; + bool isBooleanOrNull() => true; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsBoolClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.BOOLEAN_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN_OR_NULL; + if (other.isNull()) return HType.BOOLEAN_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.BOOLEAN_OR_NULL; + if (other.isBoolean()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.BOOLEAN; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HBooleanType extends HPrimitiveType { + const HBooleanType(); + bool isBoolean() => true; + bool isBooleanOrNull() => true; + String toString() => "boolean"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsBoolClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.BOOLEAN; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isBoolean()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN_OR_NULL; + if (other.isNull()) return HType.BOOLEAN_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.BOOLEAN; + if (other.isBooleanOrNull()) return HType.BOOLEAN; + if (other.isBoolean()) return HType.BOOLEAN; + return HType.CONFLICTING; + } +} + +class HNumberOrNullType extends HPrimitiveOrNullType { + const HNumberOrNullType(); + bool isNumberOrNull() => true; + String toString() => "number or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNumberClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NUMBER_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.NUMBER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.NUMBER_OR_NULL; + if (other.isInteger()) return HType.INTEGER; + if (other.isDouble()) return HType.DOUBLE; + if (other.isNumber()) return HType.NUMBER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.NUMBER; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HNumberType extends HPrimitiveType { + const HNumberType(); + bool isNumber() => true; + bool isNumberOrNull() => true; + String toString() => "number"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsNumberClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.NUMBER; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.NUMBER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.NUMBER; + if (other.isNumber()) return other; + if (other.isIntegerOrNull()) return HType.INTEGER; + if (other.isDoubleOrNull()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.NUMBER; + return HType.CONFLICTING; + } +} + +class HIntegerOrNullType extends HNumberOrNullType { + const HIntegerOrNullType(); + bool isIntegerOrNull() => true; + String toString() => "integer or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsIntClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INTEGER_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isInteger()) return HType.INTEGER_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.INTEGER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INTEGER_OR_NULL; + if (other.isInteger()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isDouble()) return HType.CONFLICTING; + if (other.isDoubleOrNull()) return HType.NULL; + if (other.isNumber()) return HType.INTEGER; + if (other.isNumberOrNull()) return HType.INTEGER_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.INTEGER; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HIntegerType extends HNumberType { + const HIntegerType(); + bool isInteger() => true; + bool isIntegerOrNull() => true; + String toString() => "integer"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsIntClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INTEGER; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isInteger()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER_OR_NULL; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.INTEGER_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INTEGER; + if (other.isIntegerOrNull()) return HType.INTEGER; + if (other.isInteger()) return HType.INTEGER; + if (other.isDouble()) return HType.CONFLICTING; + if (other.isDoubleOrNull()) return HType.CONFLICTING; + if (other.isNumber()) return HType.INTEGER; + if (other.isNumberOrNull()) return HType.INTEGER; + return HType.CONFLICTING; + } +} + +class HDoubleOrNullType extends HNumberOrNullType { + const HDoubleOrNullType(); + bool isDoubleOrNull() => true; + String toString() => "double or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsDoubleClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.DOUBLE_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isDouble()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER_OR_NULL; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.DOUBLE_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.DOUBLE_OR_NULL; + if (other.isInteger()) return HType.CONFLICTING; + if (other.isIntegerOrNull()) return HType.NULL; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isTop()) { + return other.canBeNull() ? this : HType.DOUBLE; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HDoubleType extends HNumberType { + const HDoubleType(); + bool isDouble() => true; + bool isDoubleOrNull() => true; + String toString() => "double"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsDoubleClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.DOUBLE; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE_OR_NULL; + if (other.isNumber()) return HType.NUMBER; + if (other.isNumberOrNull()) return HType.NUMBER_OR_NULL; + if (other.isNull()) return HType.DOUBLE_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.DOUBLE; + if (other.isIntegerOrNull()) return HType.CONFLICTING; + if (other.isInteger()) return HType.CONFLICTING; + if (other.isDouble()) return HType.DOUBLE; + if (other.isDoubleOrNull()) return HType.DOUBLE; + if (other.isNumber()) return HType.DOUBLE; + if (other.isNumberOrNull()) return HType.DOUBLE; + return HType.CONFLICTING; + } +} + +class HIndexablePrimitiveType extends HPrimitiveType { + const HIndexablePrimitiveType(); + bool isIndexablePrimitive() => true; + String toString() => "indexable"; + + DartType computeType(Compiler compiler) { + // TODO(ngeoffray): Represent union types. + return null; + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.INDEXABLE_PRIMITIVE; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveString) { + // TODO(ngeoffray): Represent union types. + return HType.UNKNOWN; + } + if (other is HBoundedPotentialPrimitiveArray) { + // TODO(ngeoffray): Represent union types. + return HType.UNKNOWN; + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.INDEXABLE_PRIMITIVE; + if (other.isIndexablePrimitive()) return other; + if (other is HBoundedPotentialPrimitiveString) return HType.STRING; + if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HStringOrNullType extends HPrimitiveOrNullType { + const HStringOrNullType(); + bool isStringOrNull() => true; + String toString() => "String or null"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsStringClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.STRING_OR_NULL; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING_OR_NULL; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isIndexablePrimitive()) { + // We don't have a type that represents the nullable indexable + // primitive. + return HType.UNKNOWN; + } + if (other is HBoundedPotentialPrimitiveString) { + if (other.canBeNull()) { + return other; + } else { + HBoundedType boundedType = other; + return new HBoundedPotentialPrimitiveString(boundedType.type, true); + } + } + if (other.isNull()) return HType.STRING_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.STRING_OR_NULL; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + if (other is HBoundedPotentialPrimitiveString) { + return other.canBeNull() ? HType.STRING_OR_NULL : HType.STRING; + } + if (other.isTop()) { + return other.canBeNull() ? this : HType.STRING; + } + if (other.canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } +} + +class HStringType extends HIndexablePrimitiveType { + const HStringType(); + bool isString() => true; + bool isStringOrNull() => true; + String toString() => "String"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsStringClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.STRING; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING_OR_NULL; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveString) return other; + if (other.isNull()) return HType.STRING_OR_NULL; + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.STRING; + if (other.isString()) return HType.STRING; + if (other.isArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + if (other.isStringOrNull()) return HType.STRING; + if (other is HBoundedPotentialPrimitiveString) return HType.STRING; + return HType.CONFLICTING; + } +} + +class HReadableArrayType extends HIndexablePrimitiveType { + const HReadableArrayType(); + bool isReadableArray() => true; + String toString() => "readable array"; + + DartType computeType(Compiler compiler) { + JavaScriptBackend backend = compiler.backend; + return backend.jsArrayClass.computeType(compiler); + } + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.READABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be readable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.READABLE_ARRAY; + if (other.isString()) return HType.CONFLICTING; + if (other.isReadableArray()) return other; + if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.READABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HMutableArrayType extends HReadableArrayType { + const HMutableArrayType(); + bool isMutableArray() => true; + String toString() => "mutable array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.MUTABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be mutable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.MUTABLE_ARRAY; + if (other.isMutableArray()) return other; + if (other.isString()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.MUTABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.MUTABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HFixedArrayType extends HMutableArrayType { + const HFixedArrayType(); + bool isFixedArray() => true; + String toString() => "fixed array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.FIXED_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isFixedArray()) return HType.FIXED_ARRAY; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be fixed array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.FIXED_ARRAY; + if (other.isFixedArray()) return HType.FIXED_ARRAY; + if (other.isExtendableArray()) return HType.CONFLICTING; + if (other.isString()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.FIXED_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.FIXED_ARRAY; + return HType.CONFLICTING; + } +} + +class HExtendableArrayType extends HMutableArrayType { + const HExtendableArrayType(); + bool isExtendableArray() => true; + String toString() => "extendable array"; + + HType union(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.EXTENDABLE_ARRAY; + if (other.isUnknown()) return HType.UNKNOWN; + if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY; + if (other.isMutableArray()) return HType.MUTABLE_ARRAY; + if (other.isReadableArray()) return HType.READABLE_ARRAY; + if (other.isIndexablePrimitive()) return HType.INDEXABLE_PRIMITIVE; + if (other is HBoundedPotentialPrimitiveArray) return other; + if (other.isNull()) { + // TODO(ngeoffray): This should be extendable array or null. + return new HBoundedPotentialPrimitiveArray( + compiler.listClass.computeType(compiler), true); + } + return HType.UNKNOWN; + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isUnknown()) return HType.EXTENDABLE_ARRAY; + if (other.isExtendableArray()) return HType.EXTENDABLE_ARRAY; + if (other.isString()) return HType.CONFLICTING; + if (other.isFixedArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.EXTENDABLE_ARRAY; + if (other is HBoundedPotentialPrimitiveArray) return HType.EXTENDABLE_ARRAY; + return HType.CONFLICTING; + } +} + +class HBoundedType extends HType { + final DartType type; + final bool _canBeNull; + final bool _isExact; + + String toString() { + return 'BoundedType($type, canBeNull: $_canBeNull, isExact: $_isExact)'; + } + + bool canBeNull() => _canBeNull; + + bool isExact() => _isExact; + + const HBoundedType(DartType this.type, + [bool canBeNull = false, isExact = false]) + : _canBeNull = canBeNull, _isExact = isExact; + const HBoundedType.exact(DartType type) : this(type, false, true); + const HBoundedType.withNull(DartType type) : this(type, true, false); + const HBoundedType.nonNull(DartType type) : this(type); + + DartType computeType(Compiler compiler) => type; + + Element lookupMember(SourceString name) { + if (!isExact()) return null; + ClassElement classElement = type.element; + return classElement.lookupMember(name); + } + + HType intersection(HType other, Compiler compiler) { + assert(!(isExact() && canBeNull())); + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isNull()) return canBeNull() ? HType.NULL : HType.CONFLICTING; + + if (other is HBoundedType) { + HBoundedType temp = other; + if (identical(this.type, temp.type)) { + // If the types are the same, we return the [HBoundedType] + // that has the most restrictive representation: if it's exact + // (eg cannot be a subtype), and if it cannot be null. + if (isExact()) { + return this; + } else if (other.isExact()) { + return other; + } else if (canBeNull()) { + return other; + } else { + return this; + } + // If one type is a subtype of the other, we return the former, + // which is the narrower type. + } else if (!type.isMalformed && !other.type.isMalformed) { + if (compiler.types.isSubtype(type, other.type)) { + return this; + } else if (compiler.types.isSubtype(other.type, type)) { + return other; + } + } + } + if (other.isUnknown()) return this; + if (other.canBeNull() && canBeNull()) return HType.NULL; + return HType.CONFLICTING; + } + + bool operator ==(HType other) { + if (other is !HBoundedType) return false; + HBoundedType bounded = other; + return (identical(type, bounded.type) + && identical(canBeNull(), bounded.canBeNull()) + && identical(isExact(), other.isExact())); + } + + HType union(HType other, Compiler compiler) { + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedType.withNull(type); + } + } + if (other is HBoundedType) { + HBoundedType temp = other; + if (!identical(type, temp.type)) return HType.UNKNOWN; + if (isExact()) return other; + if (other.isExact()) return this; + return canBeNull() ? this : other; + } + if (other.isConflicting()) return this; + return HType.UNKNOWN; + } +} + +class HBoundedPotentialPrimitiveType extends HBoundedType { + final bool _isObject; + const HBoundedPotentialPrimitiveType(DartType type, + bool canBeNull, + this._isObject) + : super(type, canBeNull, false); + + String toString() { + return 'BoundedPotentialPrimitiveType($type, canBeNull: $_canBeNull)'; + } + + bool canBePrimitive() => true; + bool isTop() => _isObject; + + HType union(HType other, Compiler compiler) { + if (isTop()) { + // The union of the top type and another type is the top type. + if (!canBeNull() && other.canBeNull()) { + return new HBoundedPotentialPrimitiveType(type, true, true); + } else { + return this; + } + } else { + return super.union(other, compiler); + } + } + + HType intersection(HType other, Compiler compiler) { + if (isTop()) { + // The intersection of the top type and any other type is the other type. + // TODO(ngeoffray): Also update the canBeNull information. + return other; + } else { + return super.intersection(other, compiler); + } + } +} + +class HBoundedPotentialPrimitiveNumberOrString + extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveNumberOrString(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + HType union(HType other, Compiler compiler) { + if (other.isNumber()) return this; + if (other.isNumberOrNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + if (other.isString()) return this; + if (other.isStringOrNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + if (other.isNull()) { + if (canBeNull()) return this; + return new HBoundedPotentialPrimitiveNumberOrString(type, true); + } + + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isNumber()) return other; + if (other.isNumberOrNull()) { + if (!canBeNull()) return HType.NUMBER; + return other; + } + if (other.isString()) return other; + if (other.isStringOrNull()) { + if (!canBeNull()) return HType.STRING; + return other; + } + return super.intersection(other, compiler); + } +} + +class HBoundedPotentialPrimitiveArray extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveArray(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + HType union(HType other, Compiler compiler) { + if (other.isString()) return HType.UNKNOWN; + if (other.isReadableArray()) return this; + // TODO(ngeoffray): implement union types. + if (other.isIndexablePrimitive()) return HType.UNKNOWN; + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveArray(type, true); + } + } + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isString()) return HType.CONFLICTING; + if (other.isReadableArray()) return other; + if (other.isIndexablePrimitive()) return HType.READABLE_ARRAY; + return super.intersection(other, compiler); + } +} + +class HBoundedPotentialPrimitiveString extends HBoundedPotentialPrimitiveType { + const HBoundedPotentialPrimitiveString(DartType type, bool canBeNull) + : super(type, canBeNull, false); + + bool isPrimitiveOrNull() => true; + + HType union(HType other, Compiler compiler) { + if (other.isString()) return this; + if (other.isStringOrNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveString(type, true); + } + } + if (other.isNull()) { + if (canBeNull()) { + return this; + } else { + return new HBoundedPotentialPrimitiveString(type, true); + } + } + // TODO(ngeoffray): implement union types. + if (other.isIndexablePrimitive()) return HType.UNKNOWN; + return super.union(other, compiler); + } + + HType intersection(HType other, Compiler compiler) { + if (other.isConflicting()) return HType.CONFLICTING; + if (other.isString()) return HType.STRING; + if (other.isStringOrNull()) { + return canBeNull() ? HType.STRING_OR_NULL : HType.STRING; + } + if (other.isReadableArray()) return HType.CONFLICTING; + if (other.isIndexablePrimitive()) return HType.STRING; + return super.intersection(other, compiler); + } +} + +class HTypeMap { + // Approximately 85% of methods in the sample "swarm" have less than + // 32 instructions. + static const int INITIAL_SIZE = 32; + + List<HType> _list = new List<HType>()..length = INITIAL_SIZE; + + operator [](HInstruction instruction) { + HType result; + if (instruction.id < _list.length) result = _list[instruction.id]; + if (result == null) return instruction.guaranteedType; + return result; + } + + operator []=(HInstruction instruction, HType value) { + int length = _list.length; + int id = instruction.id; + if (length <= id) { + if (id + 1 < length * 2) { + _list.length = length * 2; + } else { + _list.length = id + 1; + } + } + _list[id] = value; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart new file mode 100644 index 0000000..ee0f1a1 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart
@@ -0,0 +1,210 @@ +// 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. + +part of ssa; + +class SsaTypePropagator extends HGraphVisitor implements OptimizationPhase { + + final Map<int, HInstruction> workmap; + final List<int> worklist; + final Map<HInstruction, Function> pendingOptimizations; + final HTypeMap types; + + final Compiler compiler; + String get name => 'type propagator'; + + SsaTypePropagator(this.compiler, this.types) + : workmap = new Map<int, HInstruction>(), + worklist = new List<int>(), + pendingOptimizations = new Map<HInstruction, Function>(); + + HType computeType(HInstruction instruction) { + if (instruction.hasGuaranteedType()) return instruction.guaranteedType; + return instruction.computeTypeFromInputTypes(types, compiler); + } + + // Re-compute and update the type of the instruction. Returns + // whether or not the type was changed. + bool updateType(HInstruction instruction) { + // The [updateType] method is invoked when one of the inputs of + // the instruction changes its type. That gives us a new + // opportunity to consider this instruction for optimizations. + considerForArgumentTypeOptimization(instruction); + // Compute old and new types. + HType oldType = types[instruction]; + HType newType = computeType(instruction); + // We unconditionally replace the propagated type with the new type. The + // computeType must make sure that we eventually reach a stable state. + types[instruction] = newType; + return oldType != newType; + } + + void considerForArgumentTypeOptimization(HInstruction instruction) { + // Update the pending optimizations map based on the potentially + // new types of the operands. If the operand types no longer allow + // us to optimize, we remove the pending optimization. + if (instruction is !HInvokeDynamicMethod) return; + HInvokeDynamicMethod invoke = instruction; + if (instruction.specializer is !BinaryArithmeticSpecializer) return; + HInstruction left = instruction.inputs[1]; + HInstruction right = instruction.inputs[2]; + if (left.isNumber(types) && !right.isNumber(types)) { + pendingOptimizations[instruction] = () { + // This callback function is invoked after we're done + // propagating types. The types shouldn't have changed. + assert(left.isNumber(types) && !right.isNumber(types)); + convertInput(instruction, right, HType.NUMBER); + }; + } else { + pendingOptimizations.remove(instruction); + } + } + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + processWorklist(); + } + + visitBasicBlock(HBasicBlock block) { + if (block.isLoopHeader()) { + block.forEachPhi((HPhi phi) { + // Set the initial type for the phi. We're not using the type + // the phi thinks it has because new optimizations may imply + // changing it. + // In theory we would need to mark + // the type of all other incoming edges as "unitialized" and take this + // into account when doing the propagation inside the phis. Just + // setting the propagated type is however easier. + types[phi] = types[phi.inputs[0]]; + addToWorkList(phi); + }); + } else { + block.forEachPhi((HPhi phi) { + if (updateType(phi)) { + addDependentInstructionsToWorkList(phi); + } + }); + } + + HInstruction instruction = block.first; + while (instruction != null) { + if (updateType(instruction)) { + addDependentInstructionsToWorkList(instruction); + } + instruction = instruction.next; + } + } + + void processWorklist() { + do { + while (!worklist.isEmpty) { + int id = worklist.removeLast(); + HInstruction instruction = workmap[id]; + assert(instruction != null); + workmap.remove(id); + if (updateType(instruction)) { + addDependentInstructionsToWorkList(instruction); + } + } + // While processing the optimizable arithmetic instructions, we + // may discover better type information for dominated users of + // replaced operands, so we may need to take another stab at + // emptying the worklist afterwards. + processPendingOptimizations(); + } while (!worklist.isEmpty); + } + + void addDependentInstructionsToWorkList(HInstruction instruction) { + for (int i = 0, length = instruction.usedBy.length; i < length; i++) { + // The non-speculative type propagator only propagates types forward. We + // thus only need to add the users of the [instruction] to the list. + addToWorkList(instruction.usedBy[i]); + } + } + + void addToWorkList(HInstruction instruction) { + final int id = instruction.id; + if (!workmap.containsKey(id)) { + worklist.add(id); + workmap[id] = instruction; + } + } + + void processPendingOptimizations() { + pendingOptimizations.forEach((instruction, action) => action()); + pendingOptimizations.clear(); + } + + void convertInput(HInstruction instruction, HInstruction input, HType type) { + HTypeConversion converted = + new HTypeConversion.argumentTypeCheck(type, input); + instruction.block.addBefore(instruction, converted); + Set<HInstruction> dominatedUsers = input.dominatedUsers(instruction); + for (HInstruction user in dominatedUsers) { + user.changeUse(input, converted); + addToWorkList(user); + } + } +} + +class SsaSpeculativeTypePropagator extends SsaTypePropagator { + final String name = 'speculative type propagator'; + SsaSpeculativeTypePropagator(Compiler compiler, HTypeMap types) + : super(compiler, types); + + void addDependentInstructionsToWorkList(HInstruction instruction) { + // The speculative type propagator propagates types forward and backward. + // Not only do we need to add the users of the [instruction] to the list. + // We also need to add the inputs fo the [instruction], since they might + // want to propagate the desired outgoing type. + for (int i = 0, length = instruction.usedBy.length; i < length; i++) { + addToWorkList(instruction.usedBy[i]); + } + for (int i = 0, length = instruction.inputs.length; i < length; i++) { + addToWorkList(instruction.inputs[i]); + } + } + + HType computeDesiredType(HInstruction instruction) { + HType desiredType = HType.UNKNOWN; + for (final user in instruction.usedBy) { + HType userType = + user.computeDesiredTypeForInput(instruction, types, compiler); + // Mainly due to the "if (true)" added by hackAroundPossiblyAbortingBody + // in builder.dart uninitialized variables will propagate a type of null + // which will result in a conflicting type when combined with a primitive + // type. Avoid this to improve generated code. + // TODO(sgjesse): Reconcider this when hackAroundPossiblyAbortingBody + // has been removed. + if (desiredType.isPrimitive() && userType == HType.NULL) continue; + desiredType = desiredType.intersection(userType, compiler); + // No need to continue if two users disagree on the type. + if (desiredType.isConflicting()) break; + } + return desiredType; + } + + HType computeType(HInstruction instruction) { + // Once we are in a conflicting state don't update the type anymore. + HType oldType = types[instruction]; + if (oldType.isConflicting()) return oldType; + + HType newType = super.computeType(instruction); + // [computeDesiredType] goes to all usedBys and lets them compute their + // desired type. By setting the [newType] here we give them more context to + // work with. + types[instruction] = newType; + HType desiredType = computeDesiredType(instruction); + // If the desired type is conflicting just return the computed type. + if (desiredType.isConflicting()) return newType; + // TODO(ngeoffray): Allow speculative optimizations on + // non-primitive types? + if (!desiredType.isPrimitive()) return newType; + return newType.intersection(desiredType, compiler); + } + + // Do not use speculative argument type optimization for now. + void considerForArgumentTypeOptimization(HInstruction instruction) { } + +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart new file mode 100644 index 0000000..5e9598e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart
@@ -0,0 +1,181 @@ +// Copyright (c) 2011, 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. + +part of ssa; + +class HValidator extends HInstructionVisitor { + bool isValid = true; + HGraph graph; + + void visitGraph(HGraph visitee) { + graph = visitee; + visitDominatorTree(visitee); + } + + void markInvalid(String reason) { + print(reason); + isValid = false; + } + + // Note that during construction of the Ssa graph the basic blocks are + // not required to be valid yet. + void visitBasicBlock(HBasicBlock block) { + currentBlock = block; + if (!isValid) return; // Don't need to continue if we are already invalid. + + // Test that the last instruction is a branching instruction and that the + // basic block contains the branch-target. + if (block.first == null || block.last == null) { + markInvalid("empty block"); + } + if (block.last is !HControlFlow) { + markInvalid("block ends with non-tail node."); + } + if (block.last is HIf && block.successors.length != 2) { + markInvalid("If node without two successors"); + } + if (block.last is HConditionalBranch && block.successors.length != 2) { + markInvalid("Conditional node without two successors"); + } + if (block.last is HGoto && block.successors.length != 1) { + markInvalid("Goto node with not exactly one successor"); + } + if (block.last is HJump && block.successors.length != 1) { + markInvalid("Break or continue node without one successor"); + } + if (block.last is HReturn && + (block.successors.length != 1 || !block.successors[0].isExitBlock())) { + markInvalid("Return node with > 1 succesor or not going to exit-block"); + } + if (block.last is HExit && !block.successors.isEmpty) { + markInvalid("Exit block with successor"); + } + if (block.last is HThrow && !block.successors.isEmpty) { + markInvalid("Throw block with successor"); + } + + if (block.successors.isEmpty && + block.last is !HThrow && + !block.isExitBlock()) { + markInvalid("Non-exit or throw block without successor"); + } + + // Check that successors ids are always higher than the current one. + // TODO(floitsch): this is, of course, not true for back-branches. + if (block.id == null) markInvalid("block without id"); + for (HBasicBlock successor in block.successors) { + if (!isValid) break; + if (successor.id == null) markInvalid("successor without id"); + if (successor.id <= block.id && !successor.isLoopHeader()) { + markInvalid("successor with lower id, but not a loop-header"); + } + } + + // Check that the entries in the dominated-list are sorted. + int lastId = 0; + for (HBasicBlock dominated in block.dominatedBlocks) { + if (!isValid) break; + if (!identical(dominated.dominator, block)) { + markInvalid("dominated block not pointing back"); + } + if (dominated.id == null || dominated.id <= lastId) { + markInvalid("dominated.id == null or dominated has <= id"); + } + lastId = dominated.id; + } + + if (!isValid) return; + block.forEachPhi(visitInstruction); + + // Check that the blocks of the parameters of a phi are dominating the + // corresponding predecessor block. Note that a block dominates + // itself. + block.forEachPhi((HPhi phi) { + for (int i = 0; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + if (!input.block.dominates(block.predecessors[i])) { + markInvalid("Definition does not dominate use"); + } + } + }); + + // Check that the blocks of the inputs of an instruction dominate the + // instruction's block. + block.forEachInstruction((HInstruction instruction) { + for (HInstruction input in instruction.inputs) { + if (!input.block.dominates(block)) { + markInvalid("Definition does not dominate use"); + } + } + }); + + super.visitBasicBlock(block); + } + + /** Returns how often [instruction] is contained in [instructions]. */ + static int countInstruction(List<HInstruction> instructions, + HInstruction instruction) { + int result = 0; + for (int i = 0; i < instructions.length; i++) { + if (identical(instructions[i], instruction)) result++; + } + return result; + } + + /** + * Returns true if the predicate returns true for every instruction in the + * list. The argument to [f] is an instruction with the count of how often + * it appeared in the list [instructions]. + */ + static bool everyInstruction(List<HInstruction> instructions, Function f) { + var copy = new List<HInstruction>.from(instructions); + // TODO(floitsch): there is currently no way to sort HInstructions before + // we have assigned an ID. The loop is therefore O(n^2) for now. + for (int i = 0; i < copy.length; i++) { + var current = copy[i]; + if (current == null) continue; + int count = 1; + for (int j = i + 1; j < copy.length; j++) { + if (identical(copy[j], current)) { + copy[j] = null; + count++; + } + } + if (!f(current, count)) return false; + } + return true; + } + + void visitInstruction(HInstruction instruction) { + // Verifies that we are in the use list of our inputs. + bool hasCorrectInputs() { + bool inBasicBlock = instruction.isInBasicBlock(); + return everyInstruction(instruction.inputs, (input, count) { + if (inBasicBlock) { + return countInstruction(input.usedBy, instruction) == count; + } else { + return countInstruction(input.usedBy, instruction) == 0; + } + }); + } + + // Verifies that all our uses have us in their inputs. + bool hasCorrectUses() { + if (!instruction.isInBasicBlock()) return true; + return everyInstruction(instruction.usedBy, (use, count) { + return countInstruction(use.inputs, instruction) == count; + }); + } + + if (!identical(instruction.block, currentBlock)) { + markInvalid("Instruction in wrong block"); + } + if (!hasCorrectInputs()) { + markInvalid("Incorrect inputs"); + } + if (!hasCorrectUses()) { + markInvalid("Incorrect uses"); + } + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart new file mode 100644 index 0000000..c28d5c4 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart
@@ -0,0 +1,996 @@ +// 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. + +part of ssa; + + +class ValueRangeInfo { + final ConstantSystem constantSystem; + + IntValue intZero; + IntValue intOne; + + ValueRangeInfo(this.constantSystem) { + intZero = newIntValue(0); + intOne = newIntValue(1); + } + + Value newIntValue(int value) { + return new IntValue(value, this); + } + + Value newInstructionValue(HInstruction instruction) { + return new InstructionValue(instruction, this); + } + + Value newLengthValue(HInstruction instruction) { + return new LengthValue(instruction, this); + } + + Value newAddValue(Value left, Value right) { + return new AddValue(left, right, this); + } + + Value newSubtractValue(Value left, Value right) { + return new SubtractValue(left, right, this); + } + + Value newNegateValue(Value value) { + return new NegateValue(value, this); + } + + Range newRange(Value low, Value up) { + return new Range(low, up, this); + } + + Range newUnboundRange() { + return new Range.unbound(this); + } + + Range newNormalizedRange(Value low, Value up) { + return new Range.normalize(low, up, this); + } +} + +/** + * A [Value] represents both symbolic values like the value of a + * parameter, or the length of an array, and concrete values, like + * constants. + */ +abstract class Value { + final ValueRangeInfo info; + const Value([this.info = null]); + + Value operator +(Value other) => const UnknownValue(); + Value operator -(Value other) => const UnknownValue(); + Value operator -() => const UnknownValue(); + Value operator &(Value other) => const UnknownValue(); + + Value min(Value other) { + if (this == other) return this; + if (other == const MinIntValue()) return other; + if (other == const MaxIntValue()) return this; + Value value = this - other; + if (value.isPositive) return other; + if (value.isNegative) return this; + return const UnknownValue(); + } + + Value max(Value other) { + if (this == other) return this; + if (other == const MinIntValue()) return this; + if (other == const MaxIntValue()) return other; + Value value = this - other; + if (value.isPositive) return this; + if (value.isNegative) return other; + return const UnknownValue(); + } + + bool get isNegative => false; + bool get isPositive => false; + bool get isZero => false; +} + +/** + * An [IntValue] contains a constant integer value. + */ +class IntValue extends Value { + final int value; + + const IntValue(this.value, info) : super(info); + + Value operator +(other) { + if (other.isZero) return this; + if (other is !IntValue) return other + this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.add.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator -(other) { + if (other.isZero) return this; + if (other is !IntValue) return -other + this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.subtract.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator -() { + if (isZero) return this; + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.negate.fold( + constantSystem.createInt(value)); + if (!constant.isInt()) return const UnknownValue(); + return info.newIntValue(constant.value); + } + + Value operator &(other) { + if (other is !IntValue) return const UnknownValue(); + ConstantSystem constantSystem = info.constantSystem; + var constant = constantSystem.bitAnd.fold( + constantSystem.createInt(value), constantSystem.createInt(other.value)); + return info.newIntValue(constant.value); + } + + Value min(other) { + if (other is !IntValue) return other.min(this); + return this.value < other.value ? this : other; + } + + Value max(other) { + if (other is !IntValue) return other.max(this); + return this.value < other.value ? other : this; + } + + bool operator ==(other) { + if (other is !IntValue) return false; + return this.value == other.value; + } + + String toString() => 'IntValue $value'; + bool get isNegative => value < 0; + bool get isPositive => value >= 0; + bool get isZero => value == 0; +} + +/** + * The [MaxIntValue] represents the maximum value an integer can have, + * which is currently +infinity. + */ +class MaxIntValue extends Value { + const MaxIntValue() : super(null); + Value operator +(Value other) => this; + Value operator -(Value other) => this; + Value operator -() => const MinIntValue(); + Value min(Value other) => other; + Value max(Value other) => this; + String toString() => 'Max'; + bool get isNegative => false; + bool get isPositive => true; +} + +/** + * The [MinIntValue] represents the minimum value an integer can have, + * which is currently -infinity. + */ +class MinIntValue extends Value { + const MinIntValue() : super(null); + Value operator +(Value other) => this; + Value operator -(Value other) => this; + Value operator -() => const MaxIntValue(); + Value min(Value other) => this; + Value max(Value other) => other; + String toString() => 'Min'; + bool get isNegative => true; + bool get isPositive => false; +} + +/** + * The [UnknownValue] is the sentinel in our analysis to mark an + * operation that could not be done because of too much complexity. + */ +class UnknownValue extends Value { + const UnknownValue() : super(null); + Value operator +(Value other) => const UnknownValue(); + Value operator -(Value other) => const UnknownValue(); + Value operator -() => const UnknownValue(); + Value min(Value other) => const UnknownValue(); + Value max(Value other) => const UnknownValue(); + bool get isNegative => false; + bool get isPositive => false; + String toString() => 'Unknown'; +} + +/** + * A symbolic value representing an [HInstruction]. + */ +class InstructionValue extends Value { + final HInstruction instruction; + InstructionValue(this.instruction, info) : super(info); + + bool operator ==(other) { + if (other is !InstructionValue) return false; + return this.instruction == other.instruction; + } + + Value operator +(Value other) { + if (other.isZero) return this; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(this, -other); + } + return info.newAddValue(this, other); + } + if (other is InstructionValue) { + return info.newAddValue(this, other); + } + return other + this; + } + + Value operator -(Value other) { + if (other.isZero) return this; + if (this == other) return info.intZero; + if (other is IntValue) { + if (other.isNegative) { + return info.newAddValue(this, -other); + } + return info.newSubtractValue(this, other); + } + if (other is InstructionValue) { + return info.newSubtractValue(this, other); + } + return -other + this; + } + + Value operator -() { + return info.newNegateValue(this); + } + + bool get isNegative => false; + bool get isPositive => false; + + String toString() => 'Instruction: $instruction'; +} + +/** + * Special value for instructions that represent the length of an + * array. The difference with an [InstructionValue] is that we know + * the value is positive. + */ +class LengthValue extends InstructionValue { + LengthValue(HInstruction instruction, info) : super(instruction, info); + bool get isPositive => true; + String toString() => 'Length: $instruction'; +} + +/** + * Represents a binary operation on two [Value], where the operation + * did not yield a canonical value. + */ +class BinaryOperationValue extends Value { + final Value left; + final Value right; + BinaryOperationValue(this.left, this.right, info) : super(info); +} + +class AddValue extends BinaryOperationValue { + AddValue(left, right, info) : super(left, right, info); + + bool operator ==(other) { + if (other is !AddValue) return false; + return (left == other.left && right == other.right) + || (left == other.right && right == other.left); + } + + Value operator -() => -left - right; + + Value operator +(Value other) { + if (other.isZero) return this; + Value value = left + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value + right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + Value operator -(Value other) { + if (other.isZero) return this; + Value value = left - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value + right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + bool get isNegative => left.isNegative && right.isNegative; + bool get isPositive => left.isPositive && right.isPositive; + String toString() => '$left + $right'; +} + +class SubtractValue extends BinaryOperationValue { + SubtractValue(left, right, info) : super(left, right, info); + + bool operator ==(other) { + if (other is !SubtractValue) return false; + return left == other.left && right == other.right; + } + + Value operator -() => right - left; + + Value operator +(Value other) { + if (other.isZero) return this; + Value value = left + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value - right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = other - right; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left + value; + } + return const UnknownValue(); + } + + Value operator -(Value other) { + if (other.isZero) return this; + Value value = left - other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return value - right; + } + // If the result is not simple enough, we try the same approach + // with [right]. + value = right + other; + if (value != const UnknownValue() && value is! BinaryOperationValue) { + return left - value; + } + return const UnknownValue(); + } + + bool get isNegative => left.isNegative && right.isPositive; + bool get isPositive => left.isPositive && right.isNegative; + String toString() => '$left - $right'; +} + +class NegateValue extends Value { + final Value value; + NegateValue(this.value, info) : super(info); + + bool operator ==(other) { + if (other is !NegateValue) return false; + return value == other.value; + } + + Value operator +(other) { + if (other.isZero) return this; + if (other == value) return info.intZero; + if (other is NegateValue) return this - other.value; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(this, -other); + } + return info.newSubtractValue(other, value); + } + if (other is InstructionValue) { + return info.newSubtractValue(other, value); + } + return other - value; + } + + Value operator &(Value other) => const UnknownValue(); + + Value operator -(other) { + if (other.isZero) return this; + if (other is IntValue) { + if (other.isNegative) { + return info.newSubtractValue(-other, value); + } + return info.newSubtractValue(this, other); + } + if (other is InstructionValue) { + return info.newSubtractValue(this, other); + } + if (other is NegateValue) return this + other.value; + return -other - value; + } + + Value operator -() => value; + + bool get isNegative => value.isPositive; + bool get isPositive => value.isNegative; + String toString() => '-$value'; +} + +/** + * A [Range] represents the possible integer values an instruction + * can have, from its [lower] bound to its [upper] bound, both + * included. + */ +class Range { + final Value lower; + final Value upper; + final ValueRangeInfo info; + Range(this.lower, this.upper, this.info); + + Range.unbound(info) : this(const MinIntValue(), const MaxIntValue(), info); + + /** + * Checks if the given values are unknown, and creates a + * range that does not have any unknown values. + */ + Range.normalize(Value low, Value up, info) : this( + low == const UnknownValue() ? const MinIntValue() : low, + up == const UnknownValue() ? const MaxIntValue() : up, + info); + + Range union(Range other) { + return info.newNormalizedRange( + lower.min(other.lower), upper.max(other.upper)); + } + + intersection(Range other) { + Value low = lower.max(other.lower); + Value up = upper.min(other.upper); + // If we could not compute max or min, pick a value in the two + // ranges, with priority to [IntValue]s because they are simpler. + if (low == const UnknownValue()) { + if (lower is IntValue) low = lower; + else if (other.lower is IntValue) low = other.lower; + else low = lower; + } + if (up == const UnknownValue()) { + if (upper is IntValue) up = upper; + else if (other.upper is IntValue) up = other.upper; + else up = upper; + } + return info.newRange(low, up); + } + + Range operator +(Range other) { + return info.newNormalizedRange(lower + other.lower, upper + other.upper); + } + + Range operator -(Range other) { + return info.newNormalizedRange(lower - other.upper, upper - other.lower); + } + + Range operator -() { + return info.newNormalizedRange(-upper, -lower); + } + + Range operator &(Range other) { + if (isSingleValue + && other.isSingleValue + && lower is IntValue + && other.lower is IntValue) { + return info.newRange(lower & other.lower, upper & other.upper); + } + if (isPositive && other.isPositive) { + Value up = upper.min(other.upper); + if (up == const UnknownValue()) { + // If we could not find a trivial bound, just try to use the + // one that is an int. + up = upper is IntValue ? upper : other.upper; + // Make sure we get the same upper bound, whether it's a & b + // or b & a. + if (up is! IntValue && upper != other.upper) up = const MaxIntValue(); + } + return info.newRange(info.intZero, up); + } else if (isPositive) { + return info.newRange(info.intZero, upper); + } else if (other.isPositive) { + return info.newRange(info.intZero, other.upper); + } else { + return info.newUnboundRange(); + } + } + + bool operator ==(other) { + if (other is! Range) return false; + return other.lower == lower && other.upper == upper; + } + + bool operator <(Range other) { + return upper != other.lower && upper.min(other.lower) == upper; + } + + bool operator >(Range other) { + return lower != other.upper && lower.max(other.upper) == lower; + } + + bool operator <=(Range other) { + return upper.min(other.lower) == upper; + } + + bool operator >=(Range other) { + return lower.max(other.upper) == lower; + } + + bool get isNegative => upper.isNegative; + bool get isPositive => lower.isPositive; + bool get isSingleValue => lower == upper; + + String toString() => '[$lower, $upper]'; +} + +/** + * Visits the graph in dominator order, and computes value ranges for + * integer instructions. While visiting the graph, this phase also + * removes unnecessary bounds checks, and comparisons that are proven + * to be true or false. + */ +class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase { + String get name => 'SSA value range builder'; + + /** + * List of [HRangeConversion] instructions created by the phase. We + * save them here in order to remove them once the phase is done. + */ + final List<HRangeConversion> conversions = <HRangeConversion>[]; + + /** + * Value ranges for integer instructions. This map gets populated by + * the dominator tree visit. + */ + final Map<HInstruction, Range> ranges = new Map<HInstruction, Range>(); + + final ConstantSystem constantSystem; + final HTypeMap types; + final ValueRangeInfo info; + + CodegenWorkItem work; + HGraph graph; + + SsaValueRangeAnalyzer(constantSystem, this.types, this.work) + : info = new ValueRangeInfo(constantSystem), + this.constantSystem = constantSystem; + + void visitGraph(HGraph graph) { + this.graph = graph; + visitDominatorTree(graph); + // We remove the range conversions after visiting the graph so + // that the graph does not get polluted with these instructions + // only necessary for this phase. + removeRangeConversion(); + } + + void removeRangeConversion() { + conversions.forEach((HRangeConversion instruction) { + instruction.block.rewrite(instruction, instruction.inputs[0]);; + instruction.block.remove(instruction); + }); + } + + void visitBasicBlock(HBasicBlock block) { + + void visit(HInstruction instruction) { + Range range = instruction.accept(this); + if (instruction.isInteger(types)) { + assert(range != null); + ranges[instruction] = range; + } + } + + block.forEachPhi(visit); + block.forEachInstruction(visit); + } + + Range visitInstruction(HInstruction instruction) { + return info.newUnboundRange(); + } + + Range visitParameterValue(HParameterValue parameter) { + if (!parameter.isInteger(types)) return info.newUnboundRange(); + Value value = info.newInstructionValue(parameter); + return info.newRange(value, value); + } + + Range visitPhi(HPhi phi) { + if (!phi.isInteger(types)) return info.newUnboundRange(); + if (phi.block.isLoopHeader()) { + Range range = tryInferLoopPhiRange(phi); + if (range == null) return info.newUnboundRange(); + return range; + } + + Range range = ranges[phi.inputs[0]]; + for (int i = 1; i < phi.inputs.length; i++) { + range = range.union(ranges[phi.inputs[i]]); + } + return range; + } + + Range tryInferLoopPhiRange(HPhi phi) { + HInstruction update = phi.inputs[1]; + return update.accept(new LoopUpdateRecognizer(phi, ranges, types, info)); + } + + Range visitConstant(HConstant constant) { + if (!constant.isInteger(types)) return info.newUnboundRange(); + IntConstant constantInt = constant.constant; + Value value = info.newIntValue(constantInt.value); + return info.newRange(value, value); + } + + Range visitFieldGet(HFieldGet fieldGet) { + if (!fieldGet.isInteger(types)) return info.newUnboundRange(); + if (!fieldGet.receiver.isIndexablePrimitive(types)) { + return visitInstruction(fieldGet); + } + LengthValue value = info.newLengthValue(fieldGet); + // We know this range is above zero. To simplify the analysis, we + // put the zero value as the lower bound of this range. This + // allows to easily remove the second bound check in the following + // expression: a[1] + a[0]. + return info.newRange(info.intZero, value); + } + + Range visitBoundsCheck(HBoundsCheck check) { + // Save the next instruction, in case the check gets removed. + HInstruction next = check.next; + Range indexRange = ranges[check.index]; + Range lengthRange = ranges[check.length]; + + // Check if the index is strictly below the upper bound of the length + // range. + Value maxIndex = lengthRange.upper - info.intOne; + bool belowLength = maxIndex != const MaxIntValue() + && indexRange.upper.min(maxIndex) == indexRange.upper; + + // Check if the index is strictly below the lower bound of the length + // range. + belowLength = belowLength + || (indexRange.upper != lengthRange.lower + && indexRange.upper.min(lengthRange.lower) == indexRange.upper); + if (indexRange.isPositive && belowLength) { + check.block.rewrite(check, check.index); + check.block.remove(check); + } else if (indexRange.isNegative || lengthRange < indexRange) { + check.staticChecks = HBoundsCheck.ALWAYS_FALSE; + // The check is always false, and whatever instruction it + // dominates is dead code. + return indexRange; + } else if (indexRange.isPositive) { + check.staticChecks = HBoundsCheck.ALWAYS_ABOVE_ZERO; + } else if (belowLength) { + check.staticChecks = HBoundsCheck.ALWAYS_BELOW_LENGTH; + } + + if (indexRange.isPositive) { + // If the test passes, we know the lower bound of the length is + // greater or equal than the lower bound of the index. + Value low = lengthRange.lower.max(indexRange.lower); + if (low != const UnknownValue()) { + HInstruction instruction = + createRangeConversion(next, check.length); + ranges[instruction] = info.newRange(low, lengthRange.upper); + } + } + + if (!belowLength) { + // Update the range of the index if using the maximum index + // narrows it. + Range newIndexRange = indexRange.intersection( + info.newRange(info.intZero, maxIndex)); + if (indexRange == newIndexRange) return indexRange; + HInstruction instruction = createRangeConversion(next, check.index); + ranges[instruction] = newIndexRange; + return newIndexRange; + } + + return indexRange; + } + + Range visitRelational(HRelational relational) { + HInstruction right = relational.right; + HInstruction left = relational.left; + if (!left.isInteger(types)) return info.newUnboundRange(); + if (!right.isInteger(types)) return info.newUnboundRange(); + BinaryOperation operation = relational.operation(constantSystem); + Range rightRange = ranges[relational.right]; + Range leftRange = ranges[relational.left]; + + if (relational is HIdentity) { + handleEqualityCheck(relational); + } else if (operation.apply(leftRange, rightRange)) { + relational.block.rewrite( + relational, graph.addConstantBool(true, constantSystem)); + relational.block.remove(relational); + } else if (reverseOperation(operation).apply(leftRange, rightRange)) { + relational.block.rewrite( + relational, graph.addConstantBool(false, constantSystem)); + relational.block.remove(relational); + } + return info.newUnboundRange(); + } + + void handleEqualityCheck(HRelational node) { + Range right = ranges[node.right]; + Range left = ranges[node.left]; + if (left.isSingleValue && right.isSingleValue && left == right) { + node.block.rewrite( + node, graph.addConstantBool(true, constantSystem)); + node.block.remove(node); + } + } + + Range handleBinaryOperation(HBinaryArithmetic instruction) { + if (!instruction.isInteger(types)) return info.newUnboundRange(); + return instruction.operation(constantSystem).apply( + ranges[instruction.left], ranges[instruction.right]); + } + + Range visitAdd(HAdd add) { + return handleBinaryOperation(add); + } + + Range visitSubtract(HSubtract sub) { + return handleBinaryOperation(sub); + } + + Range visitBitAnd(HBitAnd node) { + if (!node.isInteger(types)) return info.newUnboundRange(); + HInstruction right = node.right; + HInstruction left = node.left; + if (left.isInteger(types) && right.isInteger(types)) { + return ranges[left] & ranges[right]; + } + + Range tryComputeRange(HInstruction instruction) { + Range range = ranges[instruction]; + if (range.isPositive) { + return info.newRange(info.intZero, range.upper); + } else if (range.isNegative) { + return info.newRange(range.lower, info.intZero); + } + return info.newUnboundRange(); + } + + if (left.isInteger(types)) { + return tryComputeRange(left); + } else if (right.isInteger(types)) { + return tryComputeRange(right); + } + return info.newUnboundRange(); + } + + Range visitCheck(HCheck instruction) { + if (ranges[instruction.checkedInput] == null) { + return info.newUnboundRange(); + } + return ranges[instruction.checkedInput]; + } + + HInstruction createRangeConversion(HInstruction cursor, + HInstruction instruction) { + HRangeConversion newInstruction = new HRangeConversion(instruction); + conversions.add(newInstruction); + cursor.block.addBefore(cursor, newInstruction); + // Update the users of the instruction dominated by [cursor] to + // use the new instruction, that has an narrower range. + Set<HInstruction> dominatedUsers = instruction.dominatedUsers(cursor); + for (HInstruction user in dominatedUsers) { + user.changeUse(instruction, newInstruction); + } + return newInstruction; + } + + static BinaryOperation reverseOperation(BinaryOperation operation) { + if (operation == const LessOperation()) { + return const GreaterEqualOperation(); + } else if (operation == const LessEqualOperation()) { + return const GreaterOperation(); + } else if (operation == const GreaterOperation()) { + return const LessEqualOperation(); + } else if (operation == const GreaterEqualOperation()) { + return const LessOperation(); + } else { + return null; + } + } + + Range computeConstrainedRange(BinaryOperation operation, + Range leftRange, + Range rightRange) { + Range range; + if (operation == const LessOperation()) { + range = info.newRange( + const MinIntValue(), rightRange.upper - info.intOne); + } else if (operation == const LessEqualOperation()) { + range = info.newRange(const MinIntValue(), rightRange.upper); + } else if (operation == const GreaterOperation()) { + range = info.newRange( + rightRange.lower + info.intOne, const MaxIntValue()); + } else if (operation == const GreaterEqualOperation()) { + range = info.newRange(rightRange.lower, const MaxIntValue()); + } else { + range = info.newUnboundRange(); + } + return range.intersection(leftRange); + } + + Range visitConditionalBranch(HConditionalBranch branch) { + var condition = branch.condition; + // TODO(ngeoffray): Handle complex conditions. + if (condition is !HRelational) return info.newUnboundRange(); + if (condition is HIdentity) return info.newUnboundRange(); + HInstruction right = condition.right; + HInstruction left = condition.left; + if (!left.isInteger(types)) return info.newUnboundRange(); + if (!right.isInteger(types)) return info.newUnboundRange(); + + Range rightRange = ranges[right]; + Range leftRange = ranges[left]; + Operation operation = condition.operation(constantSystem); + Operation reverse = reverseOperation(operation); + // Only update the true branch if this block is the only + // predecessor. + if (branch.trueBranch.predecessors.length == 1) { + assert(branch.trueBranch.predecessors[0] == branch.block); + // Update the true branch to use narrower ranges for [left] and + // [right]. + Range range = computeConstrainedRange(operation, leftRange, rightRange); + if (leftRange != range) { + HInstruction instruction = + createRangeConversion(branch.trueBranch.first, left); + ranges[instruction] = range; + } + + range = computeConstrainedRange(reverse, rightRange, leftRange); + if (rightRange != range) { + HInstruction instruction = + createRangeConversion(branch.trueBranch.first, right); + ranges[instruction] = range; + } + } + + // Only update the false branch if this block is the only + // predecessor. + if (branch.falseBranch.predecessors.length == 1) { + assert(branch.falseBranch.predecessors[0] == branch.block); + // Update the false branch to use narrower ranges for [left] and + // [right]. + Range range = computeConstrainedRange(reverse, leftRange, rightRange); + if (leftRange != range) { + HInstruction instruction = + createRangeConversion(branch.falseBranch.first, left); + ranges[instruction] = range; + } + + range = computeConstrainedRange(operation, rightRange, leftRange); + if (rightRange != range) { + HInstruction instruction = + createRangeConversion(branch.falseBranch.first, right); + ranges[instruction] = range; + } + } + + return info.newUnboundRange(); + } + + Range visitRangeConversion(HRangeConversion conversion) { + return ranges[conversion]; + } +} + +/** + * Recognizes a number of patterns in a loop update instruction and + * tries to infer a range for the loop phi. + */ +class LoopUpdateRecognizer extends HBaseVisitor { + final HPhi loopPhi; + final Map<HInstruction, Range> ranges; + final HTypeMap types; + final ValueRangeInfo info; + LoopUpdateRecognizer(this.loopPhi, this.ranges, this.types, this.info); + + Range visitAdd(HAdd operation) { + Range range = getRangeForRecognizableOperation(operation); + if (range == null) return info.newUnboundRange(); + Range initial = ranges[loopPhi.inputs[0]]; + if (range.isPositive) { + return info.newRange(initial.lower, const MaxIntValue()); + } else if (range.isNegative) { + return info.newRange(const MinIntValue(), initial.upper); + } + return info.newUnboundRange(); + } + + Range visitSubtract(HSubtract operation) { + Range range = getRangeForRecognizableOperation(operation); + if (range == null) return info.newUnboundRange(); + Range initial = ranges[loopPhi.inputs[0]]; + if (range.isPositive) { + return info.newRange(const MinIntValue(), initial.upper); + } else if (range.isNegative) { + return info.newRange(initial.lower, const MaxIntValue()); + } + return info.newUnboundRange(); + } + + Range visitPhi(HPhi phi) { + Range phiRange; + for (HInstruction input in phi.inputs) { + HInstruction instruction = unwrap(input); + // If one of the inputs is the loop phi, then we're only + // interested in the other inputs: a loop phi feeding itself means + // it is not being updated. + if (instruction == loopPhi) continue; + + // If another loop phi is involved, it's too complex to analyze. + if (instruction is HPhi && instruction.block.isLoopHeader()) return null; + + Range inputRange = instruction.accept(this); + if (inputRange == null) return null; + if (phiRange == null) { + phiRange = inputRange; + } else { + phiRange = phiRange.union(inputRange); + } + } + return phiRange; + } + + /** + * If [operation] is recognizable, returns the inferred range. + * Otherwise returns [null]. + */ + Range getRangeForRecognizableOperation(HBinaryArithmetic operation) { + if (!operation.left.isInteger(types)) return null; + if (!operation.right.isInteger(types)) return null; + HInstruction left = unwrap(operation.left); + HInstruction right = unwrap(operation.right); + // We only recognize operations that operate on the loop phi. + bool isLeftLoopPhi = (left == loopPhi); + bool isRightLoopPhi = (right == loopPhi); + if (!isLeftLoopPhi && !isRightLoopPhi) return null; + + var other = isLeftLoopPhi ? right : left; + // If the analysis already computed range for the update, use it. + if (ranges[other] != null) return ranges[other]; + + // We currently only handle constants in updates if the + // update does not have a range. + if (other.isConstant()) { + Value value = info.newIntValue(other.constant.value); + return info.newRange(value, value); + } + return null; + } + + /** + * [HCheck] instructions may check the loop phi. Since we only + * recognize updates on the loop phi, we must [unwrap] the [HCheck] + * instruction to check if it references the loop phi. + */ + HInstruction unwrap(instruction) { + if (instruction is HCheck) return unwrap(instruction.checkedInput); + // [HPhi] might have two different [HCheck] instructions as + // inputs, checking the same instruction. + if (instruction is HPhi && !instruction.block.isLoopHeader()) { + HInstruction result = unwrap(instruction.inputs[0]); + for (int i = 1; i < instruction.inputs.length; i++) { + if (result != unwrap(instruction.inputs[i])) return instruction; + } + return result; + } + return instruction; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart new file mode 100644 index 0000000..d565de7 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart
@@ -0,0 +1,157 @@ +// 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. + +part of ssa; + +class ValueSet { + int size = 0; + List<HInstruction> table; + ValueSetNode collisions; + ValueSet() : table = new List<HInstruction>.fixedLength(8); + + bool get isEmpty => size == 0; + int get length => size; + + void add(HInstruction instruction) { + assert(lookup(instruction) == null); + int hashCode = instruction.gvnHashCode(); + int capacity = table.length; + // Resize when half of the hash table is in use. + if (size >= capacity >> 1) { + capacity = capacity << 1; + resize(capacity); + } + // Try to insert in the hash table first. + int index = hashCode % capacity; + if (table[index] == null) { + table[index] = instruction; + } else { + collisions = new ValueSetNode(instruction, hashCode, collisions); + } + size++; + } + + HInstruction lookup(HInstruction instruction) { + int hashCode = instruction.gvnHashCode(); + int index = hashCode % table.length; + // Look in the hash table. + HInstruction probe = table[index]; + if (probe != null && probe.gvnEquals(instruction)) return probe; + // Look in the collisions list. + for (ValueSetNode node = collisions; node != null; node = node.next) { + if (node.hashCode == hashCode) { + HInstruction cached = node.value; + if (cached.gvnEquals(instruction)) return cached; + } + } + return null; + } + + void kill(int flags) { + if (flags == 0) return; + int depends = HInstruction.computeDependsOnFlags(flags); + // Kill in the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null && (instruction.flags & depends) != 0) { + table[index] = null; + size--; + } + } + // Kill in the collisions list. + ValueSetNode previous = null; + ValueSetNode current = collisions; + while (current != null) { + ValueSetNode next = current.next; + HInstruction cached = current.value; + if ((cached.flags & depends) != 0) { + if (previous == null) { + collisions = next; + } else { + previous.next = next; + } + size--; + } else { + previous = current; + } + current = next; + } + } + + ValueSet copy() { + return copyTo(new ValueSet(), table, collisions); + } + + List<HInstruction> toList() { + return copyTo(<HInstruction>[], table, collisions); + } + + // Copy the instructions in value set defined by [table] and + // [collisions] into [other] and returns [other]. The copy is done + // by iterating through the hash table and the collisions list and + // calling [:other.add:]. + static copyTo(var other, List<HInstruction> table, ValueSetNode collisions) { + // Copy elements from the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null) other.add(instruction); + } + // Copy elements from the collision list. + ValueSetNode current = collisions; + while (current != null) { + // TODO(kasperl): Maybe find a way of reusing the hash code + // rather than recomputing it every time. + other.add(current.value); + current = current.next; + } + return other; + } + + ValueSet intersection(ValueSet other) { + if (size > other.size) return other.intersection(this); + ValueSet result = new ValueSet(); + // Look in the hash table. + for (int index = 0, length = table.length; index < length; index++) { + HInstruction instruction = table[index]; + if (instruction != null && other.lookup(instruction) != null) { + result.add(instruction); + } + } + // Look in the collision list. + ValueSetNode current = collisions; + while (current != null) { + HInstruction value = current.value; + if (other.lookup(value) != null) { + result.add(value); + } + current = current.next; + } + return result; + } + + void resize(int capacity) { + var oldSize = size; + var oldTable = table; + var oldCollisions = collisions; + // Reset the table with a bigger capacity. + assert(capacity > table.length); + size = 0; + table = new List<HInstruction>.fixedLength(capacity); + collisions = null; + // Add the old instructions to the new table. + copyTo(this, oldTable, oldCollisions); + // Make sure we preserved all elements and that no resizing + // happened as part of this resizing. + assert(size == oldSize); + assert(table.length == capacity); + } +} + +class ValueSetNode { + final HInstruction value; + final int hash; + int get hashCode => hash; + ValueSetNode next; + ValueSetNode(this.value, this.hash, this.next); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart new file mode 100644 index 0000000..34f960d --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart
@@ -0,0 +1,669 @@ +// 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. + +part of ssa; + +/** + * The [LiveRange] class covers a range where an instruction is live. + */ +class LiveRange { + final int start; + // [end] is not final because it can be updated due to loops. + int end; + LiveRange(this.start, this.end) { + assert(start <= end); + } + + String toString() => '[$start $end['; +} + +/** + * The [LiveInterval] class contains the list of ranges where an + * instruction is live. + */ +class LiveInterval { + /** + * The id where the instruction is defined. + */ + int start; + final List<LiveRange> ranges; + LiveInterval() : ranges = <LiveRange>[]; + + /** + * Update all ranges that are contained in [from, to[ to + * die at [to]. + */ + void loopUpdate(int from, int to) { + for (LiveRange range in ranges) { + if (from <= range.start && range.end < to) { + range.end = to; + } + } + } + + /** + * Add a new range to this interval. + */ + void add(LiveRange interval) { + ranges.add(interval); + } + + /** + * Returns true if one of the ranges of this interval dies at [at]. + */ + bool diesAt(int at) { + for (LiveRange range in ranges) { + if (range.end == at) return true; + } + return false; + } + + String toString() { + List<String> res = new List<String>(); + for (final interval in ranges) res.add(interval.toString()); + return '(${Strings.join(res, ', ')})'; + } +} + +/** + * The [LiveEnvironment] class contains the liveIn set of a basic + * block. A liveIn set of a block contains the instructions that are + * live when entering that block. + */ +class LiveEnvironment { + /** + * The instruction id where the basic block starts. See + * [SsaLiveIntervalBuilder.instructionId]. + */ + int startId; + + /** + * The instruction id where the basic block ends. + */ + final int endId; + + /** + * Loop markers that will be updated once the loop header is + * visited. The liveIn set of the loop header will be merged into this + * environment. [loopMarkers] is a mapping from block header to the + * end instruction id of the loop exit block. + */ + final Map<HBasicBlock, int> loopMarkers; + + /** + * The instructions that are live in this basic block. The values of + * the map contain the instruction ids where the instructions die. + * It will be used when adding a range to the live interval of an + * instruction. + */ + final Map<HInstruction, int> liveInstructions; + + /** + * Map containing the live intervals of instructions. + */ + final Map<HInstruction, LiveInterval> liveIntervals; + + LiveEnvironment(this.liveIntervals, this.endId) + : liveInstructions = new Map<HInstruction, int>(), + loopMarkers = new Map<HBasicBlock, int>(); + + /** + * Remove an instruction from the liveIn set. This method also + * updates the live interval of [instruction] to contain the new + * range: [id, / id contained in [liveInstructions] /]. + */ + void remove(HInstruction instruction, int id) { + // Special case the HCheck instruction to have the same live + // interval as the instruction it is checking. + if (instruction is HCheck) { + var input = instruction.checkedInput; + while (input is HCheck) input = input.checkedInput; + liveIntervals.putIfAbsent(input, () => new LiveInterval()); + // Unconditionally force the live interval of the HCheck to + // be the live interval of the instruction it is checking. + liveIntervals[instruction] = liveIntervals[input]; + } else { + LiveInterval range = liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + int lastId = liveInstructions[instruction]; + // If [lastId] is null, then this instruction is not being used. + range.add(new LiveRange(id, lastId == null ? id : lastId)); + // The instruction is defined at [id]. + range.start = id; + } + liveInstructions.remove(instruction); + } + + /** + * Add [instruction] to the liveIn set. If the instruction is not + * already in the set, we save the id where it dies. + */ + void add(HInstruction instruction, int userId) { + // Note that we are visiting the graph in post-dominator order, so + // the first time we see a variable is when it dies. + liveInstructions.putIfAbsent(instruction, () => userId); + if (instruction is HCheck) { + // Special case the HCheck instruction to mark the actual + // checked instruction live. + var input = instruction.checkedInput; + while (input is HCheck) input = input.checkedInput; + liveInstructions.putIfAbsent(input, () => userId); + } + } + + /** + * Merge this environment with [other]. Update the end id of + * instructions in case they are different between this and [other]. + */ + void mergeWith(LiveEnvironment other) { + other.liveInstructions.forEach((HInstruction instruction, int existingId) { + // If both environments have the same instruction id of where + // [instruction] dies, there is no need to update the live + // interval of [instruction]. For example the if block and the + // else block have the same end id for an instruction that is + // being used in the join block and defined before the if/else. + if (existingId == endId) return; + LiveInterval range = liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + range.add(new LiveRange(other.startId, existingId)); + liveInstructions[instruction] = endId; + }); + other.loopMarkers.forEach((k, v) { loopMarkers[k] = v; }); + } + + void addLoopMarker(HBasicBlock header, int id) { + assert(!loopMarkers.containsKey(header)); + loopMarkers[header] = id; + } + + void removeLoopMarker(HBasicBlock header) { + assert(loopMarkers.containsKey(header)); + loopMarkers.remove(header); + } + + bool get isEmpty => liveInstructions.isEmpty && loopMarkers.isEmpty; + bool contains(HInstruction instruction) => + liveInstructions.containsKey(instruction); + String toString() => liveInstructions.toString(); +} + +/** + * Builds the live intervals of each instruction. The algorithm visits + * the graph post-dominator tree to find the last uses of an + * instruction, and computes the liveIns of each basic block. + */ +class SsaLiveIntervalBuilder extends HBaseVisitor { + final Compiler compiler; + final Set<HInstruction> generateAtUseSite; + + /** + * A counter to assign start and end ids to live ranges. The initial + * value is not relevant. Note that instructionId goes downward to ease + * reasoning about live ranges (the first instruction of a graph has + * the lowest id). + */ + int instructionId = 0; + + /** + * The liveIns of basic blocks. + */ + final Map<HBasicBlock, LiveEnvironment> liveInstructions; + + /** + * The live intervals of instructions. + */ + final Map<HInstruction, LiveInterval> liveIntervals; + + SsaLiveIntervalBuilder(this.compiler, this.generateAtUseSite) + : liveInstructions = new Map<HBasicBlock, LiveEnvironment>(), + liveIntervals = new Map<HInstruction, LiveInterval>(); + + void visitGraph(HGraph graph) { + visitPostDominatorTree(graph); + if (!liveInstructions[graph.entry].isEmpty) { + compiler.internalError('LiveIntervalBuilder', + node: compiler.currentElement.parseNode(compiler)); + } + } + + void markInputsAsLiveInEnvironment(HInstruction instruction, + LiveEnvironment environment) { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + markAsLiveInEnvironment(instruction.inputs[i], environment); + } + } + + void markAsLiveInEnvironment(HInstruction instruction, + LiveEnvironment environment) { + if (environment.contains(instruction)) return; + environment.add(instruction, instructionId); + // HPhis are treated specially. + if (generateAtUseSite.contains(instruction) && instruction is !HPhi) { + markInputsAsLiveInEnvironment(instruction, environment); + } + } + + void visitBasicBlock(HBasicBlock block) { + LiveEnvironment environment = + new LiveEnvironment(liveIntervals, instructionId); + + // Add to the environment the liveIn of its successor, as well as + // the inputs of the phis of the successor that flow from this block. + for (int i = 0; i < block.successors.length; i++) { + HBasicBlock successor = block.successors[i]; + LiveEnvironment successorEnv = liveInstructions[successor]; + if (successorEnv != null) { + environment.mergeWith(successorEnv); + } else { + environment.addLoopMarker(successor, instructionId); + } + + int index = successor.predecessors.indexOf(block); + for (HPhi phi = successor.phis.first; phi != null; phi = phi.next) { + markAsLiveInEnvironment(phi.inputs[index], environment); + } + } + + // Iterate over all instructions to remove an instruction from the + // environment and add its inputs. + HInstruction instruction = block.last; + while (instruction != null) { + environment.remove(instruction, instructionId); + markInputsAsLiveInEnvironment(instruction, environment); + instruction = instruction.previous; + instructionId--; + } + + // We just remove the phis from the environment. The inputs of the + // phis will be put in the environment of the predecessors. + for (HPhi phi = block.phis.first; phi != null; phi = phi.next) { + environment.remove(phi, instructionId); + } + + // Save the liveInstructions of that block. + environment.startId = instructionId + 1; + liveInstructions[block] = environment; + + // If the block is a loop header, we can remove the loop marker, + // because it will just recompute the loop phis. + // We also check if this loop header has any back edges. If not, + // we know there is no loop marker for it. + if (block.isLoopHeader() && block.predecessors.length > 1) { + updateLoopMarker(block); + } + } + + void updateLoopMarker(HBasicBlock header) { + LiveEnvironment env = liveInstructions[header]; + int lastId = env.loopMarkers[header]; + // Update all instructions that are liveIns in [header] to have a + // range that covers the loop. + env.liveInstructions.forEach((HInstruction instruction, int id) { + LiveInterval range = env.liveIntervals.putIfAbsent( + instruction, () => new LiveInterval()); + range.loopUpdate(env.startId, lastId); + env.liveInstructions[instruction] = lastId; + }); + + env.removeLoopMarker(header); + + // Update all liveIns set to contain the liveIns of [header]. + liveInstructions.forEach((HBasicBlock block, LiveEnvironment other) { + if (other.loopMarkers.containsKey(header)) { + env.liveInstructions.forEach((HInstruction instruction, int id) { + other.liveInstructions[instruction] = id; + }); + other.removeLoopMarker(header); + env.loopMarkers.forEach((k, v) { other.loopMarkers[k] = v; }); + } + }); + } +} + +/** + * Represents a copy from one instruction to another. The codegen + * also uses this class to represent a copy from one variable to + * another. + */ +class Copy { + final source; + final destination; + Copy(this.source, this.destination); + String toString() => '$destination <- $source'; +} + +/** + * A copy handler contains the copies that a basic block needs to do + * after executing all its instructions. + */ +class CopyHandler { + /** + * The copies from an instruction to a phi of the successor. + */ + final List<Copy> copies; + + /** + * Assignments from an instruction that does not need a name (e.g. a + * constant) to the phi of a successor. + */ + final List<Copy> assignments; + + CopyHandler() + : copies = new List<Copy>(), + assignments = new List<Copy>(); + + void addCopy(HInstruction source, HInstruction destination) { + copies.add(new Copy(source, destination)); + } + + void addAssignment(HInstruction source, HInstruction destination) { + assignments.add(new Copy(source, destination)); + } + + String toString() => 'Copies: $copies, assignments: $assignments'; + bool get isEmpty => copies.isEmpty && assignments.isEmpty; +} + +/** + * Contains the mapping between instructions and their names for code + * generation, as well as the [CopyHandler] for each basic block. + */ +class VariableNames { + final Map<HInstruction, String> ownName; + final Map<HBasicBlock, CopyHandler> copyHandlers; + + // Used to control heuristic that determines how local variables are declared. + final Set<String> allUsedNames; + /** + * Name that is used as a temporary to break cycles in + * parallel copies. We make sure this name is not being used + * anywhere by reserving it when we allocate names for instructions. + */ + final String swapTemp; + /** + * Name that is used in bailout code. We make sure this name is not being used + * anywhere by reserving it when we allocate names for instructions. + */ + final String stateName; + + String getSwapTemp() { + allUsedNames.add(swapTemp); + return swapTemp; + } + + VariableNames() + : ownName = new Map<HInstruction, String>(), + copyHandlers = new Map<HBasicBlock, CopyHandler>(), + allUsedNames = new Set<String>(), + swapTemp = computeFreshWithPrefix("t"), + stateName = computeFreshWithPrefix("state"); + + int get numberOfVariables => allUsedNames.length; + + /** Returns a fresh variable with the given prefix. */ + static String computeFreshWithPrefix(String prefix) { + String name = '${prefix}0'; + int i = 1; + return name; + } + + String getName(HInstruction instruction) { + return ownName[instruction]; + } + + CopyHandler getCopyHandler(HBasicBlock block) { + return copyHandlers[block]; + } + + void addNameUsed(String name) => allUsedNames.add(name); + + bool hasName(HInstruction instruction) => ownName.containsKey(instruction); + + void addCopy(HBasicBlock block, HInstruction source, HPhi destination) { + CopyHandler handler = + copyHandlers.putIfAbsent(block, () => new CopyHandler()); + handler.addCopy(source, destination); + } + + void addAssignment(HBasicBlock block, HInstruction source, HPhi destination) { + CopyHandler handler = + copyHandlers.putIfAbsent(block, () => new CopyHandler()); + handler.addAssignment(source, destination); + } +} + +/** + * Allocates variable names for instructions, making sure they don't collide. + */ +class VariableNamer { + final VariableNames names; + final Compiler compiler; + final Set<String> usedNames; + final List<String> freeTemporaryNames; + int temporaryIndex = 0; + static final RegExp regexp = new RegExp('t[0-9]+'); + + VariableNamer(LiveEnvironment environment, + this.names, + this.compiler) + : usedNames = new Set<String>(), + freeTemporaryNames = new List<String>() { + // [VariableNames.swapTemp] is used when there is a cycle in a copy handler. + // Therefore we make sure no one uses it. + usedNames.add(names.swapTemp); + // [VariableNames.stateName] is being used throughout a bailout function. + // Whenever a bailout-target is reached we set the state-variable to 0. We + // must therefore not have any local variable that could clash with the + // state variable. + // Therefore we make sure no one uses it at any time. + usedNames.add(names.stateName); + + // All liveIns instructions must have a name at this point, so we + // add them to the list of used names. + environment.liveInstructions.forEach((HInstruction instruction, int index) { + String name = names.getName(instruction); + if (name != null) { + usedNames.add(name); + names.addNameUsed(name); + } + }); + } + + String allocateWithHint(String originalName) { + int i = 0; + JavaScriptBackend backend = compiler.backend; + String name = backend.namer.safeVariableName(originalName); + while (usedNames.contains(name)) { + name = backend.namer.safeVariableName('$originalName${i++}'); + } + return name; + } + + String allocateTemporary() { + while (!freeTemporaryNames.isEmpty) { + String name = freeTemporaryNames.removeLast(); + if (!usedNames.contains(name)) return name; + } + String name = 't${temporaryIndex++}'; + while (usedNames.contains(name)) name = 't${temporaryIndex++}'; + return name; + } + + HPhi firstPhiUserWithElement(HInstruction instruction) { + for (HInstruction user in instruction.usedBy) { + if (user is HPhi && user.sourceElement != null) { + return user; + } + } + return null; + } + + String allocateName(HInstruction instruction) { + String name; + if (instruction is HCheck) { + // Special case this instruction to use the name of its + // input if it has one. + var temp = instruction; + do { + temp = temp.checkedInput; + name = names.ownName[temp]; + } while (name == null && temp is HCheck); + if (name != null) return addAllocatedName(instruction, name); + } + + if (instruction.sourceElement != null) { + name = allocateWithHint(instruction.sourceElement.name.slowToString()); + } else { + // We could not find an element for the instruction. If the + // instruction is used by a phi, try to use the name of the phi. + // Otherwise, just allocate a temporary name. + HPhi phi = firstPhiUserWithElement(instruction); + if (phi != null) { + name = allocateWithHint(phi.sourceElement.name.slowToString()); + } else { + name = allocateTemporary(); + } + } + return addAllocatedName(instruction, name); + } + + String addAllocatedName(HInstruction instruction, String name) { + usedNames.add(name); + names.addNameUsed(name); + names.ownName[instruction] = name; + return name; + } + + /** + * Frees [instruction]'s name so it can be used for other instructions. + */ + void freeName(HInstruction instruction) { + String ownName = names.ownName[instruction]; + if (ownName != null) { + // We check if we have already looked for temporary names + // because if we haven't, chances are the temporary we allocate + // in this block can match a phi with the same name in the + // successor block. + if (temporaryIndex != 0 && regexp.hasMatch(ownName)) { + freeTemporaryNames.addLast(ownName); + } + usedNames.remove(ownName); + } + } +} + +/** + * Visits all blocks in the graph, sets names to instructions, and + * creates the [CopyHandler] for each block. This class needs to have + * the liveIns set as well as all the live intervals of instructions. + * It visits the graph in dominator order, so that at each entry of a + * block, the instructions in its liveIns set have names. + * + * When visiting a block, it goes through all instructions. For each + * instruction, it frees the names of the inputs that die at that + * instruction, and allocates a name to the instruction. For each phi, + * it adds a copy to the CopyHandler of the corresponding predecessor. + */ +class SsaVariableAllocator extends HBaseVisitor { + + final Compiler compiler; + final Map<HBasicBlock, LiveEnvironment> liveInstructions; + final Map<HInstruction, LiveInterval> liveIntervals; + final Set<HInstruction> generateAtUseSite; + + final VariableNames names; + + SsaVariableAllocator(this.compiler, + this.liveInstructions, + this.liveIntervals, + this.generateAtUseSite) + : this.names = new VariableNames(); + + void visitGraph(HGraph graph) { + visitDominatorTree(graph); + } + + void visitBasicBlock(HBasicBlock block) { + VariableNamer namer = new VariableNamer( + liveInstructions[block], names, compiler); + + block.forEachPhi((HPhi phi) { + handlePhi(phi, namer); + }); + + block.forEachInstruction((HInstruction instruction) { + handleInstruction(instruction, namer); + }); + } + + /** + * Returns whether [instruction] needs a name. Instructions that + * have no users or that are generated at use site does not need a name. + */ + bool needsName(HInstruction instruction) { + if (instruction is HThis) return false; + if (instruction is HParameterValue) return true; + if (instruction.usedBy.isEmpty) return false; + if (generateAtUseSite.contains(instruction)) return false; + // A [HCheck] instruction that has control flow needs a name only if its + // checked input needs a name (e.g. a check [HConstant] does not + // need a name). + if (instruction is HCheck && instruction.isControlFlow()) { + HCheck check = instruction; + return needsName(instruction.checkedInput); + } + return true; + } + + /** + * Returns whether [instruction] dies at the instruction [at]. + */ + bool diesAt(HInstruction instruction, HInstruction at) { + LiveInterval atInterval = liveIntervals[at]; + LiveInterval instructionInterval = liveIntervals[instruction]; + int start = atInterval.start; + return instructionInterval.diesAt(start); + } + + void handleInstruction(HInstruction instruction, VariableNamer namer) { + // TODO(ager): We cannot perform this check to free names for + // HCheck instructions because they are special cased to have the + // same live intervals as the instruction they are checking. This + // includes sharing the start id with the checked + // input. Therefore, for HCheck(checkedInput, otherInput) we would + // end up checking that otherInput dies not here, but at the + // location of checkedInput. We should preserve the start id for + // the check instruction. + if (instruction is! HCheck) { + for (int i = 0, len = instruction.inputs.length; i < len; i++) { + HInstruction input = instruction.inputs[i]; + // If [input] has a name, and its use here is the last use, free + // its name. + if (needsName(input) && diesAt(input, instruction)) { + namer.freeName(input); + } + } + } + + if (needsName(instruction)) { + namer.allocateName(instruction); + } + } + + void handlePhi(HPhi phi, VariableNamer namer) { + if (!needsName(phi)) return; + + for (int i = 0; i < phi.inputs.length; i++) { + HInstruction input = phi.inputs[i]; + HBasicBlock predecessor = phi.block.predecessors[i]; + if (!needsName(input)) { + names.addAssignment(predecessor, input, phi); + } else { + names.addCopy(predecessor, input, phi); + } + } + + namer.allocateName(phi); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart new file mode 100644 index 0000000..ef294bd --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart
@@ -0,0 +1,214 @@ +// 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. + +// Check the validity of string literals. + +library stringvalidator; + +import "dart:collection"; + +import "dart2jslib.dart"; +import "tree/tree.dart"; +import "elements/elements.dart"; +import "util/characters.dart"; +import "scanner/scannerlib.dart" show Token; + +class StringValidator { + final DiagnosticListener listener; + + StringValidator(this.listener); + + DartString validateQuotedString(Token token) { + SourceString source = token.value; + StringQuoting quoting = quotingFromString(source); + int leftQuote = quoting.leftQuoteLength; + int rightQuote = quoting.rightQuoteLength; + SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); + return validateString(token, + token.charOffset + leftQuote, + content, + quoting); + } + + DartString validateInterpolationPart(Token token, StringQuoting quoting, + {bool isFirst: false, + bool isLast: false}) { + SourceString source = token.value; + int leftQuote = 0; + int rightQuote = 0; + if (isFirst) leftQuote = quoting.leftQuoteLength; + if (isLast) rightQuote = quoting.rightQuoteLength; + SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); + return validateString(token, + token.charOffset + leftQuote, + content, + quoting); + } + + static StringQuoting quotingFromString(SourceString sourceString) { + Iterator<int> source = sourceString.iterator; + bool raw = false; + int quoteLength = 1; + source.moveNext(); + int quoteChar = source.current; + if (quoteChar == $r) { + raw = true; + source.moveNext(); + quoteChar = source.current; + } + assert(quoteChar == $SQ || quoteChar == $DQ); + // String has at least one quote. Check it if has three. + // If it only have two, the string must be an empty string literal, + // and end after the second quote. + bool multiline = false; + if (source.moveNext() && source.current == quoteChar && source.moveNext()) { + int code = source.current; + assert(code == quoteChar); // If not, there is a bug in the parser. + quoteLength = 3; + // Check if a multiline string starts with a newline (CR, LF or CR+LF). + if (source.moveNext()) { + code = source.current; + if (code == $CR) { + quoteLength += 1; + if (source.moveNext() && source.current == $LF) { + quoteLength += 1; + } + } else if (code == $LF) { + quoteLength += 1; + } + } + } + return StringQuoting.getQuoting(quoteChar, raw, quoteLength); + } + + void stringParseError(String message, Token token, int offset) { + listener.cancel("$message @ $offset", token : token); + } + + /** + * Validates the escape sequences and special characters of a string literal. + * Returns a DartString if valid, and null if not. + */ + DartString validateString(Token token, + int startOffset, + SourceString string, + StringQuoting quoting) { + // We need to check for invalid x and u escapes, for line + // terminators in non-multiline strings, and for invalid Unicode + // scalar values (either directly or as u-escape values). We also check + // for unpaired UTF-16 surrogates. + int length = 0; + int index = startOffset; + bool containsEscape = false; + bool previousWasLeadSurrogate = false; + bool invalidUtf16 = false; + for(HasNextIterator<int> iter = new HasNextIterator(string.iterator); + iter.hasNext; + length++) { + index++; + int code = iter.next(); + if (code == $BACKSLASH) { + if (quoting.raw) continue; + containsEscape = true; + if (!iter.hasNext) { + stringParseError("Incomplete escape sequence",token, index); + return null; + } + index++; + code = iter.next(); + if (code == $x) { + for (int i = 0; i < 2; i++) { + if (!iter.hasNext) { + stringParseError("Incomplete escape sequence", token, index); + return null; + } + index++; + code = iter.next(); + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + } + // A two-byte hex escape can't generate an invalid value. + continue; + } else if (code == $u) { + int escapeStart = index - 1; + index++; + code = iter.hasNext ? iter.next() : 0; + int value = 0; + if (code == $OPEN_CURLY_BRACKET) { + // expect 1-6 hex digits. + int count = 0; + while (iter.hasNext) { + code = iter.next(); + index++; + if (code == $CLOSE_CURLY_BRACKET) { + break; + } + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + count++; + value = value * 16 + hexDigitValue(code); + } + if (code != $CLOSE_CURLY_BRACKET || count == 0 || count > 6) { + int errorPosition = index - count; + if (count > 6) errorPosition += 6; + stringParseError("Invalid character in escape sequence", + token, errorPosition); + return null; + } + } else { + // Expect four hex digits, including the one just read. + for (int i = 0; i < 4; i++) { + if (i > 0) { + if (iter.hasNext) { + index++; + code = iter.next(); + } else { + code = 0; + } + } + if (!isHexDigit(code)) { + stringParseError("Invalid character in escape sequence", + token, index); + return null; + } + value = value * 16 + hexDigitValue(code); + } + } + code = value; + } + } + if (code >= 0x10000) length++; + // This handles both unescaped characters and the value of unicode + // escapes. + if (previousWasLeadSurrogate) { + if (!isUtf16TrailSurrogate(code)) { + invalidUtf16 = true; + break; + } + previousWasLeadSurrogate = false; + } else if (isUtf16LeadSurrogate(code)) { + previousWasLeadSurrogate = true; + } else if (!isUnicodeScalarValue(code)) { + invalidUtf16 = true; + break; + } + } + if (previousWasLeadSurrogate || invalidUtf16) { + stringParseError("Invalid Utf16 surrogate", token, index); + return null; + } + // String literal successfully validated. + if (quoting.raw || !containsEscape) { + // A string without escapes could just as well have been raw. + return new DartString.rawString(string, length); + } + return new DartString.escapedString(string, length); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart new file mode 100644 index 0000000..ea56771 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart
@@ -0,0 +1,321 @@ +// 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. + +library parser; + +import 'dart:io'; +import 'dart:scalarlist'; + +import 'dart:utf'; + +import '../elements/elements.dart'; +import '../scanner/scanner_implementation.dart'; +import '../scanner/scannerlib.dart'; +import '../tree/tree.dart'; +import '../util/characters.dart'; +import '../source_file.dart'; +import '../ssa/ssa.dart'; + +import '../../compiler.dart' as api; + +part '../diagnostic_listener.dart'; +part '../scanner/byte_array_scanner.dart'; +part '../scanner/byte_strings.dart'; + +int charCount = 0; +Stopwatch stopwatch; + +void main() { + toolMain(new Options().arguments); +} + +void toolMain(List<String> arguments) { + filesWithCrashes = []; + stopwatch = new Stopwatch(); + MyOptions options = new MyOptions(); + + void printStats() { + int kb = (charCount / 1024).round().toInt(); + String stats = + '$classCount classes (${kb}Kb) in ${stopwatch.elapsedMilliseconds}ms'; + if (errorCount != 0) { + stats = '$stats with $errorCount errors'; + } + if (options.diet) { + print('Diet parsed $stats.'); + } else { + print('Parsed $stats.'); + } + if (filesWithCrashes.length != 0) { + print('The following ${filesWithCrashes.length} files caused a crash:'); + for (String file in filesWithCrashes) { + print(file); + } + } + } + + for (String argument in arguments) { + if (argument == "--diet") { + options.diet = true; + continue; + } + if (argument == "--throw") { + options.throwOnError = true; + continue; + } + if (argument == "--scan-only") { + options.scanOnly = true; + continue; + } + if (argument == "--read-only") { + options.readOnly = true; + continue; + } + if (argument == "--ast") { + options.buildAst = true; + continue; + } + if (argument == "-") { + parseFilesFrom(stdin, options, printStats); + return; + } + stopwatch.start(); + parseFile(argument, options); + stopwatch.stop(); + } + + printStats(); +} + +void parseFile(String filename, MyOptions options) { + List<int> bytes = read(filename); + charCount += bytes.length; + if (options.readOnly) return; + MySourceFile file = new MySourceFile(filename, bytes); + final Listener listener = options.buildAst + ? new MyNodeListener(file, options) + : new MyListener(file); + final Parser parser = options.diet + ? new PartialParser(listener) + : new Parser(listener); + try { + Token token = scan(file); + if (!options.scanOnly) parser.parseUnit(token); + } on ParserError catch (ex) { + if (options.throwOnError) { + throw; + } else { + print(ex); + } + } catch (ex) { + print('Error in file: $filename'); + throw; + } + if (options.buildAst) { + MyNodeListener l = listener; + if (!l.nodes.isEmpty) { + String message = 'Stack not empty after parsing'; + print(formatError(message, l.nodes.head.getBeginToken(), + l.nodes.head.getEndToken(), file)); + throw message; + } + } +} + +Token scan(MySourceFile source) { + Scanner scanner = new ByteArrayScanner(source.rawText); + return scanner.tokenize(); +} + +var filesWithCrashes; + +void parseFilesFrom(InputStream input, MyOptions options, Function whenDone) { + void readLine(String line) { + stopwatch.start(); + try { + parseFile(line, options); + } catch (ex, trace) { + filesWithCrashes.add(line); + print(ex); + print(trace); + } + stopwatch.stop(); + } + forEachLine(input, readLine, whenDone); +} + +void forEachLine(InputStream input, + void lineHandler(String line), + void closeHandler()) { + StringInputStream stringStream = new StringInputStream(input); + stringStream.onLine = () { + String line; + while ((line = stringStream.readLine()) != null) { + lineHandler(line); + } + }; + stringStream.onClosed = closeHandler; +} + +List<int> read(String filename) { + RandomAccessFile file = new File(filename).openSync(); + bool threw = true; + try { + int size = file.lengthSync(); + List<int> bytes = new Uint8List(size + 1); + file.readListSync(bytes, 0, size); + bytes[size] = $EOF; + threw = false; + return bytes; + } finally { + try { + file.closeSync(); + } catch (ex) { + if (!threw) throw; + } + } +} + +int classCount = 0; +int errorCount = 0; + +class MyListener extends Listener { + final SourceFile file; + + MyListener(this.file); + + void beginClassDeclaration(Token token) { + classCount++; + } + + void beginInterface(Token token) { + classCount++; + } + + void error(String message, Token token) { + throw new ParserError(formatError(message, token, token, file)); + } +} + +String formatError(String message, Token beginToken, Token endToken, + SourceFile file) { + ++errorCount; + if (beginToken == null) return '${file.filename}: $message'; + String tokenString = endToken.toString(); + int begin = beginToken.charOffset; + int end = endToken.charOffset + tokenString.length; + return file.getLocationMessage(message, begin, end, true, (x) => x); +} + +class MyNodeListener extends NodeListener { + MyNodeListener(SourceFile file, MyOptions options) + : super(new MyCanceller(file, options), null); + + void beginClassDeclaration(Token token) { + classCount++; + } + + void beginInterface(Token token) { + classCount++; + } + + void endClassDeclaration(int interfacesCount, Token beginToken, + Token extendsKeyword, Token implementsKeyword, + Token endToken) { + super.endClassDeclaration(interfacesCount, beginToken, + extendsKeyword, implementsKeyword, + endToken); + ClassNode node = popNode(); // Discard ClassNode and assert the type. + } + + void endInterface(int supertypeCount, Token interfaceKeyword, + Token extendsKeyword, Token endToken) { + super.endInterface(supertypeCount, interfaceKeyword, extendsKeyword, + endToken); + ClassNode node = popNode(); // Discard ClassNode and assert the type. + } + + void endTopLevelFields(int count, Token beginToken, Token endToken) { + super.endTopLevelFields(count, beginToken, endToken); + VariableDefinitions node = popNode(); // Discard node and assert the type. + } + + void endFunctionTypeAlias(Token typedefKeyword, Token endToken) { + super.endFunctionTypeAlias(typedefKeyword, endToken); + Typedef node = popNode(); // Discard Typedef and assert type type. + } + + void endLibraryTag(bool hasPrefix, Token beginToken, Token endToken) { + super.endLibraryTag(hasPrefix, beginToken, endToken); + ScriptTag node = popNode(); // Discard ScriptTag and assert type type. + } + + void log(message) { + print(message); + } +} + +class MyCanceller implements DiagnosticListener { + final SourceFile file; + final MyOptions options; + + MyCanceller(this.file, this.options); + + void log(String message) {} + + void cancel(String reason, {node, token, instruction, element}) { + Token beginToken; + Token endToken; + if (token != null) { + beginToken = token; + endToken = token; + } else if (node != null) { + beginToken = node.getBeginToken(); + endToken = node.getEndToken(); + } + String message = formatError(reason, beginToken, endToken, file); + if (options.throwOnError) throw new ParserError(message); + print(message); + } +} + +class MyOptions { + bool diet = false; + bool throwOnError = false; + bool scanOnly = false; + bool readOnly = false; + bool buildAst = false; +} + +class MySourceFile extends SourceFile { + final rawText; + var stringText; + + MySourceFile(filename, this.rawText) : super(filename, null); + + String get text { + if (rawText is String) { + return rawText; + } else { + if (stringText == null) { + stringText = new String.fromCharCodes(rawText); + if (stringText.endsWith('\u0000')) { + // Strip trailing NUL used by ByteArrayScanner to signal EOF. + stringText = stringText.substring(0, stringText.length - 1); + } + } + return stringText; + } + } + + set text(String newText) { + throw "not supported"; + } +} + +class Mock { + const Mock(); + bool get useColors => true; + internalError(message) { throw message.toString(); } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart new file mode 100644 index 0000000..5a3494d --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart
@@ -0,0 +1,235 @@ +// 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. + +part of tree; + +/** + * The [DartString] type represents a Dart string value as a sequence of Unicode + * Scalar Values. + * After parsing, any valid [LiteralString] will contain a [DartString] + * representing its content after removing quotes and resolving escapes in + * its source. + */ +abstract class DartString extends Iterable<int> { + factory DartString.empty() => const LiteralDartString(""); + // This is a convenience constructor. If you need a const literal DartString, + // use [const LiteralDartString(string)] directly. + factory DartString.literal(String string) => new LiteralDartString(string); + factory DartString.rawString(SourceString source, int length) => + new RawSourceDartString(source, length); + factory DartString.escapedString(SourceString source, int length) => + new EscapedSourceDartString(source, length); + factory DartString.concat(DartString first, DartString second) { + if (first.isEmpty) return second; + if (second.isEmpty) return first; + return new ConsDartString(first, second); + } + const DartString(); + int get length; + bool get isEmpty => length == 0; + Iterator<int> get iterator; + String slowToString(); + + bool operator ==(var other) { + if (other is !DartString) return false; + DartString otherString = other; + if (length != otherString.length) return false; + Iterator it1 = iterator; + Iterator it2 = otherString.iterator; + while (it1.moveNext()) { + if (!it2.moveNext()) return false; + if (it1.current != it2.current) return false; + } + return true; + } + String toString() => "DartString#${length}:${slowToString()}"; + SourceString get source; +} + + +/** + * A [DartString] where the content is represented by an actual [String]. + */ +class LiteralDartString extends DartString { + final String string; + const LiteralDartString(this.string); + int get length => string.length; + Iterator<int> get iterator => new StringCodeIterator(string); + String slowToString() => string; + SourceString get source => new StringWrapper(string); +} + +/** + * A [DartString] where the content comes from a slice of the program source. + */ +abstract class SourceBasedDartString extends DartString { + String toStringCache = null; + final SourceString source; + final int length; + SourceBasedDartString(this.source, this.length); + Iterator<int> get iterator; +} + +/** + * Special case of a [SourceBasedDartString] where we know the source doesn't + * contain any escapes. + */ +class RawSourceDartString extends SourceBasedDartString { + RawSourceDartString(source, length) : super(source, length); + Iterator<int> get iterator => source.iterator; + String slowToString() { + if (toStringCache != null) return toStringCache; + toStringCache = source.slowToString(); + return toStringCache; + } +} + +/** + * General case of a [SourceBasedDartString] where the source might contain + * escapes. + */ +class EscapedSourceDartString extends SourceBasedDartString { + EscapedSourceDartString(source, length) : super(source, length); + Iterator<int> get iterator { + if (toStringCache != null) return new StringCodeIterator(toStringCache); + return new StringEscapeIterator(source); + } + String slowToString() { + if (toStringCache != null) return toStringCache; + StringBuffer buffer = new StringBuffer(); + StringEscapeIterator it = new StringEscapeIterator(source); + while (it.moveNext()) { + buffer.addCharCode(it.current); + } + toStringCache = buffer.toString(); + return toStringCache; + } +} + +/** + * The concatenation of two [DartString]s. + */ +class ConsDartString extends DartString { + final DartString left; + final DartString right; + final int length; + String toStringCache; + ConsDartString(DartString left, DartString right) + : this.left = left, + this.right = right, + length = left.length + right.length; + + Iterator<int> get iterator => new ConsDartStringIterator(this); + + String slowToString() { + if (toStringCache != null) return toStringCache; + toStringCache = left.slowToString().concat(right.slowToString()); + return toStringCache; + } + SourceString get source => new StringWrapper(slowToString()); +} + +class ConsDartStringIterator implements Iterator<int> { + HasNextIterator<int> currentIterator; + DartString right; + bool hasNextLookAhead; + int _current = null; + + ConsDartStringIterator(ConsDartString cons) + : currentIterator = new HasNextIterator<int>(cons.left.iterator), + right = cons.right { + hasNextLookAhead = currentIterator.hasNext; + if (!hasNextLookAhead) { + nextPart(); + } + } + + int get current => _current; + + bool moveNext() { + if (!hasNextLookAhead) { + _current = null; + return false; + } + _current = currentIterator.next(); + hasNextLookAhead = currentIterator.hasNext; + if (!hasNextLookAhead) { + nextPart(); + } + return true; + } + void nextPart() { + if (right != null) { + currentIterator = new HasNextIterator<int>(right.iterator); + right = null; + hasNextLookAhead = currentIterator.hasNext; + } + } +} + +/** + *Iterator that returns the actual string contents of a string with escapes. + */ +class StringEscapeIterator implements Iterator<int>{ + final Iterator<int> source; + int _current = null; + + StringEscapeIterator(SourceString source) : this.source = source.iterator; + + int get current => _current; + + bool moveNext() { + if (!source.moveNext()) { + _current = null; + return false; + } + int code = source.current; + if (code != $BACKSLASH) { + _current = code; + return true; + } + source.moveNext(); + code = source.current; + switch (code) { + case $n: _current = $LF; break; + case $r: _current = $CR; break; + case $t: _current = $TAB; break; + case $b: _current = $BS; break; + case $f: _current = $FF; break; + case $v: _current = $VTAB; break; + case $x: + source.moveNext(); + int value = hexDigitValue(source.current); + source.moveNext(); + value = value * 16 + hexDigitValue(source.current); + _current = value; + break; + case $u: + int value = 0; + source.moveNext(); + code = source.current; + if (code == $OPEN_CURLY_BRACKET) { + source.moveNext(); + while (source.current != $CLOSE_CURLY_BRACKET) { + value = value * 16 + hexDigitValue(source.current); + source.moveNext(); + } + _current = value; + break; + } + // Four digit hex value. + value = hexDigitValue(code); + for (int i = 0; i < 3; i++) { + source.moveNext(); + value = value * 16 + hexDigitValue(source.current); + } + _current = value; + break; + default: + _current = code; + } + return true; + } +} +
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart new file mode 100644 index 0000000..1b3b455 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart
@@ -0,0 +1,2086 @@ +// 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. + +part of tree; + +abstract class Visitor<R> { + const Visitor(); + + R visitNode(Node node); + + R visitBlock(Block node) => visitStatement(node); + R visitBreakStatement(BreakStatement node) => visitGotoStatement(node); + R visitCascade(Cascade node) => visitExpression(node); + R visitCascadeReceiver(CascadeReceiver node) => visitExpression(node); + R visitCaseMatch(CaseMatch node) => visitNode(node); + R visitCatchBlock(CatchBlock node) => visitNode(node); + R visitClassNode(ClassNode node) => visitNode(node); + R visitCombinator(Combinator node) => visitNode(node); + R visitConditional(Conditional node) => visitExpression(node); + R visitContinueStatement(ContinueStatement node) => visitGotoStatement(node); + R visitDoWhile(DoWhile node) => visitLoop(node); + R visitEmptyStatement(EmptyStatement node) => visitStatement(node); + R visitExport(Export node) => visitLibraryDependency(node); + R visitExpression(Expression node) => visitNode(node); + R visitExpressionStatement(ExpressionStatement node) => visitStatement(node); + R visitFor(For node) => visitLoop(node); + R visitForIn(ForIn node) => visitLoop(node); + R visitFunctionDeclaration(FunctionDeclaration node) => visitStatement(node); + R visitFunctionExpression(FunctionExpression node) => visitExpression(node); + R visitGotoStatement(GotoStatement node) => visitStatement(node); + R visitIdentifier(Identifier node) => visitExpression(node); + R visitIf(If node) => visitStatement(node); + R visitImport(Import node) => visitLibraryDependency(node); + R visitLabel(Label node) => visitNode(node); + R visitLabeledStatement(LabeledStatement node) => visitStatement(node); + R visitLibraryDependency(LibraryDependency node) => visitLibraryTag(node); + R visitLibraryName(LibraryName node) => visitLibraryTag(node); + R visitLibraryTag(LibraryTag node) => visitNode(node); + R visitLiteral(Literal node) => visitExpression(node); + R visitLiteralBool(LiteralBool node) => visitLiteral(node); + R visitLiteralDouble(LiteralDouble node) => visitLiteral(node); + R visitLiteralInt(LiteralInt node) => visitLiteral(node); + R visitLiteralList(LiteralList node) => visitExpression(node); + R visitLiteralMap(LiteralMap node) => visitExpression(node); + R visitLiteralMapEntry(LiteralMapEntry node) => visitNode(node); + R visitLiteralNull(LiteralNull node) => visitLiteral(node); + R visitLiteralString(LiteralString node) => visitStringNode(node); + R visitStringJuxtaposition(StringJuxtaposition node) => visitStringNode(node); + R visitLoop(Loop node) => visitStatement(node); + R visitMixinApplication(MixinApplication node) => visitNode(node); + R visitModifiers(Modifiers node) => visitNode(node); + R visitNamedArgument(NamedArgument node) => visitExpression(node); + R visitNamedMixinApplication(NamedMixinApplication node) { + return visitMixinApplication(node); + } + R visitNewExpression(NewExpression node) => visitExpression(node); + R visitNodeList(NodeList node) => visitNode(node); + R visitOperator(Operator node) => visitIdentifier(node); + R visitParenthesizedExpression(ParenthesizedExpression node) { + return visitExpression(node); + } + R visitPart(Part node) => visitLibraryTag(node); + R visitPartOf(PartOf node) => visitNode(node); + R visitPostfix(Postfix node) => visitNodeList(node); + R visitPrefix(Prefix node) => visitNodeList(node); + R visitReturn(Return node) => visitStatement(node); + R visitScriptTag(ScriptTag node) => visitNode(node); + R visitSend(Send node) => visitExpression(node); + R visitSendSet(SendSet node) => visitSend(node); + R visitStatement(Statement node) => visitNode(node); + R visitStringNode(StringNode node) => visitExpression(node); + R visitStringInterpolation(StringInterpolation node) => visitStringNode(node); + R visitStringInterpolationPart(StringInterpolationPart node) { + return visitNode(node); + } + R visitSwitchCase(SwitchCase node) => visitNode(node); + R visitSwitchStatement(SwitchStatement node) => visitStatement(node); + R visitThrow(Throw node) => visitStatement(node); + R visitTryStatement(TryStatement node) => visitStatement(node); + R visitTypeAnnotation(TypeAnnotation node) => visitNode(node); + R visitTypedef(Typedef node) => visitNode(node); + R visitTypeVariable(TypeVariable node) => visitNode(node); + R visitVariableDefinitions(VariableDefinitions node) => visitStatement(node); + R visitWhile(While node) => visitLoop(node); +} + +Token firstBeginToken(Node first, Node second) { + Token token = null; + if (first != null) { + token = first.getBeginToken(); + } + if (token == null && second != null) { + // [token] might be null even when [first] is not, e.g. for empty Modifiers. + token = second.getBeginToken(); + } + return token; +} + +/** + * A node in a syntax tree. + * + * The abstract part of "abstract syntax tree" is invalidated when + * supporting tools such as code formatting. These tools need concrete + * syntax such as parentheses and no constant folding. + * + * We support these tools by storing additional references back to the + * token stream. These references are stored in fields ending with + * "Token". + */ +abstract class Node extends TreeElementMixin implements Spannable { + final int hashCode; + static int _HASH_COUNTER = 0; + + Node() : hashCode = ++_HASH_COUNTER; + + accept(Visitor visitor); + + visitChildren(Visitor visitor); + + /** + * Returns this node unparsed to Dart source string. + */ + toString() => unparse(this); + + /** + * Returns Xml-like tree representation of this node. + */ + toDebugString() { + return PrettyPrinter.prettyPrint(this); + } + + String getObjectDescription() => super.toString(); + + Token getBeginToken(); + + Token getEndToken(); + + Block asBlock() => null; + BreakStatement asBreakStatement() => null; + Cascade asCascade() => null; + CascadeReceiver asCascadeReceiver() => null; + CaseMatch asCaseMatch() => null; + CatchBlock asCatchBlock() => null; + ClassNode asClassNode() => null; + Combinator asCombinator() => null; + Conditional asConditional() => null; + ContinueStatement asContinueStatement() => null; + DoWhile asDoWhile() => null; + EmptyStatement asEmptyStatement() => null; + Export asExport() => null; + Expression asExpression() => null; + ExpressionStatement asExpressionStatement() => null; + For asFor() => null; + ForIn asForIn() => null; + FunctionDeclaration asFunctionDeclaration() => null; + FunctionExpression asFunctionExpression() => null; + Identifier asIdentifier() => null; + If asIf() => null; + Import asImport() => null; + Label asLabel() => null; + LabeledStatement asLabeledStatement() => null; + LibraryName asLibraryName() => null; + LiteralBool asLiteralBool() => null; + LiteralDouble asLiteralDouble() => null; + LiteralInt asLiteralInt() => null; + LiteralList asLiteralList() => null; + LiteralMap asLiteralMap() => null; + LiteralMapEntry asLiteralMapEntry() => null; + LiteralNull asLiteralNull() => null; + LiteralString asLiteralString() => null; + MixinApplication asMixinApplication() => null; + Modifiers asModifiers() => null; + NamedArgument asNamedArgument() => null; + NamedMixinApplication asNamedMixinApplication() => null; + NodeList asNodeList() => null; + Operator asOperator() => null; + ParenthesizedExpression asParenthesizedExpression() => null; + Part asPart() => null; + PartOf asPartOf() => null; + Return asReturn() => null; + ScriptTag asScriptTag() => null; + Send asSend() => null; + SendSet asSendSet() => null; + Statement asStatement() => null; + StringInterpolation asStringInterpolation() => null; + StringInterpolationPart asStringInterpolationPart() => null; + StringJuxtaposition asStringJuxtaposition() => null; + StringNode asStringNode() => null; + SwitchCase asSwitchCase() => null; + SwitchStatement asSwitchStatement() => null; + Throw asThrow() => null; + TryStatement asTryStatement() => null; + TypeAnnotation asTypeAnnotation() => null; + TypeVariable asTypeVariable() => null; + Typedef asTypedef() => null; + VariableDefinitions asVariableDefinitions() => null; + While asWhile() => null; + + bool isValidBreakTarget() => false; + bool isValidContinueTarget() => false; +} + +class ClassNode extends Node { + final Modifiers modifiers; + final Identifier name; + final Node superclass; + final NodeList interfaces; + final NodeList typeParameters; + final NodeList body; + + // TODO(ahe, karlklose): the default keyword is not recorded. + final TypeAnnotation defaultClause; + + final Token beginToken; + final Token extendsKeyword; + final Token endToken; + + ClassNode(this.modifiers, this.name, this.typeParameters, this.superclass, + this.interfaces, this.defaultClause, this.beginToken, + this.extendsKeyword, this.body, this.endToken); + + ClassNode asClassNode() => this; + + accept(Visitor visitor) => visitor.visitClassNode(this); + + visitChildren(Visitor visitor) { + if (name != null) name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + if (superclass != null) superclass.accept(visitor); + if (interfaces != null) interfaces.accept(visitor); + if (body != null) body.accept(visitor); + } + + bool get isInterface => identical(beginToken.stringValue, 'interface'); + + bool get isClass => !isInterface; + + Token getBeginToken() => beginToken; + + Token getEndToken() => endToken; +} + +class MixinApplication extends Node { + final TypeAnnotation superclass; + final NodeList mixins; + + MixinApplication(this.superclass, this.mixins); + + MixinApplication asMixinApplication() => this; + + accept(Visitor visitor) => visitor.visitMixinApplication(this); + + visitChildren(Visitor visitor) { + if (superclass != null) superclass.accept(visitor); + if (mixins != null) mixins.accept(visitor); + } + + Token getBeginToken() => superclass.getBeginToken(); + Token getEndToken() => mixins.getEndToken(); +} + +// TODO(kasperl): Let this share some structure with the typedef for function +// type aliases? +class NamedMixinApplication extends Node implements MixinApplication { + final Identifier name; + final NodeList typeParameters; + + final Modifiers modifiers; + final MixinApplication mixinApplication; + final NodeList interfaces; + + final Token typedefKeyword; + final Token endToken; + + NamedMixinApplication(this.name, this.typeParameters, + this.modifiers, this.mixinApplication, this.interfaces, + this.typedefKeyword, this.endToken); + + TypeAnnotation get superclass => mixinApplication.superclass; + NodeList get mixins => mixinApplication.mixins; + + MixinApplication asMixinApplication() => this; + NamedMixinApplication asNamedMixinApplication() => this; + + accept(Visitor visitor) => visitor.visitNamedMixinApplication(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + if (modifiers != null) modifiers.accept(visitor); + if (interfaces != null) interfaces.accept(visitor); + mixinApplication.accept(visitor); + } + + Token getBeginToken() => typedefKeyword; + Token getEndToken() => endToken; +} + +abstract class Expression extends Node { + Expression(); + + Expression asExpression() => this; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); +} + +abstract class Statement extends Node { + Statement(); + + Statement asStatement() => this; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); + + bool isValidBreakTarget() => true; +} + +/** + * A message send aka method invocation. In Dart, most operations can + * (and should) be considered as message sends. Getters and setters + * are just methods with a special syntax. Consequently, we model + * property access, assignment, operators, and method calls with this + * one node. + */ +class Send extends Expression { + final Node receiver; + final Node selector; + final NodeList argumentsNode; + Link<Node> get arguments => argumentsNode.nodes; + + Send([this.receiver, this.selector, this.argumentsNode]); + Send.postfix(this.receiver, this.selector, [Node argument = null]) + : argumentsNode = (argument == null) + ? new Postfix() + : new Postfix.singleton(argument); + Send.prefix(this.receiver, this.selector, [Node argument = null]) + : argumentsNode = (argument == null) + ? new Prefix() + : new Prefix.singleton(argument); + + Send asSend() => this; + + accept(Visitor visitor) => visitor.visitSend(this); + + visitChildren(Visitor visitor) { + if (receiver != null) receiver.accept(visitor); + if (selector != null) selector.accept(visitor); + if (argumentsNode != null) argumentsNode.accept(visitor); + } + + int argumentCount() { + return (argumentsNode == null) ? -1 : argumentsNode.slowLength(); + } + + bool get isSuperCall { + return receiver != null && + receiver.asIdentifier() != null && + receiver.asIdentifier().isSuper(); + } + bool get isOperator => selector is Operator; + bool get isPropertyAccess => argumentsNode == null; + bool get isFunctionObjectInvocation => selector == null; + bool get isPrefix => argumentsNode is Prefix; + bool get isPostfix => argumentsNode is Postfix; + bool get isCall => !isOperator && !isPropertyAccess; + bool get isIndex => + isOperator && identical(selector.asOperator().source.stringValue, '[]'); + bool get isLogicalAnd => + isOperator && identical(selector.asOperator().source.stringValue, '&&'); + bool get isLogicalOr => + isOperator && identical(selector.asOperator().source.stringValue, '||'); + bool get isParameterCheck => + isOperator && identical(selector.asOperator().source.stringValue, '?'); + + Token getBeginToken() { + if (isPrefix && !isIndex) return selector.getBeginToken(); + return firstBeginToken(receiver, selector); + } + + Token getEndToken() { + if (isPrefix) { + if (receiver != null) return receiver.getEndToken(); + if (selector != null) return selector.getEndToken(); + return null; + } + if (!isPostfix && argumentsNode != null) { + return argumentsNode.getEndToken(); + } + if (selector != null) return selector.getEndToken(); + return receiver.getBeginToken(); + } + + Send copyWithReceiver(Node newReceiver) { + assert(receiver == null); + return new Send(newReceiver, selector, argumentsNode); + } +} + +class Postfix extends NodeList { + Postfix() : super(null, const Link<Node>()); + Postfix.singleton(Node argument) : super.singleton(argument); +} + +class Prefix extends NodeList { + Prefix() : super(null, const Link<Node>()); + Prefix.singleton(Node argument) : super.singleton(argument); +} + +class SendSet extends Send { + final Operator assignmentOperator; + SendSet(receiver, selector, this.assignmentOperator, argumentsNode) + : super(receiver, selector, argumentsNode); + SendSet.postfix(receiver, + selector, + this.assignmentOperator, + [Node argument = null]) + : super.postfix(receiver, selector, argument); + SendSet.prefix(receiver, + selector, + this.assignmentOperator, + [Node argument = null]) + : super.prefix(receiver, selector, argument); + + SendSet asSendSet() => this; + + accept(Visitor visitor) => visitor.visitSendSet(this); + + visitChildren(Visitor visitor) { + super.visitChildren(visitor); + if (assignmentOperator != null) assignmentOperator.accept(visitor); + } + + Send copyWithReceiver(Node newReceiver) { + assert(receiver == null); + return new SendSet(newReceiver, selector, assignmentOperator, + argumentsNode); + } + + Token getBeginToken() { + if (isPrefix) return assignmentOperator.getBeginToken(); + return super.getBeginToken(); + } + + Token getEndToken() { + if (isPostfix) return assignmentOperator.getEndToken(); + return super.getEndToken(); + } +} + +class NewExpression extends Expression { + /** The token NEW or CONST */ + final Token newToken; + + // Note: we expect that send.receiver is null. + final Send send; + + NewExpression([this.newToken, this.send]); + + accept(Visitor visitor) => visitor.visitNewExpression(this); + + visitChildren(Visitor visitor) { + if (send != null) send.accept(visitor); + } + + bool isConst() { + return identical(newToken.stringValue, 'const') + || identical(newToken.stringValue, '@'); + } + + Token getBeginToken() => newToken; + + Token getEndToken() => send.getEndToken(); +} + +class NodeList extends Node { + final Link<Node> nodes; + final Token beginToken; + final Token endToken; + final SourceString delimiter; + bool get isEmpty => nodes.isEmpty; + + NodeList([this.beginToken, this.nodes, this.endToken, this.delimiter]); + + Iterator<Node> get iterator => nodes.iterator; + + NodeList.singleton(Node node) : this(null, const Link<Node>().prepend(node)); + NodeList.empty() : this(null, const Link<Node>()); + + NodeList asNodeList() => this; + + int slowLength() { + int result = 0; + for (Link<Node> cursor = nodes; !cursor.isEmpty; cursor = cursor.tail) { + result++; + } + return result; + } + + accept(Visitor visitor) => visitor.visitNodeList(this); + + visitChildren(Visitor visitor) { + if (nodes == null) return; + for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) { + if (link.head != null) link.head.accept(visitor); + } + } + + Token getBeginToken() { + if (beginToken != null) return beginToken; + if (nodes != null) { + for (Link<Node> link = nodes; !link.isEmpty; link = link.tail) { + if (link.head.getBeginToken() != null) { + return link.head.getBeginToken(); + } + if (link.head.getEndToken() != null) { + return link.head.getEndToken(); + } + } + } + return endToken; + } + + Token getEndToken() { + if (endToken != null) return endToken; + if (nodes != null) { + Link<Node> link = nodes; + if (link.isEmpty) return beginToken; + while (!link.tail.isEmpty) link = link.tail; + if (link.head.getEndToken() != null) return link.head.getEndToken(); + if (link.head.getBeginToken() != null) return link.head.getBeginToken(); + } + return beginToken; + } +} + +class Block extends Statement { + final NodeList statements; + + Block(this.statements); + + Block asBlock() => this; + + accept(Visitor visitor) => visitor.visitBlock(this); + + visitChildren(Visitor visitor) { + if (statements != null) statements.accept(visitor); + } + + Token getBeginToken() => statements.getBeginToken(); + + Token getEndToken() => statements.getEndToken(); +} + +class If extends Statement { + final ParenthesizedExpression condition; + final Statement thenPart; + final Statement elsePart; + + final Token ifToken; + final Token elseToken; + + If(this.condition, this.thenPart, this.elsePart, + this.ifToken, this.elseToken); + + If asIf() => this; + + bool get hasElsePart => elsePart != null; + + void validate() { + // TODO(ahe): Check that condition has size one. + } + + accept(Visitor visitor) => visitor.visitIf(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (thenPart != null) thenPart.accept(visitor); + if (elsePart != null) elsePart.accept(visitor); + } + + Token getBeginToken() => ifToken; + + Token getEndToken() { + if (elsePart == null) return thenPart.getEndToken(); + return elsePart.getEndToken(); + } +} + +class Conditional extends Expression { + final Expression condition; + final Expression thenExpression; + final Expression elseExpression; + + final Token questionToken; + final Token colonToken; + + Conditional(this.condition, this.thenExpression, + this.elseExpression, this.questionToken, this.colonToken); + + Conditional asConditional() => this; + + accept(Visitor visitor) => visitor.visitConditional(this); + + visitChildren(Visitor visitor) { + condition.accept(visitor); + thenExpression.accept(visitor); + elseExpression.accept(visitor); + } + + Token getBeginToken() => condition.getBeginToken(); + + Token getEndToken() => elseExpression.getEndToken(); +} + +class For extends Loop { + /** Either a variable declaration or an expression. */ + final Node initializer; + /** Either an expression statement or an empty statement. */ + final Statement conditionStatement; + final NodeList update; + + final Token forToken; + + For(this.initializer, this.conditionStatement, this.update, body, + this.forToken) : super(body); + + For asFor() => this; + + Expression get condition { + if (conditionStatement is ExpressionStatement) { + return conditionStatement.asExpressionStatement().expression; + } else { + return null; + } + } + + accept(Visitor visitor) => visitor.visitFor(this); + + visitChildren(Visitor visitor) { + if (initializer != null) initializer.accept(visitor); + if (conditionStatement != null) conditionStatement.accept(visitor); + if (update != null) update.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => forToken; + + Token getEndToken() { + return body.getEndToken(); + } +} + +class FunctionDeclaration extends Statement { + final FunctionExpression function; + + FunctionDeclaration(this.function); + + FunctionDeclaration asFunctionDeclaration() => this; + + accept(Visitor visitor) => visitor.visitFunctionDeclaration(this); + + visitChildren(Visitor visitor) => function.accept(visitor); + + Token getBeginToken() => function.getBeginToken(); + Token getEndToken() => function.getEndToken(); +} + +class FunctionExpression extends Expression { + final Node name; + + /** + * List of VariableDefinitions or NodeList. + * + * A NodeList can only occur at the end and holds named parameters. + */ + final NodeList parameters; + + final Statement body; + final TypeAnnotation returnType; + final Modifiers modifiers; + final NodeList initializers; + + final Token getOrSet; + + FunctionExpression(this.name, this.parameters, this.body, this.returnType, + this.modifiers, this.initializers, this.getOrSet) { + assert(modifiers != null); + } + + FunctionExpression asFunctionExpression() => this; + + accept(Visitor visitor) => visitor.visitFunctionExpression(this); + + visitChildren(Visitor visitor) { + if (modifiers != null) modifiers.accept(visitor); + if (returnType != null) returnType.accept(visitor); + if (name != null) name.accept(visitor); + if (parameters != null) parameters.accept(visitor); + if (initializers != null) initializers.accept(visitor); + if (body != null) body.accept(visitor); + } + + bool hasBody() => body.asEmptyStatement() == null; + + bool hasEmptyBody() { + Block block = body.asBlock(); + if (block == null) return false; + return block.statements.isEmpty; + } + + Token getBeginToken() { + Token token = firstBeginToken(modifiers, returnType); + if (token != null) return token; + if (getOrSet != null) return getOrSet; + return firstBeginToken(name, parameters); + } + + Token getEndToken() { + Token token = (body == null) ? null : body.getEndToken(); + token = (token == null) ? parameters.getEndToken() : token; + return (token == null) ? name.getEndToken() : token; + } +} + +typedef void DecodeErrorHandler(Token token, var error); + +abstract class Literal<T> extends Expression { + final Token token; + final DecodeErrorHandler handler; + + Literal(Token this.token, DecodeErrorHandler this.handler); + + T get value; + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => token; + + Token getEndToken() => token; +} + +class LiteralInt extends Literal<int> { + LiteralInt(Token token, DecodeErrorHandler handler) : super(token, handler); + + LiteralInt asLiteralInt() => this; + + int get value { + try { + Token valueToken = token; + if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next; + return int.parse(valueToken.value.slowToString()); + } on FormatException catch (ex) { + (this.handler)(token, ex); + } + } + + accept(Visitor visitor) => visitor.visitLiteralInt(this); +} + +class LiteralDouble extends Literal<double> { + LiteralDouble(Token token, DecodeErrorHandler handler) + : super(token, handler); + + LiteralDouble asLiteralDouble() => this; + + double get value { + try { + Token valueToken = token; + if (identical(valueToken.kind, PLUS_TOKEN)) valueToken = valueToken.next; + return double.parse(valueToken.value.slowToString()); + } on FormatException catch (ex) { + (this.handler)(token, ex); + } + } + + accept(Visitor visitor) => visitor.visitLiteralDouble(this); +} + +class LiteralBool extends Literal<bool> { + LiteralBool(Token token, DecodeErrorHandler handler) : super(token, handler); + + LiteralBool asLiteralBool() => this; + + bool get value { + if (identical(token.stringValue, 'true')) return true; + if (identical(token.stringValue, 'false')) return false; + (this.handler)(token, "not a bool ${token.value}"); + } + + accept(Visitor visitor) => visitor.visitLiteralBool(this); +} + + +class StringQuoting { + static const StringQuoting SINGLELINE_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 1); + static const StringQuoting RAW_SINGLELINE_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 1); + static const StringQuoting MULTILINE_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 3); + static const StringQuoting RAW_MULTILINE_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 3); + static const StringQuoting MULTILINE_NL_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 4); + static const StringQuoting RAW_MULTILINE_NL_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 4); + static const StringQuoting MULTILINE_NL2_DQ = + const StringQuoting($DQ, raw: false, leftQuoteLength: 5); + static const StringQuoting RAW_MULTILINE_NL2_DQ = + const StringQuoting($DQ, raw: true, leftQuoteLength: 5); + static const StringQuoting SINGLELINE_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 1); + static const StringQuoting RAW_SINGLELINE_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 1); + static const StringQuoting MULTILINE_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 3); + static const StringQuoting RAW_MULTILINE_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 3); + static const StringQuoting MULTILINE_NL_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 4); + static const StringQuoting RAW_MULTILINE_NL_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 4); + static const StringQuoting MULTILINE_NL2_SQ = + const StringQuoting($SQ, raw: false, leftQuoteLength: 5); + static const StringQuoting RAW_MULTILINE_NL2_SQ = + const StringQuoting($SQ, raw: true, leftQuoteLength: 5); + + + static const List<StringQuoting> mapping = const <StringQuoting>[ + SINGLELINE_DQ, + RAW_SINGLELINE_DQ, + MULTILINE_DQ, + RAW_MULTILINE_DQ, + MULTILINE_NL_DQ, + RAW_MULTILINE_NL_DQ, + MULTILINE_NL2_DQ, + RAW_MULTILINE_NL2_DQ, + SINGLELINE_SQ, + RAW_SINGLELINE_SQ, + MULTILINE_SQ, + RAW_MULTILINE_SQ, + MULTILINE_NL_SQ, + RAW_MULTILINE_NL_SQ, + MULTILINE_NL2_SQ, + RAW_MULTILINE_NL2_SQ + ]; + final bool raw; + final int leftQuoteCharCount; + final int quote; + const StringQuoting(this.quote, {bool raw, int leftQuoteLength}) + : this.raw = raw, this.leftQuoteCharCount = leftQuoteLength; + String get quoteChar => identical(quote, $DQ) ? '"' : "'"; + + int get leftQuoteLength => (raw ? 1 : 0) + leftQuoteCharCount; + int get rightQuoteLength => (leftQuoteCharCount > 2) ? 3 : 1; + static StringQuoting getQuoting(int quote, bool raw, int quoteLength) { + int index = quoteLength - 1; + if (quoteLength > 2) index -= 1; + return mapping[(raw ? 1 : 0) + index * 2 + (identical(quote, $SQ) ? 8 : 0)]; + } +} + +/** + * Superclass for classes representing string literals. + */ +abstract class StringNode extends Expression { + DartString get dartString; + bool get isInterpolation; + + StringNode asStringNode() => this; +} + +class LiteralString extends StringNode { + final Token token; + /** Non-null on validated string literals. */ + final DartString dartString; + + LiteralString(this.token, this.dartString); + + LiteralString asLiteralString() => this; + + void visitChildren(Visitor visitor) {} + + bool get isInterpolation => false; + bool isValidated() => dartString != null; + + Token getBeginToken() => token; + Token getEndToken() => token; + + accept(Visitor visitor) => visitor.visitLiteralString(this); +} + +class LiteralNull extends Literal<SourceString> { + LiteralNull(Token token) : super(token, null); + + LiteralNull asLiteralNull() => this; + + SourceString get value => null; + + accept(Visitor visitor) => visitor.visitLiteralNull(this); +} + +class LiteralList extends Expression { + final NodeList typeArguments; + final NodeList elements; + + final Token constKeyword; + + LiteralList(this.typeArguments, this.elements, this.constKeyword); + + bool isConst() => constKeyword != null; + + LiteralList asLiteralList() => this; + accept(Visitor visitor) => visitor.visitLiteralList(this); + + visitChildren(Visitor visitor) { + if (typeArguments != null) typeArguments.accept(visitor); + elements.accept(visitor); + } + + Token getBeginToken() { + if (constKeyword != null) return constKeyword; + return firstBeginToken(typeArguments, elements); + } + + Token getEndToken() => elements.getEndToken(); +} + +class Identifier extends Expression { + final Token token; + + SourceString get source => token.value; + + Identifier(Token this.token); + + bool isThis() => identical(source.stringValue, 'this'); + + bool isSuper() => identical(source.stringValue, 'super'); + + Identifier asIdentifier() => this; + + accept(Visitor visitor) => visitor.visitIdentifier(this); + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => token; + + Token getEndToken() => token; +} + +class Operator extends Identifier { + Operator(Token token) : super(token); + + Operator asOperator() => this; + + accept(Visitor visitor) => visitor.visitOperator(this); +} + +class Return extends Statement { + final Node expression; + final Token beginToken; + final Token endToken; + + Return(this.beginToken, this.endToken, this.expression); + + Return asReturn() => this; + + bool get hasExpression => expression != null; + + bool get isRedirectingFactoryBody => beginToken.stringValue == '='; + + accept(Visitor visitor) => visitor.visitReturn(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() { + if (endToken == null) return expression.getEndToken(); + return endToken; + } +} + +class ExpressionStatement extends Statement { + final Expression expression; + final Token endToken; + + ExpressionStatement(this.expression, this.endToken); + + ExpressionStatement asExpressionStatement() => this; + + accept(Visitor visitor) => visitor.visitExpressionStatement(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => endToken; +} + +class Throw extends Statement { + final Expression expression; + + final Token throwToken; + final Token endToken; + + Throw(this.expression, this.throwToken, this.endToken); + + Throw asThrow() => this; + + accept(Visitor visitor) => visitor.visitThrow(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => throwToken; + + Token getEndToken() => endToken; +} + +class TypeAnnotation extends Node { + final Expression typeName; + final NodeList typeArguments; + + TypeAnnotation(Expression this.typeName, NodeList this.typeArguments); + + TypeAnnotation asTypeAnnotation() => this; + + accept(Visitor visitor) => visitor.visitTypeAnnotation(this); + + visitChildren(Visitor visitor) { + typeName.accept(visitor); + if (typeArguments != null) typeArguments.accept(visitor); + } + + Token getBeginToken() => typeName.getBeginToken(); + + Token getEndToken() => typeName.getEndToken(); +} + +class TypeVariable extends Node { + final Identifier name; + final TypeAnnotation bound; + TypeVariable(Identifier this.name, TypeAnnotation this.bound); + + accept(Visitor visitor) => visitor.visitTypeVariable(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + if (bound != null) { + bound.accept(visitor); + } + } + + TypeVariable asTypeVariable() => this; + + Token getBeginToken() => name.getBeginToken(); + + Token getEndToken() { + return (bound != null) ? bound.getEndToken() : name.getEndToken(); + } +} + +class VariableDefinitions extends Statement { + final TypeAnnotation type; + final Modifiers modifiers; + final NodeList definitions; + VariableDefinitions(this.type, this.modifiers, this.definitions) { + assert(modifiers != null); + } + + VariableDefinitions asVariableDefinitions() => this; + + accept(Visitor visitor) => visitor.visitVariableDefinitions(this); + + visitChildren(Visitor visitor) { + if (type != null) type.accept(visitor); + if (definitions != null) definitions.accept(visitor); + } + + Token getBeginToken() { + var token = firstBeginToken(modifiers, type); + if (token == null) { + token = definitions.getBeginToken(); + } + return token; + } + + Token getEndToken() => definitions.getEndToken(); +} + +abstract class Loop extends Statement { + Expression get condition; + final Statement body; + + Loop(this.body); + + bool isValidContinueTarget() => true; +} + +class DoWhile extends Loop { + final Token doKeyword; + final Token whileKeyword; + final Token endToken; + + final Expression condition; + + DoWhile(Statement body, Expression this.condition, + Token this.doKeyword, Token this.whileKeyword, Token this.endToken) + : super(body); + + DoWhile asDoWhile() => this; + + accept(Visitor visitor) => visitor.visitDoWhile(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => doKeyword; + + Token getEndToken() => endToken; +} + +class While extends Loop { + final Token whileKeyword; + final Expression condition; + + While(Expression this.condition, Statement body, + Token this.whileKeyword) : super(body); + + While asWhile() => this; + + accept(Visitor visitor) => visitor.visitWhile(this); + + visitChildren(Visitor visitor) { + if (condition != null) condition.accept(visitor); + if (body != null) body.accept(visitor); + } + + Token getBeginToken() => whileKeyword; + + Token getEndToken() => body.getEndToken(); +} + +class ParenthesizedExpression extends Expression { + final Expression expression; + final BeginGroupToken beginToken; + + ParenthesizedExpression(Expression this.expression, + BeginGroupToken this.beginToken); + + ParenthesizedExpression asParenthesizedExpression() => this; + + accept(Visitor visitor) => visitor.visitParenthesizedExpression(this); + + visitChildren(Visitor visitor) { + if (expression != null) expression.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() => beginToken.endGroup; +} + +/** Representation of modifiers such as static, abstract, final, etc. */ +class Modifiers extends Node { + /** + * Pseudo-constant for empty modifiers. + */ + static final Modifiers EMPTY = new Modifiers(new NodeList.empty()); + + /* TODO(ahe): The following should be validated relating to modifiers: + * 1. The nodes must come in a certain order. + * 2. The keywords "var" and "final" may not be used at the same time. + * 3. The keywords "abstract" and "external" may not be used at the same time. + * 4. The type of an element must be null if isVar() is true. + */ + + final NodeList nodes; + /** Bit pattern to easy check what modifiers are present. */ + final int flags; + + static const int FLAG_STATIC = 1; + static const int FLAG_ABSTRACT = FLAG_STATIC << 1; + static const int FLAG_FINAL = FLAG_ABSTRACT << 1; + static const int FLAG_VAR = FLAG_FINAL << 1; + static const int FLAG_CONST = FLAG_VAR << 1; + static const int FLAG_FACTORY = FLAG_CONST << 1; + static const int FLAG_EXTERNAL = FLAG_FACTORY << 1; + + Modifiers(NodeList nodes) : this.withFlags(nodes, computeFlags(nodes.nodes)); + + Modifiers.withFlags(this.nodes, this.flags); + + static int computeFlags(Link<Node> nodes) { + int flags = 0; + for (; !nodes.isEmpty; nodes = nodes.tail) { + String value = nodes.head.asIdentifier().source.stringValue; + if (identical(value, 'static')) flags |= FLAG_STATIC; + else if (identical(value, 'abstract')) flags |= FLAG_ABSTRACT; + else if (identical(value, 'final')) flags |= FLAG_FINAL; + else if (identical(value, 'var')) flags |= FLAG_VAR; + else if (identical(value, 'const')) flags |= FLAG_CONST; + else if (identical(value, 'factory')) flags |= FLAG_FACTORY; + else if (identical(value, 'external')) flags |= FLAG_EXTERNAL; + else throw 'internal error: ${nodes.head}'; + } + return flags; + } + + Node findModifier(String modifier) { + Link<Node> nodeList = nodes.nodes; + for (; !nodeList.isEmpty; nodeList = nodeList.tail) { + String value = nodeList.head.asIdentifier().source.stringValue; + if(identical(value, modifier)) { + return nodeList.head; + } + } + return null; + } + + Modifiers asModifiers() => this; + Token getBeginToken() => nodes.getBeginToken(); + Token getEndToken() => nodes.getEndToken(); + accept(Visitor visitor) => visitor.visitModifiers(this); + visitChildren(Visitor visitor) => nodes.accept(visitor); + + bool isStatic() => (flags & FLAG_STATIC) != 0; + bool isAbstract() => (flags & FLAG_ABSTRACT) != 0; + bool isFinal() => (flags & FLAG_FINAL) != 0; + bool isVar() => (flags & FLAG_VAR) != 0; + bool isConst() => (flags & FLAG_CONST) != 0; + bool isFactory() => (flags & FLAG_FACTORY) != 0; + bool isExternal() => (flags & FLAG_EXTERNAL) != 0; + + Node getStatic() => findModifier('static'); + + /** + * Use this to check if the declaration is either explicitly or implicitly + * final. + */ + bool isFinalOrConst() => isFinal() || isConst(); + + String toString() { + LinkBuilder<String> builder = new LinkBuilder<String>(); + if (isStatic()) builder.addLast('static'); + if (isAbstract()) builder.addLast('abstract'); + if (isFinal()) builder.addLast('final'); + if (isVar()) builder.addLast('var'); + if (isConst()) builder.addLast('const'); + if (isFactory()) builder.addLast('factory'); + if (isExternal()) builder.addLast('external'); + StringBuffer buffer = new StringBuffer(); + builder.toLink().printOn(buffer, ', '); + return buffer.toString(); + } +} + +class StringInterpolation extends StringNode { + final LiteralString string; + final NodeList parts; + + StringInterpolation(this.string, this.parts); + + StringInterpolation asStringInterpolation() => this; + + DartString get dartString => null; + bool get isInterpolation => true; + + accept(Visitor visitor) => visitor.visitStringInterpolation(this); + + visitChildren(Visitor visitor) { + string.accept(visitor); + parts.accept(visitor); + } + + Token getBeginToken() => string.getBeginToken(); + Token getEndToken() => parts.getEndToken(); +} + +class StringInterpolationPart extends Node { + final Expression expression; + final LiteralString string; + + StringInterpolationPart(this.expression, this.string); + + StringInterpolationPart asStringInterpolationPart() => this; + + accept(Visitor visitor) => visitor.visitStringInterpolationPart(this); + + visitChildren(Visitor visitor) { + expression.accept(visitor); + string.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => string.getEndToken(); +} + +/** + * A class representing juxtaposed string literals. + * The string literals can be both plain literals and string interpolations. + */ +class StringJuxtaposition extends StringNode { + final Expression first; + final Expression second; + + /** + * Caches the check for whether this juxtaposition contains a string + * interpolation + */ + bool isInterpolationCache = null; + + /** + * Caches a Dart string representation of the entire juxtaposition's + * content. Only juxtapositions that don't (transitively) contains + * interpolations have a static representation. + */ + DartString dartStringCache = null; + + StringJuxtaposition(this.first, this.second); + + StringJuxtaposition asStringJuxtaposition() => this; + + bool get isInterpolation { + if (isInterpolationCache == null) { + isInterpolationCache = (first.accept(const IsInterpolationVisitor()) || + second.accept(const IsInterpolationVisitor())); + } + return isInterpolationCache; + } + + /** + * Retrieve a single DartString that represents this entire juxtaposition + * of string literals. + * Should only be called if [isInterpolation] returns false. + */ + DartString get dartString { + if (isInterpolation) { + throw new SpannableAssertionFailure( + this, "Getting dartString on interpolation;"); + } + if (dartStringCache == null) { + DartString firstString = first.accept(const GetDartStringVisitor()); + DartString secondString = second.accept(const GetDartStringVisitor()); + if (firstString == null || secondString == null) { + return null; + } + dartStringCache = new DartString.concat(firstString, secondString); + } + return dartStringCache; + } + + accept(Visitor visitor) => visitor.visitStringJuxtaposition(this); + + void visitChildren(Visitor visitor) { + first.accept(visitor); + second.accept(visitor); + } + + Token getBeginToken() => first.getBeginToken(); + + Token getEndToken() => second.getEndToken(); +} + +class EmptyStatement extends Statement { + final Token semicolonToken; + + EmptyStatement(this.semicolonToken); + + EmptyStatement asEmptyStatement() => this; + + accept(Visitor visitor) => visitor.visitEmptyStatement(this); + + visitChildren(Visitor visitor) {} + + Token getBeginToken() => semicolonToken; + + Token getEndToken() => semicolonToken; +} + +class LiteralMap extends Expression { + final NodeList typeArguments; + final NodeList entries; + + final Token constKeyword; + + LiteralMap(this.typeArguments, this.entries, this.constKeyword); + + bool isConst() => constKeyword != null; + + LiteralMap asLiteralMap() => this; + + accept(Visitor visitor) => visitor.visitLiteralMap(this); + + visitChildren(Visitor visitor) { + if (typeArguments != null) typeArguments.accept(visitor); + entries.accept(visitor); + } + + Token getBeginToken() { + if (constKeyword != null) return constKeyword; + return firstBeginToken(typeArguments, entries); + } + + Token getEndToken() => entries.getEndToken(); +} + +class LiteralMapEntry extends Node { + final Expression key; + final Expression value; + + final Token colonToken; + + LiteralMapEntry(this.key, this.colonToken, this.value); + + LiteralMapEntry asLiteralMapEntry() => this; + + accept(Visitor visitor) => visitor.visitLiteralMapEntry(this); + + visitChildren(Visitor visitor) { + key.accept(visitor); + value.accept(visitor); + } + + Token getBeginToken() => key.getBeginToken(); + + Token getEndToken() => value.getEndToken(); +} + +class NamedArgument extends Expression { + final Identifier name; + final Expression expression; + + final Token colonToken; + + NamedArgument(this.name, this.colonToken, this.expression); + + NamedArgument asNamedArgument() => this; + + accept(Visitor visitor) => visitor.visitNamedArgument(this); + + visitChildren(Visitor visitor) { + name.accept(visitor); + expression.accept(visitor); + } + + Token getBeginToken() => name.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class SwitchStatement extends Statement { + final ParenthesizedExpression parenthesizedExpression; + final NodeList cases; + + final Token switchKeyword; + + SwitchStatement(this.parenthesizedExpression, this.cases, + this.switchKeyword); + + SwitchStatement asSwitchStatement() => this; + + Expression get expression => parenthesizedExpression.expression; + + accept(Visitor visitor) => visitor.visitSwitchStatement(this); + + visitChildren(Visitor visitor) { + parenthesizedExpression.accept(visitor); + cases.accept(visitor); + } + + Token getBeginToken() => switchKeyword; + + Token getEndToken() => cases.getEndToken(); +} + +class CaseMatch extends Node { + final Token caseKeyword; + final Expression expression; + final Token colonToken; + CaseMatch(this.caseKeyword, this.expression, this.colonToken); + + CaseMatch asCaseMatch() => this; + Token getBeginToken() => caseKeyword; + Token getEndToken() => colonToken; + accept(Visitor visitor) => visitor.visitCaseMatch(this); + visitChildren(Visitor visitor) => expression.accept(visitor); +} + +class SwitchCase extends Node { + // The labels and case patterns are collected in [labelsAndCases]. + // The default keyword, if present, is collected in [defaultKeyword]. + // Any actual switch case must have at least one 'case' or 'default' + // clause. + // Notice: The labels and cases can occur interleaved in the source. + // They are separated here, since the order is irrelevant to the meaning + // of the switch. + + /** List of [Label] and [CaseMatch] nodes. */ + final NodeList labelsAndCases; + /** A "default" keyword token, if applicable. */ + final Token defaultKeyword; + /** List of statements, the body of the case. */ + final NodeList statements; + + final Token startToken; + + SwitchCase(this.labelsAndCases, this.defaultKeyword, + this.statements, this.startToken); + + SwitchCase asSwitchCase() => this; + + bool get isDefaultCase => defaultKeyword != null; + + bool isValidContinueTarget() => true; + + accept(Visitor visitor) => visitor.visitSwitchCase(this); + + visitChildren(Visitor visitor) { + labelsAndCases.accept(visitor); + statements.accept(visitor); + } + + Token getBeginToken() { + return startToken; + } + + Token getEndToken() { + if (statements.nodes.isEmpty) { + // All cases must have at least one expression or be the default. + if (defaultKeyword != null) { + // The colon after 'default'. + return defaultKeyword.next; + } + // The colon after the last expression. + return labelsAndCases.getEndToken(); + } else { + return statements.getEndToken(); + } + } +} + +abstract class GotoStatement extends Statement { + final Identifier target; + final Token keywordToken; + final Token semicolonToken; + + GotoStatement(this.target, this.keywordToken, this.semicolonToken); + + visitChildren(Visitor visitor) { + if (target != null) target.accept(visitor); + } + + Token getBeginToken() => keywordToken; + + Token getEndToken() => semicolonToken; + + // TODO(ahe): make class abstract instead of adding an abstract method. + accept(Visitor visitor); +} + +class BreakStatement extends GotoStatement { + BreakStatement(Identifier target, Token keywordToken, Token semicolonToken) + : super(target, keywordToken, semicolonToken); + + BreakStatement asBreakStatement() => this; + + accept(Visitor visitor) => visitor.visitBreakStatement(this); +} + +class ContinueStatement extends GotoStatement { + ContinueStatement(Identifier target, Token keywordToken, Token semicolonToken) + : super(target, keywordToken, semicolonToken); + + ContinueStatement asContinueStatement() => this; + + accept(Visitor visitor) => visitor.visitContinueStatement(this); +} + +class ForIn extends Loop { + final Node declaredIdentifier; + final Expression expression; + + final Token forToken; + final Token inToken; + + ForIn(this.declaredIdentifier, this.expression, + Statement body, this.forToken, this.inToken) : super(body); + + Expression get condition => null; + + ForIn asForIn() => this; + + accept(Visitor visitor) => visitor.visitForIn(this); + + visitChildren(Visitor visitor) { + declaredIdentifier.accept(visitor); + expression.accept(visitor); + body.accept(visitor); + } + + Token getBeginToken() => forToken; + + Token getEndToken() => body.getEndToken(); +} + +class Label extends Node { + final Identifier identifier; + final Token colonToken; + + Label(this.identifier, this.colonToken); + + String slowToString() => identifier.source.slowToString(); + + Label asLabel() => this; + + accept(Visitor visitor) => visitor.visitLabel(this); + + void visitChildren(Visitor visitor) { + identifier.accept(visitor); + } + + Token getBeginToken() => identifier.token; + Token getEndToken() => colonToken; +} + +class LabeledStatement extends Statement { + final NodeList labels; + final Statement statement; + + LabeledStatement(this.labels, this.statement); + + LabeledStatement asLabeledStatement() => this; + + accept(Visitor visitor) => visitor.visitLabeledStatement(this); + + visitChildren(Visitor visitor) { + labels.accept(visitor); + statement.accept(visitor); + } + + Token getBeginToken() => labels.getBeginToken(); + + Token getEndToken() => statement.getEndToken(); + + bool isValidContinueTarget() => statement.isValidContinueTarget(); + + Node getBody() => statement; +} + +class ScriptTag extends Node { + final Identifier tag; + final StringNode argument; + final Identifier prefixIdentifier; + final StringNode prefix; + + final Token beginToken; + final Token endToken; + + ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix, + this.beginToken, this.endToken); + + bool isImport() => tag.source == const SourceString("import"); + bool isSource() => tag.source == const SourceString("source"); + bool isLibrary() => tag.source == const SourceString("library"); + + ScriptTag asScriptTag() => this; + + accept(Visitor visitor) => visitor.visitScriptTag(this); + + visitChildren(Visitor visitor) { + tag.accept(visitor); + argument.accept(visitor); + if (prefixIdentifier != null) prefixIdentifier.accept(visitor); + if (prefix != null) prefix.accept(visitor); + } + + Token getBeginToken() => beginToken; + + Token getEndToken() => endToken; + + LibraryTag toLibraryTag() { + if (isImport()) { + Identifier prefixNode; + if (prefix != null) { + SourceString source = prefix.dartString.source; + Token prefixToken = prefix.getBeginToken(); + Token token = new StringToken.fromSource(IDENTIFIER_INFO, source, + prefixToken.charOffset); + token.next = prefixToken.next; + prefixNode = new Identifier(token); + } + return new Import(tag.token, argument, prefixNode, null, null); + } else if (isLibrary()) { + return new LibraryName(tag.token, argument, null); + } else if (isSource()) { + return new Part(tag.token, argument, null); + } else { + throw 'Unknown script tag ${tag.token.slowToString()}'; + } + } +} + +abstract class LibraryTag extends Node { + final Link<MetadataAnnotation> metadata; + + LibraryTag(this.metadata); + + bool get isLibraryName => false; + bool get isImport => false; + bool get isExport => false; + bool get isPart => false; + bool get isPartOf => false; +} + +class LibraryName extends LibraryTag { + final Expression name; + + final Token libraryKeyword; + + LibraryName(this.libraryKeyword, + this.name, + Link<MetadataAnnotation> metadata) + : super(metadata); + + bool get isLibraryName => true; + + LibraryName asLibraryName() => this; + + accept(Visitor visitor) => visitor.visitLibraryName(this); + + visitChildren(Visitor visitor) => name.accept(visitor); + + Token getBeginToken() => libraryKeyword; + + Token getEndToken() => name.getEndToken().next; +} + +/** + * This tag describes a dependency between one library and the exported + * identifiers of another library. The other library is specified by the [uri]. + * Combinators filter away some identifiers from the other library. + */ +abstract class LibraryDependency extends LibraryTag { + final StringNode uri; + final NodeList combinators; + + LibraryDependency(this.uri, + this.combinators, + Link<MetadataAnnotation> metadata) + : super(metadata); +} + +/** + * An [:import:] library tag. + * + * An import tag is dependency on another library where the exported identifiers + * are put into the import scope of the importing library. The import scope is + * only visible inside the library. + */ +class Import extends LibraryDependency { + final Identifier prefix; + final Token importKeyword; + + Import(this.importKeyword, StringNode uri, + this.prefix, NodeList combinators, + Link<MetadataAnnotation> metadata) + : super(uri, combinators, metadata); + + bool get isImport => true; + + Import asImport() => this; + + Token get asKeyword => prefix == null ? null : uri.getEndToken().next; + + accept(Visitor visitor) => visitor.visitImport(this); + + visitChildren(Visitor visitor) { + uri.accept(visitor); + if (prefix != null) prefix.accept(visitor); + if (combinators != null) combinators.accept(visitor); + } + + Token getBeginToken() => importKeyword; + + Token getEndToken() { + if (combinators != null) return combinators.getEndToken().next; + if (prefix != null) return prefix.getEndToken().next; + return uri.getEndToken().next; + } +} + +/** + * An [:export:] library tag. + * + * An export tag is dependency on another library where the exported identifiers + * are put into the export scope of the exporting library. The export scope is + * not visible inside the library. + */ +class Export extends LibraryDependency { + final Token exportKeyword; + + Export(this.exportKeyword, + StringNode uri, + NodeList combinators, + Link<MetadataAnnotation> metadata) + : super(uri, combinators, metadata); + + bool get isExport => true; + + Export asExport() => this; + + accept(Visitor visitor) => visitor.visitExport(this); + + visitChildren(Visitor visitor) { + uri.accept(visitor); + if (combinators != null) combinators.accept(visitor); + } + + Token getBeginToken() => exportKeyword; + + Token getEndToken() { + if (combinators != null) return combinators.getEndToken().next; + return uri.getEndToken().next; + } +} + +class Part extends LibraryTag { + final StringNode uri; + + final Token partKeyword; + + Part(this.partKeyword, this.uri, Link<MetadataAnnotation> metadata) + : super(metadata); + + bool get isPart => true; + + Part asPart() => this; + + accept(Visitor visitor) => visitor.visitPart(this); + + visitChildren(Visitor visitor) => uri.accept(visitor); + + Token getBeginToken() => partKeyword; + + Token getEndToken() => uri.getEndToken().next; +} + +class PartOf extends Node { + final Expression name; + + final Token partKeyword; + + final Link<MetadataAnnotation> metadata; + + PartOf(this.partKeyword, this.name, this.metadata); + + Token get ofKeyword => partKeyword.next; + + bool get isPartOf => true; + + PartOf asPartOf() => this; + + accept(Visitor visitor) => visitor.visitPartOf(this); + + visitChildren(Visitor visitor) => name.accept(visitor); + + Token getBeginToken() => partKeyword; + + Token getEndToken() => name.getEndToken().next; +} + +class Combinator extends Node { + final NodeList identifiers; + + final Token keywordToken; + + Combinator(this.identifiers, this.keywordToken); + + bool get isShow => identical(keywordToken.stringValue, 'show'); + + bool get isHide => identical(keywordToken.stringValue, 'hide'); + + Combinator asCombinator() => this; + + accept(Visitor visitor) => visitor.visitCombinator(this); + + visitChildren(Visitor visitor) => identifiers.accept(visitor); + + Token getBeginToken() => keywordToken; + + Token getEndToken() => identifiers.getEndToken(); +} + +class Typedef extends Node { + final TypeAnnotation returnType; + final Identifier name; + final NodeList typeParameters; + final NodeList formals; + + final Token typedefKeyword; + final Token endToken; + + Typedef(this.returnType, this.name, this.typeParameters, this.formals, + this.typedefKeyword, this.endToken); + + Typedef asTypedef() => this; + + accept(Visitor visitor) => visitor.visitTypedef(this); + + visitChildren(Visitor visitor) { + if (returnType != null) returnType.accept(visitor); + name.accept(visitor); + if (typeParameters != null) typeParameters.accept(visitor); + formals.accept(visitor); + } + + Token getBeginToken() => typedefKeyword; + + Token getEndToken() => endToken; +} + +class TryStatement extends Statement { + final Block tryBlock; + final NodeList catchBlocks; + final Block finallyBlock; + + final Token tryKeyword; + final Token finallyKeyword; + + TryStatement(this.tryBlock, this.catchBlocks, this.finallyBlock, + this.tryKeyword, this.finallyKeyword); + + TryStatement asTryStatement() => this; + + accept(Visitor visitor) => visitor.visitTryStatement(this); + + visitChildren(Visitor visitor) { + tryBlock.accept(visitor); + catchBlocks.accept(visitor); + if (finallyBlock != null) finallyBlock.accept(visitor); + } + + Token getBeginToken() => tryKeyword; + + Token getEndToken() { + if (finallyBlock != null) return finallyBlock.getEndToken(); + if (!catchBlocks.isEmpty) return catchBlocks.getEndToken(); + return tryBlock.getEndToken(); + } +} + +class Cascade extends Expression { + final Expression expression; + Cascade(this.expression); + + Cascade asCascade() => this; + accept(Visitor visitor) => visitor.visitCascade(this); + + void visitChildren(Visitor visitor) { + expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class CascadeReceiver extends Expression { + final Expression expression; + final Token cascadeOperator; + CascadeReceiver(this.expression, this.cascadeOperator); + + CascadeReceiver asCascadeReceiver() => this; + accept(Visitor visitor) => visitor.visitCascadeReceiver(this); + + void visitChildren(Visitor visitor) { + expression.accept(visitor); + } + + Token getBeginToken() => expression.getBeginToken(); + + Token getEndToken() => expression.getEndToken(); +} + +class CatchBlock extends Node { + final TypeAnnotation type; + final NodeList formals; + final Block block; + + final Token onKeyword; + final Token catchKeyword; + + CatchBlock(this.type, this.formals, this.block, + this.onKeyword, this.catchKeyword); + + CatchBlock asCatchBlock() => this; + + accept(Visitor visitor) => visitor.visitCatchBlock(this); + + Node get exception { + if (formals == null || formals.nodes.isEmpty) return null; + VariableDefinitions declarations = formals.nodes.head; + return declarations.definitions.nodes.head; + } + + Node get trace { + if (formals == null || formals.nodes.isEmpty) return null; + Link<Node> declarations = formals.nodes.tail; + if (declarations.isEmpty) return null; + VariableDefinitions head = declarations.head; + return head.definitions.nodes.head; + } + + visitChildren(Visitor visitor) { + if (type != null) type.accept(visitor); + if (formals != null) formals.accept(visitor); + block.accept(visitor); + } + + Token getBeginToken() => onKeyword != null ? onKeyword : catchKeyword; + + Token getEndToken() => block.getEndToken(); +} + +class Initializers { + static bool isSuperConstructorCall(Send node) { + return (node.receiver == null && + node.selector.asIdentifier() != null && + node.selector.asIdentifier().isSuper()) || + (node.receiver != null && + node.receiver.asIdentifier() != null && + node.receiver.asIdentifier().isSuper() && + node.selector.asIdentifier() != null); + } + + static bool isConstructorRedirect(Send node) { + return (node.receiver == null && + node.selector.asIdentifier() != null && + node.selector.asIdentifier().isThis()) || + (node.receiver != null && + node.receiver.asIdentifier() != null && + node.receiver.asIdentifier().isThis() && + node.selector.asIdentifier() != null); + } +} + +class GetDartStringVisitor extends Visitor<DartString> { + const GetDartStringVisitor(); + DartString visitNode(Node node) => null; + DartString visitStringJuxtaposition(StringJuxtaposition node) + => node.dartString; + DartString visitLiteralString(LiteralString node) => node.dartString; +} + +class IsInterpolationVisitor extends Visitor<bool> { + const IsInterpolationVisitor(); + bool visitNode(Node node) => false; + bool visitStringInterpolation(StringInterpolation node) => true; + bool visitStringJuxtaposition(StringJuxtaposition node) + => node.isInterpolation; +} + +/** + * If the given node is a send set, it visits its initializer (first + * argument). + * + * TODO(ahe): This method is controversial, the team needs to discuss + * if top-level methods are acceptable and what naming conventions to + * use. + */ +initializerDo(Node node, f(Node node)) { + SendSet send = node.asSendSet(); + if (send != null) return f(send.arguments.head); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart new file mode 100644 index 0000000..129c637 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart
@@ -0,0 +1,480 @@ +// 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. + +part of tree; + +/** + * Pretty-prints Node tree in XML-like format. + * + * TODO(smok): Add main() to run from command-line to print out tree for given + * .dart file. + */ +class PrettyPrinter implements Visitor { + + /** String used to represent one level of indent. */ + static const String INDENT = " "; + + StringBuffer sb; + Link<String> tagStack; + + PrettyPrinter() : + sb = new StringBuffer(), + tagStack = const Link<String>(); + + void pushTag(String tag) { + tagStack = tagStack.prepend(tag); + } + + String popTag() { + assert(!tagStack.isEmpty); + String tag = tagStack.head; + tagStack = tagStack.tail; + return tag; + } + + /** + * Adds given string to result string. + */ + void add(SourceString string) { + string.printOn(sb); + } + + void addBeginAndEndTokensToParams(Node node, Map params) { + params['getBeginToken'] = tokenToStringOrNull(node.getBeginToken()); + params['getEndToken'] = tokenToStringOrNull(node.getEndToken()); + } + + /** + * Adds given node type to result string. + * The method "opens" the node, meaning that all output after calling + * this method and before calling closeNode() will represent contents + * of given node. + */ + void openNode(Node node, String type, [Map params]) { + if (params == null) params = new Map(); + addCurrentIndent(); + sb.add("<"); + addBeginAndEndTokensToParams(node, params); + addTypeWithParams(type, params); + sb.add(">\n"); + pushTag(type); + } + + /** + * Adds given node to result string. + */ + void openAndCloseNode(Node node, String type, [Map params]) { + if (params == null) params = new Map(); + addCurrentIndent(); + sb.add("<"); + addBeginAndEndTokensToParams(node, params); + addTypeWithParams(type, params); + sb.add("/>\n"); + } + + /** + * Closes current node type. + */ + void closeNode() { + String tag = popTag(); + addCurrentIndent(); + sb.add("</"); + addTypeWithParams(tag); + sb.add(">\n"); + } + + void addTypeWithParams(String type, [Map params]) { + if (params == null) params = new Map(); + sb.add("${type}"); + params.forEach((k, v) { + String value; + if (v != null) { + value = v + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', "'"); + } else { + value = "[null]"; + } + sb.add(' $k="$value"'); + }); + } + + void addCurrentIndent() { + tagStack.forEach((_) { sb.add(INDENT); }); + } + + /** + * Pretty-prints given node tree into string. + */ + static String prettyPrint(Node node) { + var p = new PrettyPrinter(); + node.accept(p); + return p.sb.toString(); + } + + visitNodeWithChildren(Node node, String type) { + openNode(node, type); + node.visitChildren(this); + closeNode(); + } + + visitBlock(Block node) { + visitNodeWithChildren(node, "Block"); + } + + visitBreakStatement(BreakStatement node) { + visitNodeWithChildren(node, "BreakStatement"); + } + + visitCascade(Cascade node) { + visitNodeWithChildren(node, "Cascade"); + } + + visitCascadeReceiver(CascadeReceiver node) { + visitNodeWithChildren(node, "CascadeReceiver"); + } + + visitCaseMatch(CaseMatch node) { + visitNodeWithChildren(node, "CaseMatch"); + } + + visitCatchBlock(CatchBlock node) { + visitNodeWithChildren(node, "CatchBlock"); + } + + visitClassNode(ClassNode node) { + openNode(node, "ClassNode", { + "extendsKeyword" : tokenToStringOrNull(node.extendsKeyword) + }); + visitChildNode(node.name, "name"); + visitChildNode(node.superclass, "superclass"); + visitChildNode(node.interfaces, "interfaces"); + visitChildNode(node.typeParameters, "typeParameters"); + visitChildNode(node.defaultClause, "defaultClause"); + closeNode(); + } + + visitConditional(Conditional node) { + visitNodeWithChildren(node, "Conditional"); + } + + visitContinueStatement(ContinueStatement node) { + visitNodeWithChildren(node, "ContinueStatement"); + } + + visitDoWhile(DoWhile node) { + visitNodeWithChildren(node, "DoWhile"); + } + + visitEmptyStatement(EmptyStatement node) { + visitNodeWithChildren(node, "EmptyStatement"); + } + + visitExpressionStatement(ExpressionStatement node) { + visitNodeWithChildren(node, "ExpressionStatement"); + } + + visitFor(For node) { + visitNodeWithChildren(node, "For"); + } + + visitForIn(ForIn node) { + visitNodeWithChildren(node, "ForIn"); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visitNodeWithChildren(node, "FunctionDeclaration"); + } + + visitFunctionExpression(FunctionExpression node) { + openNode(node, "FunctionExpression", { + "getOrSet" : tokenToStringOrNull(node.getOrSet) + }); + visitChildNode(node.modifiers, "modifiers"); + visitChildNode(node.returnType, "returnType"); + visitChildNode(node.name, "name"); + visitChildNode(node.parameters, "parameters"); + visitChildNode(node.initializers, "initializers"); + visitChildNode(node.body, "body"); + closeNode(); + } + + visitIdentifier(Identifier node) { + openAndCloseNode(node, "Identifier", {"token" : node.token.slowToString()}); + } + + visitIf(If node) { + visitNodeWithChildren(node, "If"); + } + + visitLabel(Label node) { + visitNodeWithChildren(node, "Label"); + } + + visitLabeledStatement(LabeledStatement node) { + visitNodeWithChildren(node, "LabeledStatement"); + } + + // Custom. + printLiteral(Literal node, String type) { + openAndCloseNode(node, type, {"value" : node.value.toString()}); + } + + visitLiteralBool(LiteralBool node) { + printLiteral(node, "LiteralBool"); + } + + visitLiteralDouble(LiteralDouble node) { + printLiteral(node, "LiteralDouble"); + } + + visitLiteralInt(LiteralInt node) { + printLiteral(node, "LiteralInt"); + } + + /** Returns token string value or [null] if token is [null]. */ + tokenToStringOrNull(Token token) => token == null ? null : token.stringValue; + + visitLiteralList(LiteralList node) { + openNode(node, "LiteralList", { + "constKeyword" : tokenToStringOrNull(node.constKeyword) + }); + visitChildNode(node.typeArguments, "typeArguments"); + visitChildNode(node.elements, "elements"); + closeNode(); + } + + visitLiteralMap(LiteralMap node) { + visitNodeWithChildren(node, "LiteralMap"); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visitNodeWithChildren(node, "LiteralMapEntry"); + } + + visitLiteralNull(LiteralNull node) { + printLiteral(node, "LiteralNull"); + } + + visitLiteralString(LiteralString node) { + openAndCloseNode(node, "LiteralString", + {"value" : node.token.slowToString()}); + } + + visitMixinApplication(MixinApplication node) { + visitNodeWithChildren(node, "MixinApplication"); + } + + visitModifiers(Modifiers node) { + visitNodeWithChildren(node, "Modifiers"); + } + + visitNamedArgument(NamedArgument node) { + visitNodeWithChildren(node, "NamedArgument"); + } + + visitNamedMixinApplication(NamedMixinApplication node) { + visitNodeWithChildren(node, "NamedMixinApplication"); + } + + visitNewExpression(NewExpression node) { + visitNodeWithChildren(node, "NewExpression"); + } + + visitNodeList(NodeList node) { + var params = { + "delimiter" : + node.delimiter != null ? node.delimiter.stringValue : null + }; + if (node.nodes.toList().length == 0) { + openAndCloseNode(node, "NodeList", params); + } else { + openNode(node, "NodeList", params); + node.visitChildren(this); + closeNode(); + } + } + + visitOperator(Operator node) { + openAndCloseNode(node, "Operator", {"value" : node.token.slowToString()}); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + visitNodeWithChildren(node, "ParenthesizedExpression"); + } + + visitReturn(Return node) { + openNode(node, "Return"); + visitChildNode(node.expression, "expression"); + closeNode(); + } + + visitScriptTag(ScriptTag node) { + visitNodeWithChildren(node, "ScriptTag"); + } + + visitChildNode(Node node, String fieldName) { + if (node == null) return; + addCurrentIndent(); + sb.add("<$fieldName>\n"); + pushTag(fieldName); + node.accept(this); + popTag(); + addCurrentIndent(); + sb.add("</$fieldName>\n"); + } + + openSendNodeWithFields(Send node, String type) { + openNode(node, type, { + "isPrefix" : "${node.isPrefix}", + "isPostfix" : "${node.isPostfix}", + "isIndex" : "${node.isIndex}" + }); + visitChildNode(node.receiver, "receiver"); + visitChildNode(node.selector, "selector"); + visitChildNode(node.argumentsNode, "argumentsNode"); + } + + visitSend(Send node) { + openSendNodeWithFields(node, "Send"); + closeNode(); + } + + visitSendSet(SendSet node) { + openSendNodeWithFields(node, "SendSet"); + visitChildNode(node.assignmentOperator, "assignmentOperator"); + closeNode(); + } + + visitStringInterpolation(StringInterpolation node) { + visitNodeWithChildren(node, "StringInterpolation"); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + visitNodeWithChildren(node, "StringInterpolationPart"); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + visitNodeWithChildren(node, "StringJuxtaposition"); + } + + visitSwitchCase(SwitchCase node) { + visitNodeWithChildren(node, "SwitchCase"); + } + + visitSwitchStatement(SwitchStatement node) { + visitNodeWithChildren(node, "SwitchStatement"); + } + + visitThrow(Throw node) { + visitNodeWithChildren(node, "Throw"); + } + + visitTryStatement(TryStatement node) { + visitNodeWithChildren(node, "TryStatement"); + } + + visitTypeAnnotation(TypeAnnotation node) { + openNode(node, "TypeAnnotation"); + visitChildNode(node.typeName, "typeName"); + visitChildNode(node.typeArguments, "typeArguments"); + closeNode(); + } + + visitTypedef(Typedef node) { + visitNodeWithChildren(node, "Typedef"); + } + + visitTypeVariable(TypeVariable node) { + openNode(node, "TypeVariable"); + visitChildNode(node.name, "name"); + visitChildNode(node.bound, "bound"); + closeNode(); + } + + visitVariableDefinitions(VariableDefinitions node) { + openNode(node, "VariableDefinitions"); + visitChildNode(node.type, "type"); + visitChildNode(node.modifiers, "modifiers"); + visitChildNode(node.definitions, "definitions"); + closeNode(); + } + + visitWhile(While node) { + visitNodeWithChildren(node, "While"); + } + + visitNode(Node node) { + unimplemented('visitNode', node: node); + } + + visitCombinator(Combinator node) { + unimplemented('visitNode', node: node); + } + + visitExport(Export node) { + unimplemented('visitNode', node: node); + } + + visitExpression(Expression node) { + unimplemented('visitNode', node: node); + } + + visitGotoStatement(GotoStatement node) { + unimplemented('visitNode', node: node); + } + + visitImport(Import node) { + unimplemented('visitNode', node: node); + } + + visitLibraryDependency(Node node) { + unimplemented('visitNode', node: node); + } + + visitLibraryName(LibraryName node) { + unimplemented('visitNode', node: node); + } + + visitLibraryTag(LibraryTag node) { + unimplemented('visitNode', node: node); + } + + visitLiteral(Literal node) { + unimplemented('visitNode', node: node); + } + + visitLoop(Loop node) { + unimplemented('visitNode', node: node); + } + + visitPart(Part node) { + unimplemented('visitNode', node: node); + } + + visitPartOf(PartOf node) { + unimplemented('visitNode', node: node); + } + + visitPostfix(Postfix node) { + unimplemented('visitNode', node: node); + } + + visitPrefix(Prefix node) { + unimplemented('visitNode', node: node); + } + + visitStatement(Statement node) { + unimplemented('visitNode', node: node); + } + + visitStringNode(StringNode node) { + unimplemented('visitNode', node: node); + } + + unimplemented(String message, {Node node}) { + throw message; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart new file mode 100644 index 0000000..3faa36c --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart
@@ -0,0 +1,22 @@ +// 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. + +library tree; + +import 'dart:math'; +import 'dart:collection'; + +import '../scanner/scannerlib.dart'; +import '../util/util.dart'; +import '../util/characters.dart'; + +import '../resolution/secret_tree_element.dart' show TreeElementMixin; + +import '../elements/elements.dart' show MetadataAnnotation; + +part 'dartstring.dart'; +part 'nodes.dart'; +part 'prettyprint.dart'; +part 'unparser.dart'; +part 'visitors.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart new file mode 100644 index 0000000..9c20f2d --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart
@@ -0,0 +1,627 @@ +// 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. + +part of tree; + +String unparse(Node node) { + Unparser unparser = new Unparser(); + unparser.unparse(node); + return unparser.result; +} + +class Unparser implements Visitor { + final StringBuffer sb; + + String get result => sb.toString(); + + Unparser() : sb = new StringBuffer(); + + void add(SourceString string) { + string.printOn(sb); + } + + void addToken(Token token) { + if (token == null) return; + add(token.value); + if (identical(token.kind, KEYWORD_TOKEN) + || identical(token.kind, IDENTIFIER_TOKEN)) { + sb.add(' '); + } + } + + unparse(Node node) { visit(node); } + + visit(Node node) { + if (node != null) node.accept(this); + } + + visitBlock(Block node) { + visit(node.statements); + } + + visitCascade(Cascade node) { + visit(node.expression); + } + + visitCascadeReceiver(CascadeReceiver node) { + visit(node.expression); + } + + unparseClassWithBody(ClassNode node, Iterable<Node> members) { + addToken(node.beginToken); + if (node.beginToken.stringValue == 'abstract') { + addToken(node.beginToken.next); + } + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + if (node.extendsKeyword != null) { + sb.add(' '); + addToken(node.extendsKeyword); + visit(node.superclass); + } + if (!node.interfaces.isEmpty) { + sb.add(' '); + visit(node.interfaces); + } + if (node.defaultClause != null) { + sb.add(' default '); + visit(node.defaultClause); + } + sb.add('{'); + for (final member in members) { + visit(member); + } + sb.add('}'); + } + + visitClassNode(ClassNode node) { + unparseClassWithBody(node, node.body.nodes); + } + + visitMixinApplication(MixinApplication node) { + visit(node.superclass); + sb.add(' with '); + visit(node.mixins); + } + + visitNamedMixinApplication(NamedMixinApplication node) { + sb.add('typedef '); + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + sb.add(' = '); + if (!node.modifiers.nodes.isEmpty) { + visit(node.modifiers); + sb.add(' '); + } + visit(node.mixinApplication); + if (node.interfaces != null) { + sb.add(' implements '); + visit(node.interfaces); + } + sb.add(';'); + } + + visitConditional(Conditional node) { + visit(node.condition); + add(node.questionToken.value); + visit(node.thenExpression); + add(node.colonToken.value); + visit(node.elseExpression); + } + + visitExpressionStatement(ExpressionStatement node) { + visit(node.expression); + add(node.endToken.value); + } + + visitFor(For node) { + add(node.forToken.value); + sb.add('('); + visit(node.initializer); + sb.add(';'); + visit(node.conditionStatement); + visit(node.update); + sb.add(')'); + visit(node.body); + } + + visitFunctionDeclaration(FunctionDeclaration node) { + visit(node.function); + } + + void unparseFunctionName(Node name) { + // TODO(antonm): that's a workaround as currently FunctionExpression + // names are modelled with Send and it emits operator[] as only + // operator, without [] which are expected to be emitted with + // arguments. + if (name is Send) { + Send send = name; + assert(send is !SendSet); + if (!send.isOperator) { + // Looks like a factory method. + visit(send.receiver); + sb.add('.'); + } else { + visit(send.receiver); + Identifier identifier = send.selector.asIdentifier(); + if (identical(identifier.token.kind, KEYWORD_TOKEN)) { + sb.add(' '); + } else if (identifier.source == const SourceString('negate')) { + // TODO(ahe): Remove special case for negate. + sb.add(' '); + } + } + visit(send.selector); + } else { + visit(name); + } + } + + visitFunctionExpression(FunctionExpression node) { + if (!node.modifiers.nodes.isEmpty) { + visit(node.modifiers); + sb.add(' '); + } + if (node.returnType != null) { + visit(node.returnType); + sb.add(' '); + } + if (node.getOrSet != null) { + add(node.getOrSet.value); + sb.add(' '); + } + unparseFunctionName(node.name); + visit(node.parameters); + visit(node.initializers); + visit(node.body); + } + + visitIdentifier(Identifier node) { + add(node.token.value); + } + + visitIf(If node) { + add(node.ifToken.value); + visit(node.condition); + visit(node.thenPart); + if (node.hasElsePart) { + add(node.elseToken.value); + if (node.elsePart is !Block) sb.add(' '); + visit(node.elsePart); + } + } + + visitLiteralBool(LiteralBool node) { + add(node.token.value); + } + + visitLiteralDouble(LiteralDouble node) { + add(node.token.value); + // -Lit is represented as a send. + if (node.token.kind == PLUS_TOKEN) add(node.token.next.value); + } + + visitLiteralInt(LiteralInt node) { + add(node.token.value); + // -Lit is represented as a send. + if (node.token.kind == PLUS_TOKEN) add(node.token.next.value); + } + + visitLiteralString(LiteralString node) { + add(node.token.value); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + visit(node.first); + sb.add(" "); + visit(node.second); + } + + visitLiteralNull(LiteralNull node) { + add(node.token.value); + } + + visitNewExpression(NewExpression node) { + addToken(node.newToken); + visit(node.send); + } + + visitLiteralList(LiteralList node) { + if (node.constKeyword != null) add(node.constKeyword.value); + visit(node.typeArguments); + visit(node.elements); + // If list is empty, emit space after [] to disambiguate cases like []==[]. + if (node.elements.isEmpty) sb.add(' '); + } + + visitModifiers(Modifiers node) => node.visitChildren(this); + + /** + * Unparses given NodeList starting from specific node. + */ + unparseNodeListFrom(NodeList node, Link<Node> from) { + if (from.isEmpty) return; + String delimiter = (node.delimiter == null) ? "" : "${node.delimiter}"; + visit(from.head); + for (Link link = from.tail; !link.isEmpty; link = link.tail) { + sb.add(delimiter); + visit(link.head); + } + } + + visitNodeList(NodeList node) { + addToken(node.beginToken); + if (node.nodes != null) { + unparseNodeListFrom(node, node.nodes); + } + if (node.endToken != null) add(node.endToken.value); + } + + visitOperator(Operator node) { + visitIdentifier(node); + } + + visitReturn(Return node) { + if (node.isRedirectingFactoryBody) { + sb.add(' '); + } + add(node.beginToken.value); + if (node.hasExpression && node.beginToken.stringValue != '=>') { + sb.add(' '); + } + visit(node.expression); + if (node.endToken != null) add(node.endToken.value); + } + + unparseSendReceiver(Send node, {bool spacesNeeded: false}) { + if (node.receiver == null) return; + visit(node.receiver); + CascadeReceiver asCascadeReceiver = node.receiver.asCascadeReceiver(); + if (asCascadeReceiver != null) { + add(asCascadeReceiver.cascadeOperator.value); + } else if (node.selector.asOperator() == null) { + sb.add('.'); + } else if (spacesNeeded) { + sb.add(' '); + } + } + + visitSend(Send node) { + Operator op = node.selector.asOperator(); + String opString = op != null ? op.source.stringValue : null; + bool spacesNeeded = identical(opString, 'is') || identical(opString, 'as'); + + if (node.isPrefix) visit(node.selector); + unparseSendReceiver(node, spacesNeeded: spacesNeeded); + if (!node.isPrefix && !node.isIndex) visit(node.selector); + if (spacesNeeded) sb.add(' '); + // Also add a space for sequences like x + +1 and y - -y. + // TODO(ahe): remove case for '+' when we drop the support for it. + if (node.argumentsNode != null && (identical(opString, '-') + || identical(opString, '+'))) { + Token beginToken = node.argumentsNode.getBeginToken(); + if (beginToken != null && identical(beginToken.stringValue, opString)) { + sb.add(' '); + } + } + visit(node.argumentsNode); + } + + visitSendSet(SendSet node) { + if (node.isPrefix) { + sb.add(' '); + visit(node.assignmentOperator); + } + unparseSendReceiver(node); + if (node.isIndex) { + sb.add('['); + visit(node.arguments.head); + sb.add(']'); + if (!node.isPrefix) visit(node.assignmentOperator); + unparseNodeListFrom(node.argumentsNode, node.argumentsNode.nodes.tail); + } else { + visit(node.selector); + if (!node.isPrefix) { + visit(node.assignmentOperator); + if (node.assignmentOperator.source.slowToString() != '=') sb.add(' '); + } + visit(node.argumentsNode); + } + } + + visitThrow(Throw node) { + add(node.throwToken.value); + if (node.expression != null) { + sb.add(' '); + visit(node.expression); + } + node.endToken.value.printOn(sb); + } + + visitTypeAnnotation(TypeAnnotation node) { + visit(node.typeName); + visit(node.typeArguments); + } + + visitTypeVariable(TypeVariable node) { + visit(node.name); + if (node.bound != null) { + sb.add(' extends '); + visit(node.bound); + } + } + + visitVariableDefinitions(VariableDefinitions node) { + visit(node.modifiers); + if (!node.modifiers.nodes.isEmpty) { + sb.add(' '); + } + if (node.type != null) { + visit(node.type); + sb.add(' '); + } + visit(node.definitions); + } + + visitDoWhile(DoWhile node) { + add(node.doKeyword.value); + if (node.body is !Block) sb.add(' '); + visit(node.body); + add(node.whileKeyword.value); + visit(node.condition); + sb.add(node.endToken.value); + } + + visitWhile(While node) { + addToken(node.whileKeyword); + visit(node.condition); + visit(node.body); + } + + visitParenthesizedExpression(ParenthesizedExpression node) { + add(node.getBeginToken().value); + visit(node.expression); + add(node.getEndToken().value); + } + + visitStringInterpolation(StringInterpolation node) { + visit(node.string); + visit(node.parts); + } + + visitStringInterpolationPart(StringInterpolationPart node) { + sb.add('\${'); // TODO(ahe): Preserve the real tokens. + visit(node.expression); + sb.add('}'); + visit(node.string); + } + + visitEmptyStatement(EmptyStatement node) { + add(node.semicolonToken.value); + } + + visitGotoStatement(GotoStatement node) { + add(node.keywordToken.value); + if (node.target != null) { + sb.add(' '); + visit(node.target); + } + add(node.semicolonToken.value); + } + + visitBreakStatement(BreakStatement node) { + visitGotoStatement(node); + } + + visitContinueStatement(ContinueStatement node) { + visitGotoStatement(node); + } + + visitForIn(ForIn node) { + add(node.forToken.value); + sb.add('('); + visit(node.declaredIdentifier); + sb.add(' '); + addToken(node.inToken); + visit(node.expression); + sb.add(')'); + visit(node.body); + } + + visitLabel(Label node) { + visit(node.identifier); + add(node.colonToken.value); + } + + visitLabeledStatement(LabeledStatement node) { + visit(node.labels); + visit(node.statement); + } + + visitLiteralMap(LiteralMap node) { + if (node.constKeyword != null) add(node.constKeyword.value); + if (node.typeArguments != null) visit(node.typeArguments); + visit(node.entries); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + visit(node.key); + add(node.colonToken.value); + visit(node.value); + } + + visitNamedArgument(NamedArgument node) { + visit(node.name); + add(node.colonToken.value); + visit(node.expression); + } + + visitSwitchStatement(SwitchStatement node) { + addToken(node.switchKeyword); + visit(node.parenthesizedExpression); + visit(node.cases); + } + + visitSwitchCase(SwitchCase node) { + visit(node.labelsAndCases); + if (node.isDefaultCase) { + sb.add('default:'); + } + visit(node.statements); + } + + unparseImportTag(String uri, [String prefix]) { + final suffix = prefix == null ? '' : ' as $prefix'; + sb.add('import "$uri"$suffix;'); + } + + visitScriptTag(ScriptTag node) { + add(node.beginToken.value); + visit(node.tag); + sb.add('('); + visit(node.argument); + if (node.prefixIdentifier != null) { + visit(node.prefixIdentifier); + sb.add(':'); + visit(node.prefix); + } + sb.add(')'); + add(node.endToken.value); + } + + visitTryStatement(TryStatement node) { + addToken(node.tryKeyword); + visit(node.tryBlock); + visit(node.catchBlocks); + if (node.finallyKeyword != null) { + addToken(node.finallyKeyword); + visit(node.finallyBlock); + } + } + + visitCaseMatch(CaseMatch node) { + add(node.caseKeyword.value); + sb.add(" "); + visit(node.expression); + add(node.colonToken.value); + } + + visitCatchBlock(CatchBlock node) { + addToken(node.onKeyword); + if (node.type != null) { + visit(node.type); + sb.add(' '); + } + addToken(node.catchKeyword); + visit(node.formals); + visit(node.block); + } + + visitTypedef(Typedef node) { + addToken(node.typedefKeyword); + if (node.returnType != null) { + visit(node.returnType); + sb.add(' '); + } + visit(node.name); + if (node.typeParameters != null) { + visit(node.typeParameters); + } + visit(node.formals); + add(node.endToken.value); + } + + visitLibraryName(LibraryName node) { + addToken(node.libraryKeyword); + node.visitChildren(this); + add(node.getEndToken().value); + } + + visitImport(Import node) { + addToken(node.importKeyword); + visit(node.uri); + if (node.prefix != null) { + sb.add(' '); + addToken(node.asKeyword); + visit(node.prefix); + } + if (node.combinators != null) { + sb.add(' '); + visit(node.combinators); + } + add(node.getEndToken().value); + } + + visitExport(Export node) { + addToken(node.exportKeyword); + visit(node.uri); + if (node.combinators != null) { + sb.add(' '); + visit(node.combinators); + } + add(node.getEndToken().value); + } + + visitPart(Part node) { + addToken(node.partKeyword); + visit(node.uri); + add(node.getEndToken().value); + } + + visitPartOf(PartOf node) { + addToken(node.partKeyword); + addToken(node.ofKeyword); + visit(node.name); + add(node.getEndToken().value); + } + + visitCombinator(Combinator node) { + addToken(node.keywordToken); + visit(node.identifiers); + } + + visitNode(Node node) { + throw 'internal error'; // Should not be called. + } + + visitExpression(Expression node) { + throw 'internal error'; // Should not be called. + } + + visitLibraryTag(LibraryTag node) { + throw 'internal error'; // Should not be called. + } + + visitLibraryDependency(Node node) { + throw 'internal error'; // Should not be called. + } + + visitLiteral(Literal node) { + throw 'internal error'; // Should not be called. + } + + visitLoop(Loop node) { + throw 'internal error'; // Should not be called. + } + + visitPostfix(Postfix node) { + throw 'internal error'; // Should not be called. + } + + visitPrefix(Prefix node) { + throw 'internal error'; // Should not be called. + } + + visitStatement(Statement node) { + throw 'internal error'; // Should not be called. + } + + visitStringNode(StringNode node) { + throw 'internal error'; // Should not be called. + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart new file mode 100644 index 0000000..9096cff --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart
@@ -0,0 +1,21 @@ +// Copyright (c) 2011, 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. + +part of tree; + +/** + * This visitor takes another visitor and applies it to every + * node in the tree. There is currently no way to control the + * traversal. + */ +class TraversingVisitor extends Visitor { + final Visitor visitor; + + TraversingVisitor(Visitor this.visitor); + + visitNode(Node node) { + node.accept(visitor); + node.visitChildren(this); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart b/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart new file mode 100644 index 0000000..6a3b93f --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart
@@ -0,0 +1,78 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class TreeValidatorTask extends CompilerTask { + TreeValidatorTask(Compiler compiler) : super(compiler); + + void validate(Node tree) { + assert(check(tree)); + } + + bool check(Node tree) { + List<InvalidNodeError> errors = []; + void report(node, message) { + final error = new InvalidNodeError(node, message); + errors.add(error); + compiler.reportWarning(node, message); + }; + final validator = new ValidatorVisitor(report); + tree.accept(new TraversingVisitor(validator)); + + return errors.isEmpty; + } +} + +class ValidatorVisitor extends Visitor { + final Function reportInvalidNode; + + ValidatorVisitor(Function this.reportInvalidNode); + + expect(Node node, bool test, [message]) { + if (!test) reportInvalidNode(node, message); + } + + visitNode(Node node) {} + + visitSendSet(SendSet node) { + final selector = node.selector; + final name = node.assignmentOperator.source.stringValue; + final arguments = node.arguments; + + expect(node, arguments != null); + expect(node, selector is Identifier, 'selector is not assignable'); + if (identical(name, '++') || identical(name, '--')) { + expect(node, node.assignmentOperator is Operator); + if (node.isIndex) { + expect(node.arguments.tail.head, node.arguments.tail.isEmpty); + } else { + expect(node.arguments.head, node.arguments.isEmpty); + } + } else { + expect(node, !node.arguments.isEmpty); + } + } + + visitReturn(Return node) { + if (!node.isRedirectingFactoryBody && node.hasExpression) { + // We allow non-expression expressions in Return nodes, but only when + // using them to hold redirecting factory constructors. + expect(node, node.expression.asExpression() != null); + } + } +} + +class InvalidNodeError { + final Node node; + final String message; + InvalidNodeError(this.node, [this.message]); + + toString() { + String nodeString = node.toDebugString(); + String result = 'invalid node: $nodeString'; + if (message != null) result = '$result ($message)'; + return result; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart b/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart new file mode 100644 index 0000000..01fa607 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart
@@ -0,0 +1,751 @@ +// 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. + +part of dart2js; + +class TypeCheckerTask extends CompilerTask { + TypeCheckerTask(Compiler compiler) : super(compiler); + String get name => "Type checker"; + + static const bool LOG_FAILURES = false; + + void check(Node tree, TreeElements elements) { + measure(() { + Visitor visitor = + new TypeCheckerVisitor(compiler, elements, compiler.types); + try { + tree.accept(visitor); + } on CancelTypeCheckException catch (e) { + if (LOG_FAILURES) { + // Do not warn about unimplemented features; log message instead. + compiler.log("'${e.node}': ${e.reason}"); + } + } + }); + } +} + +class CancelTypeCheckException { + final Node node; + final String reason; + + CancelTypeCheckException(this.node, this.reason); +} + +class TypeCheckerVisitor implements Visitor<DartType> { + final Compiler compiler; + final TreeElements elements; + final Types types; + + Node lastSeenNode; + DartType expectedReturnType; + ClassElement currentClass; + + Link<DartType> cascadeTypes = const Link<DartType>(); + + DartType intType; + DartType doubleType; + DartType boolType; + DartType stringType; + DartType objectType; + DartType listType; + + TypeCheckerVisitor(this.compiler, this.elements, this.types) { + intType = compiler.intClass.computeType(compiler); + doubleType = compiler.doubleClass.computeType(compiler); + boolType = compiler.boolClass.computeType(compiler); + stringType = compiler.stringClass.computeType(compiler); + objectType = compiler.objectClass.computeType(compiler); + listType = compiler.listClass.computeType(compiler); + } + + DartType fail(node, [reason]) { + String message = 'cannot type-check'; + if (reason != null) { + message = '$message: $reason'; + } + throw new CancelTypeCheckException(node, message); + } + + reportTypeWarning(Node node, MessageKind kind, [Map arguments = const {}]) { + compiler.reportWarning(node, new TypeWarning(kind, arguments)); + } + + // TODO(karlklose): remove these functions. + DartType unhandledStatement() => StatementType.NOT_RETURNING; + DartType unhandledExpression() => types.dynamicType; + + DartType analyzeNonVoid(Node node) { + DartType type = analyze(node); + if (type == types.voidType) { + reportTypeWarning(node, MessageKind.VOID_EXPRESSION); + } + return type; + } + + DartType analyzeWithDefault(Node node, DartType defaultValue) { + return node != null ? analyze(node) : defaultValue; + } + + DartType analyze(Node node) { + if (node == null) { + final String error = 'internal error: unexpected node: null'; + if (lastSeenNode != null) { + fail(null, error); + } else { + compiler.cancel(error); + } + } else { + lastSeenNode = node; + } + DartType result = node.accept(this); + // TODO(karlklose): record type? + if (result == null) { + fail(node, 'internal error: type is null'); + } + return result; + } + + /** + * Check if a value of type t can be assigned to a variable, + * parameter or return value of type s. + */ + checkAssignable(Node node, DartType s, DartType t) { + if (!types.isAssignable(s, t)) { + reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE, + {'fromType': s, 'toType': t}); + } + } + + checkCondition(Expression condition) { + checkAssignable(condition, boolType, analyze(condition)); + } + + void pushCascadeType(DartType type) { + cascadeTypes = cascadeTypes.prepend(type); + } + + DartType popCascadeType() { + DartType type = cascadeTypes.head; + cascadeTypes = cascadeTypes.tail; + return type; + } + + DartType visitBlock(Block node) { + return analyze(node.statements); + } + + DartType visitCascade(Cascade node) { + analyze(node.expression); + return popCascadeType(); + } + + DartType visitCascadeReceiver(CascadeReceiver node) { + DartType type = analyze(node.expression); + pushCascadeType(type); + return type; + } + + DartType visitClassNode(ClassNode node) { + fail(node); + } + + DartType visitMixinApplication(MixinApplication node) { + fail(node); + } + + DartType visitNamedMixinApplication(NamedMixinApplication node) { + fail(node); + } + + DartType visitDoWhile(DoWhile node) { + StatementType bodyType = analyze(node.body); + checkCondition(node.condition); + return bodyType.join(StatementType.NOT_RETURNING); + } + + DartType visitExpressionStatement(ExpressionStatement node) { + analyze(node.expression); + return StatementType.NOT_RETURNING; + } + + /** Dart Programming Language Specification: 11.5.1 For Loop */ + DartType visitFor(For node) { + analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING); + checkCondition(node.condition); + analyzeWithDefault(node.update, StatementType.NOT_RETURNING); + StatementType bodyType = analyze(node.body); + return bodyType.join(StatementType.NOT_RETURNING); + } + + DartType visitFunctionDeclaration(FunctionDeclaration node) { + analyze(node.function); + return StatementType.NOT_RETURNING; + } + + DartType visitFunctionExpression(FunctionExpression node) { + DartType type; + DartType returnType; + DartType previousType; + final FunctionElement element = elements[node]; + if (Elements.isUnresolved(element)) return types.dynamicType; + if (identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR) || + identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY)) { + type = types.dynamicType; + returnType = types.voidType; + } else { + FunctionType functionType = computeType(element); + returnType = functionType.returnType; + type = functionType; + } + DartType previous = expectedReturnType; + expectedReturnType = returnType; + if (element.isMember()) currentClass = element.getEnclosingClass(); + StatementType bodyType = analyze(node.body); + if (returnType != types.voidType && returnType != types.dynamicType + && bodyType != StatementType.RETURNING) { + MessageKind kind; + if (bodyType == StatementType.MAYBE_RETURNING) { + kind = MessageKind.MAYBE_MISSING_RETURN; + } else { + kind = MessageKind.MISSING_RETURN; + } + reportTypeWarning(node.name, kind); + } + expectedReturnType = previous; + return type; + } + + DartType visitIdentifier(Identifier node) { + if (node.isThis()) { + return currentClass.computeType(compiler); + } else { + // This is an identifier of a formal parameter. + return types.dynamicType; + } + } + + DartType visitIf(If node) { + checkCondition(node.condition); + StatementType thenType = analyze(node.thenPart); + StatementType elseType = node.hasElsePart ? analyze(node.elsePart) + : StatementType.NOT_RETURNING; + return thenType.join(elseType); + } + + DartType visitLoop(Loop node) { + return unhandledStatement(); + } + + DartType lookupMethodType(Node node, ClassElement classElement, + SourceString name) { + Element member = classElement.lookupLocalMember(name); + if (member == null) { + classElement.ensureResolved(compiler); + for (Link<DartType> supertypes = classElement.allSupertypes; + !supertypes.isEmpty && member == null; + supertypes = supertypes.tail) { + ClassElement lookupTarget = supertypes.head.element; + member = lookupTarget.lookupLocalMember(name); + } + } + if (member != null && member.kind == ElementKind.FUNCTION) { + return computeType(member); + } + reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND, + {'className': classElement.name, 'methodName': name}); + return types.dynamicType; + } + + // TODO(johnniwinther): Provide the element from which the type came in order + // to give better error messages. + void analyzeArguments(Send send, DartType type) { + Link<Node> arguments = send.arguments; + if (type == null || identical(type, types.dynamicType)) { + while(!arguments.isEmpty) { + analyze(arguments.head); + arguments = arguments.tail; + } + } else { + FunctionType funType = type; + Link<DartType> parameterTypes = funType.parameterTypes; + Link<DartType> optionalParameterTypes = funType.optionalParameterTypes; + while (!arguments.isEmpty) { + Node argument = arguments.head; + NamedArgument namedArgument = argument.asNamedArgument(); + if (namedArgument != null) { + argument = namedArgument.expression; + SourceString argumentName = namedArgument.name.source; + DartType namedParameterType = + funType.getNamedParameterType(argumentName); + if (namedParameterType == null) { + // TODO(johnniwinther): Provide better information on the called + // function. + reportTypeWarning(argument, MessageKind.NAMED_ARGUMENT_NOT_FOUND, + {'argumentName': argumentName}); + + analyze(argument); + } else { + checkAssignable(argument, namedParameterType, analyze(argument)); + } + } else { + if (parameterTypes.isEmpty) { + if (optionalParameterTypes.isEmpty) { + // TODO(johnniwinther): Provide better information on the + // called function. + reportTypeWarning(argument, MessageKind.ADDITIONAL_ARGUMENT); + + analyze(argument); + } else { + checkAssignable(argument, optionalParameterTypes.head, + analyze(argument)); + optionalParameterTypes = optionalParameterTypes.tail; + } + } else { + checkAssignable(argument, parameterTypes.head, analyze(argument)); + parameterTypes = parameterTypes.tail; + } + } + arguments = arguments.tail; + } + if (!parameterTypes.isEmpty) { + // TODO(johnniwinther): Provide better information on the called + // function. + reportTypeWarning(send, MessageKind.MISSING_ARGUMENT, + {'argumentType': parameterTypes.head}); + } + } + } + + DartType visitSend(Send node) { + Element element = elements[node]; + + if (Elements.isClosureSend(node, element)) { + // TODO(karlklose): Finish implementation. + return types.dynamicType; + } + + Identifier selector = node.selector.asIdentifier(); + String name = selector.source.stringValue; + + if (node.isOperator && identical(name, 'is')) { + analyze(node.receiver); + return boolType; + } else if (node.isOperator) { + final Node firstArgument = node.receiver; + final DartType firstArgumentType = analyze(node.receiver); + final arguments = node.arguments; + final Node secondArgument = arguments.isEmpty ? null : arguments.head; + final DartType secondArgumentType = + analyzeWithDefault(secondArgument, null); + + if (identical(name, '+') || identical(name, '=') || identical(name, '-') + || identical(name, '*') || identical(name, '/') || identical(name, '%') + || identical(name, '~/') || identical(name, '|') || identical(name, '&') + || identical(name, '^') || identical(name, '~')|| identical(name, '<<') + || identical(name, '>>') || identical(name, '[]')) { + return types.dynamicType; + } else if (identical(name, '<') || identical(name, '>') || identical(name, '<=') + || identical(name, '>=') || identical(name, '==') || identical(name, '!=') + || identical(name, '===') || identical(name, '!==')) { + return boolType; + } else if (identical(name, '||') || identical(name, '&&') || identical(name, '!')) { + checkAssignable(firstArgument, boolType, firstArgumentType); + if (!arguments.isEmpty) { + // TODO(karlklose): check number of arguments in validator. + checkAssignable(secondArgument, boolType, secondArgumentType); + } + return boolType; + } + fail(selector, 'unexpected operator ${name}'); + + } else if (node.isPropertyAccess) { + if (node.receiver != null) { + // TODO(karlklose): we cannot handle fields. + return unhandledExpression(); + } + if (element == null) return types.dynamicType; + return computeType(element); + + } else if (node.isFunctionObjectInvocation) { + fail(node.receiver, 'function object invocation unimplemented'); + + } else { + FunctionType computeFunType() { + if (node.receiver != null) { + DartType receiverType = analyze(node.receiver); + if (receiverType.element == compiler.dynamicClass) return null; + if (receiverType == null) { + fail(node.receiver, 'receivertype is null'); + } + if (identical(receiverType.element.kind, ElementKind.GETTER)) { + FunctionType getterType = receiverType; + receiverType = getterType.returnType; + } + ElementKind receiverKind = receiverType.element.kind; + if (identical(receiverKind, ElementKind.TYPEDEF)) { + // TODO(karlklose): handle typedefs. + return null; + } + if (identical(receiverKind, ElementKind.TYPE_VARIABLE)) { + // TODO(karlklose): handle type variables. + return null; + } + if (!identical(receiverKind, ElementKind.CLASS)) { + fail(node.receiver, 'unexpected receiver kind: ${receiverKind}'); + } + ClassElement classElement = receiverType.element; + // TODO(karlklose): substitute type arguments. + DartType memberType = + lookupMethodType(selector, classElement, selector.source); + if (identical(memberType.element, compiler.dynamicClass)) return null; + return memberType; + } else { + if (Elements.isUnresolved(element)) { + fail(node, 'unresolved ${node.selector}'); + } else if (identical(element.kind, ElementKind.FUNCTION)) { + return computeType(element); + } else if (element.isForeign(compiler)) { + return null; + } else if (identical(element.kind, ElementKind.VARIABLE) + || identical(element.kind, ElementKind.FIELD)) { + // TODO(karlklose): handle object invocations. + return null; + } else { + fail(node, 'unexpected element kind ${element.kind}'); + } + } + } + FunctionType funType = computeFunType(); + analyzeArguments(node, funType); + return (funType != null) ? funType.returnType : types.dynamicType; + } + } + + visitSendSet(SendSet node) { + Identifier selector = node.selector; + final name = node.assignmentOperator.source.stringValue; + if (identical(name, '++') || identical(name, '--')) { + final Element element = elements[node.selector]; + final DartType receiverType = computeType(element); + // TODO(karlklose): this should be the return type instead of int. + return node.isPrefix ? intType : receiverType; + } else { + DartType targetType = computeType(elements[node]); + Node value = node.arguments.head; + checkAssignable(value, targetType, analyze(value)); + return targetType; + } + } + + DartType visitLiteralInt(LiteralInt node) { + return intType; + } + + DartType visitLiteralDouble(LiteralDouble node) { + return doubleType; + } + + DartType visitLiteralBool(LiteralBool node) { + return boolType; + } + + DartType visitLiteralString(LiteralString node) { + return stringType; + } + + DartType visitStringJuxtaposition(StringJuxtaposition node) { + analyze(node.first); + analyze(node.second); + return stringType; + } + + DartType visitLiteralNull(LiteralNull node) { + return types.dynamicType; + } + + DartType visitNewExpression(NewExpression node) { + Element element = elements[node.send]; + analyzeArguments(node.send, computeType(element)); + return analyze(node.send.selector); + } + + DartType visitLiteralList(LiteralList node) { + return listType; + } + + DartType visitNodeList(NodeList node) { + DartType type = StatementType.NOT_RETURNING; + bool reportedDeadCode = false; + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + DartType nextType = analyze(link.head); + if (type == StatementType.RETURNING) { + if (!reportedDeadCode) { + reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE); + reportedDeadCode = true; + } + } else if (type == StatementType.MAYBE_RETURNING){ + if (nextType == StatementType.RETURNING) { + type = nextType; + } + } else { + type = nextType; + } + } + return type; + } + + DartType visitOperator(Operator node) { + fail(node, 'internal error'); + } + + /** Dart Programming Language Specification: 11.10 Return */ + DartType visitReturn(Return node) { + if (identical(node.getBeginToken().stringValue, 'native')) { + return StatementType.RETURNING; + } + if (node.isRedirectingFactoryBody) { + // TODO(lrn): Typecheck the body. It must refer to the constructor + // of a subtype. + return StatementType.RETURNING; + } + + final expression = node.expression; + final isVoidFunction = (identical(expectedReturnType, types.voidType)); + + // Executing a return statement return e; [...] It is a static type warning + // if the type of e may not be assigned to the declared return type of the + // immediately enclosing function. + if (expression != null) { + final expressionType = analyze(expression); + if (isVoidFunction + && !types.isAssignable(expressionType, types.voidType)) { + reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID); + } else { + checkAssignable(expression, expectedReturnType, expressionType); + } + + // Let f be the function immediately enclosing a return statement of the + // form 'return;' It is a static warning if both of the following conditions + // hold: + // - f is not a generative constructor. + // - The return type of f may not be assigned to void. + } else if (!types.isAssignable(expectedReturnType, types.voidType)) { + reportTypeWarning(node, MessageKind.RETURN_NOTHING, + {'returnType': expectedReturnType}); + } + return StatementType.RETURNING; + } + + DartType visitThrow(Throw node) { + if (node.expression != null) analyze(node.expression); + return StatementType.RETURNING; + } + + DartType computeType(Element element) { + if (Elements.isUnresolved(element)) return types.dynamicType; + DartType result = element.computeType(compiler); + return (result != null) ? result : types.dynamicType; + } + + DartType visitTypeAnnotation(TypeAnnotation node) { + return elements.getType(node); + } + + visitTypeVariable(TypeVariable node) { + return types.dynamicType; + } + + DartType visitVariableDefinitions(VariableDefinitions node) { + DartType type = analyzeWithDefault(node.type, types.dynamicType); + if (type == types.voidType) { + reportTypeWarning(node.type, MessageKind.VOID_VARIABLE); + type = types.dynamicType; + } + for (Link<Node> link = node.definitions.nodes; !link.isEmpty; + link = link.tail) { + Node initialization = link.head; + compiler.ensure(initialization is Identifier + || initialization is Send); + if (initialization is Send) { + DartType initializer = analyzeNonVoid(link.head); + checkAssignable(node, type, initializer); + } + } + return StatementType.NOT_RETURNING; + } + + DartType visitWhile(While node) { + checkCondition(node.condition); + StatementType bodyType = analyze(node.body); + Expression cond = node.condition.asParenthesizedExpression().expression; + if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) { + // If the condition is a constant boolean expression denoting true, + // control-flow always enters the loop body. + // TODO(karlklose): this should be StatementType.RETURNING unless there + // is a break in the loop body that has the loop or a label outside the + // loop as a target. + return bodyType; + } else { + return bodyType.join(StatementType.NOT_RETURNING); + } + } + + DartType visitParenthesizedExpression(ParenthesizedExpression node) { + return analyze(node.expression); + } + + DartType visitConditional(Conditional node) { + checkCondition(node.condition); + DartType thenType = analyzeNonVoid(node.thenExpression); + DartType elseType = analyzeNonVoid(node.elseExpression); + if (types.isSubtype(thenType, elseType)) { + return thenType; + } else if (types.isSubtype(elseType, thenType)) { + return elseType; + } else { + return objectType; + } + } + + DartType visitModifiers(Modifiers node) {} + + visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + return stringType; + } + + visitStringInterpolationPart(StringInterpolationPart node) { + node.visitChildren(this); + return stringType; + } + + visitEmptyStatement(EmptyStatement node) { + return StatementType.NOT_RETURNING; + } + + visitBreakStatement(BreakStatement node) { + return StatementType.NOT_RETURNING; + } + + visitContinueStatement(ContinueStatement node) { + return StatementType.NOT_RETURNING; + } + + visitForIn(ForIn node) { + analyze(node.expression); + StatementType bodyType = analyze(node.body); + return bodyType.join(StatementType.NOT_RETURNING); + } + + visitLabel(Label node) { } + + visitLabeledStatement(LabeledStatement node) { + return node.statement.accept(this); + } + + visitLiteralMap(LiteralMap node) { + return unhandledExpression(); + } + + visitLiteralMapEntry(LiteralMapEntry node) { + return unhandledExpression(); + } + + visitNamedArgument(NamedArgument node) { + return unhandledExpression(); + } + + visitSwitchStatement(SwitchStatement node) { + return unhandledStatement(); + } + + visitSwitchCase(SwitchCase node) { + return unhandledStatement(); + } + + visitCaseMatch(CaseMatch node) { + return unhandledStatement(); + } + + visitTryStatement(TryStatement node) { + return unhandledStatement(); + } + + visitScriptTag(ScriptTag node) { + return unhandledExpression(); + } + + visitCatchBlock(CatchBlock node) { + return unhandledStatement(); + } + + visitTypedef(Typedef node) { + return unhandledStatement(); + } + + DartType visitNode(Node node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitCombinator(Combinator node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitExport(Export node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitExpression(Expression node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitGotoStatement(GotoStatement node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitImport(Import node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryName(LibraryName node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryTag(LibraryTag node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLiteral(Literal node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPart(Part node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPartOf(PartOf node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPostfix(Postfix node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitPrefix(Prefix node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitStatement(Statement node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitStringNode(StringNode node) { + compiler.unimplemented('visitNode', node: node); + } + + DartType visitLibraryDependency(LibraryDependency node) { + compiler.unimplemented('visitNode', node: node); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart b/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart new file mode 100644 index 0000000..6c05203 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart
@@ -0,0 +1,1678 @@ +// 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. + +part of types; + +class CancelTypeInferenceException { + final Node node; + final String reason; + + CancelTypeInferenceException(this.node, this.reason); +} + +/** + * A singleton concrete type. More precisely, a [BaseType] is one of the + * following: + * + * - a non-asbtract class like [: int :] or [: Uri :] but not [: List :] + * - the null base type + * - the unknown base type + */ +abstract class BaseType { + bool isClass(); + bool isUnknown(); + bool isNull(); +} + +/** + * A non-asbtract class like [: int :] or [: Uri :] but not [: List :]. + */ +class ClassBaseType implements BaseType { + final ClassElement element; + + ClassBaseType(this.element); + + bool operator ==(BaseType other) { + if (identical(this, other)) return true; + if (other is! ClassBaseType) return false; + return element == other.element; + } + int get hashCode => element.hashCode; + String toString() => element.name.slowToString(); + bool isClass() => true; + bool isUnknown() => false; + bool isNull() => false; +} + +/** + * The unknown base type. + */ +class UnknownBaseType implements BaseType { + const UnknownBaseType(); + bool operator ==(BaseType other) => other is UnknownBaseType; + int get hashCode => 0; + bool isClass() => false; + bool isUnknown() => true; + bool isNull() => false; + toString() => "unknown"; +} + +/** + * The null base type. + */ +class NullBaseType implements BaseType { + const NullBaseType(); + bool operator ==(BaseType other) => other is NullBaseType; + int get hashCode => 1; + bool isClass() => false; + bool isUnknown() => false; + bool isNull() => true; + toString() => "null"; +} + +/** + * An immutable set of base types, like [: {int, bool} :] or the unknown + * concrete type. + */ +abstract class ConcreteType { + factory ConcreteType.empty() { + return new UnionType(new Set<BaseType>()); + } + + /** + * The singleton constituted of the unknown base type is the unknown concrete + * type. + */ + factory ConcreteType.singleton(int maxConcreteTypeSize, BaseType baseType) { + if (baseType.isUnknown() || maxConcreteTypeSize < 1) { + return new UnknownConcreteType(); + } + Set<BaseType> singletonSet = new Set<BaseType>(); + singletonSet.add(baseType); + return new UnionType(singletonSet); + } + + factory ConcreteType.unknown() { + return const UnknownConcreteType(); + } + + ConcreteType union(int maxConcreteTypeSize, ConcreteType other); + bool isUnkown(); + bool isEmpty(); + Set<BaseType> get baseTypes; + + /** + * Returns the unique element of [: this :] if [: this :] is a singleton, + * null otherwise. + */ + ClassElement getUniqueType(); +} + +/** + * The unkown concrete type: it is absorbing for the union. + */ +class UnknownConcreteType implements ConcreteType { + const UnknownConcreteType(); + bool isUnkown() => true; + bool isEmpty() => false; + bool operator ==(ConcreteType other) => identical(this, other); + Set<BaseType> get baseTypes => + new Set<BaseType>.from([const UnknownBaseType()]); + int get hashCode => 0; + ConcreteType union(int maxConcreteTypeSize, ConcreteType other) => this; + ClassElement getUniqueType() => null; + toString() => "unknown"; +} + +/** + * An immutable set of base types, like [: {int, bool} :]. + */ +class UnionType implements ConcreteType { + final Set<BaseType> baseTypes; + + /** + * The argument should NOT be mutated later. Do not call directly, use + * ConcreteType.singleton instead. + */ + UnionType(this.baseTypes); + + bool isUnkown() => false; + bool isEmpty() => baseTypes.isEmpty; + + bool operator ==(ConcreteType other) { + if (other is! UnionType) return false; + if (baseTypes.length != other.baseTypes.length) return false; + return baseTypes.containsAll(other.baseTypes); + } + + int get hashCode { + int result = 1; + for (final baseType in baseTypes) { + result = 31 * result + baseType.hashCode; + } + return result; + } + + // TODO(polux): Collapse {num, int, ...}, {num, double, ...} and + // {int, double,...} into {num, ...} as an optimization. It will require + // UnionType to know about these class elements, which is cumbersome because + // there are no nested classes. We need factory methods instead. + ConcreteType union(int maxConcreteTypeSize, ConcreteType other) { + if (other.isUnkown()) { + return const UnknownConcreteType(); + } + UnionType otherUnion = other; // cast + Set<BaseType> newBaseTypes = new Set<BaseType>.from(baseTypes); + newBaseTypes.addAll(otherUnion.baseTypes); + return newBaseTypes.length > maxConcreteTypeSize + ? const UnknownConcreteType() + : new UnionType(newBaseTypes); + } + + ClassElement getUniqueType() { + if (baseTypes.length == 1) { + var iterator = baseTypes.iterator; + iterator.moveNext(); + BaseType uniqueBaseType = iterator.current; + if (uniqueBaseType.isClass()) { + ClassBaseType uniqueClassType = uniqueBaseType; + return uniqueClassType.element; + } + } + return null; + } + + String toString() => baseTypes.toString(); +} + +/** + * The cartesian product of concrete types: an iterable of [BaseTypeTuple]s. For + * instance, the cartesian product of the concrete types [: {A, B} :] and + * [: {C, D} :] is an itearble whose iterators will yield [: (A, C) :], + * [: (A, D) :], [: (B, C) :] and finally [: (B, D) :]. + */ +class ConcreteTypeCartesianProduct + extends Iterable<ConcreteTypesEnvironment> { + final ConcreteTypesInferrer inferrer; + final ClassElement typeOfThis; + final Map<Element, ConcreteType> concreteTypes; + ConcreteTypeCartesianProduct(this.inferrer, this.typeOfThis, + this.concreteTypes); + Iterator get iterator => concreteTypes.isEmpty + ? [new ConcreteTypesEnvironment(inferrer, new ClassBaseType(typeOfThis))] + .iterator + : new ConcreteTypeCartesianProductIterator(inferrer, + new ClassBaseType(typeOfThis), concreteTypes); + String toString() { + List<ConcreteTypesEnvironment> cartesianProduct = + new List<ConcreteTypesEnvironment>.from(this); + return cartesianProduct.toString(); + } +} + +/** + * An helper class for [ConcreteTypeCartesianProduct]. + */ +class ConcreteTypeCartesianProductIterator + implements Iterator<ConcreteTypesEnvironment> { + final ConcreteTypesInferrer inferrer; + final BaseType baseTypeOfThis; + final Map<Element, ConcreteType> concreteTypes; + final Map<Element, BaseType> nextValues; + final Map<Element, Iterator> state; + int size = 1; + int counter = 0; + ConcreteTypesEnvironment _current; + + ConcreteTypeCartesianProductIterator(this.inferrer, this.baseTypeOfThis, + Map<Element, ConcreteType> concreteTypes) + : this.concreteTypes = concreteTypes, + nextValues = new Map<Element, BaseType>(), + state = new Map<Element, Iterator>() { + if (concreteTypes.isEmpty) { + size = 0; + return; + } + for (final e in concreteTypes.keys) { + final baseTypes = concreteTypes[e].baseTypes; + size *= baseTypes.length; + } + } + + ConcreteTypesEnvironment get current => _current; + + ConcreteTypesEnvironment takeSnapshot() { + Map<Element, ConcreteType> result = new Map<Element, ConcreteType>(); + nextValues.forEach((k, v) { + result[k] = inferrer.singletonConcreteType(v); + }); + return new ConcreteTypesEnvironment.of(inferrer, result, baseTypeOfThis); + } + + bool moveNext() { + if (counter >= size) { + _current = null; + return false; + } + Element keyToIncrement = null; + for (final key in concreteTypes.keys) { + final iterator = state[key]; + if (iterator != null && iterator.moveNext()) { + nextValues[key] = state[key].current; + break; + } + Iterator newIterator = concreteTypes[key].baseTypes.iterator; + state[key] = newIterator; + newIterator.moveNext(); + nextValues[key] = newIterator.current; + } + counter++; + _current = takeSnapshot(); + return true; + } +} + +/** + * [BaseType] Constants. + */ +class BaseTypes { + final ClassBaseType intBaseType; + final ClassBaseType doubleBaseType; + final ClassBaseType numBaseType; + final ClassBaseType boolBaseType; + final ClassBaseType stringBaseType; + final ClassBaseType listBaseType; + final ClassBaseType mapBaseType; + final ClassBaseType objectBaseType; + final ClassBaseType typeBaseType; + + static _getNativeListClass(Compiler compiler) { + // TODO(polux): switch to other implementations on other backends + JavaScriptBackend backend = compiler.backend; + return backend.jsArrayClass; + } + + BaseTypes(Compiler compiler) : + intBaseType = new ClassBaseType(compiler.intClass), + doubleBaseType = new ClassBaseType(compiler.doubleClass), + numBaseType = new ClassBaseType(compiler.numClass), + boolBaseType = new ClassBaseType(compiler.boolClass), + stringBaseType = new ClassBaseType(compiler.stringClass), + // in the Javascript backend, lists are implemented by JsArray + listBaseType = new ClassBaseType(_getNativeListClass(compiler)), + mapBaseType = new ClassBaseType(compiler.mapClass), + objectBaseType = new ClassBaseType(compiler.objectClass), + typeBaseType = new ClassBaseType(compiler.typeClass); +} + +/** + * A method-local immutable mapping from variables to their inferred + * [ConcreteTypes]. Each visitor owns one. + */ +class ConcreteTypesEnvironment { + final ConcreteTypesInferrer inferrer; + final Map<Element, ConcreteType> environment; + final BaseType typeOfThis; + + ConcreteTypesEnvironment(this.inferrer, [this.typeOfThis]) : + this.environment = new Map<Element, ConcreteType>(); + ConcreteTypesEnvironment.of(this.inferrer, this.environment, this.typeOfThis); + + ConcreteType lookupType(Element element) => environment[element]; + ConcreteType lookupTypeOfThis() { + return (typeOfThis == null) + ? null + : inferrer.singletonConcreteType(typeOfThis); + } + + ConcreteTypesEnvironment put(Element element, ConcreteType type) { + Map<Element, ConcreteType> newMap = + new Map<Element, ConcreteType>.from(environment); + newMap[element] = type; + return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis); + } + + ConcreteTypesEnvironment join(ConcreteTypesEnvironment other) { + if (typeOfThis != other.typeOfThis) { + throw "trying to join incompatible environments"; + } + Map<Element, ConcreteType> newMap = + new Map<Element, ConcreteType>.from(environment); + other.environment.forEach((element, type) { + ConcreteType currentType = newMap[element]; + if (element == null) { + newMap[element] = type; + } else { + newMap[element] = inferrer.union(currentType, type); + } + }); + return new ConcreteTypesEnvironment.of(inferrer, newMap, typeOfThis); + } + + bool operator ==(ConcreteTypesEnvironment other) { + if (other is! ConcreteTypesEnvironment) return false; + if (typeOfThis != other.typeOfThis) return false; + if (environment.length != other.environment.length) return false; + for (Element key in environment.keys) { + if (!other.environment.containsKey(key) + || (environment[key] != other.environment[key])) { + return false; + } + } + return true; + } + + int get hashCode { + int result = (typeOfThis != null) ? typeOfThis.hashCode : 1; + environment.forEach((element, concreteType) { + result = 31 * (31 * result + element.hashCode) + + concreteType.hashCode; + }); + return result; + } + + String toString() => "{ this: $typeOfThis, env: ${environment.toString()} }"; +} + +/** + * A work item for the type inference queue. + */ +class InferenceWorkItem { + FunctionElement method; + ConcreteTypesEnvironment environment; + InferenceWorkItem(this.method, this.environment); + + toString() => "{ method = ${method.name.slowToString()}, " + "environment = $environment }"; +} + +/** + * A task which conservatively infers a [ConcreteType] for each sub expression + * of the program. The entry point is [analyzeMain]. + */ +class ConcreteTypesInferrer { + static final bool LOG_FAILURES = true; + + final String name = "Type inferrer"; + + final Compiler compiler; + + /** + * When true, the string literal [:"__dynamic_for_test":] is inferred to + * have the unknown type. + */ + // TODO(polux): get rid of this hack once we have a natural way of inferring + // the unknown type. + bool testMode = false; + + /** + * Constants representing builtin base types. Initialized in [initialize] + * and not in the constructor because the compiler elements are not yet + * populated. + */ + BaseTypes baseTypes; + + /** + * Constant representing [:ConcreteList#[]:] where [:ConcreteList:] is the + * concrete implmentation of lists for the selected backend. + */ + FunctionElement listIndex; + + /** + * Constant representing [:ConcreteList#[]=:] where [:ConcreteList:] is the + * concrete implmentation of lists for the selected backend. + */ + FunctionElement listIndexSet; + + /** + * Constant representing [:List():]. + */ + FunctionElement listConstructor; + + /** + * A cache from (function x argument base types) to concrete types, + * used to memoize [analyzeMonoSend]. Another way of seeing [cache] is as a + * map from [FunctionElement]s to "templates" in the sense of "The Cartesian + * Product Algorithm - Simple and Precise Type Inference of Parametric + * Polymorphism" by Ole Agesen. + */ + final Map<FunctionElement, Map<ConcreteTypesEnvironment, ConcreteType>> cache; + + /** A map from expressions to their inferred concrete types. */ + final Map<Node, ConcreteType> inferredTypes; + + /** A map from fields to their inferred concrete types. */ + final Map<Element, ConcreteType> inferredFieldTypes; + + /** The work queue consumed by [analyzeMain]. */ + final Queue<InferenceWorkItem> workQueue; + + /** [: callers[f] :] is the list of [: f :]'s possible callers. */ + final Map<FunctionElement, Set<FunctionElement>> callers; + + /** [: readers[field] :] is the list of [: field :]'s possible readers. */ + final Map<Element, Set<FunctionElement>> readers; + + /** The inferred type of elements stored in Lists. */ + ConcreteType listElementType; + + /** + * A map from parameters to their inferred concrete types. It plays no role + * in the analysis, it is write only. + */ + final Map<VariableElement, ConcreteType> inferredParameterTypes; + + ConcreteTypesInferrer(Compiler compiler) + : this.compiler = compiler, + cache = new Map<FunctionElement, + Map<ConcreteTypesEnvironment, ConcreteType>>(), + inferredTypes = new Map<Node, ConcreteType>(), + inferredFieldTypes = new Map<Element, ConcreteType>(), + inferredParameterTypes = new Map<VariableElement, ConcreteType>(), + workQueue = new Queue<InferenceWorkItem>(), + callers = new Map<FunctionElement, Set<FunctionElement>>(), + readers = new Map<Element, Set<FunctionElement>>(), + listElementType = new ConcreteType.empty() { + unknownConcreteType = new ConcreteType.unknown(); + emptyConcreteType = new ConcreteType.empty(); + } + + /** + * Populates [cache] with ad hoc rules like: + * + * {int} + {int} -> {int} + * {int} + {double} -> {num} + * {int} + {num} -> {double} + * ... + */ + populateCacheWithBuiltinRules() { + // Builds the environment that would be looked up if we were to analyze + // o.method(arg) where o has concrete type {receiverType} and arg has + // concrete type {argumentType}. + ConcreteTypesEnvironment makeEnvironment(BaseType receiverType, + FunctionElement method, + BaseType argumentType) { + ArgumentsTypes argumentsTypes = new ArgumentsTypes( + [singletonConcreteType(argumentType)], + new Map()); + Map<Element, ConcreteType> argumentMap = + associateArguments(method, argumentsTypes); + return new ConcreteTypesEnvironment.of(this, argumentMap, receiverType); + } + + // Adds the rule {receiverType}.method({argumentType}) -> {returnType} + // to cache. + void rule(ClassBaseType receiverType, String method, + BaseType argumentType, BaseType returnType) { + // The following line shouldn't be needed but the mock compiler doesn't + // resolve num for some reason. + receiverType.element.ensureResolved(compiler); + FunctionElement methodElement = + receiverType.element.lookupMember(new SourceString(method)); + ConcreteTypesEnvironment environment = + makeEnvironment(receiverType, methodElement, argumentType); + Map<ConcreteTypesEnvironment, ConcreteType> map = + cache.containsKey(methodElement) + ? cache[methodElement] + : new Map<ConcreteTypesEnvironment, ConcreteType>(); + map[environment] = singletonConcreteType(returnType); + cache[methodElement] = map; + } + + // The hardcoded typing rules. + final ClassBaseType int = baseTypes.intBaseType; + final ClassBaseType double = baseTypes.doubleBaseType; + final ClassBaseType num = baseTypes.numBaseType; + for (String method in ['+', '*', '-']) { + rule(int, method, int, int); + rule(int, method, double, num); + rule(int, method, num, num); + + rule(double, method, double, double); + rule(double, method, int, num); + rule(double, method, num, num); + + rule(num, method, int, num); + rule(num, method, double, num); + rule(num, method, num, num); + } + } + + // --- utility methods --- + + /** The unknown concrete type */ + ConcreteType unknownConcreteType; + + /** The empty concrete type */ + ConcreteType emptyConcreteType; + + /** Creates a singleton concrete type containing [baseType]. */ + ConcreteType singletonConcreteType(BaseType baseType) { + return new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseType); + } + + /** Returns the union of its two arguments */ + ConcreteType union(ConcreteType concreteType1, ConcreteType concreteType2) { + return concreteType1.union(compiler.maxConcreteTypeSize, concreteType2); + } + + /** + * Returns all the members with name [methodName]. + */ + List<Element> getMembersByName(SourceString methodName) { + // TODO(polux): memoize? + var result = new List<Element>(); + for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) { + Element elem = cls.lookupLocalMember(methodName); + if (elem != null) { + result.add(elem); + } + } + return result; + } + + /** + * Sets the concrete type associated to [node] to the union of the inferred + * concrete type so far and [type]. + */ + void augmentInferredType(Node node, ConcreteType type) { + ConcreteType currentType = inferredTypes[node]; + inferredTypes[node] = (currentType == null) + ? type + : union(currentType, type); + } + + /** + * Returns the current inferred concrete type of [field]. + */ + ConcreteType getFieldType(Element field) { + ConcreteType result = inferredFieldTypes[field]; + return (result == null) ? emptyConcreteType : result; + } + + /** + * Sets the concrete type associated to [field] to the union of the inferred + * concrete type so far and [type]. + */ + void augmentFieldType(Element field, ConcreteType type) { + ConcreteType oldType = inferredFieldTypes[field]; + ConcreteType newType = (oldType != null) + ? union(oldType, type) + : type; + if (oldType != newType) { + inferredFieldTypes[field] = newType; + final fieldReaders = readers[field]; + if (fieldReaders != null) { + for (final reader in fieldReaders) { + final readerInstances = cache[reader]; + if (readerInstances != null) { + readerInstances.forEach((environment, _) { + workQueue.addLast(new InferenceWorkItem(reader, environment)); + }); + } + } + } + } + } + + /// Augment the inferred type of elements stored in Lists. + void augmentListElementType(ConcreteType type) { + ConcreteType newType = union(listElementType, type); + if (newType != listElementType) { + invalidateCallers(listIndex); + listElementType = newType; + } + } + + /** + * Sets the concrete type associated to [parameter] to the union of the + * inferred concrete type so far and [type]. + */ + void augmentParameterType(VariableElement parameter, ConcreteType type) { + ConcreteType oldType = inferredParameterTypes[parameter]; + inferredParameterTypes[parameter] = + (oldType == null) ? type : union(oldType, type); + } + + /** + * Add [caller] to the set of [callee]'s callers. + */ + void addCaller(FunctionElement callee, FunctionElement caller) { + Set<FunctionElement> current = callers[callee]; + if (current != null) { + current.add(caller); + } else { + Set<FunctionElement> newSet = new Set<FunctionElement>(); + newSet.add(caller); + callers[callee] = newSet; + } + } + + /** + * Add [reader] to the set of [field]'s readers. + */ + void addReader(Element field, FunctionElement reader) { + Set<FunctionElement> current = readers[field]; + if (current != null) { + current.add(reader); + } else { + Set<FunctionElement> newSet = new Set<FunctionElement>(); + newSet.add(reader); + readers[field] = newSet; + } + } + + /** + * Add callers of [function] to the workqueue. + */ + void invalidateCallers(FunctionElement function) { + Set<FunctionElement> methodCallers = callers[function]; + if (methodCallers == null) return; + for (FunctionElement caller in methodCallers) { + Map<ConcreteTypesEnvironment, ConcreteType> callerInstances = + cache[caller]; + if (callerInstances != null) { + callerInstances.forEach((environment, _) { + workQueue.addLast( + new InferenceWorkItem(caller, environment)); + }); + } + } + } + + // -- query -- + + /** + * Get the inferred concrete type of [node]. + */ + ConcreteType getConcreteTypeOfNode(Node node) => inferredTypes[node]; + + /** + * Get the inferred concrete type of [parameter]. + */ + ConcreteType getConcreteTypeOfParameter(VariableElement parameter) { + return inferredParameterTypes[parameter]; + } + + // --- analysis --- + + /** + * Returns the concrete type returned by [function] given arguments of + * concrete types [argumentsTypes]. If [function] is static then + * [receiverType] must be null, else [function] must be a member of the class + * of [receiverType]. + */ + ConcreteType getSendReturnType(FunctionElement function, + ClassElement receiverType, + ArgumentsTypes argumentsTypes) { + ConcreteType result = emptyConcreteType; + Map<Element, ConcreteType> argumentMap = + associateArguments(function, argumentsTypes); + // if the association failed, this send will never occur or will fail + if (argumentMap == null) { + return emptyConcreteType; + } + + argumentMap.forEach(augmentParameterType); + ConcreteTypeCartesianProduct product = + new ConcreteTypeCartesianProduct(this, receiverType, argumentMap); + for (ConcreteTypesEnvironment environment in product) { + result = union(result, + getMonomorphicSendReturnType(function, environment)); + } + return result; + } + + /** + * Given a method signature and a list of concrete types, builds a map from + * formals to their corresponding concrete types. Returns null if the + * association is impossible (for instance: too many arguments). + */ + Map<Element, ConcreteType> associateArguments(FunctionElement function, + ArgumentsTypes argumentsTypes) { + final Map<Element, ConcreteType> result = new Map<Element, ConcreteType>(); + final FunctionSignature signature = function.computeSignature(compiler); + + // guard 1: too many arguments + if (argumentsTypes.length > signature.parameterCount) { + return null; + } + // guard 2: not enough arguments + if (argumentsTypes.positional.length < signature.requiredParameterCount) { + return null; + } + // guard 3: too many positional arguments + if (signature.optionalParametersAreNamed && + argumentsTypes.positional.length > signature.requiredParameterCount) { + return null; + } + + handleLeftoverOptionalParameter(Element parameter) { + // TODO(polux): use default value whenever available + // TODO(polux): add a marker to indicate whether an argument was provided + // in order to handle "?parameter" tests + result[parameter] = singletonConcreteType(const NullBaseType()); + } + + final Iterator<ConcreteType> remainingPositionalArguments = + argumentsTypes.positional.iterator; + // we attach each positional parameter to its corresponding positional + // argument + for (Link<Element> requiredParameters = signature.requiredParameters; + !requiredParameters.isEmpty; + requiredParameters = requiredParameters.tail) { + final Element requiredParameter = requiredParameters.head; + // we know moveNext() succeeds because of guard 2 + remainingPositionalArguments.moveNext(); + result[requiredParameter] = remainingPositionalArguments.current; + } + if (signature.optionalParametersAreNamed) { + // we build a map out of the remaining named parameters + Link<Element> remainingOptionalParameters = signature.optionalParameters; + final Map<SourceString, Element> leftOverNamedParameters = + new Map<SourceString, Element>(); + for (; + !remainingOptionalParameters.isEmpty; + remainingOptionalParameters = remainingOptionalParameters.tail) { + final Element namedParameter = remainingOptionalParameters.head; + leftOverNamedParameters[namedParameter.name] = namedParameter; + } + // we attach the named arguments to their corresponding optional + // parameters + for (Identifier identifier in argumentsTypes.named.keys) { + final ConcreteType concreteType = argumentsTypes.named[identifier]; + SourceString source = identifier.source; + final Element namedParameter = leftOverNamedParameters[source]; + // unexisting or already used named parameter + if (namedParameter == null) return null; + result[namedParameter] = concreteType; + leftOverNamedParameters.remove(source); + } + leftOverNamedParameters.forEach((_, Element parameter) { + handleLeftoverOptionalParameter(parameter); + }); + } else { // optional parameters are positional + // we attach the remaining positional arguments to their corresponding + // optional parameters + Link<Element> remainingOptionalParameters = signature.optionalParameters; + while (remainingPositionalArguments.moveNext()) { + final Element optionalParameter = remainingOptionalParameters.head; + result[optionalParameter] = remainingPositionalArguments.current; + // we know tail is defined because of guard 1 + remainingOptionalParameters = remainingOptionalParameters.tail; + } + for (; + !remainingOptionalParameters.isEmpty; + remainingOptionalParameters = remainingOptionalParameters.tail) { + handleLeftoverOptionalParameter(remainingOptionalParameters.head); + } + } + return result; + } + + ConcreteType getMonomorphicSendReturnType( + FunctionElement function, + ConcreteTypesEnvironment environment) { + ConcreteType specialType = getSpecialCaseReturnType(function, environment); + if (specialType != null) return specialType; + + Map<ConcreteTypesEnvironment, ConcreteType> template = cache[function]; + if (template == null) { + template = new Map<ConcreteTypesEnvironment, ConcreteType>(); + cache[function] = template; + } + ConcreteType type = template[environment]; + if (type != null) { + return type; + } else { + workQueue.addLast( + new InferenceWorkItem(function, environment)); + // in case of a constructor, optimize by returning the class + return emptyConcreteType; + } + } + + /** + * Handles external methods that cannot be cached because they depend on some + * other state of [ConcreteTypesInferrer] like [:List#[]:] and + * [:List#[]=:]. Returns null if [function] and [environment] don't form a + * special case + */ + ConcreteType getSpecialCaseReturnType(FunctionElement function, + ConcreteTypesEnvironment environment) { + if (function == listIndex) { + ConcreteType indexType = environment.lookupType( + listIndex.functionSignature.requiredParameters.head); + if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { + return new ConcreteType.empty(); + } + return listElementType; + } else if (function == listIndexSet) { + Link<Element> parameters = + listIndexSet.functionSignature.requiredParameters; + ConcreteType indexType = environment.lookupType(parameters.head); + if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { + return new ConcreteType.empty(); + } + ConcreteType elementType = environment.lookupType(parameters.tail.head); + augmentListElementType(elementType); + return new ConcreteType.empty(); + } + return null; + } + + ConcreteType analyze(FunctionElement element, + ConcreteTypesEnvironment environment) { + return element.isGenerativeConstructor() + ? analyzeConstructor(element, environment) + : analyzeMethod(element, environment); + } + + ConcreteType analyzeMethod(FunctionElement element, + ConcreteTypesEnvironment environment) { + TreeElements elements = + compiler.enqueuer.resolution.resolvedElements[element]; + ConcreteType specialResult = handleSpecialMethod(element, environment); + if (specialResult != null) return specialResult; + FunctionExpression tree = element.parseNode(compiler); + if (tree.hasBody()) { + Visitor visitor = + new TypeInferrerVisitor(elements, element, this, environment); + return tree.accept(visitor); + } else { + // TODO(polux): implement visitForeingCall and always use the + // implementation element instead of this hack + return new ConcreteType.unknown(); + } + } + + ConcreteType analyzeConstructor(FunctionElement element, + ConcreteTypesEnvironment environment) { + ClassElement enclosingClass = element.enclosingElement; + FunctionExpression tree = compiler.parser.parse(element); + TreeElements elements = + compiler.enqueuer.resolution.resolvedElements[element]; + Visitor visitor = + new TypeInferrerVisitor(elements, element, this, environment); + + // handle initializing formals + element.functionSignature.forEachParameter((param) { + if (param.kind == ElementKind.FIELD_PARAMETER) { + FieldParameterElement fieldParam = param; + augmentFieldType(fieldParam.fieldElement, + environment.lookupType(param)); + } + }); + + // analyze initializers, including a possible call to super or a redirect + bool foundSuperOrRedirect = false; + if (tree.initializers != null) { + // we look for a possible call to super in the initializer list + for (final init in tree.initializers) { + init.accept(visitor); + if (init.asSendSet() == null) { + foundSuperOrRedirect = true; + } + } + } + + // if no call to super or redirect has been found, call the default + // constructor (if the current class is not Object). + if (!foundSuperOrRedirect) { + ClassElement superClass = enclosingClass.superclass; + if (enclosingClass != compiler.objectClass) { + FunctionElement target = superClass.lookupConstructor( + new Selector.callDefaultConstructor(enclosingClass.getLibrary())); + final superClassConcreteType = singletonConcreteType( + new ClassBaseType(enclosingClass)); + getSendReturnType(target, enclosingClass, + new ArgumentsTypes(new List(), new Map())); + } + } + + tree.accept(visitor); + return singletonConcreteType(new ClassBaseType(enclosingClass)); + } + + /** + * Hook that performs side effects on some special method calls (like + * [:List(length):]) and possibly returns a concrete type + * (like [:{JsArray}:]). + */ + ConcreteType handleSpecialMethod(FunctionElement element, + ConcreteTypesEnvironment environment) { + // When List([length]) is called with some length, we must augment + // listElementType with {null}. + if (element == listConstructor) { + Link<Element> parameters = + listConstructor.functionSignature.optionalParameters; + ConcreteType lengthType = environment.lookupType(parameters.head); + if (lengthType.baseTypes.contains(baseTypes.intBaseType)) { + augmentListElementType(singletonConcreteType(new NullBaseType())); + } + return singletonConcreteType(baseTypes.listBaseType); + } + } + + /* Initialization code that cannot be run in the constructor because it + * requires the compiler's elements to be populated. + */ + void initialize() { + baseTypes = new BaseTypes(compiler); + ClassElement jsArrayClass = baseTypes.listBaseType.element; + listIndex = jsArrayClass.lookupMember(const SourceString('[]')); + listIndexSet = + jsArrayClass.lookupMember(const SourceString('[]=')); + listConstructor = + compiler.listClass.lookupConstructor( + new Selector.callConstructor(const SourceString(''), + compiler.listClass.getLibrary())); + } + + /** + * Performs concrete type inference of the code reachable from [element]. + * Returns [:true:] if and only if analysis succeeded. + */ + bool analyzeMain(Element element) { + initialize(); + cache[element] = new Map<ConcreteTypesEnvironment, ConcreteType>(); + populateCacheWithBuiltinRules(); + try { + workQueue.addLast( + new InferenceWorkItem(element, new ConcreteTypesEnvironment(this))); + while (!workQueue.isEmpty) { + InferenceWorkItem item = workQueue.removeFirst(); + ConcreteType concreteType = analyze(item.method, item.environment); + var template = cache[item.method]; + if (template[item.environment] == concreteType) continue; + template[item.environment] = concreteType; + invalidateCallers(item.method); + } + return true; + } on CancelTypeInferenceException catch(e) { + if (LOG_FAILURES) { + compiler.log(e.reason); + } + return false; + } + } + + /** + * Dumps debugging information on the standard output. + */ + void debug() { + print("callers :"); + callers.forEach((k,v) { + print(" $k: $v"); + }); + print("readers :"); + readers.forEach((k,v) { + print(" $k: $v"); + }); + print("inferredFieldTypes:"); + inferredFieldTypes.forEach((k,v) { + print(" $k: $v"); + }); + print("inferredParameterTypes:"); + inferredParameterTypes.forEach((k,v) { + print(" $k: $v"); + }); + print("cache:"); + cache.forEach((k,v) { + print(" $k: $v"); + }); + print("inferred expression types: "); + inferredTypes.forEach((k,v) { + print(" $k: $v"); + }); + } + + /** + * Fail with a message and abort. + */ + void fail(node, [reason]) { + String message = 'cannot infer types'; + if (reason != null) { + message = '$message: $reason'; + } + throw new CancelTypeInferenceException(node, message); + } +} + +/** + * Represents the concrete types of the arguments of a send, indexed by + * position or name. + */ +class ArgumentsTypes { + final List<ConcreteType> positional; + final Map<Identifier, ConcreteType> named; + ArgumentsTypes(this.positional, this.named); + int get length => positional.length + named.length; + toString() => "{ positional = $positional, named = $named }"; +} + +/** + * The core logic of the type inference algorithm. + */ +class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> { + final ConcreteTypesInferrer inferrer; + + final FunctionElement currentMethod; + ConcreteTypesEnvironment environment; + Node lastSeenNode; + + TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer, + this.environment) + : super(elements); + + ArgumentsTypes analyzeArguments(Link<Node> arguments) { + final positional = new List<ConcreteType>(); + final named = new Map<Identifier, ConcreteType>(); + for(Link<Node> iterator = arguments; + !iterator.isEmpty; + iterator = iterator.tail) { + Node node = iterator.head; + NamedArgument namedArgument = node.asNamedArgument(); + if (namedArgument != null) { + named[namedArgument.name] = analyze(namedArgument.expression); + } else { + positional.add(analyze(node)); + } + } + return new ArgumentsTypes(positional, named); + } + + /** + * A proxy to accept which does book keeping and error reporting. Returns null + * if [node] is a non-returning statement, its inferred concrete type + * otherwise. + */ + ConcreteType analyze(Node node) { + if (node == null) { + final String error = 'internal error: unexpected node: null'; + inferrer.fail(lastSeenNode, error); + } else { + lastSeenNode = node; + } + ConcreteType result = node.accept(this); + if (result == null) { + inferrer.fail(node, 'internal error: inferred type is null'); + } + inferrer.augmentInferredType(node, result); + return result; + } + + ConcreteType visitBlock(Block node) { + return analyze(node.statements); + } + + ConcreteType visitCascade(Cascade node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCascadeReceiver(CascadeReceiver node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitClassNode(ClassNode node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitDoWhile(DoWhile node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitExpressionStatement(ExpressionStatement node) { + analyze(node.expression); + return inferrer.emptyConcreteType; + } + + ConcreteType visitFor(For node) { + if (node.initializer != null) { + analyze(node.initializer); + } + analyze(node.conditionStatement); + ConcreteType result = inferrer.emptyConcreteType; + ConcreteTypesEnvironment oldEnvironment; + do { + oldEnvironment = environment; + analyze(node.conditionStatement); + analyze(node.body); + analyze(node.update); + environment = oldEnvironment.join(environment); + // TODO(polux): Maybe have a destructive join-method that returns a boolean + // value indicating whether something changed to avoid performing this + // comparison twice. + } while (oldEnvironment != environment); + return result; + } + + ConcreteType visitFunctionDeclaration(FunctionDeclaration node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitFunctionExpression(FunctionExpression node) { + return analyze(node.body); + } + + ConcreteType visitIdentifier(Identifier node) { + if (node.isThis()) { + ConcreteType result = environment.lookupTypeOfThis(); + if (result == null) { + inferrer.fail(node, '"this" has no type'); + } + return result; + } + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitIf(If node) { + analyze(node.condition); + ConcreteType thenType = analyze(node.thenPart); + ConcreteTypesEnvironment snapshot = environment; + ConcreteType elseType = node.hasElsePart ? analyze(node.elsePart) + : inferrer.emptyConcreteType; + environment = environment.join(snapshot); + return inferrer.union(thenType, elseType); + } + + ConcreteType visitLoop(Loop node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType analyzeSetElement(Element receiver, ConcreteType argumentType) { + environment = environment.put(receiver, argumentType); + if (receiver.isField()) { + inferrer.augmentFieldType(receiver, argumentType); + } else if (receiver.isSetter()){ + FunctionElement setter = receiver; + // TODO(polux): A setter always returns void so there's no need to + // invalidate its callers even if it is called with new arguments. + // However, if we start to record more than returned types, like + // exceptions for instance, we need to do it by uncommenting the following + // line. + // inferrer.addCaller(setter, currentMethod); + inferrer.getSendReturnType(setter, receiver.enclosingElement, + new ArgumentsTypes([argumentType], new Map())); + } + return argumentType; + } + + ConcreteType analyzeSetNode(Node receiver, ConcreteType argumentType, + SourceString name) { + ConcreteType receiverType = analyze(receiver); + + void augmentField(ClassElement receiverType, Element member) { + if (member.isField()) { + inferrer.augmentFieldType(member, argumentType); + } else if (member.isAbstractField()){ + AbstractFieldElement abstractField = member; + FunctionElement setter = abstractField.setter; + // TODO(polux): A setter always returns void so there's no need to + // invalidate its callers even if it is called with new arguments. + // However, if we start to record more than returned types, like + // exceptions for instance, we need to do it by uncommenting the + // following line. + // inferrer.addCaller(setter, currentMethod); + inferrer.getSendReturnType(setter, receiverType, + new ArgumentsTypes([argumentType], new Map())); + } + // since this is a sendSet we ignore non-fields + } + + if (receiverType.isUnkown()) { + for (Element member in inferrer.getMembersByName(name)) { + if (!(member.isField() || member.isAbstractField())) continue; + Element cls = member.getEnclosingClass(); + augmentField(cls, member); + } + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isClass()) continue; + ClassBaseType baseReceiverClassType = baseReceiverType; + Element member = baseReceiverClassType.element.lookupMember(name); + if (member != null) { + augmentField(baseReceiverClassType.element, member); + } + } + } + return argumentType; + } + + SourceString canonicalizeCompoundOperator(SourceString op) { + // TODO(ahe): This class should work on elements or selectors, not + // names. Otherwise, it is repeating work the resolver has + // already done (or should have done). In this case, the problem + // is that the resolver is not recording the selectors it is + // registering in registerBinaryOperator in + // ResolverVisitor.visitSendSet. + String stringValue = op.stringValue; + if (stringValue == '++') return const SourceString(r'+'); + else if (stringValue == '--') return const SourceString(r'-'); + else return Elements.mapToUserOperatorOrNull(op); + } + + ConcreteType visitSendSet(SendSet node) { + // Operator []= has a different behaviour than other send sets: it is + // actually a send whose return type is that of its second argument. + if (node.selector.asIdentifier().source.stringValue == '[]') { + ConcreteType receiverType = analyze(node.receiver); + ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments); + analyzeDynamicSend(receiverType, const SourceString('[]='), + argumentsTypes); + return argumentsTypes.positional[1]; + } + + // All other operators have a single argument (++ and -- have an implicit + // argument: 1). We will store its type in argumentType. + ConcreteType argumentType; + SourceString operatorName = node.assignmentOperator.source; + SourceString compoundOperatorName = + canonicalizeCompoundOperator(node.assignmentOperator.source); + // ++, --, +=, -=, ... + if (compoundOperatorName != null) { + ConcreteType receiverType = visitGetterSend(node); + // argumentsTypes is either computed from the actual arguments or [{int}] + // in case of ++ or --. + ArgumentsTypes argumentsTypes; + if (operatorName.stringValue == '++' + || operatorName.stringValue == '--') { + List<ConcreteType> positionalArguments = <ConcreteType>[ + inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType)]; + argumentsTypes = new ArgumentsTypes(positionalArguments, new Map()); + } else { + argumentsTypes = analyzeArguments(node.arguments); + } + argumentType = analyzeDynamicSend(receiverType, compoundOperatorName, + argumentsTypes); + // The simple assignment case: receiver = argument. + } else { + argumentType = analyze(node.argumentsNode); + } + + Element element = elements[node]; + if (element != null) { + return analyzeSetElement(element, argumentType); + } else { + return analyzeSetNode(node.receiver, argumentType, + node.selector.asIdentifier().source); + } + } + + ConcreteType visitLiteralInt(LiteralInt node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.intBaseType); + } + + ConcreteType visitLiteralDouble(LiteralDouble node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.doubleBaseType); + } + + ConcreteType visitLiteralBool(LiteralBool node) { + return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } + + ConcreteType visitLiteralString(LiteralString node) { + // TODO(polux): get rid of this hack once we have a natural way of inferring + // the unknown type. + if (inferrer.testMode + && node.dartString.slowToString() == "__dynamic_for_test") { + return inferrer.unknownConcreteType; + } + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitStringJuxtaposition(StringJuxtaposition node) { + analyze(node.first); + analyze(node.second); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitLiteralNull(LiteralNull node) { + return inferrer.singletonConcreteType(const NullBaseType()); + } + + ConcreteType visitNewExpression(NewExpression node) { + Element constructor = elements[node.send]; + inferrer.addCaller(constructor, currentMethod); + ClassElement cls = constructor.enclosingElement; + return inferrer.getSendReturnType(constructor, cls, + analyzeArguments(node.send.arguments)); + } + + ConcreteType visitLiteralList(LiteralList node) { + ConcreteType elementsType = new ConcreteType.empty(); + // We compute the union of the types of the list literal's elements. + for (Link<Node> link = node.elements.nodes; + !link.isEmpty; + link = link.tail) { + elementsType = inferrer.union(elementsType, analyze(link.head)); + } + inferrer.augmentListElementType(elementsType); + return inferrer.singletonConcreteType(inferrer.baseTypes.listBaseType); + } + + ConcreteType visitNodeList(NodeList node) { + ConcreteType type = inferrer.emptyConcreteType; + // The concrete type of a sequence of statements is the union of the + // statement's types. + for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) { + type = inferrer.union(type, analyze(link.head)); + } + return type; + } + + ConcreteType visitOperator(Operator node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitReturn(Return node) { + final expression = node.expression; + return (expression == null) + ? inferrer.singletonConcreteType(const NullBaseType()) + : analyze(expression); + } + + ConcreteType visitThrow(Throw node) { + if (node.expression != null) analyze(node.expression); + return inferrer.emptyConcreteType; + } + + ConcreteType visitTypeAnnotation(TypeAnnotation node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTypeVariable(TypeVariable node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitVariableDefinitions(VariableDefinitions node) { + for (Link<Node> link = node.definitions.nodes; !link.isEmpty; + link = link.tail) { + analyze(link.head); + } + return inferrer.emptyConcreteType; + } + + ConcreteType visitWhile(While node) { + analyze(node.condition); + ConcreteType result = inferrer.emptyConcreteType; + ConcreteTypesEnvironment oldEnvironment; + do { + oldEnvironment = environment; + analyze(node.condition); + analyze(node.body); + environment = oldEnvironment.join(environment); + } while (oldEnvironment != environment); + return result; + } + + ConcreteType visitParenthesizedExpression(ParenthesizedExpression node) { + return analyze(node.expression); + } + + ConcreteType visitConditional(Conditional node) { + analyze(node.condition); + ConcreteType thenType = analyze(node.thenExpression); + ConcreteType elseType = analyze(node.elseExpression); + return inferrer.union(thenType, elseType); + } + + ConcreteType visitModifiers(Modifiers node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitStringInterpolationPart(StringInterpolationPart node) { + node.visitChildren(this); + return inferrer.singletonConcreteType(inferrer.baseTypes.stringBaseType); + } + + ConcreteType visitEmptyStatement(EmptyStatement node) { + return inferrer.emptyConcreteType; + } + + ConcreteType visitBreakStatement(BreakStatement node) { + return inferrer.emptyConcreteType; + } + + ConcreteType visitContinueStatement(ContinueStatement node) { + // TODO(polux): we can be more precise + return inferrer.emptyConcreteType; + } + + ConcreteType visitForIn(ForIn node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitLabel(Label node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitLabeledStatement(LabeledStatement node) { + return analyze(node.statement); + } + + ConcreteType visitLiteralMap(LiteralMap node) { + visitNodeList(node.entries); + return inferrer.singletonConcreteType(inferrer.baseTypes.mapBaseType); + } + + ConcreteType visitLiteralMapEntry(LiteralMapEntry node) { + // We don't need to visit the key, it's always a string. + return analyze(node.value); + } + + ConcreteType visitNamedArgument(NamedArgument node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitSwitchStatement(SwitchStatement node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitSwitchCase(SwitchCase node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCaseMatch(CaseMatch node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTryStatement(TryStatement node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitScriptTag(ScriptTag node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitCatchBlock(CatchBlock node) { + inferrer.fail(node, 'not yet implemented'); + } + + ConcreteType visitTypedef(Typedef node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitSuperSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitOperatorSend(Send node) { + SourceString name = + canonicalizeMethodName(node.selector.asIdentifier().source); + if (name == const SourceString('is')) { + return inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } + return visitDynamicSend(node); + } + + ConcreteType analyzeFieldRead(Element field) { + inferrer.addReader(field, currentMethod); + return inferrer.getFieldType(field); + } + + ConcreteType analyzeGetterSend(ClassElement receiverType, + FunctionElement getter) { + inferrer.addCaller(getter, currentMethod); + return inferrer.getSendReturnType(getter, + receiverType, + new ArgumentsTypes([], new Map())); + } + + ConcreteType visitGetterSend(Send node) { + Element element = elements[node]; + if (element != null) { + // node is a local variable or a field of this + ConcreteType result = environment.lookupType(element); + if (result != null) { + // node is a local variable + return result; + } else { + // node is a field or a getter of this + if (element.isField()) { + return analyzeFieldRead(element); + } else { + assert(element.isGetter()); + ClassElement receiverType = element.enclosingElement; + return analyzeGetterSend(receiverType, element); + } + } + } else { + // node is a field of not(this) + assert(node.receiver != null); + + ConcreteType result = inferrer.emptyConcreteType; + void augmentResult(ClassElement baseReceiverType, Element member) { + if (member.isField()) { + result = inferrer.union(result, analyzeFieldRead(member)); + } else if (member.isAbstractField()){ + // call to a getter + AbstractFieldElement abstractField = member; + result = inferrer.union( + result, + analyzeGetterSend(baseReceiverType, abstractField.getter)); + } + // since this is a get we ignore non-fields + } + + ConcreteType receiverType = analyze(node.receiver); + if (receiverType.isUnkown()) { + List<Element> members = + inferrer.getMembersByName(node.selector.asIdentifier().source); + for (Element member in members) { + if (!(member.isField() || member.isAbstractField())) continue; + Element cls = member.getEnclosingClass(); + augmentResult(cls, member); + } + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isNull()) { + ClassBaseType classBaseType = baseReceiverType; + ClassElement cls = classBaseType.element; + Element getterOrField = + cls.lookupMember(node.selector.asIdentifier().source); + if (getterOrField != null) { + augmentResult(cls, getterOrField); + } + } + } + } + return result; + } + } + + ConcreteType visitClosureSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType analyzeDynamicSend(ConcreteType receiverType, + SourceString canonicalizedMethodName, + ArgumentsTypes argumentsTypes) { + ConcreteType result = inferrer.emptyConcreteType; + + if (receiverType.isUnkown()) { + List<Element> methods = + inferrer.getMembersByName(canonicalizedMethodName); + for (Element element in methods) { + // TODO(polux): when we handle closures, we must handle sends to fields + // that are closures. + if (!element.isFunction()) continue; + FunctionElement method = element; + inferrer.addCaller(method, currentMethod); + Element cls = method.enclosingElement; + result = inferrer.union( + result, + inferrer.getSendReturnType(method, cls, argumentsTypes)); + } + + } else { + for (BaseType baseReceiverType in receiverType.baseTypes) { + if (!baseReceiverType.isNull()) { + ClassBaseType classBaseReceiverType = baseReceiverType; + ClassElement cls = classBaseReceiverType.element; + FunctionElement method = cls.lookupMember(canonicalizedMethodName); + if (method != null) { + inferrer.addCaller(method, currentMethod); + result = inferrer.union( + result, + inferrer.getSendReturnType(method, cls, argumentsTypes)); + } + } + } + } + return result; + } + + SourceString canonicalizeMethodName(SourceString name) { + // TODO(polux): handle unary- + SourceString operatorName = + Elements.constructOperatorNameOrNull(name, false); + if (operatorName != null) return operatorName; + return name; + } + + ConcreteType visitDynamicSend(Send node) { + ConcreteType receiverType = (node.receiver != null) + ? analyze(node.receiver) + : inferrer.singletonConcreteType( + new ClassBaseType(currentMethod.getEnclosingClass())); + SourceString name = + canonicalizeMethodName(node.selector.asIdentifier().source); + ArgumentsTypes argumentsTypes = analyzeArguments(node.arguments); + if (name.stringValue == '!=') { + ConcreteType returnType = analyzeDynamicSend(receiverType, + const SourceString('=='), + argumentsTypes); + return returnType.isEmpty() + ? returnType + : inferrer.singletonConcreteType(inferrer.baseTypes.boolBaseType); + } else { + return analyzeDynamicSend(receiverType, name, argumentsTypes); + } + } + + ConcreteType visitForeignSend(Send node) { + inferrer.fail(node, 'not implemented'); + } + + ConcreteType visitStaticSend(Send node) { + Element element = elements[node]; + inferrer.addCaller(element, currentMethod); + return inferrer.getSendReturnType(element, null, + analyzeArguments(node.arguments)); + } + + void internalError(String reason, {Node node}) { + inferrer.fail(node, reason); + } + + ConcreteType visitTypeReferenceSend(Send) { + return inferrer.singletonConcreteType(inferrer.baseTypes.typeBaseType); + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart b/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart new file mode 100644 index 0000000..fdce427 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart
@@ -0,0 +1,260 @@ +// 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. + +library types; + +import 'dart:collection' show Queue; + +import '../dart2jslib.dart' hide Selector; +import '../js_backend/js_backend.dart' show JavaScriptBackend; +import '../tree/tree.dart'; +import '../elements/elements.dart'; +import '../util/util.dart'; +import '../universe/universe.dart'; + +part 'concrete_types_inferrer.dart'; + +/** + * The types task infers guaranteed types globally. + */ +class TypesTask extends CompilerTask { + final String name = 'Type inference'; + final Set<Element> untypedElements; + final Map<Element, Link<Element>> typedSends; + ConcreteTypesInferrer concreteTypesInferrer; + + TypesTask(Compiler compiler) + : untypedElements = new Set<Element>(), + typedSends = new Map<Element, Link<Element>>(), + concreteTypesInferrer = compiler.enableConcreteTypeInference + ? new ConcreteTypesInferrer(compiler) : null, + super(compiler); + + /** + * Called once for each method during the resolution phase of the + * compiler. + */ + void analyze(Node node, TreeElements elements) { + measure(() { + node.accept(new ConcreteTypeInferencer(this, elements)); + }); + } + + /** + * Called when resolution is complete. + */ + void onResolutionComplete(Element mainElement) { + measure(() { + if (concreteTypesInferrer != null) { + bool success = concreteTypesInferrer.analyzeMain(mainElement); + if (!success) { + // If the concrete type inference bailed out, we pretend it didn't + // happen. In the future we might want to record that it failed but + // use the partial results as hints. + concreteTypesInferrer = null; + } + } + }); + } + + /** + * Return the (inferred) guaranteed concrete type of [element] or null. + */ + ConcreteType getGuaranteedTypeOfElement(Element element) { + return measure(() { + if (!element.isParameter()) return null; + if (concreteTypesInferrer != null) { + ConcreteType guaranteedType = concreteTypesInferrer + .getConcreteTypeOfParameter(element); + if (guaranteedType != null) return guaranteedType; + } + Element holder = element.enclosingElement; + Link<Element> types = typedSends[holder]; + if (types == null) return null; + if (!holder.isFunction()) return null; + if (untypedElements.contains(holder)) return null; + FunctionElement function = holder; + FunctionSignature signature = function.computeSignature(compiler); + for (Element parameter in signature.requiredParameters) { + if (types.isEmpty) return null; + if (element == parameter) { + return new ConcreteType.singleton(compiler.maxConcreteTypeSize, + new ClassBaseType(types.head)); + } + types = types.tail; + } + return null; + }); + } + + /** + * Return the (inferred) guaranteed concrete type of [node] or null. + * [node] must be an AST node of [owner]. + */ + ConcreteType getGuaranteedTypeOfNode(Node node, Element owner) { + return measure(() { + if (concreteTypesInferrer != null) { + return concreteTypesInferrer.getConcreteTypeOfNode(node); + } + return null; + }); + } +} + +/** + * Infers concrete types for a single method or expression. + */ +class ConcreteTypeInferencer extends Visitor { + final TypesTask task; + final TreeElements elements; + final ClassElement boolClass; + final ClassElement doubleClass; + final ClassElement intClass; + final ClassElement listClass; + final ClassElement nullClass; + final ClassElement stringClass; + + final Map<Node, ClassElement> concreteTypes; + + ConcreteTypeInferencer(TypesTask task, this.elements) + : this.task = task, + this.boolClass = task.compiler.boolClass, + this.doubleClass = task.compiler.doubleClass, + this.intClass = task.compiler.intClass, + this.listClass = task.compiler.listClass, + this.nullClass = task.compiler.nullClass, + this.stringClass = task.compiler.stringClass, + this.concreteTypes = new Map<Node, ClassElement>(); + + visitNode(Node node) => node.visitChildren(this); + + visitLiteralString(LiteralString node) { + recordConcreteType(node, stringClass); + } + + visitStringInterpolation(StringInterpolation node) { + node.visitChildren(this); + recordConcreteType(node, stringClass); + } + + visitStringJuxtaposition(StringJuxtaposition node) { + node.visitChildren(this); + recordConcreteType(node, stringClass); + } + + recordConcreteType(Node node, ClassElement cls) { + concreteTypes[node] = cls; + } + + visitLiteralBool(LiteralBool node) { + recordConcreteType(node, boolClass); + } + + visitLiteralDouble(LiteralDouble node) { + recordConcreteType(node, doubleClass); + } + + visitLiteralInt(LiteralInt node) { + recordConcreteType(node, intClass); + } + + visitLiteralList(LiteralList node) { + node.visitChildren(this); + recordConcreteType(node, listClass); + } + + visitLiteralMap(LiteralMap node) { + node.visitChildren(this); + // TODO(ahe): map class? + } + + visitLiteralNull(LiteralNull node) { + recordConcreteType(node, nullClass); + } + + Link<Element> computeConcreteSendArguments(Send node) { + if (node.argumentsNode == null) return null; + if (node.arguments.isEmpty) return const Link<Element>(); + if (node.receiver != null && concreteTypes[node.receiver] == null) { + return null; + } + LinkBuilder<Element> types = new LinkBuilder<Element>(); + for (Node argument in node.arguments) { + Element type = concreteTypes[argument]; + if (type == null) return null; + types.addLast(type); + } + return types.toLink(); + } + + visitSend(Send node) { + node.visitChildren(this); + Element element = elements[node.selector]; + if (element == null) return; + if (!Elements.isStaticOrTopLevelFunction(element)) return; + if (node.argumentsNode == null) { + // interest(node, 'closurized method'); + task.untypedElements.add(element); + return; + } + Link<Element> types = computeConcreteSendArguments(node); + if (types != null) { + Link<Element> existing = task.typedSends[element]; + if (existing == null) { + task.typedSends[element] = types; + } else { + // interest(node, 'multiple invocations'); + Link<Element> lub = computeLubs(existing, types); + if (lub == null) { + task.untypedElements.add(element); + } else { + task.typedSends[element] = lub; + } + } + } else { + // interest(node, 'dynamically typed invocation'); + task.untypedElements.add(element); + } + } + + visitSendSet(SendSet node) { + // TODO(ahe): Implement this. For now, overridden to avoid calling + // visitSend through super. + node.visitChildren(this); + } + + void interest(Node node, String note) { + var message = MessageKind.GENERIC.message({'text': note}); + task.compiler.reportWarning(node, message); + } + + /** + * Computes the pairwise Least Upper Bound (LUB) of the elements of + * [a] and [b]. Returns [:null:] if it gives up, or if the lists + * aren't the same length. + */ + Link<Element> computeLubs(Link<Element> a, Link<Element> b) { + LinkBuilder<Element> lubs = new LinkBuilder<Element>(); + while (!a.isEmpty && !b.isEmpty) { + Element lub = computeLub(a.head, b.head); + if (lub == null) return null; + lubs.addLast(lub); + a = a.tail; + b = b.tail; + } + return (a.isEmpty && b.isEmpty) ? lubs.toLink() : null; + } + + /** + * Computes the Least Upper Bound (LUB) of [a] and [b]. Returns + * [:null:] if it gives up. + */ + Element computeLub(Element a, Element b) { + // Fast common case, but also simple initial implementation. + if (identical(a, b)) return a; + + // TODO(ahe): Improve the following "computation"... + return null; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart new file mode 100644 index 0000000..23bc782 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart
@@ -0,0 +1,140 @@ +// 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. + +part of universe; + +// TODO(kasperl): This actually holds getters and setters just fine +// too and stricly they aren't functions. Maybe this needs a better +// name -- something like ElementSet seems a bit too generic. +class FunctionSet extends PartialTypeTree { + + FunctionSet(Compiler compiler) : super(compiler); + + FunctionSetNode newSpecializedNode(ClassElement type) + => new FunctionSetNode(type); + + // TODO(kasperl): Allow static members too? + void add(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), true); + node.membersByName[element.name] = element; + } + + // TODO(kasperl): Allow static members too? + void remove(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), false); + if (node != null) node.membersByName.remove(element.name); + } + + // TODO(kasperl): Allow static members too? + bool contains(Element element) { + assert(element.isMember()); + FunctionSetNode node = findNode(element.getEnclosingClass(), false); + return (node != null) + ? node.membersByName.containsKey(element.name) + : false; + } + + /** + * Returns all elements that may be invoked with the given [selector]. + */ + Set<Element> filterBySelector(Selector selector) { + // TODO(kasperl): For now, we use a different implementation for + // filtering if the tree contains interface subtypes. + return containsInterfaceSubtypes + ? filterAllBySelector(selector) + : filterHierarchyBySelector(selector); + } + + /** + * Returns whether the set has any element matching the given + * [selector]. + */ + bool hasAnyElementMatchingSelector(Selector selector) { + // TODO(kasperl): For now, we use a different implementation for + // filtering if the tree contains interface subtypes. + return containsInterfaceSubtypes + ? hasAnyInAll(selector) + : hasAnyInHierarchy(selector); + } + + Set<Element> filterAllBySelector(Selector selector) { + Set<Element> result = new Set<Element>(); + if (root == null) return result; + root.visitRecursively((FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (member != null && selector.appliesUnnamed(member, compiler)) { + result.add(member); + } + return true; + }); + return result; + } + + Set<Element> filterHierarchyBySelector(Selector selector) { + Set<Element> result = new Set<Element>(); + if (root == null) return result; + visitHierarchy(selectorType(selector), (FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + if (member != null && selector.appliesUntyped(member, compiler)) { + result.add(member); + } + return true; + }); + return result; + } + + bool hasAnyInAll(Selector selector) { + bool result = false; + if (root == null) return result; + root.visitRecursively((FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (member != null && selector.appliesUnnamed(member, compiler)) { + result = true; + // End the traversal. + return false; + } + return true; + }); + return result; + } + + bool hasAnyInHierarchy(Selector selector) { + bool result = false; + if (root == null) return result; + visitHierarchy(selectorType(selector), (FunctionSetNode node) { + Element member = node.membersByName[selector.name]; + if (member != null && selector.appliesUntyped(member, compiler)) { + result = true; + // End the traversal. + return false; + } + return true; + }); + return result; + } + + void forEach(Function f) { + if (root == null) return; + root.visitRecursively((FunctionSetNode node) { + node.membersByName.forEach( + (SourceString _, Element element) => f(element)); + return true; + }); + } +} + +class FunctionSetNode extends PartialTypeTreeNode { + + final Map<SourceString, Element> membersByName; + + FunctionSetNode(ClassElement type) : super(type), + membersByName = new Map<SourceString, Element>(); + +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart new file mode 100644 index 0000000..f310174 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart
@@ -0,0 +1,190 @@ +// 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. + +part of universe; + +abstract class PartialTypeTree { + + final Compiler compiler; + PartialTypeTreeNode root; + + // TODO(kasperl): This should be final but the VM will not allow + // that without making the map a compile-time constant. + Map<ClassElement, PartialTypeTreeNode> nodes = + new Map<ClassElement, PartialTypeTreeNode>(); + + // TODO(kasperl): For now, we keep track of whether or not the tree + // contains two classes with a subtype relationship that isn't a + // subclass relationship. + bool containsInterfaceSubtypes = false; + + // TODO(kasperl): This should be final but the VM will not allow + // that without making the set a compile-time constant. + Set<ClassElement> unseenInterfaceSubtypes = + new Set<ClassElement>(); + + PartialTypeTree(this.compiler); + + PartialTypeTreeNode newSpecializedNode(ClassElement type); + + PartialTypeTreeNode newNode(ClassElement type) { + PartialTypeTreeNode node = newSpecializedNode(type); + nodes[type] = node; + if (containsInterfaceSubtypes) return node; + + // Check if the implied interface of the new class is implemented + // by another class that is already in the tree. + if (unseenInterfaceSubtypes.contains(type)) { + containsInterfaceSubtypes = true; + unseenInterfaceSubtypes.clear(); + return node; + } + + // Run through all the implied interfaces the class that we're + // adding implements and see if any of them are already in the + // tree. If so, we have a tree with interface subtypes. If not, + // keep track of them so we can deal with it if the interface is + // added to the tree later. + for (Link link = type.interfaces; !link.isEmpty; link = link.tail) { + InterfaceType superType = link.head; + ClassElement superTypeElement = superType.element; + if (nodes.containsKey(superTypeElement)) { + containsInterfaceSubtypes = true; + unseenInterfaceSubtypes.clear(); + break; + } else { + unseenInterfaceSubtypes.add(superTypeElement); + } + } + return node; + } + + // TODO(kasperl): Move this to the Selector class? + /** + * Returns a [ClassElement] that is an upper bound of the receiver type on + * [selector]. + */ + ClassElement selectorType(Selector selector) { + // TODO(ngeoffray): Should the tree be specialized with DartType? + DartType type = selector.receiverType; + if (type == null) return compiler.objectClass; + // TODO(kasperl): Should [dynamic] return Object? + if (identical(type.kind, TypeKind.MALFORMED_TYPE)) + return compiler.objectClass; + // TODO(johnniwinther): Change to use [DartType.unalias]. + if (type.element.isTypedef()) return compiler.functionClass; + return type.element; + } + + /** + * Finds the tree node corresponding to the given [type]. If [insert] + * is true, we always return a node that matches the type by + * inserting a new node if necessary. If [insert] is false, we + * return null if we cannot find a node that matches the [type]. + */ + PartialTypeTreeNode findNode(ClassElement type, bool insert) { + if (root == null) { + if (!insert) return null; + root = newNode(compiler.objectClass); + } + + PartialTypeTreeNode current = root; + L: while (!identical(current.type, type)) { + assert(type.isSubclassOf(current.type)); + + // Run through the children. If we find a subtype of the type + // we are looking for we go that way. If not, we keep track of + // the subtypes so we can move them from being children of the + // current node to being children of a new node if we need + // to insert that. + Link<PartialTypeTreeNode> subtypes = const Link(); + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + ClassElement childType = child.type; + if (type.isSubclassOf(childType)) { + assert(subtypes.isEmpty); + current = child; + continue L; + } else if (childType.isSubclassOf(type)) { + if (insert) subtypes = subtypes.prepend(child); + } + } + + // If we are not inserting any nodes, we are done. + if (!insert) return null; + + // Create a new node and move the children of the current node + // that are subtypes of the type of the new node below the new + // node in the hierarchy. + PartialTypeTreeNode node = newNode(type); + if (!subtypes.isEmpty) { + node.children = subtypes; + Link<PartialTypeTreeNode> remaining = const Link(); + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + if (!child.type.isSubclassOf(type)) { + remaining = remaining.prepend(child); + } + } + current.children = remaining; + } + + // Add the new node as a child node of the current node and return it. + current.children = current.children.prepend(node); + return node; + } + + // We found an exact match. No need to insert new nodes. + assert(identical(current.type, type)); + return current; + } + + /** + * Visits all superclass and subclass nodes for the given [type]. If + * the [visit] function ever returns false, we abort the traversal. + */ + void visitHierarchy(ClassElement type, bool visit(PartialTypeTreeNode node)) { + assert(!containsInterfaceSubtypes); + PartialTypeTreeNode current = root; + L: while (!identical(current.type, type)) { + assert(type.isSubclassOf(current.type)); + if (!visit(current)) return; + for (Link link = current.children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + ClassElement childType = child.type; + if (type.isSubclassOf(childType)) { + current = child; + continue L; + } else if (childType.isSubclassOf(type)) { + if (!child.visitRecursively(visit)) return; + } + } + return; + } + current.visitRecursively(visit); + } + +} + +class PartialTypeTreeNode { + + final ClassElement type; + Link<PartialTypeTreeNode> children; + + PartialTypeTreeNode(this.type) : children = const Link(); + + /** + * Visits this node and its children recursively. If the visit + * callback ever returns false, the visiting stops early. + */ + bool visitRecursively(bool visit(PartialTypeTreeNode node)) { + if (!visit(this)) return false; + for (Link link = children; !link.isEmpty; link = link.tail) { + PartialTypeTreeNode child = link.head; + if (!child.visitRecursively(visit)) return false; + } + return true; + } + +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart new file mode 100644 index 0000000..cf98a10 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart
@@ -0,0 +1,132 @@ +// 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. + +part of universe; + +class SelectorMap<T> extends PartialTypeTree { + + SelectorMap(Compiler compiler) : super(compiler); + + SelectorMapNode<T> newSpecializedNode(ClassElement type) + => new SelectorMapNode<T>(type); + + T operator [](Selector selector) { + SelectorMapNode<T> node = findNode(selectorType(selector), false); + if (node == null) return null; + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return null; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + if (existing.selector.equalsUntyped(selector)) return existing.value; + } + return null; + } + + void operator []=(Selector selector, T value) { + SelectorMapNode<T> node = findNode(selectorType(selector), true); + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) { + // No existing selectors with the given name. Create a new + // linked list. + SelectorValue<T> head = new SelectorValue<T>(selector, value); + node.selectorsByName[selector.name] = + new Link<SelectorValue<T>>().prepend(head); + } else { + // Run through the linked list of selectors with the same name. If + // we find one that matches, we update the value in the mapping. + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + // It is safe to ignore the type here, because all selector + // mappings that are stored in a single node have the same type. + if (existing.selector.equalsUntyped(selector)) { + existing.value = value; + return; + } + } + // We could not find an existing mapping for the selector, so + // we add a new one to the existing linked list. + SelectorValue<T> head = new SelectorValue<T>(selector, value); + node.selectorsByName[selector.name] = selectors.prepend(head); + } + } + + // TODO(kasperl): Share code with the [] operator? + bool containsKey(Selector selector) { + SelectorMapNode<T> node = findNode(selectorType(selector), false); + if (node == null) return false; + Link<SelectorValue<T>> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return false; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + if (existing.selector.equalsUntyped(selector)) return true; + } + return false; + } + + /** + * Visits all mappings for selectors that may be used to invoke the + * given [member] element. If the [visit] function ever returns false, + * we abort the traversal early. + */ + void visitMatching(Element member, bool visit(Selector selector, T value)) { + assert(member.isMember()); + if (root == null) return; + // TODO(kasperl): For now, we use a different implementation for + // visiting if the tree contains interface subtypes. + if (containsInterfaceSubtypes) { + visitAllMatching(member, visit); + } else { + visitHierarchyMatching(member, visit); + } + } + + void visitAllMatching(Element member, bool visit(selector, value)) { + root.visitRecursively((SelectorMapNode<T> node) { + Link<SelectorValue<T>> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + Selector selector = existing.selector; + // Since we're running through the entire tree we have to use + // the applies method that takes types into account. + if (selector.appliesUnnamed(member, compiler)) { + if (!visit(selector, existing.value)) return false; + } + } + return true; + }); + } + + void visitHierarchyMatching(Element member, bool visit(selector, value)) { + visitHierarchy(member.getEnclosingClass(), (SelectorMapNode<T> node) { + Link<SelectorValue<T>> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue<T> existing = link.head; + Selector selector = existing.selector; + if (selector.appliesUntyped(member, compiler)) { + if (!visit(selector, existing.value)) return false; + } + } + return true; + }); + } + +} + +class SelectorMapNode<T> extends PartialTypeTreeNode { + + final Map<SourceString, Link<SelectorValue<T>>> selectorsByName; + + SelectorMapNode(ClassElement type) : super(type), + selectorsByName = new Map<SourceString, Link<SelectorValue<T>>>(); + +} + +class SelectorValue<T> { + final Selector selector; + T value; + SelectorValue(this.selector, this.value); + toString() => "$selector -> $value"; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart b/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart new file mode 100644 index 0000000..6b64382 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart
@@ -0,0 +1,495 @@ +// 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. + +library universe; + +import '../closure.dart'; +import '../elements/elements.dart'; +import '../dart2jslib.dart'; +import '../dart_types.dart'; +import '../tree/tree.dart'; +import '../util/util.dart'; +import '../js/js.dart' as js; + +part 'function_set.dart'; +part 'partial_type_tree.dart'; +part 'selector_map.dart'; + +class Universe { + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements are declaration elements. + */ + // TODO(karlklose): these sets should be merged. + final Set<ClassElement> instantiatedClasses; + final Set<DartType> instantiatedTypes; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements are declaration elements. + */ + final Set<FunctionElement> staticFunctionsNeedingGetter; + final Map<SourceString, Set<Selector>> invokedNames; + final Map<SourceString, Set<Selector>> invokedGetters; + final Map<SourceString, Set<Selector>> invokedSetters; + + /** + * Fields accessed. Currently only the codegen knows this + * information. The resolver is too conservative when seeing a + * getter and only registers an invoked getter. + */ + final Map<SourceString, Set<Selector>> fieldGetters; + + /** + * Fields set. See comment in [fieldGetters]. + */ + final Map<SourceString, Set<Selector>> fieldSetters; + final Set<DartType> isChecks; + + Universe() : instantiatedClasses = new Set<ClassElement>(), + instantiatedTypes = new Set<DartType>(), + staticFunctionsNeedingGetter = new Set<FunctionElement>(), + invokedNames = new Map<SourceString, Set<Selector>>(), + invokedGetters = new Map<SourceString, Set<Selector>>(), + fieldGetters = new Map<SourceString, Set<Selector>>(), + fieldSetters = new Map<SourceString, Set<Selector>>(), + invokedSetters = new Map<SourceString, Set<Selector>>(), + isChecks = new Set<DartType>(); + + bool hasMatchingSelector(Set<Selector> selectors, + Element member, + Compiler compiler) { + if (selectors == null) return false; + for (Selector selector in selectors) { + if (selector.appliesUnnamed(member, compiler)) return true; + } + return false; + } + + bool hasInvocation(Element member, Compiler compiler) { + return hasMatchingSelector(invokedNames[member.name], member, compiler); + } + + bool hasInvokedGetter(Element member, Compiler compiler) { + return hasMatchingSelector(invokedGetters[member.name], member, compiler); + } + + bool hasInvokedSetter(Element member, Compiler compiler) { + return hasMatchingSelector(invokedSetters[member.name], member, compiler); + } + + bool hasFieldGetter(Element member, Compiler compiler) { + return hasMatchingSelector(fieldGetters[member.name], member, compiler); + } + + bool hasFieldSetter(Element member, Compiler compiler) { + return hasMatchingSelector(fieldSetters[member.name], member, compiler); + } +} + +class SelectorKind { + final String name; + const SelectorKind(this.name); + + static const SelectorKind GETTER = const SelectorKind('getter'); + static const SelectorKind SETTER = const SelectorKind('setter'); + static const SelectorKind CALL = const SelectorKind('call'); + static const SelectorKind OPERATOR = const SelectorKind('operator'); + static const SelectorKind INDEX = const SelectorKind('index'); + + toString() => name; +} + +class Selector { + final SelectorKind kind; + final SourceString name; + final LibraryElement library; // Library is null for non-private selectors. + + // The numbers of arguments of the selector. Includes named arguments. + final int argumentCount; + final List<SourceString> namedArguments; + final List<SourceString> orderedNamedArguments; + + Selector( + this.kind, + SourceString name, + LibraryElement library, + this.argumentCount, + [List<SourceString> namedArguments = const <SourceString>[]]) + : this.name = name, + this.library = name.isPrivate() ? library : null, + this.namedArguments = namedArguments, + this.orderedNamedArguments = namedArguments.isEmpty + ? namedArguments + : <SourceString>[] { + assert(!name.isPrivate() || library != null); + } + + Selector.getter(SourceString name, LibraryElement library) + : this(SelectorKind.GETTER, name, library, 0); + + Selector.getterFrom(Selector selector) + : this(SelectorKind.GETTER, selector.name, selector.library, 0); + + Selector.setter(SourceString name, LibraryElement library) + : this(SelectorKind.SETTER, name, library, 1); + + Selector.unaryOperator(SourceString name) + : this(SelectorKind.OPERATOR, + Elements.constructOperatorName(name, true), + null, 0); + + Selector.binaryOperator(SourceString name) + : this(SelectorKind.OPERATOR, + Elements.constructOperatorName(name, false), + null, 1); + + Selector.index() + : this(SelectorKind.INDEX, + Elements.constructOperatorName(const SourceString("[]"), false), + null, 1); + + Selector.indexSet() + : this(SelectorKind.INDEX, + Elements.constructOperatorName(const SourceString("[]="), false), + null, 2); + + Selector.call(SourceString name, + LibraryElement library, + int arity, + [List<SourceString> named = const []]) + : this(SelectorKind.CALL, name, library, arity, named); + + Selector.callClosure(int arity, [List<SourceString> named = const []]) + : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null, + arity, named); + + Selector.callClosureFrom(Selector selector) + : this(SelectorKind.CALL, Compiler.CALL_OPERATOR_NAME, null, + selector.argumentCount, selector.namedArguments); + + Selector.callConstructor(SourceString constructorName, + LibraryElement library) + : this(SelectorKind.CALL, + constructorName, + library, + 0, + const []); + + Selector.callDefaultConstructor(LibraryElement library) + : this(SelectorKind.CALL, const SourceString(""), library, 0, const []); + + // TODO(kasperl): This belongs somewhere else. + Selector.noSuchMethod() + : this(SelectorKind.CALL, Compiler.NO_SUCH_METHOD, null, + Compiler.NO_SUCH_METHOD_ARG_COUNT); + + bool isGetter() => identical(kind, SelectorKind.GETTER); + bool isSetter() => identical(kind, SelectorKind.SETTER); + bool isCall() => identical(kind, SelectorKind.CALL); + bool isClosureCall() { + SourceString callName = Compiler.CALL_OPERATOR_NAME; + return isCall() && name == callName; + } + + bool isIndex() => identical(kind, SelectorKind.INDEX) && argumentCount == 1; + bool isIndexSet() => identical(kind, SelectorKind.INDEX) && argumentCount == 2; + + bool isOperator() => identical(kind, SelectorKind.OPERATOR); + bool isUnaryOperator() => isOperator() && argumentCount == 0; + bool isBinaryOperator() => isOperator() && argumentCount == 1; + + /** Check whether this is a call to 'assert'. */ + bool isAssert() => isCall() && identical(name.stringValue, "assert"); + + int get hashCode => argumentCount + 1000 * namedArguments.length; + int get namedArgumentCount => namedArguments.length; + int get positionalArgumentCount => argumentCount - namedArgumentCount; + DartType get receiverType => null; + + Selector get asUntyped => this; + + /** + * The member name for invocation mirrors created from this selector. + */ + String get invocationMirrorMemberName => + isSetter() ? '${name.slowToString()}=' : name.slowToString(); + + int get invocationMirrorKind { + const int METHOD = 0; + const int GETTER = 1; + const int SETTER = 2; + int kind = METHOD; + if (isGetter()) { + kind = GETTER; + } else if (isSetter()) { + kind = SETTER; + } + return kind; + } + + bool appliesUnnamed(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + return appliesUntyped(element, compiler); + } + + bool appliesUntyped(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + if (Elements.isUnresolved(element)) return false; + if (name.isPrivate() && library != element.getLibrary()) return false; + if (element.isForeign(compiler)) return true; + if (element.isSetter()) return isSetter(); + if (element.isGetter()) return isGetter() || isCall(); + if (element.isField()) return isGetter() || isSetter() || isCall(); + if (isGetter()) return true; + if (isSetter()) return false; + + FunctionElement function = element; + FunctionSignature parameters = function.computeSignature(compiler); + if (argumentCount > parameters.parameterCount) return false; + int requiredParameterCount = parameters.requiredParameterCount; + int optionalParameterCount = parameters.optionalParameterCount; + if (positionalArgumentCount < requiredParameterCount) return false; + + if (!parameters.optionalParametersAreNamed) { + // We have already checked that the number of arguments are + // not greater than the number of parameters. Therefore the + // number of positional arguments are not greater than the + // number of parameters. + assert(positionalArgumentCount <= parameters.parameterCount); + return namedArguments.isEmpty; + } else { + if (positionalArgumentCount > requiredParameterCount) return false; + assert(positionalArgumentCount == requiredParameterCount); + if (namedArgumentCount > optionalParameterCount) return false; + Set<SourceString> nameSet = new Set<SourceString>(); + parameters.optionalParameters.forEach((Element element) { + nameSet.add(element.name); + }); + for (SourceString name in namedArguments) { + if (!nameSet.contains(name)) return false; + // TODO(5213): By removing from the set we are checking + // that we are not passing the name twice. We should have this + // check in the resolver also. + nameSet.remove(name); + } + return true; + } + } + + bool sameNameHack(Element element, Compiler compiler) { + // TODO(ngeoffray): Remove workaround checks. + return element == compiler.assertMethod + || element.isConstructor() + || name == element.name; + } + + bool applies(Element element, Compiler compiler) { + if (!sameNameHack(element, compiler)) return false; + return appliesUnnamed(element, compiler); + } + + /** + * Fills [list] with the arguments in a defined order. + * + * [compileArgument] is a function that returns a compiled version + * of an argument located in [arguments]. + * + * [compileConstant] is a function that returns a compiled constant + * of an optional argument that is not in [arguments. + * + * Returns [:true:] if the selector and the [element] match; [:false:] + * otherwise. + * + * Invariant: [element] must be the implementation element. + */ + bool addArgumentsToList(Link<Node> arguments, + List list, + FunctionElement element, + compileArgument(Node argument), + compileConstant(Element element), + Compiler compiler) { + assert(invariant(element, element.isImplementation)); + if (!this.applies(element, compiler)) return false; + + FunctionSignature parameters = element.computeSignature(compiler); + parameters.forEachRequiredParameter((element) { + list.add(compileArgument(arguments.head)); + arguments = arguments.tail; + }); + + if (!parameters.optionalParametersAreNamed) { + parameters.forEachOptionalParameter((element) { + if (!arguments.isEmpty) { + list.add(compileArgument(arguments.head)); + arguments = arguments.tail; + } else { + list.add(compileConstant(element)); + } + }); + } else { + // Visit named arguments and add them into a temporary list. + List compiledNamedArguments = []; + for (; !arguments.isEmpty; arguments = arguments.tail) { + NamedArgument namedArgument = arguments.head; + compiledNamedArguments.add(compileArgument(namedArgument.expression)); + } + // Iterate over the optional parameters of the signature, and try to + // find them in [compiledNamedArguments]. If found, we use the + // value in the temporary list, otherwise the default value. + parameters.orderedOptionalParameters.forEach((element) { + int foundIndex = namedArguments.indexOf(element.name); + if (foundIndex != -1) { + list.add(compiledNamedArguments[foundIndex]); + } else { + list.add(compileConstant(element)); + } + }); + } + return true; + } + + static bool sameNames(List<SourceString> first, List<SourceString> second) { + for (int i = 0; i < first.length; i++) { + if (first[i] != second[i]) return false; + } + return true; + } + + bool operator ==(other) { + if (other is !Selector) return false; + return identical(receiverType, other.receiverType) + && equalsUntyped(other); + } + + bool equalsUntyped(Selector other) { + return name == other.name + && kind == other.kind + && identical(library, other.library) + && argumentCount == other.argumentCount + && namedArguments.length == other.namedArguments.length + && sameNames(namedArguments, other.namedArguments); + } + + List<SourceString> getOrderedNamedArguments() { + if (namedArguments.isEmpty) return namedArguments; + if (!orderedNamedArguments.isEmpty) return orderedNamedArguments; + + orderedNamedArguments.addAll(namedArguments); + orderedNamedArguments.sort((SourceString first, SourceString second) { + return first.slowToString().compareTo(second.slowToString()); + }); + return orderedNamedArguments; + } + + String namedArgumentsToString() { + if (namedArgumentCount > 0) { + StringBuffer result = new StringBuffer(); + for (int i = 0; i < namedArgumentCount; i++) { + if (i != 0) result.add(', '); + result.add(namedArguments[i].slowToString()); + } + return "[$result]"; + } + return ''; + } + + String toString() { + String named = ''; + String type = ''; + if (namedArgumentCount > 0) named = ', named=${namedArgumentsToString()}'; + if (receiverType != null) type = ', type=$receiverType'; + return 'Selector($kind, ${name.slowToString()}, ' + 'arity=$argumentCount$named$type)'; + } +} + +class TypedSelector extends Selector { + /** + * The type of the receiver. Any subtype of that type can be the + * target of the invocation. + */ + final DartType receiverType; + + final Selector asUntyped; + + TypedSelector(DartType this.receiverType, Selector selector) + : asUntyped = selector.asUntyped, + super(selector.kind, + selector.name, + selector.library, + selector.argumentCount, + selector.namedArguments) { + // Invariant: Typed selector can not be based on a malformed type. + assert(!identical(receiverType.kind, TypeKind.MALFORMED_TYPE)); + assert(asUntyped.receiverType == null); + } + + /** + * Check if [element] will be the one used at runtime when being + * invoked on an instance of [cls]. + */ + bool hasElementIn(ClassElement cls, Element element) { + // Use the selector for the lookup instead of [:element.name:] + // because the selector has the right privacy information. + Element resolved = cls.lookupSelector(this); + if (resolved == element) return true; + if (resolved == null) return false; + if (resolved.isAbstractField()) { + AbstractFieldElement field = resolved; + if (element == field.getter || element == field.setter) { + return true; + } else { + ClassElement otherCls = field.getEnclosingClass(); + // We have not found a match, but another class higher in the + // hierarchy may define the getter or the setter. + return hasElementIn(otherCls.superclass, element); + } + } + return false; + } + + bool appliesUnnamed(Element element, Compiler compiler) { + assert(sameNameHack(element, compiler)); + // [TypedSelector] are only used when compiling. + assert(compiler.phase == Compiler.PHASE_COMPILING); + if (!element.isMember()) return false; + + // A closure can be called through any typed selector: + // class A { + // get foo => () => 42; + // bar() => foo(); // The call to 'foo' is a typed selector. + // } + ClassElement other = element.getEnclosingClass(); + if (identical(other.superclass, compiler.closureClass)) { + return appliesUntyped(element, compiler); + } + + Element self = receiverType.element; + if (self.isTypedef()) { + // A typedef is a function type that doesn't have any + // user-defined members. + return false; + } + + if (other.implementsInterface(self) + || other.isSubclassOf(self) + || compiler.world.hasAnySubclassThatImplements(other, receiverType)) { + return appliesUntyped(element, compiler); + } + + // If [self] is a subclass of [other], it inherits the + // implementation of [element]. + ClassElement cls = self; + if (cls.isSubclassOf(other)) { + // Resolve an invocation of [element.name] on [self]. If it + // is found, this selector is a candidate. + return hasElementIn(self, element) && appliesUntyped(element, compiler); + } + + return false; + } +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart new file mode 100644 index 0000000..5961d07 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart
@@ -0,0 +1,143 @@ +// Copyright (c) 2011, 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. + +library characters; + +const int $EOF = 0; +const int $STX = 2; +const int $BS = 8; +const int $TAB = 9; +const int $LF = 10; +const int $VTAB = 11; +const int $FF = 12; +const int $CR = 13; +const int $SPACE = 32; +const int $BANG = 33; +const int $DQ = 34; +const int $HASH = 35; +const int $$ = 36; +const int $PERCENT = 37; +const int $AMPERSAND = 38; +const int $SQ = 39; +const int $OPEN_PAREN = 40; +const int $CLOSE_PAREN = 41; +const int $STAR = 42; +const int $PLUS = 43; +const int $COMMA = 44; +const int $MINUS = 45; +const int $PERIOD = 46; +const int $SLASH = 47; +const int $0 = 48; +const int $1 = 49; +const int $2 = 50; +const int $3 = 51; +const int $4 = 52; +const int $5 = 53; +const int $6 = 54; +const int $7 = 55; +const int $8 = 56; +const int $9 = 57; +const int $COLON = 58; +const int $SEMICOLON = 59; +const int $LT = 60; +const int $EQ = 61; +const int $GT = 62; +const int $QUESTION = 63; +const int $AT = 64; +const int $A = 65; +const int $B = 66; +const int $C = 67; +const int $D = 68; +const int $E = 69; +const int $F = 70; +const int $G = 71; +const int $H = 72; +const int $I = 73; +const int $J = 74; +const int $K = 75; +const int $L = 76; +const int $M = 77; +const int $N = 78; +const int $O = 79; +const int $P = 80; +const int $Q = 81; +const int $R = 82; +const int $S = 83; +const int $T = 84; +const int $U = 85; +const int $V = 86; +const int $W = 87; +const int $X = 88; +const int $Y = 89; +const int $Z = 90; +const int $OPEN_SQUARE_BRACKET = 91; +const int $BACKSLASH = 92; +const int $CLOSE_SQUARE_BRACKET = 93; +const int $CARET = 94; +const int $_ = 95; +const int $BACKPING = 96; +const int $a = 97; +const int $b = 98; +const int $c = 99; +const int $d = 100; +const int $e = 101; +const int $f = 102; +const int $g = 103; +const int $h = 104; +const int $i = 105; +const int $j = 106; +const int $k = 107; +const int $l = 108; +const int $m = 109; +const int $n = 110; +const int $o = 111; +const int $p = 112; +const int $q = 113; +const int $r = 114; +const int $s = 115; +const int $t = 116; +const int $u = 117; +const int $v = 118; +const int $w = 119; +const int $x = 120; +const int $y = 121; +const int $z = 122; +const int $OPEN_CURLY_BRACKET = 123; +const int $BAR = 124; +const int $CLOSE_CURLY_BRACKET = 125; +const int $TILDE = 126; +const int $DEL = 127; +const int $NBSP = 160; +const int $LS = 0x2028; +const int $PS = 0x2029; + +const int $FIRST_SURROGATE = 0xd800; +const int $LAST_SURROGATE = 0xdfff; +const int $LAST_CODE_POINT = 0x10ffff; + +bool isHexDigit(int characterCode) { + if (characterCode <= $9) return $0 <= characterCode; + characterCode |= $a ^ $A; + return ($a <= characterCode && characterCode <= $f); +} + +int hexDigitValue(int hexDigit) { + assert(isHexDigit(hexDigit)); + // hexDigit is one of '0'..'9', 'A'..'F' and 'a'..'f'. + if (hexDigit <= $9) return hexDigit - $0; + return (hexDigit | ($a ^ $A)) - ($a - 10); +} + +bool isUnicodeScalarValue(int value) { + return value < $FIRST_SURROGATE || + (value > $LAST_SURROGATE && value <= $LAST_CODE_POINT); +} + +bool isUtf16LeadSurrogate(int value) { + return value >= 0xd800 && value <= 0xdbff; +} + +bool isUtf16TrailSurrogate(int value) { + return value >= 0xdc00 && value <= 0xdfff; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart new file mode 100644 index 0000000..9cc81cc --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart
@@ -0,0 +1,81 @@ +// Copyright (c) 2011, 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. + +part of org_dartlang_compiler_util; + +class Link<T> extends Iterable<T> { + T get head => null; + Link<T> get tail => null; + + factory Link.fromList(List<T> list) { + switch (list.length) { + case 0: + return new Link<T>(); + case 1: + return new LinkEntry<T>(list[0]); + case 2: + return new LinkEntry<T>(list[0], new LinkEntry<T>(list[1])); + case 3: + return new LinkEntry<T>( + list[0], new LinkEntry<T>(list[1], new LinkEntry<T>(list[2]))); + } + Link link = new Link<T>(); + for (int i = list.length ; i > 0; i--) { + link = link.prepend(list[i - 1]); + } + return link; + } + + const Link(); + + Link<T> prepend(T element) { + return new LinkEntry<T>(element, this); + } + + Iterator<T> get iterator => new LinkIterator<T>(this); + + void printOn(StringBuffer buffer, [separatedBy]) { + } + + List toList() => new List<T>.fixedLength(0); + + bool get isEmpty => true; + + Link<T> reverse() => this; + + Link<T> reversePrependAll(Link<T> from) { + if (from.isEmpty) return this; + return this.prepend(from.head).reversePrependAll(from.tail); + } + + Link<T> skip(int n) { + if (n == 0) return this; + throw new RangeError('Index $n out of range'); + } + + void forEach(void f(T element)) {} + + bool operator ==(other) { + if (other is !Link<T>) return false; + return other.isEmpty; + } + + String toString() => "[]"; + + get length { + throw new UnsupportedError('get:length'); + } + + int slowLength() => 0; +} + +abstract class LinkBuilder<T> { + factory LinkBuilder() = LinkBuilderImplementation; + + Link<T> toLink(); + void addLast(T t); + + final int length; + final bool isEmpty; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart new file mode 100644 index 0000000..d00ec86 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart
@@ -0,0 +1,142 @@ +// Copyright (c) 2011, 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. + +part of util_implementation; + +class LinkIterator<T> implements Iterator<T> { + T _current; + Link<T> _link; + + LinkIterator(Link<T> this._link); + + T get current => _current; + + bool moveNext() { + if (_link.isEmpty) { + _current = null; + return false; + } + _current = _link.head; + _link = _link.tail; + return true; + } +} + +class LinkEntry<T> extends Link<T> { + final T head; + Link<T> tail; + + LinkEntry(T this.head, [Link<T> tail]) + : this.tail = ((tail == null) ? new Link<T>() : tail); + + Link<T> prepend(T element) { + // TODO(ahe): Use new Link<T>, but this cost 8% performance on VM. + return new LinkEntry<T>(element, this); + } + + void printOn(StringBuffer buffer, [separatedBy]) { + buffer.add(head); + if (separatedBy == null) separatedBy = ''; + for (Link link = tail; !link.isEmpty; link = link.tail) { + buffer.add(separatedBy); + buffer.add(link.head); + } + } + + String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.add('[ '); + printOn(buffer, ', '); + buffer.add(' ]'); + return buffer.toString(); + } + + Link<T> reverse() { + Link<T> result = const Link(); + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + result = result.prepend(link.head); + } + return result; + } + + Link<T> reversePrependAll(Link<T> from) { + Link<T> result; + for (result = this; !from.isEmpty; from = from.tail) { + result = result.prepend(from.head); + } + return result; + } + + Link<T> skip(int n) { + Link<T> link = this; + for (int i = 0 ; i < n ; i++) { + if (link.isEmpty) { + throw new RangeError('Index $n out of range'); + } + link = link.tail; + } + return link; + } + + bool get isEmpty => false; + + List<T> toList() { + List<T> list = new List<T>(); + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + list.addLast(link.head); + } + return list; + } + + void forEach(void f(T element)) { + for (Link<T> link = this; !link.isEmpty; link = link.tail) { + f(link.head); + } + } + + bool operator ==(other) { + if (other is !Link<T>) return false; + Link<T> myElements = this; + while (!myElements.isEmpty && !other.isEmpty) { + if (myElements.head != other.head) { + return false; + } + myElements = myElements.tail; + other = other.tail; + } + return myElements.isEmpty && other.isEmpty; + } + + int slowLength() => 1 + tail.slowLength(); +} + +class LinkBuilderImplementation<T> implements LinkBuilder<T> { + LinkEntry<T> head = null; + LinkEntry<T> lastLink = null; + int length = 0; + + LinkBuilderImplementation(); + + Link<T> toLink() { + if (head == null) return const Link(); + lastLink.tail = const Link(); + Link<T> link = head; + lastLink = null; + head = null; + return link; + } + + void addLast(T t) { + length++; + LinkEntry<T> entry = new LinkEntry<T>(t, null); + if (head == null) { + head = entry; + } else { + lastLink.tail = entry; + } + lastLink = entry; + } + + bool get isEmpty => length == 0; +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart new file mode 100644 index 0000000..fed5f15 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart
@@ -0,0 +1,65 @@ +// 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. + +library uri_extras; + +import 'dart:math'; +import 'dart:uri'; + +String relativize(Uri base, Uri uri, bool isWindows) { + if (!base.path.startsWith('/')) { + // Also throw an exception if [base] or base.path is null. + throw new ArgumentError('Expected absolute path: ${base.path}'); + } + if (!uri.path.startsWith('/')) { + // Also throw an exception if [uri] or uri.path is null. + throw new ArgumentError('Expected absolute path: ${uri.path}'); + } + bool equalsNCS(String a, String b) { + return a.toLowerCase() == b.toLowerCase(); + } + + String normalize(String path) { + if (isWindows) { + return path.toLowerCase(); + } else { + return path; + } + } + + if (equalsNCS(base.scheme, 'file') && + equalsNCS(base.scheme, uri.scheme) && + base.userInfo == uri.userInfo && + equalsNCS(base.domain, uri.domain) && + base.port == uri.port && + uri.query == "" && uri.fragment == "") { + if (normalize(uri.path).startsWith(normalize(base.path))) { + return uri.path.substring(base.path.length); + } + List<String> uriParts = uri.path.split('/'); + List<String> baseParts = base.path.split('/'); + int common = 0; + int length = min(uriParts.length, baseParts.length); + while (common < length && + normalize(uriParts[common]) == normalize(baseParts[common])) { + common++; + } + if (common == 1 || (isWindows && common == 2)) { + // The first part will always be an empty string because the + // paths are absolute. On Windows, we must also consider drive + // letters or hostnames. + return uri.path; + } + StringBuffer sb = new StringBuffer(); + for (int i = common + 1; i < baseParts.length; i++) { + sb.add('../'); + } + for (int i = common; i < uriParts.length - 1; i++) { + sb.add('${uriParts[i]}/'); + } + sb.add('${uriParts.last}'); + return sb.toString(); + } + return uri.toString(); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart new file mode 100644 index 0000000..8a07687 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart
@@ -0,0 +1,109 @@ +// Copyright (c) 2011, 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. + +library org_dartlang_compiler_util; + +import 'util_implementation.dart'; +import 'characters.dart'; + +part 'link.dart'; + +/** + * Tagging interface for classes from which source spans can be generated. + */ +// TODO(johnniwinther): Find a better name. +// TODO(ahe): How about "Bolt"? +abstract class Spannable {} + +class _SpannableSentinel implements Spannable { + final String name; + + const _SpannableSentinel(this.name); + + String toString() => name; +} + +const Spannable CURRENT_ELEMENT_SPANNABLE = + const _SpannableSentinel("Current element"); + +class SpannableAssertionFailure { + final Spannable node; + final String message; + SpannableAssertionFailure(this.node, this.message); + + String toString() => 'Compiler crashed: $message.'; +} + +/// Writes the characters of [string] on [buffer]. The characters +/// are escaped as suitable for JavaScript and JSON. [buffer] is +/// anything which supports [:add:] and [:addCharCode:], for example, +/// [StringBuffer]. Note that JS supports \xnn and \unnnn whereas JSON only +/// supports the \unnnn notation. Therefore we use the \unnnn notation. +void writeJsonEscapedCharsOn(String string, buffer) { + void addCodeUnitEscaped(var buffer, int code) { + assert(code < 0x10000); + buffer.add(r'\u'); + if (code < 0x1000) { + buffer.add('0'); + if (code < 0x100) { + buffer.add('0'); + if (code < 0x10) { + buffer.add('0'); + } + } + } + buffer.add(code.toRadixString(16)); + } + + void writeEscapedOn(String string, var buffer) { + for (int i = 0; i < string.length; i++) { + int code = string.charCodeAt(i); + if (code == $DQ) { + buffer.add(r'\"'); + } else if (code == $TAB) { + buffer.add(r'\t'); + } else if (code == $LF) { + buffer.add(r'\n'); + } else if (code == $CR) { + buffer.add(r'\r'); + } else if (code == $DEL) { + addCodeUnitEscaped(buffer, $DEL); + } else if (code == $LS) { + // This Unicode line terminator and $PS are invalid in JS string + // literals. + addCodeUnitEscaped(buffer, $LS); // 0x2028. + } else if (code == $PS) { + addCodeUnitEscaped(buffer, $PS); // 0x2029. + } else if (code == $BACKSLASH) { + buffer.add(r'\\'); + } else { + if (code < 0x20) { + addCodeUnitEscaped(buffer, code); + // We emit DEL (ASCII 0x7f) as an escape because it would be confusing + // to have it unescaped in a string literal. We also escape + // everything above 0x7f because that means we don't have to worry + // about whether the web server serves it up as Latin1 or UTF-8. + } else if (code < 0x7f) { + buffer.addCharCode(code); + } else { + // This will output surrogate pairs in the form \udxxx\udyyy, rather + // than the more logical \u{zzzzzz}. This should work in JavaScript + // (especially old UCS-2 based implementations) and is the only + // format that is allowed in JSON. + addCodeUnitEscaped(buffer, code); + } + } + } + } + + for (int i = 0; i < string.length; i++) { + int code = string.charCodeAt(i); + if (code < 0x20 || code == $DEL || code == $DQ || code == $LS || + code == $PS || code == $BACKSLASH || code >= 0x80) { + writeEscapedOn(string, buffer); + return; + } + } + buffer.add(string); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart b/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart new file mode 100644 index 0000000..bbb852a --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart
@@ -0,0 +1,9 @@ +// Copyright (c) 2011, 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. + +library util_implementation; + +import 'util.dart'; + +part 'link_implementation.dart';
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart b/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart new file mode 100644 index 0000000..56f3698 --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart
@@ -0,0 +1,568 @@ +// Copyright (c) 2011, 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. + +part of dart2js; + +class MessageKind { + final String template; + const MessageKind(this.template); + + static const GENERIC = const MessageKind('#{text}'); + + static const NOT_ASSIGNABLE = const MessageKind( + '#{fromType} is not assignable to #{toType}'); + static const VOID_EXPRESSION = const MessageKind( + 'expression does not yield a value'); + static const VOID_VARIABLE = const MessageKind( + 'variable cannot be of type void'); + static const RETURN_VALUE_IN_VOID = const MessageKind( + 'cannot return value from void function'); + static const RETURN_NOTHING = const MessageKind( + 'value of type #{returnType} expected'); + static const MISSING_ARGUMENT = const MessageKind( + 'missing argument of type #{argumentType}'); + static const ADDITIONAL_ARGUMENT = const MessageKind( + 'additional argument'); + static const NAMED_ARGUMENT_NOT_FOUND = const MessageKind( + "no named argument '#{argumentName}' found on method"); + static const METHOD_NOT_FOUND = const MessageKind( + 'no method named #{methodName} in class #{className}'); + static const MEMBER_NOT_STATIC = const MessageKind( + '#{className}.#{memberName} is not static'); + static const NO_INSTANCE_AVAILABLE = const MessageKind( + '#{name} is only available in instance methods'); + + static const UNREACHABLE_CODE = const MessageKind( + 'unreachable code'); + static const MISSING_RETURN = const MessageKind( + 'missing return'); + static const MAYBE_MISSING_RETURN = const MessageKind( + 'not all paths lead to a return or throw statement'); + + static const CANNOT_RESOLVE = const MessageKind( + 'cannot resolve #{name}'); + static const CANNOT_RESOLVE_CONSTRUCTOR = const MessageKind( + 'cannot resolve constructor #{constructorName}'); + static const CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind( + 'cannot resolve constructor #{constructorName} for implicit super call'); + static const CANNOT_RESOLVE_TYPE = const MessageKind( + 'cannot resolve type #{typeName}'); + static const DUPLICATE_DEFINITION = const MessageKind( + 'duplicate definition of #{name}'); + static const DUPLICATE_IMPORT = const MessageKind( + 'duplicate import of #{name}'); + static const DUPLICATE_EXPORT = const MessageKind( + 'duplicate export of #{name}'); + static const NOT_A_TYPE = const MessageKind( + '#{node} is not a type'); + static const NOT_A_PREFIX = const MessageKind( + '#{node} is not a prefix'); + static const NO_SUPER_IN_OBJECT = const MessageKind( + "'Object' does not have a superclass"); + static const CANNOT_FIND_CONSTRUCTOR = const MessageKind( + 'cannot find constructor #{constructorName}'); + static const CANNOT_FIND_CONSTRUCTOR2 = const MessageKind( + 'cannot find constructor #{constructorName} in #{className}'); + static const CYCLIC_CLASS_HIERARCHY = const MessageKind( + '#{className} creates a cycle in the class hierarchy'); + static const INVALID_RECEIVER_IN_INITIALIZER = const MessageKind( + 'field initializer expected'); + static const NO_SUPER_IN_STATIC = const MessageKind( + "'super' is only available in instance methods"); + static const DUPLICATE_INITIALIZER = const MessageKind( + 'field #{fieldName} is initialized more than once'); + static const ALREADY_INITIALIZED = const MessageKind( + '#{fieldName} was already initialized here'); + static const INIT_STATIC_FIELD = const MessageKind( + 'cannot initialize static field #{fieldName}'); + static const NOT_A_FIELD = const MessageKind( + '#{fieldName} is not a field'); + static const CONSTRUCTOR_CALL_EXPECTED = const MessageKind( + "only call to 'this' or 'super' constructor allowed"); + static const INVALID_FOR_IN = const MessageKind( + 'invalid for-in variable declaration.'); + static const INVALID_INITIALIZER = const MessageKind( + 'invalid initializer'); + static const FUNCTION_WITH_INITIALIZER = const MessageKind( + 'only constructors can have initializers'); + static const REDIRECTING_CONSTRUCTOR_CYCLE = const MessageKind( + 'cyclic constructor redirection'); + static const REDIRECTING_CONSTRUCTOR_HAS_BODY = const MessageKind( + 'redirecting constructor cannot have a body'); + static const REDIRECTING_CONSTRUCTOR_HAS_INITIALIZER = const MessageKind( + 'redirecting constructor cannot have other initializers'); + static const SUPER_INITIALIZER_IN_OBJECT = const MessageKind( + "'Object' cannot have a super initializer"); + static const DUPLICATE_SUPER_INITIALIZER = const MessageKind( + 'cannot have more than one super initializer'); + static const INVALID_ARGUMENTS = const MessageKind( + "arguments do not match the expected parameters of #{methodName}"); + static const NO_MATCHING_CONSTRUCTOR = const MessageKind( + "super call arguments and constructor parameters don't match"); + static const NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT = const MessageKind( + "implicit super call arguments and constructor parameters don't match"); + static const FIELD_PARAMETER_NOT_ALLOWED = const MessageKind( + 'a field parameter is only allowed in generative constructors'); + static const INVALID_PARAMETER = const MessageKind( + "cannot resolve parameter"); + static const NOT_INSTANCE_FIELD = const MessageKind( + '#{fieldName} is not an instance field'); + static const NO_CATCH_NOR_FINALLY = const MessageKind( + "expected 'catch' or 'finally'"); + static const EMPTY_CATCH_DECLARATION = const MessageKind( + 'expected an identifier in catch declaration'); + static const EXTRA_CATCH_DECLARATION = const MessageKind( + 'extra parameter in catch declaration'); + static const PARAMETER_WITH_TYPE_IN_CATCH = const MessageKind( + 'cannot use type annotations in catch'); + static const PARAMETER_WITH_MODIFIER_IN_CATCH = const MessageKind( + 'cannot use modifiers in catch'); + static const OPTIONAL_PARAMETER_IN_CATCH = const MessageKind( + 'cannot use optional parameters in catch'); + static const THROW_WITHOUT_EXPRESSION = const MessageKind( + 'cannot use re-throw outside of catch block (expression expected after ' + '"throw")'); + static const UNBOUND_LABEL = const MessageKind( + 'cannot resolve label #{labelName}'); + static const NO_BREAK_TARGET = const MessageKind( + 'break statement not inside switch or loop'); + static const NO_CONTINUE_TARGET = const MessageKind( + 'continue statement not inside loop'); + static const EXISTING_LABEL = const MessageKind( + 'original declaration of duplicate label #{labelName}'); + static const DUPLICATE_LABEL = const MessageKind( + 'duplicate declaration of label #{labelName}'); + static const UNUSED_LABEL = const MessageKind( + 'unused label #{labelName}'); + static const INVALID_CONTINUE = const MessageKind( + 'target of continue is not a loop or switch case'); + static const INVALID_BREAK = const MessageKind( + 'target of break is not a statement'); + + static const TYPE_VARIABLE_AS_CONSTRUCTOR = const MessageKind( + 'cannot use type variable as constructor'); + static const DUPLICATE_TYPE_VARIABLE_NAME = const MessageKind( + 'type variable #{typeVariableName} already declared'); + static const TYPE_VARIABLE_WITHIN_STATIC_MEMBER = const MessageKind( + 'cannot refer to type variable #{typeVariableName} ' + 'within a static member'); + + static const INVALID_USE_OF_SUPER = const MessageKind( + 'super not allowed here'); + static const INVALID_CASE_DEFAULT = const MessageKind( + 'default only allowed on last case of a switch'); + + static const SWITCH_CASE_TYPES_NOT_EQUAL = const MessageKind( + "case expressions don't all have the same type."); + static const SWITCH_CASE_VALUE_OVERRIDES_EQUALS = const MessageKind( + "case expression value overrides 'operator=='."); + static const SWITCH_INVALID = const MessageKind( + "switch cases contain invalid expressions."); + + static const INVALID_ARGUMENT_AFTER_NAMED = const MessageKind( + 'non-named argument after named argument'); + + static const NOT_A_COMPILE_TIME_CONSTANT = const MessageKind( + 'not a compile-time constant'); + static const CYCLIC_COMPILE_TIME_CONSTANTS = const MessageKind( + 'cycle in the compile-time constant computation'); + static const CONSTRUCTOR_IS_NOT_CONST = const MessageKind( + 'constructor is not a const constructor'); + + static const KEY_NOT_A_STRING_LITERAL = const MessageKind( + 'map-literal key not a string literal'); + + static const NO_SUCH_LIBRARY_MEMBER = const MessageKind( + '#{libraryName} has no member named #{memberName}'); + + static const CANNOT_INSTANTIATE_INTERFACE = const MessageKind( + "cannot instantiate interface '#{interfaceName}'"); + + static const CANNOT_INSTANTIATE_TYPEDEF = const MessageKind( + "cannot instantiate typedef '#{typedefName}'"); + + static const CANNOT_INSTANTIATE_TYPE_VARIABLE = const MessageKind( + "cannot instantiate type variable '#{typeVariableName}'"); + + static const NO_DEFAULT_CLASS = const MessageKind( + "no default class on enclosing interface '#{interfaceName}'"); + + static const CYCLIC_TYPE_VARIABLE = const MessageKind( + "cyclic reference to type variable #{typeVariableName}"); + + static const CLASS_NAME_EXPECTED = const MessageKind( + "class name expected"); + + static const INTERFACE_TYPE_EXPECTED = const MessageKind( + "interface type expected"); + + static const CANNOT_EXTEND = const MessageKind( + "#{type} cannot be extended"); + + static const CANNOT_IMPLEMENT = const MessageKind( + "#{type} cannot be implemented"); + + static const DUPLICATE_EXTENDS_IMPLEMENTS = const MessageKind( + "Error: #{type} can not be both extended and implemented."); + + static const DUPLICATE_IMPLEMENTS = const MessageKind( + "Error: #{type} must not occur more than once " + "in the implements clause."); + + static const ILLEGAL_SUPER_SEND = const MessageKind( + "#{name} cannot be called on super"); + + static const ADDITIONAL_TYPE_ARGUMENT = const MessageKind( + "additional type argument"); + + static const MISSING_TYPE_ARGUMENT = const MessageKind( + "missing type argument"); + + // TODO(johnniwinther): Use ADDITIONAL_TYPE_ARGUMENT or MISSING_TYPE_ARGUMENT + // instead. + static const TYPE_ARGUMENT_COUNT_MISMATCH = const MessageKind( + "incorrect number of type arguments on #{type}"); + + static const MISSING_ARGUMENTS_TO_ASSERT = const MessageKind( + "missing arguments to assert"); + + static const GETTER_MISMATCH = const MessageKind( + "Error: setter disagrees on: #{modifiers}."); + + static const SETTER_MISMATCH = const MessageKind( + "Error: getter disagrees on: #{modifiers}."); + + static const ILLEGAL_SETTER_FORMALS = const MessageKind( + "Error: a setter must have exactly one argument."); + + static const NO_STATIC_OVERRIDE = const MessageKind( + "Error: static member cannot override instance member '#{memberName}' of " + "'#{className}'."); + + static const NO_STATIC_OVERRIDE_CONT = const MessageKind( + "Info: this is the instance member that cannot be overridden " + "by a static member."); + + static const CANNOT_OVERRIDE_FIELD_WITH_METHOD = const MessageKind( + "Error: method cannot override field '#{memberName}' of '#{className}'."); + + static const CANNOT_OVERRIDE_FIELD_WITH_METHOD_CONT = const MessageKind( + "Info: this is the field that cannot be overridden by a method."); + + static const CANNOT_OVERRIDE_METHOD_WITH_FIELD = const MessageKind( + "Error: field cannot override method '#{memberName}' of '#{className}'."); + + static const CANNOT_OVERRIDE_METHOD_WITH_FIELD_CONT = const MessageKind( + "Info: this is the method that cannot be overridden by a field."); + + static const BAD_ARITY_OVERRIDE = const MessageKind( + "Error: cannot override method '#{memberName}' in '#{className}'; " + "the parameters do not match."); + + static const BAD_ARITY_OVERRIDE_CONT = const MessageKind( + "Info: this is the method whose parameters do not match."); + + static const MISSING_FORMALS = const MessageKind( + "Error: Formal parameters are missing."); + + static const EXTRA_FORMALS = const MessageKind( + "Error: Formal parameters are not allowed here."); + + static const UNARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have no parameters."); + + static const MINUS_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator - must have 0 or 1 parameters."); + + static const BINARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have exactly 1 parameter."); + + static const TERNARY_OPERATOR_BAD_ARITY = const MessageKind( + "Error: Operator #{operatorName} must have exactly 2 parameters."); + + static const OPERATOR_OPTIONAL_PARAMETERS = const MessageKind( + "Error: Operator #{operatorName} cannot have optional parameters."); + + static const OPERATOR_NAMED_PARAMETERS = const MessageKind( + "Error: Operator #{operatorName} cannot have named parameters."); + + // TODO(ahe): This message is hard to localize. This is acceptable, + // as it will be removed when we ship Dart version 1.0. + static const DEPRECATED_FEATURE_WARNING = const MessageKind( + "Warning: deprecated language feature, #{featureName}, " + "will be removed in a future Dart milestone."); + + // TODO(ahe): This message is hard to localize. This is acceptable, + // as it will be removed when we ship Dart version 1.0. + static const DEPRECATED_FEATURE_ERROR = const MessageKind( + "Error: #{featureName} are not legal " + "due to option --reject-deprecated-language-features."); + + static const CONSTRUCTOR_WITH_RETURN_TYPE = const MessageKind( + "Error: cannot have return type for constructor."); + + static const ILLEGAL_FINAL_METHOD_MODIFIER = const MessageKind( + "Error: cannot have final modifier on method."); + + static const ILLEGAL_CONSTRUCTOR_MODIFIERS = const MessageKind( + "Error: illegal constructor modifiers: #{modifiers}."); + + static const ILLEGAL_MIXIN_APPLICATION_MODIFIERS = const MessageKind( + "Error: illegal mixin application modifiers: #{modifiers}."); + + static const ILLEGAL_MIXIN_SUPERCLASS = const MessageKind( + "Error: class used as mixin must have Object as superclass."); + + static const ILLEGAL_MIXIN_CONSTRUCTOR = const MessageKind( + "Error: class used as mixin cannot have non-factory constructor."); + + static const ILLEGAL_MIXIN_CYCLE = const MessageKind( + "Error: class used as mixin introduces mixin cycle: " + "#{mixinName1} <-> #{mixinName2}."); + + static const ILLEGAL_MIXIN_WITH_SUPER = const MessageKind( + "Error: cannot use class #{className} as a mixin because it uses super."); + + static const ILLEGAL_MIXIN_SUPER_USE = const MessageKind( + "Use of super in class used as mixin."); + + static const PARAMETER_NAME_EXPECTED = const MessageKind( + "Error: parameter name expected."); + + static const CANNOT_RESOLVE_GETTER = const MessageKind( + 'cannot resolve getter.'); + + static const CANNOT_RESOLVE_SETTER = const MessageKind( + 'cannot resolve setter.'); + + static const VOID_NOT_ALLOWED = const MessageKind( + 'type void is only allowed in a return type.'); + + static const BEFORE_TOP_LEVEL = const MessageKind( + 'Error: part header must come before top-level definitions.'); + + static const LIBRARY_NAME_MISMATCH = const MessageKind( + 'Warning: expected part of library name "#{libraryName}".'); + + static const MISSING_PART_OF_TAG = const MessageKind( + 'Note: This file has no part-of tag, but it is being used as a part.'); + + static const DUPLICATED_PART_OF = const MessageKind( + 'Error: duplicated part-of directive.'); + + static const ILLEGAL_DIRECTIVE = const MessageKind( + 'Error: directive not allowed here.'); + + static const DUPLICATED_LIBRARY_NAME = const MessageKind( + 'Warning: duplicated library name "#{libraryName}".'); + + static const INVALID_SOURCE_FILE_LOCATION = const MessageKind(''' +Invalid offset (#{offset}) in source map. +File: #{fileName} +Length: #{length}'''); + + static const TOP_LEVEL_VARIABLE_DECLARED_STATIC = const MessageKind( + "Top-level variable cannot be declared static."); + + static const WRONG_NUMBER_OF_ARGUMENTS_FOR_ASSERT = const MessageKind( + "Wrong number of arguments to assert. Should be 1, but given " + "#{argumentCount}."); + + static const ASSERT_IS_GIVEN_NAMED_ARGUMENTS = const MessageKind( + "assert takes no named arguments, but given #{argumentCount}."); + + static const FACTORY_REDIRECTION_IN_NON_FACTORY = const MessageKind( + "Error: Factory redirection only allowed in factories."); + + static const MISSING_FACTORY_KEYWORD = const MessageKind( + "Did you forget a factory keyword here?"); + + static const COMPILER_CRASHED = const MessageKind( + "Error: The compiler crashed when compiling this element."); + + static const PLEASE_REPORT_THE_CRASH = const MessageKind(''' +The compiler is broken. + +When compiling the above element, the compiler crashed. It is not +possible to tell if this is caused by a problem in your program or +not. Regardless, the compiler should not crash. + +The Dart team would greatly appreciate if you would take a moment to +report this problem at http://dartbug.com/new. + +Please include the following information: + +* the name and version of your operating system, + +* the Dart SDK build number (#{buildId}), and + +* the entire message you see here (including the full stack trace + below as well as the source location above). +'''); + + + ////////////////////////////////////////////////////////////////////////////// + // Patch errors start. + ////////////////////////////////////////////////////////////////////////////// + + static const PATCH_RETURN_TYPE_MISMATCH = const MessageKind( + "Patch return type '#{patchReturnType}' doesn't match " + "'#{originReturnType}' on origin method '#{methodName}'."); + + static const PATCH_REQUIRED_PARAMETER_COUNT_MISMATCH = const MessageKind( + "Required parameter count of patch method (#{patchParameterCount}) " + "doesn't match parameter count on origin method '#{methodName}' " + "(#{originParameterCount})."); + + static const PATCH_OPTIONAL_PARAMETER_COUNT_MISMATCH = const MessageKind( + "Optional parameter count of patch method (#{patchParameterCount}) " + "doesn't match parameter count on origin method '#{methodName}' " + "(#{originParameterCount})."); + + static const PATCH_OPTIONAL_PARAMETER_NAMED_MISMATCH = const MessageKind( + "Optional parameters of origin and patch method '#{methodName}' must " + "both be either named or positional."); + + static const PATCH_PARAMETER_MISMATCH = const MessageKind( + "Patch method parameter '#{patchParameter}' doesn't match " + "'#{originParameter}' on origin method #{methodName}."); + + static const PATCH_EXTERNAL_WITHOUT_IMPLEMENTATION = const MessageKind( + "External method without an implementation."); + + static const PATCH_POINT_TO_FUNCTION = const MessageKind( + "Info: This is the function patch '#{functionName}'."); + + static const PATCH_POINT_TO_CLASS = const MessageKind( + "Info: This is the class patch '#{className}'."); + + static const PATCH_POINT_TO_GETTER = const MessageKind( + "Info: This is the getter patch '#{getterName}'."); + + static const PATCH_POINT_TO_SETTER = const MessageKind( + "Info: This is the setter patch '#{setterName}'."); + + static const PATCH_POINT_TO_CONSTRUCTOR = const MessageKind( + "Info: This is the constructor patch '#{constructorName}'."); + + static const PATCH_NON_EXISTING = const MessageKind( + "Error: Origin does not exist for patch '#{name}'."); + + static const PATCH_NONPATCHABLE = const MessageKind( + "Error: Only classes and functions can be patched."); + + static const PATCH_NON_EXTERNAL = const MessageKind( + "Error: Only external functions can be patched."); + + static const PATCH_NON_CLASS = const MessageKind( + "Error: Patching non-class with class patch '#{className}'."); + + static const PATCH_NON_GETTER = const MessageKind( + "Error: Cannot patch non-getter '#{name}' with getter patch."); + + static const PATCH_NO_GETTER = const MessageKind( + "Error: No getter found for getter patch '#{getterName}'."); + + static const PATCH_NON_SETTER = const MessageKind( + "Error: Cannot patch non-setter '#{name}' with setter patch."); + + static const PATCH_NO_SETTER = const MessageKind( + "Error: No setter found for setter patch '#{setterName}'."); + + static const PATCH_NON_CONSTRUCTOR = const MessageKind( + "Error: Cannot patch non-constructor with constructor patch " + "'#{constructorName}'."); + + static const PATCH_NON_FUNCTION = const MessageKind( + "Error: Cannot patch non-function with function patch " + "'#{functionName}'."); + + ////////////////////////////////////////////////////////////////////////////// + // Patch errors end. + ////////////////////////////////////////////////////////////////////////////// + + toString() => template; + + Message message([Map arguments = const {}]) { + return new Message(this, arguments); + } + + CompilationError error([Map arguments = const {}]) { + return new CompilationError(this, arguments); + } +} + +class Message { + final kind; + final Map arguments; + String message; + + Message(this.kind, this.arguments) { + assert(() { computeMessage(); return true; }); + } + + String computeMessage() { + if (message == null) { + message = kind.template; + arguments.forEach((key, value) { + String string = slowToString(value); + message = message.replaceAll('#{${key}}', string); + }); + assert(invariant( + CURRENT_ELEMENT_SPANNABLE, + !message.contains(new RegExp(r"#\{.+\}")), + message: 'Missing arguments in error message: "$message"')); + } + return message; + } + + String toString() { + return computeMessage(); + } + + bool operator==(other) { + if (other is !Message) return false; + return (kind == other.kind) && (toString() == other.toString()); + } + + String slowToString(object) { + if (object is SourceString) { + return object.slowToString(); + } else { + return object.toString(); + } + } +} + +class Diagnostic { + final Message message; + Diagnostic(MessageKind kind, [Map arguments = const {}]) + : message = new Message(kind, arguments); + String toString() => message.toString(); +} + +class TypeWarning extends Diagnostic { + TypeWarning(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class ResolutionError extends Diagnostic { + ResolutionError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class ResolutionWarning extends Diagnostic { + ResolutionWarning(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class CompileTimeConstantError extends Diagnostic { + CompileTimeConstantError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +} + +class CompilationError extends Diagnostic { + CompilationError(MessageKind kind, [Map arguments = const {}]) + : super(kind, arguments); +}
diff --git a/pkgs/markdown/test/lib/src/compiler/implementation/world.dart b/pkgs/markdown/test/lib/src/compiler/implementation/world.dart new file mode 100644 index 0000000..a40050e --- /dev/null +++ b/pkgs/markdown/test/lib/src/compiler/implementation/world.dart
@@ -0,0 +1,232 @@ +// 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. + +part of dart2js; + +class World { + final Compiler compiler; + final Map<ClassElement, Set<ClassElement>> subtypes; + final Map<ClassElement, Set<MixinApplicationElement>> mixinUses; + final Map<ClassElement, Set<ClassElement>> typesImplementedBySubclasses; + final Set<ClassElement> classesNeedingRti; + final Map<ClassElement, Set<ClassElement>> rtiDependencies; + final FunctionSet userDefinedGetters; + final FunctionSet userDefinedSetters; + + World(Compiler compiler) + : subtypes = new Map<ClassElement, Set<ClassElement>>(), + mixinUses = new Map<ClassElement, Set<MixinApplicationElement>>(), + typesImplementedBySubclasses = + new Map<ClassElement, Set<ClassElement>>(), + userDefinedGetters = new FunctionSet(compiler), + userDefinedSetters = new FunctionSet(compiler), + classesNeedingRti = new Set<ClassElement>(), + rtiDependencies = new Map<ClassElement, Set<ClassElement>>(), + this.compiler = compiler; + + void populate() { + void addSubtypes(ClassElement cls) { + if (cls.resolutionState != STATE_DONE) { + compiler.internalErrorOnElement( + cls, 'Class "${cls.name.slowToString()}" is not resolved.'); + } + + for (DartType type in cls.allSupertypes) { + Set<Element> subtypesOfCls = + subtypes.putIfAbsent(type.element, () => new Set<ClassElement>()); + subtypesOfCls.add(cls); + } + + // Walk through the superclasses, and record the types + // implemented by that type on the superclasses. + DartType type = cls.supertype; + while (type != null) { + Set<Element> typesImplementedBySubclassesOfCls = + typesImplementedBySubclasses.putIfAbsent( + type.element, () => new Set<ClassElement>()); + for (DartType current in cls.allSupertypes) { + typesImplementedBySubclassesOfCls.add(current.element); + } + ClassElement classElement = type.element; + type = classElement.supertype; + } + } + + compiler.resolverWorld.instantiatedClasses.forEach(addSubtypes); + + // Find the classes that need runtime type information. Such + // classes are: + // (1) used in a is check with type variables, + // (2) dependencies of classes in (1), + // (3) subclasses of (2) and (3). + + void potentiallyAddForRti(ClassElement cls) { + if (cls.typeVariables.isEmpty) return; + if (classesNeedingRti.contains(cls)) return; + classesNeedingRti.add(cls); + + Set<ClassElement> classes = subtypes[cls]; + if (classes != null) { + classes.forEach((ClassElement sub) { + potentiallyAddForRti(sub); + }); + } + + Set<ClassElement> dependencies = rtiDependencies[cls]; + if (dependencies != null) { + dependencies.forEach((ClassElement other) { + potentiallyAddForRti(other); + }); + } + } + + compiler.resolverWorld.isChecks.forEach((DartType type) { + if (type is InterfaceType) { + InterfaceType itf = type; + if (!itf.isRaw) { + potentiallyAddForRti(itf.element); + } + } + }); + } + + bool needsRti(ClassElement cls) { + return classesNeedingRti.contains(cls) || compiler.enabledRuntimeType; + } + + void registerMixinUse(MixinApplicationElement mixinApplication, + ClassElement mixin) { + Set<MixinApplicationElement> users = + mixinUses.putIfAbsent(mixin, () => + new Set<MixinApplicationElement>()); + users.add(mixinApplication); + } + + void registerRtiDependency(Element element, Element dependency) { + // We're not dealing with typedef for now. + if (!element.isClass() || !dependency.isClass()) return; + Set<ClassElement> classes = + rtiDependencies.putIfAbsent(element, () => new Set<ClassElement>()); + classes.add(dependency); + } + + void recordUserDefinedGetter(Element element) { + assert(element.isGetter()); + userDefinedGetters.add(element); + } + + void recordUserDefinedSetter(Element element) { + assert(element.isSetter()); + userDefinedSetters.add(element); + } + + bool hasAnyUserDefinedGetter(Selector selector) { + return userDefinedGetters.hasAnyElementMatchingSelector(selector); + } + + bool hasAnyUserDefinedSetter(Selector selector) { + return userDefinedSetters.hasAnyElementMatchingSelector(selector); + } + + // Returns whether a subclass of [superclass] implements [type]. + bool hasAnySubclassThatImplements(ClassElement superclass, DartType type) { + Set<ClassElement> subclasses= typesImplementedBySubclasses[superclass]; + if (subclasses == null) return false; + return subclasses.contains(type.element); + } + + bool hasNoOverridingMember(Element element) { + ClassElement cls = element.getEnclosingClass(); + Set<ClassElement> subclasses = compiler.world.subtypes[cls]; + // TODO(ngeoffray): Implement the full thing. + return subclasses == null || subclasses.isEmpty; + } + + void registerUsedElement(Element element) { + if (element.isMember()) { + if (element.isGetter()) { + // We're collecting user-defined getters to let the codegen know which + // field accesses might have side effects. + recordUserDefinedGetter(element); + } else if (element.isSetter()) { + recordUserDefinedSetter(element); + } + } + } + + /** + * Returns a [MemberSet] that contains the possible targets of the given + * [selector] on a receiver with the given [type]. This includes all sub + * types. + */ + MemberSet _memberSetFor(DartType type, Selector selector) { + assert(compiler != null); + ClassElement cls = type.element; + SourceString name = selector.name; + LibraryElement library = selector.library; + MemberSet result = new MemberSet(name); + Element element = cls.implementation.lookupSelector(selector); + if (element != null) result.add(element); + + bool isPrivate = name.isPrivate(); + Set<ClassElement> subtypesOfCls = subtypes[cls]; + if (subtypesOfCls != null) { + for (ClassElement sub in subtypesOfCls) { + // Private members from a different library are not visible. + if (isPrivate && sub.getLibrary() != library) continue; + element = sub.implementation.lookupLocalMember(name); + if (element != null) result.add(element); + } + } + return result; + } + + /** + * Returns the field in [type] described by the given [selector]. + * If no such field exists, or a subclass overrides the field + * returns [:null:]. + */ + VariableElement locateSingleField(DartType type, Selector selector) { + MemberSet memberSet = _memberSetFor(type, selector); + ClassElement cls = type.element; + Element result = cls.implementation.lookupSelector(selector); + if (result == null) return null; + if (!result.isField()) return null; + + // Verify that no subclass overrides the field. + if (memberSet.elements.length != 1) return null; + assert(memberSet.elements.contains(result)); + return result; + } + + Set<ClassElement> findNoSuchMethodHolders(DartType type) { + Set<ClassElement> result = new Set<ClassElement>(); + Selector noSuchMethodSelector = new Selector.noSuchMethod(); + MemberSet memberSet = _memberSetFor(type, noSuchMethodSelector); + for (Element element in memberSet.elements) { + ClassElement holder = element.getEnclosingClass(); + if (!identical(holder, compiler.objectClass) && + noSuchMethodSelector.applies(element, compiler)) { + result.add(holder); + } + } + return result; + } +} + +/** + * A [MemberSet] contains all the possible targets for a selector. + */ +class MemberSet { + final Set<Element> elements; + final SourceString name; + + MemberSet(SourceString this.name) : elements = new Set<Element>(); + + void add(Element element) { + elements.add(element); + } + + bool get isEmpty => elements.isEmpty; +}
diff --git a/pkgs/markdown/test/lib/src/libraries.dart b/pkgs/markdown/test/lib/src/libraries.dart new file mode 100644 index 0000000..5c35483 --- /dev/null +++ b/pkgs/markdown/test/lib/src/libraries.dart
@@ -0,0 +1,200 @@ +// 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. + +library libraries; + +/** + * A bit flag used by [LibraryInfo] indicating that a library is used by dart2js + */ +const int DART2JS_PLATFORM = 1; + +/** + * A bit flag used by [LibraryInfo] indicating that a library is used by the VM + */ +const int VM_PLATFORM = 2; + +/** + * Mapping of "dart:" library name (e.g. "core") to information about that library. + * This information is structured such that Dart Editor can parse this file + * and extract the necessary information without executing it + * while other tools can access via execution. + */ +const Map<String, LibraryInfo> LIBRARIES = const { + + "async": const LibraryInfo( + "async/async.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/async_patch.dart"), + + "chrome": const LibraryInfo( + "chrome/dartium/chrome_dartium.dart", + category: "Client", + dart2jsPath: "chrome/dart2js/chrome_dart2js.dart", + documented: false, + implementation: true), // Not really, just hiding it for now. + + "collection": const LibraryInfo("collection/collection.dart"), + + "core": const LibraryInfo( + "core/core.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/core_patch.dart"), + + "crypto": const LibraryInfo( + "crypto/crypto.dart"), + + "html": const LibraryInfo( + "html/dartium/html_dartium.dart", + category: "Client", + dart2jsPath: "html/dart2js/html_dart2js.dart"), + + "html_common": const LibraryInfo( + "html/html_common/html_common.dart", + category: "Client", + dart2jsPath: "html/html_common/html_common_dart2js.dart", + documented: false, + implementation: true), + + "indexed_db": const LibraryInfo( + "indexed_db/dartium/indexed_db_dartium.dart", + category: "Client", + dart2jsPath: "indexed_db/dart2js/indexed_db_dart2js.dart"), + + "io": const LibraryInfo( + "io/io.dart", + category: "Server", + dart2jsPatchPath: "_internal/compiler/implementation/lib/io_patch.dart"), + + "isolate": const LibraryInfo( + "isolate/isolate.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/isolate_patch.dart"), + + "json": const LibraryInfo( + "json/json.dart"), + + "math": const LibraryInfo( + "math/math.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/math_patch.dart"), + + "mirrors": const LibraryInfo( + "mirrors/mirrors.dart", + dart2jsPatchPath: "_internal/compiler/implementation/lib/mirrors_patch.dart"), + + "nativewrappers": const LibraryInfo( + "html/dartium/nativewrappers.dart", + category: "Client", + implementation: true, + documented: false, + platforms: VM_PLATFORM), + + "scalarlist": const LibraryInfo( + "scalarlist/scalarlist.dart", + category: "Server", + dart2jsPatchPath: "_internal/compiler/implementation/lib/scalarlist_patch.dart"), + + "svg": const LibraryInfo( + "svg/dartium/svg_dartium.dart", + category: "Client", + dart2jsPath: "svg/dart2js/svg_dart2js.dart"), + + "uri": const LibraryInfo( + "uri/uri.dart"), + + "utf": const LibraryInfo( + "utf/utf.dart"), + + "web_audio": const LibraryInfo( + "web_audio/dartium/web_audio_dartium.dart", + category: "Client", + dart2jsPath: "web_audio/dart2js/web_audio_dart2js.dart"), + + "_collection-dev": const LibraryInfo( + "_collection_dev/collection_dev.dart", + category: "Internal", + documented: false), + + "_js_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/js_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_interceptors": const LibraryInfo( + "_internal/compiler/implementation/lib/interceptors.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_foreign_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/foreign_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), + + "_isolate_helper": const LibraryInfo( + "_internal/compiler/implementation/lib/isolate_helper.dart", + category: "Internal", + documented: false, + platforms: DART2JS_PLATFORM), +}; + +/** + * Information about a "dart:" library. + */ +class LibraryInfo { + + /** + * Path to the library's *.dart file relative to this file. + */ + final String path; + + /** + * The category in which the library should appear in the editor + * (e.g. "Common", "Client", "Server", ...). + */ + final String category; + + /** + * Path to the dart2js library's *.dart file relative to this file + * or null if dart2js uses the common library path defined above. + * Access using the [#getDart2JsPath()] method. + */ + final String dart2jsPath; + + /** + * Path to the dart2js library's patch file relative to this file + * or null if no dart2js patch file associated with this library. + * Access using the [#getDart2JsPatchPath()] method. + */ + final String dart2jsPatchPath; + + /** + * True if this library is documented and should be shown to the user. + */ + final bool documented; + + /** + * Bit flags indicating which platforms consume this library. + * See [DART2JS_LIBRARY] and [VM_LIBRARY]. + */ + final int platforms; + + /** + * True if the library contains implementation details for another library. + * The implication is that these libraries are less commonly used + * and that tools like Dart Editor should not show these libraries + * in a list of all libraries unless the user specifically asks the tool to + * do so. + */ + final bool implementation; + + const LibraryInfo(this.path, { + this.category: "Shared", + this.dart2jsPath, + this.dart2jsPatchPath, + this.implementation: false, + this.documented: true, + this.platforms: DART2JS_PLATFORM | VM_PLATFORM}); + + bool get isDart2jsLibrary => (platforms & DART2JS_PLATFORM) != 0; + bool get isVmLibrary => (platforms & VM_PLATFORM) != 0; +}
diff --git a/pkgs/markdown/test/lib/src/markdown/ast.dart b/pkgs/markdown/test/lib/src/markdown/ast.dart new file mode 100644 index 0000000..c966cea --- /dev/null +++ b/pkgs/markdown/test/lib/src/markdown/ast.dart
@@ -0,0 +1,65 @@ +// 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. + +part of markdown; + +/// Base class for any AST item. Roughly corresponds to Node in the DOM. Will +/// be either an Element or Text. +abstract class Node { + void accept(NodeVisitor visitor); +} + +/// A named tag that can contain other nodes. +class Element implements Node { + final String tag; + final List<Node> children; + final Map<String, String> attributes; + + Element(this.tag, this.children) + : attributes = <String, String>{}; + + Element.empty(this.tag) + : children = null, + attributes = <String, String>{}; + + Element.withTag(this.tag) + : children = [], + attributes = <String, String>{}; + + Element.text(this.tag, String text) + : children = [new Text(text)], + attributes = <String, String>{}; + + bool get isEmpty => children == null; + + void accept(NodeVisitor visitor) { + if (visitor.visitElementBefore(this)) { + for (final child in children) child.accept(visitor); + visitor.visitElementAfter(this); + } + } +} + +/// A plain text element. +class Text implements Node { + final String text; + Text(this.text); + + void accept(NodeVisitor visitor) => visitor.visitText(this); +} + +/// Visitor pattern for the AST. Renderers or other AST transformers should +/// implement this. +abstract class NodeVisitor { + /// Called when a Text node has been reached. + void visitText(Text text); + + /// Called when an Element has been reached, before its children have been + /// visited. Return `false` to skip its children. + bool visitElementBefore(Element element); + + /// Called when an Element has been reached, after its children have been + /// visited. Will not be called if [visitElementBefore] returns `false`. + void visitElementAfter(Element element); +}
diff --git a/pkgs/markdown/test/lib/src/markdown/block_parser.dart b/pkgs/markdown/test/lib/src/markdown/block_parser.dart new file mode 100644 index 0000000..67109a4 --- /dev/null +++ b/pkgs/markdown/test/lib/src/markdown/block_parser.dart
@@ -0,0 +1,464 @@ +// 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. + +part of markdown; + +/// The line contains only whitespace or is empty. +final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); + +/// A series of `=` or `-` (on the next line) define setext-style headers. +final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); + +/// Leading (and trailing) `#` define atx-style headers. +final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); + +/// The line starts with `>` with one optional space after. +final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); + +/// A line indented four spaces. Used for code blocks and lists. +final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); + +/// Three or more hyphens, asterisks or underscores by themselves. Note that +/// a line like `----` is valid as both HR and SETEXT. In case of a tie, +/// SETEXT should win. +final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' + r'(_+[ ]{0,2}){3,}|' + r'(\*+[ ]{0,2}){3,})$'); + +/// Really hacky way to detect block-level embedded HTML. Just looks for +/// "<somename". +final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]'); + +/// A line starting with one of these markers: `-`, `*`, `+`. May have up to +/// three leading spaces before the marker and any number of spaces or tabs +/// after. +final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); + +/// A line starting with a number like `123.`. May have up to three leading +/// spaces before the marker and any number of spaces or tabs after. +final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); + +/// Maintains the internal state needed to parse a series of lines into blocks +/// of markdown suitable for further inline parsing. +class BlockParser { + final List<String> lines; + + /// The markdown document this parser is parsing. + final Document document; + + /// Index of the current line. + int pos; + + BlockParser(this.lines, this.document) + : pos = 0; + + /// Gets the current line. + String get current => lines[pos]; + + /// Gets the line after the current one or `null` if there is none. + String get next { + // Don't read past the end. + if (pos >= lines.length - 1) return null; + return lines[pos + 1]; + } + + void advance() { + pos++; + } + + bool get isDone => pos >= lines.length; + + /// Gets whether or not the current line matches the given pattern. + bool matches(RegExp regex) { + if (isDone) return false; + return regex.firstMatch(current) != null; + } + + /// Gets whether or not the current line matches the given pattern. + bool matchesNext(RegExp regex) { + if (next == null) return false; + return regex.firstMatch(next) != null; + } +} + +abstract class BlockSyntax { + /// Gets the collection of built-in block parsers. To turn a series of lines + /// into blocks, each of these will be tried in turn. Order matters here. + static List<BlockSyntax> get syntaxes { + // Lazy initialize. + if (_syntaxes == null) { + _syntaxes = [ + new EmptyBlockSyntax(), + new BlockHtmlSyntax(), + new SetextHeaderSyntax(), + new HeaderSyntax(), + new CodeBlockSyntax(), + new BlockquoteSyntax(), + new HorizontalRuleSyntax(), + new UnorderedListSyntax(), + new OrderedListSyntax(), + new ParagraphSyntax() + ]; + } + + return _syntaxes; + } + + static List<BlockSyntax> _syntaxes; + + /// Gets the regex used to identify the beginning of this block, if any. + RegExp get pattern => null; + + bool get canEndBlock => true; + + bool canParse(BlockParser parser) { + return pattern.firstMatch(parser.current) != null; + } + + Node parse(BlockParser parser); + + List<String> parseChildLines(BlockParser parser) { + // Grab all of the lines that form the blockquote, stripping off the ">". + final childLines = <String>[]; + + while (!parser.isDone) { + final match = pattern.firstMatch(parser.current); + if (match == null) break; + childLines.add(match[1]); + parser.advance(); + } + + return childLines; + } + + /// Gets whether or not [parser]'s current line should end the previous block. + static bool isAtBlockEnd(BlockParser parser) { + if (parser.isDone) return true; + return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); + } +} + +class EmptyBlockSyntax extends BlockSyntax { + RegExp get pattern => _RE_EMPTY; + + Node parse(BlockParser parser) { + parser.advance(); + + // Don't actually emit anything. + return null; + } +} + +/// Parses setext-style headers. +class SetextHeaderSyntax extends BlockSyntax { + bool canParse(BlockParser parser) { + // Note: matches *next* line, not the current one. We're looking for the + // underlining after this line. + return parser.matchesNext(_RE_SETEXT); + } + + Node parse(BlockParser parser) { + final match = _RE_SETEXT.firstMatch(parser.next); + + final tag = (match[1][0] == '=') ? 'h1' : 'h2'; + final contents = parser.document.parseInline(parser.current); + parser.advance(); + parser.advance(); + + return new Element(tag, contents); + } +} + +/// Parses atx-style headers: `## Header ##`. +class HeaderSyntax extends BlockSyntax { + RegExp get pattern => _RE_HEADER; + + Node parse(BlockParser parser) { + final match = pattern.firstMatch(parser.current); + parser.advance(); + final level = match[1].length; + final contents = parser.document.parseInline(match[2].trim()); + return new Element('h$level', contents); + } +} + +/// Parses email-style blockquotes: `> quote`. +class BlockquoteSyntax extends BlockSyntax { + RegExp get pattern => _RE_BLOCKQUOTE; + + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser); + + // Recursively parse the contents of the blockquote. + final children = parser.document.parseLines(childLines); + + return new Element('blockquote', children); + } +} + +/// Parses preformatted code blocks that are indented four spaces. +class CodeBlockSyntax extends BlockSyntax { + RegExp get pattern => _RE_INDENT; + + List<String> parseChildLines(BlockParser parser) { + final childLines = <String>[]; + + while (!parser.isDone) { + var match = pattern.firstMatch(parser.current); + if (match != null) { + childLines.add(match[1]); + parser.advance(); + } else { + // If there's a codeblock, then a newline, then a codeblock, keep the + // code blocks together. + var nextMatch = parser.next != null ? + pattern.firstMatch(parser.next) : null; + if (parser.current.trim() == '' && nextMatch != null) { + childLines.add(''); + childLines.add(nextMatch[1]); + parser.advance(); + parser.advance(); + } else { + break; + } + } + } + return childLines; + } + + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser); + + // The Markdown tests expect a trailing newline. + childLines.add(''); + + // Escape the code. + final escaped = classifySource(Strings.join(childLines, '\n')); + + return new Element.text('pre', escaped); + } +} + +/// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. +class HorizontalRuleSyntax extends BlockSyntax { + RegExp get pattern => _RE_HR; + + Node parse(BlockParser parser) { + final match = pattern.firstMatch(parser.current); + parser.advance(); + return new Element.empty('hr'); + } +} + +/// Parses inline HTML at the block level. This differs from other markdown +/// implementations in several ways: +/// +/// 1. This one is way way WAY simpler. +/// 2. All HTML tags at the block level will be treated as blocks. If you +/// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. +/// As soon as it sees something like HTML, it stops mucking with it until +/// it hits the next block. +/// 3. Absolutely no HTML parsing or validation is done. We're a markdown +/// parser not an HTML parser! +class BlockHtmlSyntax extends BlockSyntax { + RegExp get pattern => _RE_HTML; + + bool get canEndBlock => false; + + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit a blank line. + while (!parser.isDone && !parser.matches(_RE_EMPTY)) { + childLines.add(parser.current); + parser.advance(); + } + + return new Text(Strings.join(childLines, '\n')); + } +} + +class ListItem { + bool forceBlock = false; + final List<String> lines; + + ListItem(this.lines); +} + +/// Base class for both ordered and unordered lists. +abstract class ListSyntax extends BlockSyntax { + bool get canEndBlock => false; + + String get listTag; + + Node parse(BlockParser parser) { + final items = <ListItem>[]; + var childLines = <String>[]; + + endItem() { + if (childLines.length > 0) { + items.add(new ListItem(childLines)); + childLines = <String>[]; + } + } + + var match; + tryMatch(RegExp pattern) { + match = pattern.firstMatch(parser.current); + return match != null; + } + + bool afterEmpty = false; + while (!parser.isDone) { + if (tryMatch(_RE_EMPTY)) { + // Add a blank line to the current list item. + childLines.add(''); + } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) { + // End the current list item and start a new one. + endItem(); + childLines.add(match[1]); + } else if (tryMatch(_RE_INDENT)) { + // Strip off indent and add to current item. + childLines.add(match[1]); + } else if (BlockSyntax.isAtBlockEnd(parser)) { + // Done with the list. + break; + } else { + // Anything else is paragraph text or other stuff that can be in a list + // item. However, if the previous item is a blank line, this means we're + // done with the list and are starting a new top-level paragraph. + if ((childLines.length > 0) && (childLines.last == '')) break; + childLines.add(parser.current); + } + parser.advance(); + } + + endItem(); + + // Markdown, because it hates us, specifies two kinds of list items. If you + // have a list like: + // + // * one + // * two + // + // Then it will insert the conents of the lines directly in the <li>, like: + // <ul> + // <li>one</li> + // <li>two</li> + // <ul> + // + // If, however, there are blank lines between the items, each is wrapped in + // paragraphs: + // + // * one + // + // * two + // + // <ul> + // <li><p>one</p></li> + // <li><p>two</p></li> + // <ul> + // + // In other words, sometimes we parse the contents of a list item like a + // block, and sometimes line an inline. The rules our parser implements are: + // + // - If it has more than one line, it's a block. + // - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT, + // UL, OL) it's a block. (This is for cases like "* > quote".) + // - If there was a blank line between this item and the previous one, it's + // a block. + // - If there was a blank line between this item and the next one, it's a + // block. + // - Otherwise, parse it as an inline. + + // Remove any trailing empty lines and note which items are separated by + // empty lines. Do this before seeing which items are single-line so that + // trailing empty lines on the last item don't force it into being a block. + for (int i = 0; i < items.length; i++) { + for (int j = items[i].lines.length - 1; j > 0; j--) { + if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) { + // Found an empty line. Item and one after it are blocks. + if (i < items.length - 1) { + items[i].forceBlock = true; + items[i + 1].forceBlock = true; + } + items[i].lines.removeLast(); + } else { + break; + } + } + } + + // Convert the list items to Nodes. + final itemNodes = <Node>[]; + for (final item in items) { + bool blockItem = item.forceBlock || (item.lines.length > 1); + + // See if it matches some block parser. + final blocksInList = [ + _RE_BLOCKQUOTE, + _RE_HEADER, + _RE_HR, + _RE_INDENT, + _RE_UL, + _RE_OL + ]; + + if (!blockItem) { + for (final pattern in blocksInList) { + if (pattern.firstMatch(item.lines[0]) != null) { + blockItem = true; + break; + } + } + } + + // Parse the item as a block or inline. + if (blockItem) { + // Block list item. + final children = parser.document.parseLines(item.lines); + itemNodes.add(new Element('li', children)); + } else { + // Raw list item. + final contents = parser.document.parseInline(item.lines[0]); + itemNodes.add(new Element('li', contents)); + } + } + + return new Element(listTag, itemNodes); + } +} + +/// Parses unordered lists. +class UnorderedListSyntax extends ListSyntax { + RegExp get pattern => _RE_UL; + String get listTag => 'ul'; +} + +/// Parses ordered lists. +class OrderedListSyntax extends ListSyntax { + RegExp get pattern => _RE_OL; + String get listTag => 'ol'; +} + +/// Parses paragraphs of regular text. +class ParagraphSyntax extends BlockSyntax { + bool get canEndBlock => false; + + bool canParse(BlockParser parser) => true; + + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit something that ends a paragraph. + while (!BlockSyntax.isAtBlockEnd(parser)) { + childLines.add(parser.current); + parser.advance(); + } + + final contents = parser.document.parseInline( + Strings.join(childLines, '\n')); + return new Element('p', contents); + } +}
diff --git a/pkgs/markdown/test/lib/src/markdown/html_renderer.dart b/pkgs/markdown/test/lib/src/markdown/html_renderer.dart new file mode 100644 index 0000000..1695e96 --- /dev/null +++ b/pkgs/markdown/test/lib/src/markdown/html_renderer.dart
@@ -0,0 +1,61 @@ +// 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. + +part of markdown; + +String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes); + +/// Translates a parsed AST to HTML. +class HtmlRenderer implements NodeVisitor { + static final _BLOCK_TAGS = new RegExp( + 'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre'); + + StringBuffer buffer; + + HtmlRenderer(); + + String render(List<Node> nodes) { + buffer = new StringBuffer(); + + for (final node in nodes) node.accept(this); + + return buffer.toString(); + } + + void visitText(Text text) { + buffer.add(text.text); + } + + bool visitElementBefore(Element element) { + // Hackish. Separate block-level elements with newlines. + if (!buffer.isEmpty && + _BLOCK_TAGS.firstMatch(element.tag) != null) { + buffer.add('\n'); + } + + buffer.add('<${element.tag}'); + + // Sort the keys so that we generate stable output. + // TODO(rnystrom): This assumes keys returns a fresh mutable + // collection. + final attributeNames = element.attributes.keys.toList(); + attributeNames.sort((a, b) => a.compareTo(b)); + for (final name in attributeNames) { + buffer.add(' $name="${element.attributes[name]}"'); + } + + if (element.isEmpty) { + // Empty element like <hr/>. + buffer.add(' />'); + return false; + } else { + buffer.add('>'); + return true; + } + } + + void visitElementAfter(Element element) { + buffer.add('</${element.tag}>'); + } +}
diff --git a/pkgs/markdown/test/lib/src/markdown/inline_parser.dart b/pkgs/markdown/test/lib/src/markdown/inline_parser.dart new file mode 100644 index 0000000..af42e3e --- /dev/null +++ b/pkgs/markdown/test/lib/src/markdown/inline_parser.dart
@@ -0,0 +1,410 @@ +// 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. + +part of markdown; + +/// Maintains the internal state needed to parse inline span elements in +/// markdown. +class InlineParser { + static List<InlineSyntax> get syntaxes { + // Lazy initialize. + if (_syntaxes == null) { + _syntaxes = <InlineSyntax>[ + // This first regexp matches plain text to accelerate parsing. It must + // be written so that it does not match any prefix of any following + // syntax. Most markdown is plain text, so it is faster to match one + // regexp per 'word' rather than fail to match all the following regexps + // at each non-syntax character position. It is much more important + // that the regexp is fast than complete (for example, adding grouping + // is likely to slow the regexp down enough to negate its benefit). + // Since it is purely for optimization, it can be removed for debugging. + new TextSyntax(r'\s*[A-Za-z0-9]+'), + + // The real syntaxes. + + new AutolinkSyntax(), + new LinkSyntax(), + // "*" surrounded by spaces is left alone. + new TextSyntax(r' \* '), + // "_" surrounded by spaces is left alone. + new TextSyntax(r' _ '), + // Leave already-encoded HTML entities alone. Ensures we don't turn + // "&" into "&amp;" + new TextSyntax(r'&[#a-zA-Z0-9]*;'), + // Encode "&". + new TextSyntax(r'&', sub: '&'), + // Encode "<". (Why not encode ">" too? Gruber is toying with us.) + new TextSyntax(r'<', sub: '<'), + // Parse "**strong**" tags. + new TagSyntax(r'\*\*', tag: 'strong'), + // Parse "__strong__" tags. + new TagSyntax(r'__', tag: 'strong'), + // Parse "*emphasis*" tags. + new TagSyntax(r'\*', tag: 'em'), + // Parse "_emphasis_" tags. + // TODO(rnystrom): Underscores in the middle of a word should not be + // parsed as emphasis like_in_this. + new TagSyntax(r'_', tag: 'em'), + // Parse inline code within double backticks: "``code``". + new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'), + // Parse inline code within backticks: "`code`". + new CodeSyntax(r'`([^`]*)`') + ]; + } + + return _syntaxes; + } + + static List<InlineSyntax> _syntaxes; + + /// The string of markdown being parsed. + final String source; + + /// The markdown document this parser is parsing. + final Document document; + + /// The current read position. + int pos = 0; + + /// Starting position of the last unconsumed text. + int start = 0; + + final List<TagState> _stack; + + InlineParser(this.source, this.document) + : _stack = <TagState>[]; + + List<Node> parse() { + // Make a fake top tag to hold the results. + _stack.add(new TagState(0, 0, null)); + + while (!isDone) { + bool matched = false; + + // See if any of the current tags on the stack match. We don't allow tags + // of the same kind to nest, so this takes priority over other possible // matches. + for (int i = _stack.length - 1; i > 0; i--) { + if (_stack[i].tryMatch(this)) { + matched = true; + break; + } + } + if (matched) continue; + + // See if the current text matches any defined markdown syntax. + for (final syntax in syntaxes) { + if (syntax.tryMatch(this)) { + matched = true; + break; + } + } + if (matched) continue; + + // If we got here, it's just text. + advanceBy(1); + } + + // Unwind any unmatched tags and get the results. + return _stack[0].close(this, null); + } + + writeText() { + writeTextRange(start, pos); + start = pos; + } + + writeTextRange(int start, int end) { + if (end > start) { + final text = source.substring(start, end); + final nodes = _stack.last.children; + + // If the previous node is text too, just append. + if ((nodes.length > 0) && (nodes.last is Text)) { + final newNode = new Text('${nodes.last.text}$text'); + nodes[nodes.length - 1] = newNode; + } else { + nodes.add(new Text(text)); + } + } + } + + addNode(Node node) { + _stack.last.children.add(node); + } + + // TODO(rnystrom): Only need this because RegExp doesn't let you start + // searching from a given offset. + String get currentSource => source.substring(pos, source.length); + + bool get isDone => pos == source.length; + + void advanceBy(int length) { + pos += length; + } + + void consume(int length) { + pos += length; + start = pos; + } +} + +/// Represents one kind of markdown tag that can be parsed. +abstract class InlineSyntax { + final RegExp pattern; + + InlineSyntax(String pattern) + : pattern = new RegExp(pattern, multiLine: true); + + bool tryMatch(InlineParser parser) { + final startMatch = pattern.firstMatch(parser.currentSource); + if ((startMatch != null) && (startMatch.start == 0)) { + // Write any existing plain text up to this point. + parser.writeText(); + + if (onMatch(parser, startMatch)) { + parser.consume(startMatch[0].length); + } + return true; + } + return false; + } + + bool onMatch(InlineParser parser, Match match); +} + +/// Matches stuff that should just be passed through as straight text. +class TextSyntax extends InlineSyntax { + String substitute; + TextSyntax(String pattern, {String sub}) + : super(pattern), + substitute = sub; + + bool onMatch(InlineParser parser, Match match) { + if (substitute == null) { + // Just use the original matched text. + parser.advanceBy(match[0].length); + return false; + } + + // Insert the substitution. + parser.addNode(new Text(substitute)); + return true; + } +} + +/// Matches autolinks like `<http://foo.com>`. +class AutolinkSyntax extends InlineSyntax { + AutolinkSyntax() + : super(r'<((http|https|ftp)://[^>]*)>'); + // TODO(rnystrom): Make case insensitive. + + bool onMatch(InlineParser parser, Match match) { + final url = match[1]; + + final anchor = new Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = url; + parser.addNode(anchor); + + return true; + } +} + +/// Matches syntax that has a pair of tags and becomes an element, like `*` for +/// `<em>`. Allows nested tags. +class TagSyntax extends InlineSyntax { + final RegExp endPattern; + final String tag; + + TagSyntax(String pattern, {String tag, String end}) + : super(pattern), + endPattern = new RegExp((end != null) ? end : pattern, multiLine: true), + tag = tag; + // TODO(rnystrom): Doing this.field doesn't seem to work with named args. + + bool onMatch(InlineParser parser, Match match) { + parser._stack.add(new TagState(parser.pos, + parser.pos + match[0].length, this)); + return true; + } + + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + parser.addNode(new Element(tag, state.children)); + return true; + } +} + +/// Matches inline links like `[blah] [id]` and `[blah] (url)`. +class LinkSyntax extends TagSyntax { + /// The regex for the end of a link needs to handle both reference style and + /// inline styles as well as optional titles for inline links. To make that + /// a bit more palatable, this breaks it into pieces. + static get linkPattern { + final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. + final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. + final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. + return '\](?:($refLink|$inlineLink)|)'; + + // The groups matched by this are: + // 1: Will be non-empty if it's either a ref or inline link. Will be empty + // if it's just a bare pair of square brackets with nothing after them. + // 2: Contains the id inside [] for a reference-style link. + // 3: Contains the URL for an inline link. + // 4: Contains the title, if present, for an inline link. + } + + LinkSyntax() + : super(r'\[', end: linkPattern); + + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + var url; + var title; + + // If we didn't match refLink or inlineLink, then it means there was + // nothing after the first square bracket, so it isn't a normal markdown + // link at all. Instead, we allow users of the library to specify a special + // resolver function ([setImplicitLinkResolver]) that may choose to handle + // this. Otherwise, it's just treated as plain text. + if ((match[1] == null) || (match[1] == '')) { + if (_implicitLinkResolver == null) return false; + + // Only allow implicit links if the content is just text. + // TODO(rnystrom): Do we want to relax this? + if (state.children.length != 1) return false; + if (state.children[0] is! Text) return false; + + Text link = state.children[0]; + + // See if we have a resolver that will generate a link for us. + final node = _implicitLinkResolver(link.text); + if (node == null) return false; + + parser.addNode(node); + return true; + } + + if ((match[3] != null) && (match[3] != '')) { + // Inline link like [foo](url). + url = match[3]; + title = match[4]; + + // For whatever reason, markdown allows angle-bracketed URLs here. + if (url.startsWith('<') && url.endsWith('>')) { + url = url.substring(1, url.length - 1); + } + } else { + // Reference link like [foo] [bar]. + var id = match[2]; + if (id == '') { + // The id is empty ("[]") so infer it from the contents. + id = parser.source.substring(state.startPos + 1, parser.pos); + } + + // References are case-insensitive. + id = id.toLowerCase(); + + // Look up the link. + final link = parser.document.refLinks[id]; + // If it's an unknown link just emit plaintext. + if (link == null) return false; + + url = link.url; + title = link.title; + } + + final anchor = new Element('a', state.children); + anchor.attributes['href'] = escapeHtml(url); + if ((title != null) && (title != '')) { + anchor.attributes['title'] = escapeHtml(title); + } + + parser.addNode(anchor); + return true; + } +} + +/// Matches backtick-enclosed inline code blocks. +class CodeSyntax extends InlineSyntax { + CodeSyntax(String pattern) + : super(pattern); + + bool onMatch(InlineParser parser, Match match) { + parser.addNode(new Element.text('code', escapeHtml(match[1]))); + return true; + } +} + +/// Keeps track of a currently open tag while it is being parsed. The parser +/// maintains a stack of these so it can handle nested tags. +class TagState { + /// The point in the original source where this tag started. + int startPos; + + /// The point in the original source where open tag ended. + int endPos; + + /// The syntax that created this node. + final TagSyntax syntax; + + /// The children of this node. Will be `null` for text nodes. + final List<Node> children; + + TagState(this.startPos, this.endPos, this.syntax) + : children = <Node>[]; + + /// Attempts to close this tag by matching the current text against its end + /// pattern. + bool tryMatch(InlineParser parser) { + Match endMatch = syntax.endPattern.firstMatch(parser.currentSource); + if ((endMatch != null) && (endMatch.start == 0)) { + // Close the tag. + close(parser, endMatch); + return true; + } + + return false; + } + + /// Pops this tag off the stack, completes it, and adds it to the output. + /// Will discard any unmatched tags that happen to be above it on the stack. + /// If this is the last node in the stack, returns its children. + List<Node> close(InlineParser parser, Match endMatch) { + // If there are unclosed tags on top of this one when it's closed, that + // means they are mismatched. Mismatched tags are treated as plain text in + // markdown. So for each tag above this one, we write its start tag as text + // and then adds its children to this one's children. + int index = parser._stack.indexOf(this); + + // Remove the unmatched children. + final unmatchedTags = parser._stack.getRange(index + 1, + parser._stack.length - index - 1); + parser._stack.removeRange(index + 1, parser._stack.length - index - 1); + + // Flatten them out onto this tag. + for (final unmatched in unmatchedTags) { + // Write the start tag as text. + parser.writeTextRange(unmatched.startPos, unmatched.endPos); + + // Bequeath its children unto this tag. + children.addAll(unmatched.children); + } + + // Pop this off the stack. + parser.writeText(); + parser._stack.removeLast(); + + // If the stack is empty now, this is the special "results" node. + if (parser._stack.length == 0) return children; + + // We are still parsing, so add this to its parent's children. + if (syntax.onMatchEnd(parser, endMatch, this)) { + parser.consume(endMatch[0].length); + } else { + // Didn't close correctly so revert to text. + parser.start = startPos; + parser.advanceBy(endMatch[0].length); + } + + return null; + } +}
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart new file mode 100644 index 0000000..3875de0 --- /dev/null +++ b/pkgs/markdown/test/markdown_test.dart
@@ -0,0 +1,883 @@ +// Copyright (c) 2011, 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. + +/// Unit tests for markdown. +library markdownTests; + +// TODO(rnystrom): Use "package:" URL (#4968). +import '../lib/markdown.dart'; + +// TODO(rnystrom): Better path to unittest. +import 'package:unittest/unittest.dart'; + +/// Most of these tests are based on observing how showdown behaves: +/// http://softwaremaniacs.org/playground/showdown-highlight/ +void main() { + group('Paragraphs', () { + validate('consecutive lines form a single paragraph', ''' + This is the first line. + This is the second line. + ''', ''' + <p>This is the first line. + This is the second line.</p> + '''); + + // TODO(rnystrom): The rules here for what happens to lines following a + // paragraph appear to be completely arbitrary in markdown. If it makes the + // code significantly cleaner, we should consider ourselves free to change + // these tests. + + validate('are terminated by a header', ''' + para + # header + ''', ''' + <p>para</p> + <h1>header</h1> + '''); + + validate('are terminated by a setext header', ''' + para + header + == + ''', ''' + <p>para</p> + <h1>header</h1> + '''); + + validate('are terminated by a hr', ''' + para + ___ + ''', ''' + <p>para</p> + <hr /> + '''); + + validate('consume an unordered list', ''' + para + * list + ''', ''' + <p>para + * list</p> + '''); + + validate('consume an ordered list', ''' + para + 1. list + ''', ''' + <p>para + 1. list</p> + '''); + + // Windows line endings have a \r\n format + // instead of the unix \n format. + validate('take account of windows line endings', ''' + line1\r\n\r\n line2\r\n + ''', ''' + <p>line1</p> + <p>line2</p> + '''); + }); + + group('Setext headers', () { + validate('h1', ''' + text + === + ''', ''' + <h1>text</h1> + '''); + + validate('h2', ''' + text + --- + ''', ''' + <h2>text</h2> + '''); + + validate('h1 on first line becomes text', ''' + === + ''', ''' + <p>===</p> + '''); + + validate('h2 on first line becomes text', ''' + - + ''', ''' + <p>-</p> + '''); + + validate('h1 turns preceding list into text', ''' + - list + === + ''', ''' + <h1>- list</h1> + '''); + + validate('h2 turns preceding list into text', ''' + - list + === + ''', ''' + <h1>- list</h1> + '''); + + validate('h1 turns preceding blockquote into text', ''' + > quote + === + ''', ''' + <h1>> quote</h1> + '''); + + validate('h2 turns preceding blockquote into text', ''' + > quote + === + ''', ''' + <h1>> quote</h1> + '''); + }); + + group('Headers', () { + validate('h1', ''' + # header + ''', ''' + <h1>header</h1> + '''); + + validate('h2', ''' + ## header + ''', ''' + <h2>header</h2> + '''); + + validate('h3', ''' + ### header + ''', ''' + <h3>header</h3> + '''); + + validate('h4', ''' + #### header + ''', ''' + <h4>header</h4> + '''); + + validate('h5', ''' + ##### header + ''', ''' + <h5>header</h5> + '''); + + validate('h6', ''' + ###### header + ''', ''' + <h6>header</h6> + '''); + + validate('trailing "#" are removed', ''' + # header ###### + ''', ''' + <h1>header</h1> + '''); + + }); + + group('Unordered lists', () { + validate('asterisk, plus and hyphen', ''' + * star + - dash + + plus + ''', ''' + <ul> + <li>star</li> + <li>dash</li> + <li>plus</li> + </ul> + '''); + + validate('allow numbered lines after first', ''' + * a + 1. b + ''', ''' + <ul> + <li>a</li> + <li>b</li> + </ul> + '''); + + validate('allow a tab after the marker', ''' + *\ta + +\tb + -\tc + 1.\td + ''', ''' + <ul> + <li>a</li> + <li>b</li> + <li>c</li> + <li>d</li> + </ul> + '''); + + validate('wrap items in paragraphs if blank lines separate', ''' + * one + + * two + ''', ''' + <ul> + <li><p>one</p></li> + <li><p>two</p></li> + </ul> + '''); + + validate('force paragraph on item before and after blank lines', ''' + * one + * two + + * three + ''', ''' + <ul> + <li>one</li> + <li> + <p>two</p> + </li> + <li> + <p>three</p> + </li> + </ul> + '''); + + validate('do not force paragraph if item is already block', ''' + * > quote + + * # header + ''', ''' + <ul> + <li><blockquote><p>quote</p></blockquote></li> + <li><h1>header</h1></li> + </ul> + '''); + + validate('can contain multiple paragraphs', ''' + * one + + two + + * three + ''', ''' + <ul> + <li> + <p>one</p> + <p>two</p> + </li> + <li> + <p>three</p> + </li> + </ul> + '''); + + validate('can span newlines', ''' + * one + two + * three + ''', ''' + <ul> + <li> + <p>one + two</p> + </li> + <li> + three + </li> + </ul> + '''); + + // TODO(rnystrom): This is how most other markdown parsers handle + // this but that seems like a nasty special case. For now, let's not + // worry about it. + /* + validate('can nest using indentation', ''' + * parent + * child + ''', ''' + <ul> + <li>parent + <ul><li>child</li></ul></li> + </ul> + '''); + */ + }); + + group('Ordered lists', () { + validate('start with numbers', ''' + 1. one + 45. two + 12345. three + ''', ''' + <ol> + <li>one</li> + <li>two</li> + <li>three</li> + </ol> + '''); + + validate('allow unordered lines after first', ''' + 1. a + * b + ''', ''' + <ol> + <li>a</li> + <li>b</li> + </ol> + '''); + }); + + group('Blockquotes', () { + validate('single line', ''' + > blah + ''', ''' + <blockquote> + <p>blah</p> + </blockquote> + '''); + + validate('with two paragraphs', ''' + > first + > + > second + ''', ''' + <blockquote> + <p>first</p> + <p>second</p> + </blockquote> + '''); + + validate('nested', ''' + > one + >> two + > > > three + ''', ''' + <blockquote> + <p>one</p> + <blockquote> + <p>two</p> + <blockquote> + <p>three</p> + </blockquote> + </blockquote> + </blockquote> + '''); + }); + + group('Code blocks', () { + validate('single line', ''' + code + ''', ''' + <pre><code>code</code></pre> + '''); + + validate('include leading whitespace after indentation', ''' + zero + one + two + three + ''', ''' + <pre><code>zero + one + two + three</code></pre> + '''); + + validate('code blocks separated by newlines form one block', ''' + zero + one + + two + + three + ''', ''' + <pre><code>zero + one + + two + + three</code></pre> + '''); + + validate('code blocks separated by two newlines form multiple blocks', ''' + zero + one + + + two + + + three + ''', ''' + <pre><code>zero + one</code></pre> + <pre><code>two</code></pre> + <pre><code>three</code></pre> + '''); + + validate('escape HTML characters', ''' + <&> + ''', ''' + <pre><code><&></code></pre> + '''); + }); + + group('Horizontal rules', () { + validate('from dashes', ''' + --- + ''', ''' + <hr /> + '''); + + validate('from asterisks', ''' + *** + ''', ''' + <hr /> + '''); + + validate('from underscores', ''' + ___ + ''', ''' + <hr /> + '''); + + validate('can include up to two spaces', ''' + _ _ _ + ''', ''' + <hr /> + '''); + }); + + group('Block-level HTML', () { + validate('single line', ''' + <table></table> + ''', ''' + <table></table> + '''); + + validate('multi-line', ''' + <table> + blah + </table> + ''', ''' + <table> + blah + </table> + '''); + + validate('blank line ends block', ''' + <table> + blah + </table> + + para + ''', ''' + <table> + blah + </table> + <p>para</p> + '''); + + validate('HTML can be bogus', ''' + <bogus> + blah + </weird> + + para + ''', ''' + <bogus> + blah + </weird> + <p>para</p> + '''); + }); + + group('Strong', () { + validate('using asterisks', ''' + before **strong** after + ''', ''' + <p>before <strong>strong</strong> after</p> + '''); + + validate('using underscores', ''' + before __strong__ after + ''', ''' + <p>before <strong>strong</strong> after</p> + '''); + + validate('unmatched asterisks', ''' + before ** after + ''', ''' + <p>before ** after</p> + '''); + + validate('unmatched underscores', ''' + before __ after + ''', ''' + <p>before __ after</p> + '''); + + validate('multiple spans in one text', ''' + a **one** b __two__ c + ''', ''' + <p>a <strong>one</strong> b <strong>two</strong> c</p> + '''); + + validate('multi-line', ''' + before **first + second** after + ''', ''' + <p>before <strong>first + second</strong> after</p> + '''); + }); + + group('Emphasis and strong', () { + validate('single asterisks', ''' + before *em* after + ''', ''' + <p>before <em>em</em> after</p> + '''); + + validate('single underscores', ''' + before _em_ after + ''', ''' + <p>before <em>em</em> after</p> + '''); + + validate('double asterisks', ''' + before **strong** after + ''', ''' + <p>before <strong>strong</strong> after</p> + '''); + + validate('double underscores', ''' + before __strong__ after + ''', ''' + <p>before <strong>strong</strong> after</p> + '''); + + validate('unmatched asterisk', ''' + before *after + ''', ''' + <p>before *after</p> + '''); + + validate('unmatched underscore', ''' + before _after + ''', ''' + <p>before _after</p> + '''); + + validate('multiple spans in one text', ''' + a *one* b _two_ c + ''', ''' + <p>a <em>one</em> b <em>two</em> c</p> + '''); + + validate('multi-line', ''' + before *first + second* after + ''', ''' + <p>before <em>first + second</em> after</p> + '''); + + validate('not processed when surrounded by spaces', ''' + a * b * c _ d _ e + ''', ''' + <p>a * b * c _ d _ e</p> + '''); + + validate('strong then emphasis', ''' + **strong***em* + ''', ''' + <p><strong>strong</strong><em>em</em></p> + '''); + + validate('emphasis then strong', ''' + *em***strong** + ''', ''' + <p><em>em</em><strong>strong</strong></p> + '''); + + validate('emphasis inside strong', ''' + **strong *em*** + ''', ''' + <p><strong>strong <em>em</em></strong></p> + '''); + + validate('mismatched in nested', ''' + *a _b* c_ + ''', ''' + <p><em>a _b</em> c_</p> + '''); + + validate('cannot nest tags of same type', ''' + *a _b *c* d_ e* + ''', ''' + <p><em>a _b </em>c<em> d_ e</em></p> + '''); + }); + + group('Inline code', () { + validate('simple case', ''' + before `source` after + ''', ''' + <p>before <code>source</code> after</p> + '''); + + validate('unmatched backtick', ''' + before ` after + ''', ''' + <p>before ` after</p> + '''); + validate('multiple spans in one text', ''' + a `one` b `two` c + ''', ''' + <p>a <code>one</code> b <code>two</code> c</p> + '''); + + validate('multi-line', ''' + before `first + second` after + ''', ''' + <p>before <code>first + second</code> after</p> + '''); + + validate('double backticks', ''' + before ``can `contain` backticks`` after + ''', ''' + <p>before <code>can `contain` backticks</code> after</p> + '''); + + validate('double backticks with spaces', ''' + before `` `tick` `` after + ''', ''' + <p>before <code>`tick`</code> after</p> + '''); + + validate('multiline double backticks with spaces', ''' + before ``in `tick` + another`` after + ''', ''' + <p>before <code>in `tick` + another</code> after</p> + '''); + + validate('ignore markup inside code', ''' + before `*b* _c_` after + ''', ''' + <p>before <code>*b* _c_</code> after</p> + '''); + + validate('escape HTML characters', ''' + `<&>` + ''', ''' + <p><code><&></code></p> + '''); + + validate('escape HTML tags', ''' + '*' `<em>` + ''', ''' + <p>'*' <code><em></code></p> + '''); + }); + + group('HTML encoding', () { + validate('less than and ampersand are escaped', ''' + < & + ''', ''' + <p>< &</p> + '''); + validate('greater than is not escaped', ''' + not you > + ''', ''' + <p>not you ></p> + '''); + validate('existing entities are untouched', ''' + & + ''', ''' + <p>&</p> + '''); + }); + + group('Autolinks', () { + validate('basic link', ''' + before <http://foo.com/> after + ''', ''' + <p>before <a href="http://foo.com/">http://foo.com/</a> after</p> + '''); + validate('handles ampersand in url', ''' + <http://foo.com/?a=1&b=2> + ''', ''' + <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p> + '''); + }); + + group('Reference links', () { + validate('double quotes for title', ''' + links [are] [a] awesome + + [a]: http://foo.com "woo" + ''', ''' + <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> + '''); + validate('single quoted title', """ + links [are] [a] awesome + + [a]: http://foo.com 'woo' + """, ''' + <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> + '''); + validate('parentheses for title', ''' + links [are] [a] awesome + + [a]: http://foo.com (woo) + ''', ''' + <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> + '''); + validate('no title', ''' + links [are] [a] awesome + + [a]: http://foo.com + ''', ''' + <p>links <a href="http://foo.com">are</a> awesome</p> + '''); + validate('unknown link becomes plaintext', ''' + [not] [known] + ''', ''' + <p>[not] [known]</p> + '''); + validate('can style link contents', ''' + links [*are*] [a] awesome + + [a]: http://foo.com + ''', ''' + <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> + '''); + validate('inline styles after a bad link are processed', ''' + [bad] `code` + ''', ''' + <p>[bad] <code>code</code></p> + '''); + validate('empty reference uses text from link', ''' + links [are][] awesome + + [are]: http://foo.com + ''', ''' + <p>links <a href="http://foo.com">are</a> awesome</p> + '''); + validate('references are case-insensitive', ''' + links [ARE][] awesome + + [are]: http://foo.com + ''', ''' + <p>links <a href="http://foo.com">ARE</a> awesome</p> + '''); + }); + + group('Inline links', () { + validate('double quotes for title', ''' + links [are](http://foo.com "woo") awesome + ''', ''' + <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> + '''); + validate('no title', ''' + links [are] (http://foo.com) awesome + ''', ''' + <p>links <a href="http://foo.com">are</a> awesome</p> + '''); + validate('can style link contents', ''' + links [*are*](http://foo.com) awesome + ''', ''' + <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> + '''); + }); +} + +/** + * Removes eight spaces of leading indentation from a multiline string. + * + * Note that this is very sensitive to how the literals are styled. They should + * be: + * ''' + * Text starts on own line. Lines up with subsequent lines. + * Lines are indented exactly 8 characters from the left margin.''' + * + * This does nothing if text is only a single line. + */ +// TODO(nweiz): Make this auto-detect the indentation level from the first +// non-whitespace line. +String cleanUpLiteral(String text) { + var lines = text.split('\n'); + if (lines.length <= 1) return text; + + for (var j = 0; j < lines.length; j++) { + if (lines[j].length > 8) { + lines[j] = lines[j].substring(8, lines[j].length); + } else { + lines[j] = ''; + } + } + + return Strings.join(lines, '\n'); +} + +validate(String description, String markdown, String html, + {bool verbose: false}) { + test(description, () { + markdown = cleanUpLiteral(markdown); + html = cleanUpLiteral(html); + + var result = markdownToHtml(markdown); + var passed = compareOutput(html, result); + + if (!passed) { + // Remove trailing newline. + html = html.substring(0, html.length - 1); + + print('FAIL: $description'); + print(' expect: ${html.replaceAll("\n", "\n ")}'); + print(' actual: ${result.replaceAll("\n", "\n ")}'); + print(''); + } + + expect(passed, isTrue, verbose: verbose); + }); +} + +/// Does a loose comparison of the two strings of HTML. Ignores differences in +/// newlines and indentation. +compareOutput(String a, String b) { + int i = 0; + int j = 0; + + skipIgnored(String s, int i) { + // Ignore newlines. + while ((i < s.length) && (s[i] == '\n')) { + i++; + // Ignore indentation. + while ((i < s.length) && (s[i] == ' ')) i++; + } + + return i; + } + + while (true) { + i = skipIgnored(a, i); + j = skipIgnored(b, j); + + // If one string runs out of non-ignored strings, the other must too. + if (i == a.length) return j == b.length; + if (j == b.length) return i == a.length; + + if (a[i] != b[j]) return false; + i++; + j++; + } +}
diff --git a/pkgs/markdown/test/pubspec.yaml b/pkgs/markdown/test/pubspec.yaml new file mode 100644 index 0000000..f45db3a --- /dev/null +++ b/pkgs/markdown/test/pubspec.yaml
@@ -0,0 +1,7 @@ +name: markdown +author: "Dart Team <misc@dartlang.org>" +# homepage: https://github.com/dart-lang/csslib +description: A library for converting markdown to HTML. +version: 0.3.4 +dependencies: + unittest: any \ No newline at end of file