From 18624b73f20aa349500c9753ccb78c2ac455beda Mon Sep 17 00:00:00 2001 From: David Peek Date: Tue, 19 Feb 2013 14:11:04 -0800 Subject: [PATCH 001/517] Initial commit --- pkgs/markdown/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pkgs/markdown/README.md diff --git a/pkgs/markdown/README.md b/pkgs/markdown/README.md new file mode 100644 index 000000000..04ffb5c12 --- /dev/null +++ b/pkgs/markdown/README.md @@ -0,0 +1,4 @@ +dart-markdown +============= + +A standalone version of the dartdoc markdown library. \ No newline at end of file From f2f136a16597f5aa20c04a88da1748c2f647c0a1 Mon Sep 17 00:00:00 2001 From: David Peek Date: Wed, 20 Feb 2013 09:14:56 +1100 Subject: [PATCH 002/517] Imported markdown library from dartdoc --- pkgs/markdown/.gitignore | 5 + pkgs/markdown/LICENSE | 24 + pkgs/markdown/lib/classify.dart | 209 + pkgs/markdown/lib/markdown.dart | 118 + pkgs/markdown/lib/src/compiler/compiler.dart | 177 + .../src/compiler/implementation/README.txt | 12 + .../src/compiler/implementation/apiimpl.dart | 266 + .../src/compiler/implementation/closure.dart | 663 +++ .../compiler/implementation/code_buffer.dart | 106 + .../src/compiler/implementation/colors.dart | 15 + .../compile_time_constants.dart | 888 +++ .../src/compiler/implementation/compiler.dart | 1130 ++++ .../implementation/constant_system.dart | 80 + .../implementation/constant_system_dart.dart | 380 ++ .../compiler/implementation/constants.dart | 473 ++ .../src/compiler/implementation/dart2js.dart | 497 ++ .../implementation/dart2js.dart.snapshot | Bin 0 -> 1658537 bytes .../compiler/implementation/dart2jslib.dart | 61 + .../implementation/dart_backend/backend.dart | 593 ++ .../dart_backend/dart_backend.dart | 25 + .../implementation/dart_backend/emitter.dart | 24 + .../dart_backend/placeholder_collector.dart | 626 ++ .../implementation/dart_backend/renamer.dart | 359 ++ .../implementation/dart_backend/utils.dart | 292 + .../compiler/implementation/dart_types.dart | 806 +++ .../implementation/diagnostic_listener.dart | 28 + .../implementation/elements/elements.dart | 792 +++ .../implementation/elements/modelx.dart | 1981 +++++++ .../src/compiler/implementation/enqueue.dart | 552 ++ .../compiler/implementation/filenames.dart | 29 + .../src/compiler/implementation/js/js.dart | 15 + .../src/compiler/implementation/js/nodes.dart | 906 +++ .../implementation/js/precedence.dart | 25 + .../compiler/implementation/js/printer.dart | 1111 ++++ .../implementation/js_backend/backend.dart | 1262 +++++ .../js_backend/constant_emitter.dart | 325 ++ .../constant_system_javascript.dart | 240 + .../implementation/js_backend/emitter.dart | 2359 ++++++++ .../js_backend/emitter_no_eval.dart | 138 + .../implementation/js_backend/js_backend.dart | 33 + .../js_backend/minify_namer.dart | 200 + .../implementation/js_backend/namer.dart | 754 +++ .../js_backend/native_emitter.dart | 546 ++ .../js_backend/runtime_types.dart | 173 + .../implementation/lib/async_patch.dart | 21 + .../implementation/lib/constant_map.dart | 75 + .../implementation/lib/core_patch.dart | 250 + .../implementation/lib/foreign_helper.dart | 138 + .../implementation/lib/interceptors.dart | 90 + .../compiler/implementation/lib/io_patch.dart | 217 + .../implementation/lib/isolate_helper.dart | 1329 +++++ .../implementation/lib/isolate_patch.dart | 34 + .../compiler/implementation/lib/js_array.dart | 295 + .../implementation/lib/js_helper.dart | 1513 +++++ .../implementation/lib/js_number.dart | 272 + .../implementation/lib/js_string.dart | 229 + .../implementation/lib/math_patch.dart | 68 + .../implementation/lib/mirrors_patch.dart | 114 + .../implementation/lib/native_helper.dart | 431 ++ .../implementation/lib/regexp_helper.dart | 151 + .../implementation/lib/scalarlist_patch.dart | 130 + .../implementation/lib/string_helper.dart | 234 + .../implementation/library_loader.dart | 837 +++ .../mirrors/dart2js_mirror.dart | 1742 ++++++ .../implementation/mirrors/mirrors.dart | 738 +++ .../implementation/mirrors/mirrors_util.dart | 161 + .../compiler/implementation/mirrors/util.dart | 173 + .../implementation/native_handler.dart | 902 +++ .../compiler/implementation/patch_parser.dart | 596 ++ .../implementation/resolution/members.dart | 3663 ++++++++++++ .../implementation/resolution/resolution.dart | 30 + .../implementation/resolution/scope.dart | 163 + .../resolution/secret_tree_element.dart | 46 + .../implementation/resolved_visitor.dart | 67 + .../scanner/array_based_scanner.dart | 183 + .../scanner/byte_array_scanner.dart | 40 + .../implementation/scanner/byte_strings.dart | 154 + .../scanner/class_element_parser.dart | 197 + .../implementation/scanner/keyword.dart | 235 + .../implementation/scanner/listener.dart | 2064 +++++++ .../implementation/scanner/parser.dart | 2231 ++++++++ .../implementation/scanner/parser_task.dart | 14 + .../scanner/partial_parser.dart | 130 + .../implementation/scanner/scanner.dart | 875 +++ .../scanner/scanner_implementation.dart | 11 + .../implementation/scanner/scanner_task.dart | 53 + .../implementation/scanner/scannerlib.dart | 38 + .../scanner/string_scanner.dart | 106 + .../implementation/scanner/token.dart | 530 ++ .../src/compiler/implementation/script.dart | 24 + .../compiler/implementation/source_file.dart | 105 + .../implementation/source_file_provider.dart | 125 + .../implementation/source_map_builder.dart | 192 + .../compiler/implementation/ssa/bailout.dart | 630 +++ .../compiler/implementation/ssa/builder.dart | 5034 +++++++++++++++++ .../compiler/implementation/ssa/codegen.dart | 3006 ++++++++++ .../implementation/ssa/codegen_helpers.dart | 380 ++ .../ssa/invoke_dynamic_specializers.dart | 620 ++ .../compiler/implementation/ssa/nodes.dart | 2711 +++++++++ .../compiler/implementation/ssa/optimize.dart | 1545 +++++ .../src/compiler/implementation/ssa/ssa.dart | 43 + .../compiler/implementation/ssa/tracer.dart | 563 ++ .../compiler/implementation/ssa/types.dart | 997 ++++ .../implementation/ssa/types_propagation.dart | 210 + .../compiler/implementation/ssa/validate.dart | 181 + .../ssa/value_range_analyzer.dart | 996 ++++ .../implementation/ssa/value_set.dart | 157 + .../ssa/variable_allocator.dart | 669 +++ .../implementation/string_validator.dart | 214 + .../implementation/tools/mini_parser.dart | 321 ++ .../implementation/tree/dartstring.dart | 235 + .../compiler/implementation/tree/nodes.dart | 2086 +++++++ .../implementation/tree/prettyprint.dart | 480 ++ .../compiler/implementation/tree/tree.dart | 22 + .../implementation/tree/unparser.dart | 627 ++ .../implementation/tree/visitors.dart | 21 + .../implementation/tree_validator.dart | 78 + .../compiler/implementation/typechecker.dart | 751 +++ .../types/concrete_types_inferrer.dart | 1678 ++++++ .../compiler/implementation/types/types.dart | 260 + .../implementation/universe/function_set.dart | 140 + .../universe/partial_type_tree.dart | 190 + .../implementation/universe/selector_map.dart | 132 + .../implementation/universe/universe.dart | 495 ++ .../implementation/util/characters.dart | 143 + .../compiler/implementation/util/link.dart | 81 + .../util/link_implementation.dart | 142 + .../implementation/util/uri_extras.dart | 65 + .../compiler/implementation/util/util.dart | 109 + .../util/util_implementation.dart | 9 + .../src/compiler/implementation/warnings.dart | 568 ++ .../src/compiler/implementation/world.dart | 232 + pkgs/markdown/lib/src/libraries.dart | 200 + pkgs/markdown/lib/src/markdown/ast.dart | 65 + .../lib/src/markdown/block_parser.dart | 464 ++ .../lib/src/markdown/html_renderer.dart | 61 + .../lib/src/markdown/inline_parser.dart | 410 ++ pkgs/markdown/pubspec.yaml | 7 + pkgs/markdown/test/LICENSE | 24 + pkgs/markdown/test/lib/classify.dart | 209 + pkgs/markdown/test/lib/markdown.dart | 118 + .../test/lib/src/compiler/compiler.dart | 177 + .../src/compiler/implementation/README.txt | 12 + .../src/compiler/implementation/apiimpl.dart | 266 + .../src/compiler/implementation/closure.dart | 663 +++ .../compiler/implementation/code_buffer.dart | 106 + .../src/compiler/implementation/colors.dart | 15 + .../compile_time_constants.dart | 888 +++ .../src/compiler/implementation/compiler.dart | 1130 ++++ .../implementation/constant_system.dart | 80 + .../implementation/constant_system_dart.dart | 380 ++ .../compiler/implementation/constants.dart | 473 ++ .../src/compiler/implementation/dart2js.dart | 497 ++ .../implementation/dart2js.dart.snapshot | Bin 0 -> 1658537 bytes .../compiler/implementation/dart2jslib.dart | 61 + .../implementation/dart_backend/backend.dart | 593 ++ .../dart_backend/dart_backend.dart | 25 + .../implementation/dart_backend/emitter.dart | 24 + .../dart_backend/placeholder_collector.dart | 626 ++ .../implementation/dart_backend/renamer.dart | 359 ++ .../implementation/dart_backend/utils.dart | 292 + .../compiler/implementation/dart_types.dart | 806 +++ .../implementation/diagnostic_listener.dart | 28 + .../implementation/elements/elements.dart | 792 +++ .../implementation/elements/modelx.dart | 1981 +++++++ .../src/compiler/implementation/enqueue.dart | 552 ++ .../compiler/implementation/filenames.dart | 29 + .../src/compiler/implementation/js/js.dart | 15 + .../src/compiler/implementation/js/nodes.dart | 906 +++ .../implementation/js/precedence.dart | 25 + .../compiler/implementation/js/printer.dart | 1111 ++++ .../implementation/js_backend/backend.dart | 1262 +++++ .../js_backend/constant_emitter.dart | 325 ++ .../constant_system_javascript.dart | 240 + .../implementation/js_backend/emitter.dart | 2359 ++++++++ .../js_backend/emitter_no_eval.dart | 138 + .../implementation/js_backend/js_backend.dart | 33 + .../js_backend/minify_namer.dart | 200 + .../implementation/js_backend/namer.dart | 754 +++ .../js_backend/native_emitter.dart | 546 ++ .../js_backend/runtime_types.dart | 173 + .../implementation/lib/async_patch.dart | 21 + .../implementation/lib/constant_map.dart | 75 + .../implementation/lib/core_patch.dart | 250 + .../implementation/lib/foreign_helper.dart | 138 + .../implementation/lib/interceptors.dart | 90 + .../compiler/implementation/lib/io_patch.dart | 217 + .../implementation/lib/isolate_helper.dart | 1329 +++++ .../implementation/lib/isolate_patch.dart | 34 + .../compiler/implementation/lib/js_array.dart | 295 + .../implementation/lib/js_helper.dart | 1513 +++++ .../implementation/lib/js_number.dart | 272 + .../implementation/lib/js_string.dart | 229 + .../implementation/lib/math_patch.dart | 68 + .../implementation/lib/mirrors_patch.dart | 114 + .../implementation/lib/native_helper.dart | 431 ++ .../implementation/lib/regexp_helper.dart | 151 + .../implementation/lib/scalarlist_patch.dart | 130 + .../implementation/lib/string_helper.dart | 234 + .../implementation/library_loader.dart | 837 +++ .../mirrors/dart2js_mirror.dart | 1742 ++++++ .../implementation/mirrors/mirrors.dart | 738 +++ .../implementation/mirrors/mirrors_util.dart | 161 + .../compiler/implementation/mirrors/util.dart | 173 + .../implementation/native_handler.dart | 902 +++ .../compiler/implementation/patch_parser.dart | 596 ++ .../implementation/resolution/members.dart | 3663 ++++++++++++ .../implementation/resolution/resolution.dart | 30 + .../implementation/resolution/scope.dart | 163 + .../resolution/secret_tree_element.dart | 46 + .../implementation/resolved_visitor.dart | 67 + .../scanner/array_based_scanner.dart | 183 + .../scanner/byte_array_scanner.dart | 40 + .../implementation/scanner/byte_strings.dart | 154 + .../scanner/class_element_parser.dart | 197 + .../implementation/scanner/keyword.dart | 235 + .../implementation/scanner/listener.dart | 2064 +++++++ .../implementation/scanner/parser.dart | 2231 ++++++++ .../implementation/scanner/parser_task.dart | 14 + .../scanner/partial_parser.dart | 130 + .../implementation/scanner/scanner.dart | 875 +++ .../scanner/scanner_implementation.dart | 11 + .../implementation/scanner/scanner_task.dart | 53 + .../implementation/scanner/scannerlib.dart | 38 + .../scanner/string_scanner.dart | 106 + .../implementation/scanner/token.dart | 530 ++ .../src/compiler/implementation/script.dart | 24 + .../compiler/implementation/source_file.dart | 105 + .../implementation/source_file_provider.dart | 125 + .../implementation/source_map_builder.dart | 192 + .../compiler/implementation/ssa/bailout.dart | 630 +++ .../compiler/implementation/ssa/builder.dart | 5034 +++++++++++++++++ .../compiler/implementation/ssa/codegen.dart | 3006 ++++++++++ .../implementation/ssa/codegen_helpers.dart | 380 ++ .../ssa/invoke_dynamic_specializers.dart | 620 ++ .../compiler/implementation/ssa/nodes.dart | 2711 +++++++++ .../compiler/implementation/ssa/optimize.dart | 1545 +++++ .../src/compiler/implementation/ssa/ssa.dart | 43 + .../compiler/implementation/ssa/tracer.dart | 563 ++ .../compiler/implementation/ssa/types.dart | 997 ++++ .../implementation/ssa/types_propagation.dart | 210 + .../compiler/implementation/ssa/validate.dart | 181 + .../ssa/value_range_analyzer.dart | 996 ++++ .../implementation/ssa/value_set.dart | 157 + .../ssa/variable_allocator.dart | 669 +++ .../implementation/string_validator.dart | 214 + .../implementation/tools/mini_parser.dart | 321 ++ .../implementation/tree/dartstring.dart | 235 + .../compiler/implementation/tree/nodes.dart | 2086 +++++++ .../implementation/tree/prettyprint.dart | 480 ++ .../compiler/implementation/tree/tree.dart | 22 + .../implementation/tree/unparser.dart | 627 ++ .../implementation/tree/visitors.dart | 21 + .../implementation/tree_validator.dart | 78 + .../compiler/implementation/typechecker.dart | 751 +++ .../types/concrete_types_inferrer.dart | 1678 ++++++ .../compiler/implementation/types/types.dart | 260 + .../implementation/universe/function_set.dart | 140 + .../universe/partial_type_tree.dart | 190 + .../implementation/universe/selector_map.dart | 132 + .../implementation/universe/universe.dart | 495 ++ .../implementation/util/characters.dart | 143 + .../compiler/implementation/util/link.dart | 81 + .../util/link_implementation.dart | 142 + .../implementation/util/uri_extras.dart | 65 + .../compiler/implementation/util/util.dart | 109 + .../util/util_implementation.dart | 9 + .../src/compiler/implementation/warnings.dart | 568 ++ .../src/compiler/implementation/world.dart | 232 + pkgs/markdown/test/lib/src/libraries.dart | 200 + pkgs/markdown/test/lib/src/markdown/ast.dart | 65 + .../test/lib/src/markdown/block_parser.dart | 464 ++ .../test/lib/src/markdown/html_renderer.dart | 61 + .../test/lib/src/markdown/inline_parser.dart | 410 ++ pkgs/markdown/test/markdown_test.dart | 883 +++ pkgs/markdown/test/pubspec.yaml | 7 + 276 files changed, 139562 insertions(+) create mode 100644 pkgs/markdown/.gitignore create mode 100644 pkgs/markdown/LICENSE create mode 100644 pkgs/markdown/lib/classify.dart create mode 100644 pkgs/markdown/lib/markdown.dart create mode 100644 pkgs/markdown/lib/src/compiler/compiler.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/README.txt create mode 100644 pkgs/markdown/lib/src/compiler/implementation/apiimpl.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/closure.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/code_buffer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/colors.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/compile_time_constants.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/compiler.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/constant_system.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/constant_system_dart.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/constants.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart2js.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart2js.dart.snapshot create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart2jslib.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/backend.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/dart_backend.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/emitter.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/renamer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_backend/utils.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/dart_types.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/diagnostic_listener.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/elements/elements.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/elements/modelx.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/enqueue.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/filenames.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js/js.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js/nodes.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js/precedence.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js/printer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/backend.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_emitter.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/js_backend.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/minify_namer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/namer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/native_emitter.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/js_backend/runtime_types.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/async_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/constant_map.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/core_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/foreign_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/interceptors.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/io_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/isolate_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/isolate_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/js_array.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/js_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/js_number.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/js_string.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/math_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/mirrors_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/native_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/regexp_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/scalarlist_patch.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/lib/string_helper.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/library_loader.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/mirrors/mirrors_util.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/mirrors/util.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/native_handler.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/patch_parser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/resolution/members.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/resolution/resolution.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/resolution/scope.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/resolution/secret_tree_element.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/resolved_visitor.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/array_based_scanner.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/byte_array_scanner.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/byte_strings.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/class_element_parser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/keyword.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/listener.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/parser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/parser_task.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/partial_parser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/scanner.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_implementation.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/scanner_task.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/scannerlib.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/string_scanner.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/scanner/token.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/script.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/source_file.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/source_file_provider.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/source_map_builder.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/bailout.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/builder.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/codegen.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/codegen_helpers.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/nodes.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/optimize.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/ssa.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/tracer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/types.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/types_propagation.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/validate.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/value_range_analyzer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/value_set.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/ssa/variable_allocator.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/string_validator.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tools/mini_parser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/dartstring.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/nodes.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/prettyprint.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/tree.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/unparser.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree/visitors.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/tree_validator.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/typechecker.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/types/concrete_types_inferrer.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/types/types.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/universe/function_set.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/universe/partial_type_tree.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/universe/selector_map.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/universe/universe.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/characters.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/link.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/link_implementation.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/uri_extras.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/util.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/util/util_implementation.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/warnings.dart create mode 100644 pkgs/markdown/lib/src/compiler/implementation/world.dart create mode 100644 pkgs/markdown/lib/src/libraries.dart create mode 100644 pkgs/markdown/lib/src/markdown/ast.dart create mode 100644 pkgs/markdown/lib/src/markdown/block_parser.dart create mode 100644 pkgs/markdown/lib/src/markdown/html_renderer.dart create mode 100644 pkgs/markdown/lib/src/markdown/inline_parser.dart create mode 100644 pkgs/markdown/pubspec.yaml create mode 100644 pkgs/markdown/test/LICENSE create mode 100644 pkgs/markdown/test/lib/classify.dart create mode 100644 pkgs/markdown/test/lib/markdown.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/compiler.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/README.txt create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/apiimpl.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/closure.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/code_buffer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/colors.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/compile_time_constants.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/compiler.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/constant_system.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/constant_system_dart.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/constants.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart2js.dart.snapshot create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart2jslib.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/backend.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/dart_backend.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/emitter.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/placeholder_collector.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/renamer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_backend/utils.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/dart_types.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/diagnostic_listener.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/elements/elements.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/elements/modelx.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/enqueue.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/filenames.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js/js.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js/nodes.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js/precedence.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js/printer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/backend.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_emitter.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/constant_system_javascript.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/emitter_no_eval.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/js_backend.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/minify_namer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/namer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/native_emitter.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/js_backend/runtime_types.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/async_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/constant_map.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/core_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/foreign_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/interceptors.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/io_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/isolate_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/js_array.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/js_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/js_number.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/js_string.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/math_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/mirrors_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/native_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/regexp_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/scalarlist_patch.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/lib/string_helper.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/library_loader.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/mirrors/dart2js_mirror.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/mirrors/mirrors_util.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/mirrors/util.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/native_handler.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/patch_parser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/resolution/members.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/resolution/resolution.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/resolution/scope.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/resolution/secret_tree_element.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/resolved_visitor.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/array_based_scanner.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_array_scanner.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/byte_strings.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/class_element_parser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/keyword.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/listener.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/parser_task.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/partial_parser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_implementation.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/scanner_task.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/scannerlib.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/string_scanner.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/scanner/token.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/script.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/source_file.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/source_file_provider.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/source_map_builder.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/bailout.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/builder.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/codegen_helpers.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/invoke_dynamic_specializers.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/nodes.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/optimize.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/ssa.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/tracer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/types.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/types_propagation.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/validate.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_range_analyzer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/value_set.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/ssa/variable_allocator.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/string_validator.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tools/mini_parser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/dartstring.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/nodes.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/prettyprint.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/tree.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/unparser.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree/visitors.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/tree_validator.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/typechecker.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/types/concrete_types_inferrer.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/types/types.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/universe/function_set.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/universe/partial_type_tree.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/universe/selector_map.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/universe/universe.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/characters.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/link.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/link_implementation.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/uri_extras.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/util.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/util/util_implementation.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/warnings.dart create mode 100644 pkgs/markdown/test/lib/src/compiler/implementation/world.dart create mode 100644 pkgs/markdown/test/lib/src/libraries.dart create mode 100644 pkgs/markdown/test/lib/src/markdown/ast.dart create mode 100644 pkgs/markdown/test/lib/src/markdown/block_parser.dart create mode 100644 pkgs/markdown/test/lib/src/markdown/html_renderer.dart create mode 100644 pkgs/markdown/test/lib/src/markdown/inline_parser.dart create mode 100644 pkgs/markdown/test/markdown_test.dart create mode 100644 pkgs/markdown/test/pubspec.yaml diff --git a/pkgs/markdown/.gitignore b/pkgs/markdown/.gitignore new file mode 100644 index 000000000..aeb4e1395 --- /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 000000000..81764fd4e --- /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 000000000..2838fbd3b --- /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('$escapedText'); + } 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 000000000..ef111fbe9 --- /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 refLinks; + + Document() + : refLinks = {}; + + parseRefLinks(List 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 parseLines(List 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: + /// `this is a markdown`. + List 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 000000000..bbee705a0 --- /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 CompilerInputProvider(Uri uri); + +/// Deprecated, please use [CompilerInputProvider] instead. +typedef Future 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 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 compile(Uri script, + Uri libraryRoot, + Uri packageRoot, + CompilerInputProvider inputProvider, + DiagnosticHandler handler, + [List 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 000000000..8fe0a2970 --- /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 000000000..663e003bc --- /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 options; + bool mockableLibraryUsed = false; + final Set allowedLibraryCategories; + + Compiler(this.provider, + api.CompilerOutputProvider outputProvider, + this.handler, + this.libraryRoot, + this.packageRoot, + List 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 getStrips(List options) { + for (String option in options) { + if (option.startsWith('--force-strip=')) { + return option.substring('--force-strip='.length).split(','); + } + } + return const []; + } + + static Set getAllowedLibraryCategories(List 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.from(result); + } + } + return new Set.from(['Client', 'Shared', 'Internal']); + } + + static bool hasOption(List 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 000000000..4ed156409 --- /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 closureMappingCache; + ClosureNamer namer; + ClosureTask(Compiler compiler, this.namer) + : closureMappingCache = new Map(), + 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(); + allSupertypes = const Link().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 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 boxedLoopVariables; + + ClosureScope(this.boxElement, this.capturedVariableMapping) + : boxedLoopVariables = const []; + + 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 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 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 capturingScopes; + + final Set usedVariablesInTry; + + // A map from the parameter element to the variable element that + // holds the sentinel check. + final Map parametersWithSentinel; + + ClosureClassMap(this.closureElement, + this.closureClassElement, + this.callElement, + this.thisElement) + : this.freeVariableMapping = new Map(), + this.capturedFieldMapping = new Map(), + this.capturingScopes = new Map(), + this.usedVariablesInTry = new Set(), + this.parametersWithSentinel = new Map(); + + 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 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 capturedVariableMapping; + // List of encountered closures. + List closures; + + // The variables that have been declared in the current scope. + List scopeVariables; + + // Keep track of the mutated variables so that we don't need to box + // non-mutated variables. + Set 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(), + closures = [], + mutatedVariables = new Set(); + + 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 fieldCaptures = new Set(); + Set boxes = new Set(); + ClosureClassMap data = closureMappingCache[closure]; + Map 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 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 scopeMapping = new Map(); + 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 oldScopeVariables = scopeVariables; + scopeVariables = new List(); + 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 result = []; + for (Link 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 parts = const Link(); + 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 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 000000000..c3465e878 --- /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 markers; + int lastBufferOffset = 0; + int mappedRangeCounter = 0; + + CodeBuffer() + : buffer = new StringBuffer(), + markers = new List(); + + 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 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 000000000..42b3e7fde --- /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 000000000..691cbac7a --- /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 initialVariableValues; + + /** Set of all registered compiled constants. */ + final Set compiledConstants; + + /** The set of variable elements that are in the process of being computed. */ + final Set pendingVariables; + + /** Caches the statics where the initial value cannot be eagerly compiled. */ + final Set lazyStatics; + + /** Caches the createRuntimeType function if registered. */ + Element createRuntimeTypeFunction = null; + + ConstantHandler(Compiler compiler, this.constantSystem, + { bool this.isMetadata: false }) + : initialVariableValues = new Map(), + compiledConstants = new Set(), + pendingVariables = new Set(), + lazyStatics = new Set(), + 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 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 getStaticFinalFieldsForEmission() { + return initialVariableValues.keys.where((element) { + return element.kind == ElementKind.FIELD + && !element.isInstanceMember() + && element.modifiers.isFinal(); + }); + } + + List getLazilyInitializedFieldsForEmission() { + return new List.from(lazyStatics); + } + + List getConstantsForEmission() { + // We must emit dependencies before their uses. + Set seenConstants = new Set(); + List result = new List(); + + 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 arguments = []; + for (Link 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 keys = []; + Map map = new Map(); + for (Link 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 values = []; + 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 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 evaluateArgumentsToConstructor(Node node, + Selector selector, + Link arguments, + FunctionElement target) { + assert(invariant(node, target.isImplementation)); + List compiledArguments = []; + + 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 arguments = evaluateArgumentsToConstructor( + node, selector, send.arguments, constructor); + ConstructorEvaluator evaluator = + new ConstructorEvaluator(node, constructor, handler, compiler); + evaluator.evaluateConstructorFieldValues(arguments); + List 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 definitions; + final Map 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(), + this.fieldValues = new Map(), + 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 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 arguments, + FunctionElement targetConstructor) { + List 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 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 arguments = call.arguments; + evaluateSuperOrRedirectSend( + call, selector, arguments, targetConstructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link 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(), + 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 arguments) { + compiler.withCurrentElement(constructor, () { + assignArgumentsToParameters(arguments); + evaluateConstructorInitializers(); + }); + } + + List buildJsNewArguments(ClassElement classElement) { + List jsNewArguments = []; + 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 000000000..4fef80535 --- /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 guards = const []; + + 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 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 comments = new Map(); + + 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 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 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 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 strips: const []}) + : libraries = new Map(), + 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 { + 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 000000000..0fff58025 --- /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 000000000..6732b8cba --- /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 000000000..ad5dc74d6 --- /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 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 getDependencies(); + + accept(ConstantVisitor visitor); +} + +class SentinelConstant extends Constant { + const SentinelConstant(); + static final SENTINEL = const SentinelConstant(); + + List getDependencies() => const []; + + // 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 getDependencies() => const []; + 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 getDependencies() => const []; + 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 getDependencies() => const []; + + accept(ConstantVisitor visitor) => visitor.visitType(this); +} + +class ListConstant extends ObjectConstant { + final List entries; + final int hashCode; + + ListConstant(DartType type, List entries) + : this.entries = entries, + hashCode = _computeHash(entries), + super(type); + bool isList() => true; + + static int _computeHash(List 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 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 values; + final Constant protoValue; + final int hashCode; + + MapConstant(DartType type, this.keys, List values, this.protoValue) + : this.values = values, + this.hashCode = computeHash(values), + super(type); + bool isMap() => true; + + static int computeHash(List 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 getDependencies() { + List result = [keys]; + result.addAll(values); + return result; + } + + int get length => keys.length; + + accept(ConstantVisitor visitor) => visitor.visitMap(this); +} + +class ConstructedConstant extends ObjectConstant { + final List fields; + final int hashCode; + + ConstructedConstant(DartType type, List fields) + : this.fields = fields, + hashCode = computeHash(type, fields), + super(type) { + assert(type != null); + } + bool isConstructedObject() => true; + + static int computeHash(DartType type, List 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 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 000000000..ac7d42253 --- /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 handlers, List argv) { + // TODO(ahe): Use ../../args/args.dart for parsing options instead. + var patterns = []; + 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 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 options = new List(); + 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 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 categories = extractParameter(argument).split(','); + Set allowedCategories = + LIBRARIES.values.map((x) => x.category).toSet(); + allowedCategories.remove('Shared'); + allowedCategories.remove('Internal'); + List allowedCategoriesList = + new List.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 arguments = []; + List handlers = [ + 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 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(); + 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 { + final StreamSink 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 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 Generate the output into . + -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, --out= + Generate the output into . + + -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, --package-root= + 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= + 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= + + 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 0000000000000000000000000000000000000000..758a05af00972ddf9d8502302203a4eff33c5857 GIT binary patch literal 1658537 zcmdSCYgE)%wlB&6jRq5w?mRl_o6|`&CigejTK@u* z?!9xy{&0F{#6N4z*P3gsxn6TU>R*q<1q22J1jKt-z`}3dxa|rI400xVw!7zqZgr2$ zSy~q2tJj&W8D}j?00wN_>fY+UP*joH*Pw4!a?;Y$j+vTm2E~$Aue0jXYSU_M`sVsH zOSA600+89NRqFLtlUZxf)oHC}oAI2aAY9soI#azOILee}MQ2fp^0s6MK1=<%RI8yS ztyy22R--ps6|+&-oK|Ntv>;(~n%>aTtQZudRcFj1 z7hO~Tvuo-vUw+m5b=TCBuBoS8Q{Qw=J^S{%uBialH5G_|LHHMpe{)#ZNC*pMJz+dB zm-U3Ru0MS~kFgLI!R%eb^Vzi!7RkEiu?4JaK4TwsN3!0Jx)%UxpM13QV9`$+}lD zmcZ=)Z6&**VTlNKUH^PFV{6z;^VzS~vR(v2*sqe1>}Ts(4~2i-HOc0)_3Xh~0N1hZ z4Ga-z3MD^_6pAIY*EX{56vo!E-c8K@x)#V(c4Lxf-~kDXW@)S^lBw$7LbQ7`dvPu6 zP6zEI%V1kr&sNs8g}uaK2>ciOwfi8ux`q97Cj03xAK9<1rN~cFKD-FtNQLg#Ki-D? zZ>P}bSv(t3`)roOde{!uy_3C|izIfj-K=L1)}M;%j9(B+dZe@15N+TMli>|))=8Pl*6tO_Yz zKdCa^M*=UNLbX8mxeUhiiy}n)Qci=|Ul`TIc^!(AvM{bQjMb2h^uEA;r32SzwY+e; z>lo9q?s^c_vTlWP{#e*VE$eL{)u-?m{C&RX|2WOFelpRt64-&Rt>tO5(>tq4;Kzoo~ z1Ip{yuRlgsgYB|`oMpYx|8XPMAG%+6EzY_9!52t@vo&Bgb2 zxcx>M>z-#vAq{rjjPx<7RaNK_8UlK0b2NA*XRv2i6}d764ynB7usb*MwGYm z9{O{28eLAby?2h?-b=wn_FfiaxBm>uEJiW{7&`-8V(+;pfJ^PYCz%K?o57QA0vKoS zt&sTT_TFPMxWe9BE`uv)g7Nm=MhRUtBP+q5mgYI2OdwGLMW#ift7l-V?J`SiB#g>Q zP_4E1R?6g(W|UYr6I^faZIT2VW}wM4!HqM)6nn2;(r!XZqPL#aC0`e5YW=u{cIrLR zNJaa$|FjETFFIZHkZ6PlnLX8x-U78u(L?N)bJ@>}VPg=PW=C`Fx{iix@7@f104N<) z0+7yr1;e@aSq99=-o1s)s272B_AdZ;fpn{Cei`=OOuIC{&(|}yi*2*_X0Yw_$+Gu` zuxxwpcilPm9Y_L-lrh2s3mh}zt4^^`(yC#wG%H|PCjw%1B<`cDnLD=zd6df(bL$wgv9;! z*8nKAzka}8WIt#>WIt@rv=`f7J7Px{-@BLf=Ew#nyR{HjV(&R>N686oLs+T3XEt$H znVpr}E9{k!bj*HSw%ZeS8vLqA-IG)@r=TJE1~p{&4rkPH##%6fr+{}lD33aOz0~Cn z!T0i^O2ZU;gNnEheYzX%zia|081(knc>MLV_H!VD_|KZ{J<|p|L-v7Li9+l)QukuA zx7fQ{5rX2U?KHNAFtfeKVz=5GSXYa^+h+dYXTLmRA+gTnZIc&23UR1=j3Go-a8O+8_3F1ELZUGtER0!*UPEZJ8gu0-A z2iO1l6+7#+2VAxP3`m3#>Y`jwl;`I9aNyNz6WR8zz^f3K4c?%uKnGu)b5(h;f;%Gk22-EjyK2>neqLwAFG(U&M_#3R10>(FK^7lR%|sLA1wN2)+~ zQu+Gl%Sr5~*P!zB0wH#Yt+?8|5~cMiB_4lu71#ax5K91z*hw9Z15wxYx33|kL<(KM z{*#|n(Gnvg$92Z*aIEoEdwK%qeD=ilYum4NA{hNj^eYN28|G|v53CNJThwo9GPUUe znUD3$;K6g*W?!jIu_*=u&E4vr++Sn8;5w`~o>S@%=qydex|UWcRWgmt-7)v1ht@$k zdQ+RHU0^#TcJxeDa;uYLZDxI^C(z4P3h{~*M;9I3{(@Jatqoz`aDk2ldJa;5c(}Fs ze1B@He;LNtTT$L$I3`<~pT@ z7Lr^}f;Ul+B@x-Yt?oZ(tq+@=MOD*cHl5ekD`rl<-kU7QwNmQ+hWYwRH;FUqup;F| zY2I`}lqrcmJa%A1irZ$@H>aThYLp9Bvrdq1_ht*yT$S|5;`3TBa*O}53N`D@dMS0c zHxD#aMMI%s)*bG0t66Vs-0dk40*X|HLNn4^skmnSf5=;`ieG51jq5L|tT?O z(560KUUk-^Ix~tGEUl7b^ypzjfLmv7gfGBowMppU+?<`+{lz+KT~qGM?W*vV)}jn= znbKHrp`}CQG_Vi3f_**CT2Xe%b!LP1lumoG`m$D=nv$EmA$4PNa9;>WNZ+x&^Y@?j zHt5Y3E0-HitbmVSsSMP6nv~|2JY)TSoxVA;k6U&$;bBXkmTfrM&{tqKo6I?yO5-`B zsm-VnrKDLM(-+IBpIN*XZ=7I^Cq}aArm&{H?$xOYeTf|LGzo|RAl&mKOP9MdyveEt z`vO+Q`}_rDZPEfktzzKp+42*8DB`AcB1Jx@vetI!`;Bm2oL4GLm1g}>ATx;^IT_uv z>O`wsG1gm->#a@MLYvH+nKyGqu=>^NSMzxASz2FOUxAeQFk@kxr$uMBDETIX0ab@i zLTUe95ur(HbHkpMZc(LXOEE!7wPksc%llAm=Sx+(g$XCye9|5)m4I?9vF^CCUK)hC zihyAD?JKHJ=+o8LR|~~-Qm8j4_I!Zn%!ax?MP*-hqAA)XT8Tw3*#}a$S;JjtO?sn2 z@V83-f&B-TA9W_Ly}ZHFXA`J)RrJoJ(%pT@YcKV65Cr)eY&sWu7mcW(EYK%(9o(>} zxUAnmMi(MMV{^(k&+l(En`|xlCY#Y(`fA>9i(d}+k%c^s1QD;#W39DyeVd)p0vxM? z7X_d8rMu%e^hrFRRD;vI;`hdQi=TLe{4Bu)^y}n*!G+8xV zt@XZSLPPtJJ(`Gf{nDvr_^lRTD(*X%=QSkz^e_Npy{XNTF2SR7jz{ct9c|WG8%$2~9V)^|uLeyM1{CrW(AmA+y*6`zufZ zOEQ-2bJexg7XiRqpce4E*fhP_X3R_fr+YcD1|G#x*-FgndAddK!MlgV02 z2okNixJBbO*{qc$nzB02GPllcRxGCG^GcOauUbkOT9mBo18wSAON~z_ffE}qhlG1X z_m5$&#Aa0lmtGQ#SxueMF2R;+(6yZN8Dv`HO^ZU$d0TXK=X8wM=96+hT<*v4c2o|7u@XIbP@8_lr8hz$6%|J)bbEhU z*A{ltwIAk!9v;@QPp@kc5XpBsA6YG9`BzO4MShXhJh; zY)X=R_nV`0wEY&vdYF%p+y;_W+&;^mw}Co3QxRGz@6MdfF&A9Gp}3)TI>2Nkg9nau z=3+ELFP6!R^jW#D+?Op%HmG^a@s{TzPmq9CN+6JY0X8aUtUFfi&7xCiX_E* zyOtgc@@ciA;q09R#1-eS2Cze-ispD2hXwu?T zf3>Q`qNYXYXB6u`rNtt0rvIHzGLBueG2=5S8jr5I+0>@g&!iGB zQ@K+cQd#B_{`jJT@5fxmRavawM4(1EV)OQpQughmpZm< zCG+6w(8>&#)dVflRe3?l`wdRBVlbUoGKJRJQpV89Q~Q_pmpAFmO8t(3T$v>`0nOY^ zT9-N+JnqLT)MXQ!yPru>6D* z{N)3cQsk#A>*FsI==p*YV{4UQFMg%?m1>etjb&|9`C?ZCCPX@cR7lW*t?q-)fWo6$ zUO6MO87;P!7TAkYuaUjEMlCSS>c#;*XMUD4kTPHpa#~d#H-!d9_pjchu?*M*rd_J~ z#m-~t2i!AqXN*LHQq0j@8t4#Gj}IIl2)reF&|AvfEU9-f*3mxNLj(a&#(u@RX+KQd$6?3i0q9h4?>C2@rCySu%f7t$q zRs@xom6y{+Fq4?cJSuiy%<*WoSg+JJE84myr4GwFtpS^2+izua+Gi_nt+kt};Gig>$dtA!?2T_jMhr#@@@OKbv1Iw>onu<9L2uMI zw8sp_3T&KA@Zp8z+|RXIOy=qoEuR$Rrfys@7%!+2Ws-lnQli}nE2-C8xS7n1(MY=4 zWVWmsOp?h>%q@uD+$YQ_pW~8+EUlFF#r|bgiBMl!+z&*h4WNvIN?Q{)_R>8<|e6UIoSF4F%jJ|jp#w@2{RMRzsIzgjIn)~ybVoGPx zm@rps9MlUU0}*kHJ`u2QdEZRMiS-`L@VqPmo`gkjF%7l~RvWSMQV*Tqo&k-`%2~{} zwex41VL*UKVQA5*eTcO1sC+zc)^iKcx3ySbMKL=7IjE^u3=!|)UKgo*3>E* zqx6`GTgUc&YjKa~8DTR+ND2=@A!o02wrLSZQ>A6HtUBaf|uzaT+M+?ZG zgyBv2K`c3%-M8}udF##An*}6~3N2NW72<{esYp%pI>Nj`}l8R$x-jS5q45JvDlZ#&o-tlRY!}Og@_= zo!)chN_c-AMp0cIyhE1bW<4BB#&(IG+L;-+UM_cehq=#WR65l3R~?9I_R)!uz#(CX zL!yAc*tm6Zh3dM==LbMIvxa6hsJgJBa8Obajc=-mTI*>;qKcRhMG8sLBnkMzm8&kz z^`xRXSz?A_1ujnF?rhswt$0Oe4)a^sr=^%s?5z^Q-+=d3%s%g0xW zr9_JCO$7w+y6k~1k`JoFbc8sRN=ZMo>CA#)pBR6uR2lORr0sOKU;<^W7K-Rd6r_(e z9LjC)pJ`onLyAzkWT<3FFK7)i*~yce4upC%{_?h(G^U|eK~wZf(JMB=d3@;jP`gZc zEUPx(NWNA@XDEjUyhDn*pL1wYrd*J#>)*g(Y1A7oM;N{24-O#+l)O`GOEtRSrc;a- zxJ|bW@aGXHIQ3LZbSbbyx`)>y-;74M~D0nS=p9w5#O& zM>x=gg-pt@R^ZYlZglzH6D#2jgv3bE+AADg(QA2{>MhT%zCya-TC!E{^NRBd?P`fJW zNYK1XeHJn*f>8N4Srb-zOh)RV0!JkO;zKJ|U{y`q7R9=j3SDE!h?pG`0Zr&rPwolo zOH13M@e{QmrOL9yxe+6goc3A7aKvyl=y`KoeOfu~sUw$UtO_ct-hbIy*No*voJ@A? zsIBpwiEGheDFj@@FJH&PSRX}s4hl>66V$V@@g?dIjl4$|0YkAWC5Wu zWbbv(3rPreb3A(EdYhs_5AprgOOB>+q(~HN`R(%C#grWBd~E+oOTQH+Up`zaRT$lS zKx=Z7Wt2m^N)@`Vu6U`NET|acPL+fXX0@HKavv`%swk)?5K3vPqUd6bZ`Q~{Qgo93 z&Zf|XyS=>52&@p8w-Bv)12zQoRy`ox01t+A6rjGv8OCC)_zhCTNYJT>Fqd4%wh}oB zIJqn27LB(yk;mPh-JW()P8~!-dKu@PKZG^Qb8DN60V|8#w8e?@Zje)H%9`-6Fsr}{6Qa0(xN%TlUPC=*y`TkH5#?a?n;_|AnI+#6`IPi29vt?V#%4O(P&LOiE)>vP%)Ah8+F~J zXhe%gw*UbGpGvfK8u4F|(V}1)ha;s{y#XBX=V8Fa3fiK<=7&bCa5QjOf(re-MYBOJ zQ#7<{N#?Seq^x~u+_cs z&8|)uBsf}j()Qdg;(KmIDZpID@g}TRsS|3zq*|lSpEto~bTl8{q**OtIjO0st2H>E zjIPZh#Lr$#a$(ZP%B_fIyr~)uZT692aT^qsO2T2(vbzw=AGs{Ga52QHDxg6Zj>#t+ zNgDDEYfc%^y;MVR1sSyvNb`0qo*Hb;WYt^*UAS}8PxOg{dYN3w}qhg~* zZ-qP1h%_v=W@Oi>sns_&DrRbGT#Tg|{z$WIv){`!E!&>UHHob^@%^wywZ-#N;B_Ir zC7i?GA#e{0)@jeD@B@bvXWA!3W3|s4wa(bCQ5{lJv%ls35L%+y3&#r8_Kb@}?HH&| zD^M>D7>sdN1BD}-AbB-KCGiC+^&9@Wt5?n|&88OG9OvB=mb3G)k`=DzdMZ$zrol!F zdJT+}CZ;)}Fhnmnvt7YeEa8(O(YmTS631&LWySjmJDkTo{djeD#MG<1dL>>U`e|_M8Uw?N~4A z>ol_DVT)A6($2VkYMuC-qv&MxqE|91b>yTlV>~r!EXahVpRf{twAZ;Q!d(W7=`;rs}P8<$tc z9dl?Js$)*0I_5O0WA5MR)42$L-@tX#)lp0Qp%f^L@r8O8qe;_jg8Rv=f!k6qx?OOY z%)I|bQB$YHTN@U^jX0l!KC@Ay?oeyWg{uqRJXGi=wU^I8$sPe!I}0z z_63D`2gpVID2FSxc3O#Hu;d*NMA~Rh40|&RV-tpmrU(1ty(q?C^Y_P~S#H&xGvQs- z>uWT5)D;_a8jFIxaPs>?Ym1f27F{6i{K;IjtDAImGEHQsUTM%_jOJp{XjbPp>rrB> zfjufseHITN)G-dBEn-M-*R=4H1!OlmBbP9HUr=hJuOf?!;lf#iY!wzuYuB+Qce%4zM^%;RqK6^3M*H`7umwWR8bmpXne z(O{;1=lu2~XPo{4ES(4e9$Z+__6B^gbxNj~d1n&>E2W3~cISllse82QiJcv95RdP!t>gYK*ajCf!7I4kaIc8|0I z;j?kX5lT036e2_DgLEC?qq(mK%-Q2~E%inM z=8Ybun{!X2O1$|Vll!GXWFhi6a5=s+7c0((KBx_jRwc*r}=WCIK^; zOagJ8&aau41Jb$3&2Qzr<-~tfAW?6SVwu1?v*|;M<#LgihrKSZGvBE>u^6Ksq5}{vD_Dly~>2#a%>+6w}AyDCI&rR3Vk4ur>H~{#?6pdx3L<92hrggr_7~ zle#H{DjdC8&E?(H39*jyQH7^|bW9mjOSfi#GNC6%1Lvk(I9fTK_3q&@ z(vmV8F+_AWlGHB6b#OI1dGWQ}(Xfp_Q9hv(Ii$LZSMiXK-BC4#3|KqN(y?nAn>88A1?HQ>mnWeeMTw-( zE|K&ty4r2&wGEk6BH2_Rx%l9=vBQxkOQnF9%AY*ERL=Dm5&Ooi?kso1nJqys@`U~U z#m)j4D4XW83!awOdLlrB#b7 z!$}^#*ON?`|33GbE9NzSXSM#{4}j&{DWWi;l^?QiD4 znerxGoev1jLhro86EjM4n}9i47#EW6E_KZhUF_Nx>k3M~dvorxf!J5tf2Y0suk+`O zxnn#Ing9t(9NG1UV>~qH5pau_ra2_r=SaUx2oiK5Y=Fy^2zPTmbN zy#c{A6%#y)ET*W#&LF)kCesK;9zTV}NAoiw#~^E{l^f!ymm8u@=4RBoWwXVAw+tSmwWo_04j=>&7@EHiF580!G=9;3IVmvhE zRPBN$nKY3YbVQDkU6eV?oMadCVHeS3qFIigIifx6CA*M!Al8h=cw!;i9Y-HXkDuYC zdE^tPGZDOeWM8!TM4}JJWM_CS?j*2zlj#FqE$2P%Jnm07UGk1)oGY|)dlcKHYeqB2 zMCDgHgLaQP9NCn7uGArKMvl~>K$TNpcqNFA@#d& zO1;cUg)?Xka^j$gJJku^-SCHKsxIoOjnBvhrwL7-rsg6R|7wkwCfX_;O|k{XAXCV9 z29;9^H18Ie?PCs_VhffS56!eixcoLvsRdCh*s0TbGsreZT~6DZ`Dn*69-40pio)9^ zBQ(hln3v@makTR?47?lY?^ZfchO{ajy&T_I&a06&lRHNP-x8JhC@ADDl5G3+rJ?Y* z{&kM%%p%`Hokr6KI>)|6HW_+1RP-9K$+)*c5#jORCH=RCI6P$Cw-39(WE}J-E43jI9v(ETt@0F)^!WMGD=*y!I zxC=P<(!1$*`HBi@pXwu`B9dM#lPKjxTkr1k6KxqGqDsP7Nup{_RCu@4-(xQ2L^|Nz z3Vo0qt#3qa=-(pK3%nbCX}EJV_${x2$3yQ%$ABxsJzS@l)LSs%Fc~B*59R-kRUnKSJFr89>GeB9}%Ku4u#x~ zC=ac6M0m|DTGohgjE7b?B3yo(Rw+W_IgiOhbLoJ^S*~fQ$p7njOD?$s$Avu%9ZB91 ziUR?3_%mc2#TY(L%jrw~wDvve))Lq5bhd(7R-9^#?7BuFnj5n?m4*q3&1zsAq;Qb^akT*dl)?_63qU45VH?< z1+wnBcmRVg5{n?QfW+SQT@PN%x&HXOuIt~1GxqH~2qGPj3|`W~g=F?p5c@~)M6mx( zxR2-Kt(*mT%Vr^qX0MTy*B62R+Ze9Rw~HCX0SLVovtL2}wQplt&tmrfEMZ;WE@eG2 z?4M&1T1I47mm(WHg=L@vCos773`t8_*JAd6QuO-c7?K_b&6cxXuHEB#>|X-pD?3l{ z6w8y9K*ZzUD%P8TOVPbg5&>L|tJT*&TTZY2;Hd|D*Jo>352Qc{kFCY)BT4j9&TCI! zU(a4!PGv!XH9W9^B?Cow?CDhjJcWa@3nd{NsYoE?pO-NEOW*NQ-$k{dRCr*Os4Io_X7BFN; zfYWZD*3`%Yrew!;(7;lL@7U0zz>zZx#N(By6{=mPG?}m9XP{n_h=4w(tXm+XEcE4-)p}|gX_h9-|KzOAx&L> z$Gsh{JAo541-j{2I?nkVH$wpMV#*j!eaESlpbWX)X~^~rxEW&w#4d|v2T?Q!-Ak8siisupek(^Xk}k2%Gb*vt{|1K9k< zDZ%6kQoC(feXveTnxy07gxZ-CG~Q>Eod0zGw%iFC_|2p0z~43XW!KbKT~l8_dHT(> zuBmUo;jh;{`QGDo^mZK{{-B}sGdy0$rZJ!)V(-Qab`h+5J`GEF0B$a$7ugVw!pOFe zy%f!U)xC&etOf`pD$tAR1-jl?)=MzN@tz1?fte0r-AfspUPf4ZH-<i%b!Ez0cXuYrO+ z){BR8UeCfyDX(R+?i}{|4veMi*gtG$cm)qHuUrNDPH6Yjp6Rs=Z_&*#^P>BHD2k7U!@d(w=e=)-joc$WIh#aD5yB^~?q#FNYcQp|40^a#~0d^|&;X5hC_7W@st8p;&47HD40{&7F*Wi3M!#rsk)M?4kC%@H*=m-BsG!Ss09u8Usr zL-WHMdO|=iED-khNpB4oMzVg6N64evap!s0GZTX4(zOsV6eWkCq6ATKBRV<2diTMegMedN`p@U7wQu{F! zW6Y)LW8m9@e0<4*B?tO(Fh;3A%qKM=5;3^H#}Vo3Og>I4TH>Uz@v})QFMBGj4O#mXI$C6omx!@V zZ3Q*1dJZhZ0iT$D!*A1kTYTGni9EF@yM4QT`MyHmA)o$tb^VR~2}4Peb!c7LnL_V= zevB?XDnM6*5>axbVjs5Di33+D$ z^G0k~Xx49kJDXEKOL;rx?OefFd9(6n;_u3b()#j956pYK3(FQqxlG0ailY>!4x1~? zefu%-EGj8bPuvwzdPLlmAGpk|TqqXON(l^}4^QUDmaBIt@^wafJcJBEv%384LqnB< zxJs3gn!YdAvs!cN?P@9IX6a2GrB1W$rtpj7CQAXGzE=cVuktk;1Iv8LspsBK{*6JB z-#MDzdd#ag81Qf}&ULJxD7<~>b_u3mr*5z4ZzV>^91WPe_mEGV7E#P~x7OUUNzB;V zwWb68dJ8|fk3$ga2an%sm$<=}uv4r0_UBdP9qv#;d-R=i`t!^3$`1ri3fUtIqvsv% z=f|3K%^?yvxG*t3(Nj=XR#FyDAevKeejzT$Q*>m1Nu(sgqeyc#?qdaIdrQg-Y6#PK zyYkgjL(!AGiPI!A)-NyAcG}%&#<`a`fsI!&o7aZL<2F@;DJNkvQQ&CCn8bs8BAP6q zTFEiAX?15Zzc`npNt;aQ4<1ez1euax?EISKjdI5qhZkG6PiCu$UC^w!;BC<15Miz= zEp<_2E()*+8sy)LemRfeG?`vj6dmTF11SLTdfnS(`0jXK*^#0ng+-Ei#29no9Rm&{ z;aFm^WE?oW<8XodaM6*9QlVBQAz)(nn_AmX`YoOA3g&2zPgaS1ojIr>ROAc}7m-_#3oy0Bvq&{kFa+|YI9?i{h;qT_qE=4Q*}_Iq1sw!Qui=D2V_ z;b6lgPJXo0ho-$9lYFv0?0enkTG1 zJn5itXVM3fhG=J3OypB05T`v}vEP;bj+ob%zX_-eTM*8Z;YNfDWVjRIA{kEb6m!|7 zGLB`1qf&+o5w4QqI)tlbSgaHrv;ixY3OcU()Tiocit-_a7tsa(DnO4!1`PPnDxmcp zS_RnNfjMmaPqF{p{tiw3FT4Cx|1ZB{Uw&=B_IUaU``X_9lt%b3zhPfK6QF1I?r#b7 zT>vJu*CxJ%Gwv%k9mE}Ya23%!;qml$!q;~rjP*Xd0S8qepFVtv5XAiKSvS@RaM=B~ zXE!47aP&OZ^^>1;p!fn-Wx2j^IL`G4-sgP_O%y^zkfsm$-YtDw#>4N6hBJ`WmY43K z`<2PRp>ZOT;51oSXD)o#Dz97;Iv;~tv}E!fIchZa<9M}cI93QIFGHNldNBTQybA~U zn~9Ic;&{SPtKJznf18(&xwtq0SBlYUIW(LoM9_$rBt?v$%{kKP(Y4@8RDC&IEKS?{ zk_8n#Hl`Ip>NC@MkUut7LT#7Kk=kyIC-XjN_)3TS6FeR!G!boM~B z#m094sq#m4_eEQzHmAriwMD%Q^Y%mg_RqQ7xln`9ZoZfHekmV^hFb4aZ`1B?N;cB6 zovU%v(Q%)eQsD2=l>RdvWeE8_HK7iVAUDO zSP**+px3zv9O$P%L<1zy-=KwkiDm7VaQR+)jP-FCTHK4F;xF1MwXkm~5o)d9axASF z9)F8j`h5oGCun=UXnQ|b!%qRQ|NIGj=r2CSN5JRPvJT64z#|DB$$tJ6Yxoy~>2DPM zr8JNj6tg2Q2q6b}RQcLt(taV2c1Ke`v539&<*%^HCpeb*$aNP7!Szn%AVEv;N`22V zhNp<5xi371fg271F5}NY!fEhxq!PnD^Y#}Jj1@t=Ny`2Ut$>!Y7nietWY2z%zzQl` zQl;zqO1KGnrdOhv@F@2ZxV7QI|L5hbYZZHmi$?(7Xt)v*XgLan{ZvH4_4pS~?xzZ7 zuR)Df?5D3&X08iX4(v{3yqX>Z#=Q~Op@RtgenpR8HK&6D=nU}rM=%pg%h`irc zNnt~0FIbA;e>LD5Wi{9QsSc>wE3k9-6oJsI2;qY_2xzMcl}}if+_RBvn&UOiU$Mq# zsV#o_RA0L&l@6fz1hjt3xpI2VwDb3k<2cYlLjlc3#84181&3$|eR%)raMMvJ(JNc- z1Jv%1hEIuJc?Bj;kuprZa!bF;s5PJ|}zB(Ba)NAeVP&RP<%_V4P1Rr(ZlzvpkE`yKh8f9d%W z->-VU?izXW5<1{tJ?j~{_U(<4Z`gOe0qh3mCA~q6qQR_p4!eP#HI&h;WiGpMuQ#0C zz@zZjL915?dU-FXYBroOoWwY@3 znU3D&9J+!-v3-cNXdiABhbHhix~bko9>-21_TngQyb%vdJnG!Lj>qv*HsTu)Pv-HB zYATc|k-Zdl<10IUjf2>=vyw{%QyPbER+&&J_!SO30Z+7B5ZcN^aD(=49PxwC=2P~tE4-d!96pC?L6l1t1Uj)4#A2bjGa0|(>}FxR_=zqvN? zbRV3YPqE!Q@f2H|6Hl>aJW+s=s%zqZrtlzsYX!OasCyslz4j5Nt~d76Z>wO3`lIdu z`k~7v~S4?G3ai>l$nhWnzM9+~&tq1+>_}Zm05$>fb1Ci97CJdBvt|yg}VlcGwV0ORGf_d!msVaf1CLA^RWU#c^r;%Qs1DrgSQP zk74B@r*yetHeA<4Q({F3(G$@k?ASRM9S>OO{r7)J2uC1ADm55%>Y8|3=cWuoTB}rd zXm0(Y59)A<5@!deYy#I#xcNB7>daXba=A}n`R@|xZH6=vtsyNDnMb>S%^0MyotW?- zQ4l1N3=HC5>`SznaGp>lz+>TRkmq~-gJi*=B?cNm_g<{|0QcCGv4a2L`XTRIQYUUAKfoK{-8>rstJY4`sQd_ON*CK ze%qkPbCyx_Kqr_Kl_?-w)8_FrQT6(n)XHsqpchOAVxpG1?XsoS#p%Rc!StY2P}ro{ zz0N&ZD=N62LY|i7Q;W&X(UsGQj_Ap!!L=pi~sBj*ww;dN}3pjGF(LzG3UxG$! zSUD>VE`f^fdlFP7mp+UW65>e$&4pGb%rkrRri6!y0+FO@e?d8-ck4}QxHn8Kdi}#> zL7)Wzx1xZQMHk)WiWzsg(;lV^WF~Rr23dnM<6v8|`^VX<-ZaZvw?E7lqH<+ABgb|u zU(tsL(| zG)u9PhowSZB`HP?;K;$F3q5BoCgbskRRUElwYqQ3nqS^es5JU;eQF-+1d&2S#NUvU z*6PHA8%>Q5^#Wy3`H$r+?7&SxYf~D1IKSy(t01xw5$_XDpEHH^|F|+HF|#t$_OM+D z>wqw-&5qP1>`O_P7QR$=dk3(`m;<)xfjw4=@Q zBefFg%~CAt-Dn{%RwjLaL1j~QpSTO&W+qRL*|PNAI6)LoMCfq#I744J@*Z!ha`Z`i zo`dU}@NOc}Be!>Il?9jF>Ve62%A+dJKoI|BJ)TjtjBjba@Vmw%lXFq zcMAljNNV?$b>$qZ3mAWQg79qD+LQV!K*~D znz&J#6LVJWilxgg^lKBm0BYXV2@(Y))K+|(R_qD)*{ltX@9G7{pfaB^uCF0X8vb!+ z)4Q#L#3o7Zl*I+C@U|(nH9A{8e(&Smc7g98Jo?e2&LgHxwJtw_Ki_`HbGh$)H*i{5 z_q592PaUrZ0^kkG^HI~$f-07%V59q; zXG6{?zFA~7Ae%LDpeQeZrPFbOHGd#~AYKF$Neaq(Y;%UG%2R_k%5c{0hVrr3q^Ts3%CM8Vx;naTX=0zp+I6E!>cM^&f)$gcc( zSv=W8)5StyshVtkU^-a^ z$F<4rZk;Bt!gy4|K*E&C{aYK4-j2$jq9~n=hsR=?TnYy4F5Pl=cvhE8bxdO`D)13* z+G6h{WeU=0D=Gv4!2q5Cx)~+B9tl&3(;X=h4SyuO9^AWu+Z=@8N5t#y(uE@t&V7~c z5z$d_pQG+>r`zPFD@7un`)b^)<+teyk;+FmiBvwi1|$3>k&j4i8^6N*g(muuYWgcL zlx^;tISXEC)^EabNUQvaGkKddGsnng-)@dY9^H<(N90C{CyDF+E-wfrJ|cI@l77E8 z8z;m5^wmpW|LO^QiX-4W_)G-91@t@iH5Sib!?A)NGR3aY({FI#dk&rYhHvF-EUv?_ z1h;pQ0Biz1od-vA1pdL{`~;4bAROQBy&A=UUC4Sye%2{Yg~&HGniYB@@k0aN*3?{{`pna{O6C?qx_x1oH9pSKVtl z7-!YO*tHKw!ti5TJ^0d(pRNZXK{f~oj*e}VkQ4#g#Co+7f@9OfmB#ofG2%)WkPOxf zCk&UfRY352e%k<<2&P{;oj?_I5$8wU@XL@l19`+xe%XHpINTFSu)KhSS+4nTPqf`l ze#8eSI;*16AMb1boyFIQJ;UvNMKYV*ab7GU zr6d#s=#vvg`C&LDOg~b^e}Y3}u;INB?Cgni%sNe4`J+m~Q>F4`g`Pc#Ukvj9NXid+ zPCcq7VsxmZ%knOi_p7DfI4Ue&3mGe3GM zV=Q!Cq17oKw4xvAFbI8?4lf-(G|(y{hx!lo+e9#7IAOS51j*rls&DCt>Fuz|4i0)6 z_}+8wsgU=$W$YN)F;X}g&LdAFRTAlviWW4n08^GmF1&Cdx=+3gw)DM{$vD9ludxNyJ7=lX>ZBm+^}?M(g_bA|{gsk5=Vr3DkJAM$-jL5LP)t6h6X;Sp6P# z@GBm1j^pp$)FJN0;S2?ThxGKp9fn6Y4IJFcC001Mx}AM)zB!uWvvHYo{XdW04oY_i zK6K82%jF|);QQjDUHJbO6!N~f54Zm9w7ZV*_c5wQ(g%ZP^!ub=3)k;WP3-$9Aij=S zTe2+9_1p-?$0%{$Yo{2&b|EuykmJux{QH#sH{VpxbMJVR$i@AtI`tlO#LxM?pUnkXxS$U-xuhp1nP;43E2BT3QBxgR#b2&5 zg7OLDSz6~oxL@l6uJs2W@SDe|wERa!JTbFOET6P4p3PDC{>@UZUhrF$`0J~pkHfL! z{hQSstmFLW`I8~r+^dh@RsbH;<3nd%CmipF8j{# z&;?#mBj?6wQfYG2ZC(MPX=S7LoR_Ym3R#x7=tin4moA#BaJp}*!s!~RNWt<}goo~v z&ZI||L@DNTSl*)a=)S0kQ)-un>3*oniz6TY80C+yg{ruu$D+5m9hCf7bQasuKhT{~ zL6Q6FjuE;cs_I7fHA~r+nUzacm50wj0e$$jce=_cvft*WODX~Lvs`x^MUSZi937=|HuL~OF~pHq zYhpuQa6Z3OOf8Du__%a0##0Ju?n?TgC0B9QG5%r^u~tjg(G!(>&T&?{ghOmP;!q@; zp0iC(Qi^o7-T*f48rl?;62!08|Mlx?m+rO73SZah#HCx?WB%wvgvZfNk~^gMzz--x z5HIgKhkPK#FN}-)6J2%wThcA=@DI?RN74tmi{^5E|9kZsd@RYK)_bC=v9zz;|7&Zh zm+qO|abOyVrw=$2Ip?Pf`4d<3C!NkDV#Va={_^Cib#$>U$A4eTa_OGsaWWX)TKa%H zopUdCZ4`Hmkc*n}OZOr?nZSFq=>wu%PL%4-ck>|tL}~YkC=YmF0ewJJ#EA;sv3-1S z2hnyAp+x~dR7xKZ(M=_42tG&U2ejrL4c~&~a9_WQN5{fTMkwm5=F!Q(?Z8?d572!k z(SCFvh%4-G2GJEJ2VHOy2)dk4H=RT*{?+K0>9&)IDU#`=J1AltTy*wA=8=AfCX8uF^7SdK(Z@YR0WK! zRaV7hYW306+}Qj-qu4na<*}hu-0k#%QXMng(v|{06cstSD0g=P{i zIDNn!`61b1wkzA8b~JIrf=2dj*qZMqGr%t?c1~KxV!`5$qYwDvXZU2Qi7H={R7`3;r(OtJe~6o%IO9$y%`KginIDRZ%X0-~e@Ojj>?``; z<$g$y4|rodbssqLNTUL&R?&=jsaCNnzGcV05U$p9;w|n{(yUUdSH=0OTvdMcs)-*9 zvX8gzj7j0$1v#MUCcw;(Xmn0Scyy$JBGu5(h>>a-R54f1oN`l@{52^ScPr^(lghMn zDUGf`5AP))DP^Q{GWh)t!Uz5VA5=uhAE*M?xYoEh5gvF6|AVOh$4#l4jkA8LPhH2Q zc1}kAK@{pXS5W69IuVi;s|qB$gfR~|;{HHQE!lAjwKvz2#?+gyj4G(* zCZHb5k9sHS59v_Mok<^LH=8r3dD1vDb#d{@G^pYNwt2SsHK>;Aj~~mq9LUv|D>}DdG`4ekC*HZca>gA;Ozp# zFBZ%Be9))#6DjE=nSSz&0GYm46)|b}|6;Fv5NDYc+1gI6S3OE^iCWXH?^Sl65JSAQ!HK^hO%01;=13r}?0kGRU z`?st)%{AcHLZaS5cR;BsGHE^MGGh@#VnlcJ)tQ(@mp%Lc_D`awM!jQl2Y!SxHiSN80w0kHEb^Z9I^8ZOJ?MmmJt{hq z@X?YemJ=DhnFH!*oX}51aljA7(+861astiEMjeSCQ5Uh%6K;7M1^x$b5|5{OqU&x^ z1^AMAe48hF4UZ3Kc|6w>-A44D!E_$q=ZRiFNb#Xe9zW!XKF;Y!vU$AR6L$&p5uVXp z9yfU6A|@$*hi*lPE++3I(WPzj1VzySYPuUCFa;mUMcdsX=#R!g*CYh0SfaK(1_#}Z z5HnAl3HOJ$jMJ3{0pC4BHxESTc<6rJJIC+uzE9T>#4Lc$k*oZ4oLq!)p~@eQ@zCjU z6;7wbReCxsF2Y;J>0~&Coenx4E<$unTi{Li>9n?h(s4z~2$m>gma!-gom>?0rN1w{ zo#vzSgMzD+SL--`?lNjG4x(3nBwGLE=2f9*d7tI~T~gXSz&hFCpp%DE+*I4NWtz4Q z#EhkiiwpDydettx8uDmDKH5|sp2~Y+oIGl}G>us`6}loy-l6A=+@R%5-av{_->=C$ z-^3Z^le+E1+)8{l$=uGFBfJs*R6ArYruNU9lfoMy4XfDFqyt98ANVo&L+FG2gnvv8 zV4-)RpFi?rQ3@X(Y+P>Pr7*)UPGFIcXyT8RLgKiPCEg`|A@Nek{SBGh7x2m2%#ox^ z)943@GfA>1bM_V96@GTDWS^Y(!rnzZ5B_cIOVe1C5@V+1&gR^S-b6oluH=3%pltaD zo`dIo#-u?>clT|F3fax|CuPK{ar6!J_kha;u3BxTRj2Hn~f|cc+p* zD8VYuTC>X2TBiSDX!wzQUVY!+A5-rW=nYTnqk8go@OY>M` z_+U7XFY$)Q0FT}$lE+tg!`Hk^ML!bFyJVu{i2X$LHo@MYOVcPA+FKGW zt<6ntNHt6@NWl>&Ilc|zt~c^YQYMD5kEbpg&V4{mN&$_3b!MeN4oe{lG{v<*9zMw+@kRcYh_RKw)`6AbG= zagYm;B0iK+3>H=74HWVCS7|eXe1d{lE2V!Bw1cFRS5S0R>7Vdvk2@EmozuyCzRXXA zx!>7Y*tU@mz5GIE-TUW0;JscpXDM-)%&_E2mhl$d*4=$H(~vh7{Y-hBN$1whFcnCq z(T1$O{AU+)==P$Yqlj}@+?E**(;xlVVvY^*gy74{FwaA* zC~5=e)q1pk-d6DPkznVp%6#4lRM;7crC)|gG3`grCQzVDddTYrzt6cXyDzk^Z^lhg~cQ#?o;Xpa@`4G zla%M*#`AcIJE2g-6M0scVOKNbDSmq8XXU~;#Ljqu!0Ki9E{%$fws)q~ePueqheLqXBqzUQtSkpi;$G0ne*t?TIF(Uuni~;o(kQH5Y=X z234}9UWJ47D!`zoYi^3c&&FAFt%Ak&DeZ3Y91tNZWV>mB|l+H~JHHEYxTtFODJo_zJRYwDY?=s}jR_=7B8&3ce!cn%wT ziev28zQ*NCoOu5lx4-DLdpKRN#5aO<&1Ww~vR^G=UGzkncpZcQcq9$C!T8blud&6x znD&h4(g(ZqAK^|IHp~T|{l-VQ1=fRI^pCI`--A7Px(4wF?8M)|JvMr)g>v;3%ZI|vX3@^Lz8+n9LnxT*TuMD%oFm zUH>qemobX;-;}kRWPP@W^$2nNArHT}J*+#Azx&Z8Li|mWX(1T59PpeBY5jN>7AE@^s>xhcD0DVly`{U z5qbOw$0dGoh@N7RVVv1G!a&$fkC*@*$ud|EqDSelMFxL+L;y1A$?%%h)4;ANA^Ru%)|}b=OhxhO>ISw+CK@ z;r>tq?mu@ovL=wcHb%^ZiDc(l4@&aWYxvtn4+H{F&%q3_3tX2b ze#E7np|~&NpI_pgYnLcn63Vl68S*}!e%jrk5<|@OE36a6c7?=t2iVc7&V%YPsLpa7 zIw@QDKb%1)o+Y&y{s!lCkNAe7D1rNgzm0?VN#49+ zrD1e`RbbHee=!HK`=~mI6*5pVbL~`@z2WwG_6U19dyTWd{{K_=u3=H0X}YL@sECND zh=?c$5fK3q5fD)eD4>MmRFsN}q@)TgpsIl4sG{?IRhYna@oW9sbgy4~{niwS6`@F~RJwDGG zl~(}DDO|Q?IY=Zyi{W(tmREQ#RP9?!!iIv?-_NHhbWZ-%g)qMkyLCQvT5G@H|WW+|5w@^X6k*WjopW1}1q;I3EgPfC; zBPu=;p&YIDp~`Pk!;oTvZdM2XJOI>5yoIY<)IoF%_msO4>gQV#=@#`rMW_RjYCm|8 zLkMqEKisZTAX5D>iee2{qt(F}bs&}z?NEOgt$rA%?xf%UvW3&kZ3f*ua`MGMyt+$8 zXCzu#0xOl>s*p|FR3H|XQ_g|#?UA?Yxpw8c)@caU$YZ4 zbbu<92kBT*^?wrmP%x_F_3xor2n`aP^HKKBTxJK^pkW>o+V?iY+w0T45J_u?tR)A zS@zeCCpnz#$k>rF4G)rSrP?o!eBt~<;TI>y^CsvpRSzztIVWuIYlXDAH3yqkjFRU$ zT6-09cO(47g?*$G`aQk8+gv*;JT3`}Jg={#yf3cY)C{d}%i*BN*ilwTWfXM2{8#5z1Jr=ewOoyd3UPq=ig6+=E!x`bD z=S|Ks=D^Ctlb-Q$#>W;ZWA{c&DO|`?0+F(|1sPUB@#tLc?qN=#x7w}sR&FBF#$|1| zvEfE5QPQgEx~R+c5l&hPJCdEqvQ7)r3|@eHoK|NCXL8(d+~^VF@0XXXJ2z^vS=$#p z!1i)7I_Tx|l&AGk{pqqSn7VMXtTx)6rh1DOPR{(FvMPGw#)%sNM2h^I+SwdgJ7J z5h)n{(j3x`3t;3(R?_2=o6c@9H$`*mD>ts(h!g7Ew1>SFGwOt=supL0fZfU89vVwC zKRWT#;!F~PpXb+?2Tk$}%oF|sDf_To1jnNA8l1|-AGvYlMmq7(gmq2MifyBCF^9tI z#Qp$`GGXSC#R+o+(cJp<jojt5VFar%qe zv4}tF`D=v`dOx@I_@R-cBpeX0vm~(u<`|yR#dTaz=cJnVH1AdO;L49zep~}inkC&l zT+n#Q16|;R?DV*a6VQQf3lGx4eaEDgzxaLb_j%d0jlH~Rg@nb2xjTd|vn^+9w8PB~ zM^i_OXjrsJ*(1;oe3u2LjZ>#2kfoS3mpy3sWcx>Tv&A1JezfxQULKpzJhGLJ@Ext~ zD1trQb18oF{_|AFm~oQ;7W|P88wL?2b$Ge$ek(_K)QFQWY}P#p5$OBt{Z>Xzh-kHZ zVi+oLkIW~7a^X7NWY6ueHp3s7AS?Xj#6&pJ(7NH7^SDg|5{DfvuNh~ux8E=RG?ME< z=O3emuzMRXZC?YkuIzkgiNgeYX>mfv^acMTp-|BzJDmg|*x}4mhE*$(Z4HJ`PT&|~ zk`VRyO!L7S%yLb3C*2yWgk7}ePKz0FrwWRP2OA>xj$knqCJkW0OX!_0S*;WB>GJsF&1l6YGwaIQta^%qx`UHpk?kY2c?W%9DWXC|bZ^<&lmy)U!<7nj z70mRRk>_f)q&RpY_z#s9WC6M{X58)cd=0M)#q-7UCQfZ3YU=&=Z%&yI-tjDE@@q-T z7?_f~kc5PWd8-io;ONPAXin>%0uj|y~QacJc|f5Ib*XC?WmSGjMug zn^-soknrDwzF~cI2na}%TlU?1&JT-2#T&i}6&lQ4$h(*@;Vt-be&`tK3>Si@*9X^E zP?Mws%(Q|YNeI#{_t=(R{tecN;uIB91CNR zfJ@+1>C?H)EFyr~Pcu_`YCNps9gLESd!06Bf0M(=UuDc^%xgHOR&wrfl*dmaw)o`3vNV#`5y0>c*?uJnTQ3J>WY2mjz!8lbFolzx@#~9EC^aQ7DJh)}e znK5b=r(t*T${#9(u-S6saT9EVlQPT%d4UO9nd!oVQIZ^sIF5_VdJM;T+8JFnS1RyE z;EfsT%kAd{^xkwr{hCSC6*h zxo7x#2YyC+zRknN$8>YOJ@$FV)?S|>J-hdh92m*H6msG}+|f{0rhNJn z@QfB;ci27u>)Chxx1RqM%Luoge^1XJejI#3wneUf^y#a#;OZd=A$a>f63pOX#1+S`01BVs(h_9$o!3P?>tw=K~{P z{ef8d0N2ac>N|fX_SZ1LEUC#%p4#WjtYrsbv6gIjVFLQ;TH?OT*q_6^_P_OW``TEY zy+wAm|G6KQw@IiE)bm{;+p8t1HMBu_XQL8Icd8)P8*3PgmHc1~;0_h;5y1d6>;s1{ zQecZR6`}NP;fW%}S3$8KX0|GCM`AtaqffWNS~XvYCI=X>sRc!ldKbM7M&ouD)|3$f zY95!VV#$yV%NcSI0WTQJ8#uebI?PUGAf8sipvf+}NChoP(F9tsq^y{SPHW+bLhVmf z2KK<74byyXmc0=*3nc#ycs>PEDbpg9U(dki_dJY!!^rl_ee^Ro`;u76<~K}!V^R2R zlpoA@lcjHHiG|%h6hSspcu2`n`m-r*Ip8>~%p6hP&QS)AD*ccCLD7Iwqdd%3ewm~E zgN96fqu6tmzC7ihVC?3Q^6Px8rFmb#e@J=s&&nZ~_@S!!Dn@p^uv4^w_lG8>;Iut z=`T~TID0o&sQ^VTQQUoA3BYqKC*FNtMGvgB-aQQ4-Uyzc$4Por)8iC9PSfKI9yQ9q zSs0S;fBqJ2HXx5p%CFA>UWYYO*yy$>{co`H84FX^c$}w)l^(D|9gC$2YuMhss z_^NM16F-$TmE~B^RQuletAoC3f4xG!W}fuT5w!p63U#G=6U%^<QemXhb;2}dpE`)Ak_M&ebw7Vi7q18SYX*BgtYoT# zS)A@5M`R1SL;N*IydLJSN5tz<{;I+2^ITe_eV)gj`Ru8sr%ov#!H1PXTJvS0A`#hP zMW+rPWAzsNC5#6-`&mEkJHm{U8CId{P;0S6W#IOks`!tQ{~TA|(G#+gzgE-F^HLQi zo}QPnXSvF(KAqtBllFFo{lWm^Cp(U!d~# zg{a7od@VKNzpqsB@=mSNSD+3Us7|Z~lODYWE2=8hNb9tvDDva#zzOvvM6dvQ`!aQ? z9QkEb2T!U4)oMRhI8UNB+@Yn;*;DGkX%$2jpty>q)!8V#p=9BWDHU(%o4-_twj*?g z;6q^uZDOHtgf_EK1ktfjBtlzRC>Eg@7TSprt4oN)sL~4Q;A}j@BqGEr770$qOA^3C zdsKAk>d--?ZZDC!q02yxii$Tt{ti->&<&kMzC-a^b>=PgZETR4)PZwqKVIwDYq&zM zhZQq2?CoOMdYreY%JX{O1o6{=WpqmTyj4|xXjF&#nrPvjI^>~d)us;N>vt8KC#vF5 zThvx8C9CfMpQF430-O2{C}1d!uE<`U2hk5`xIZre@wBN<)$zZT(9XLMNMTVzm{&L& zHB_OF1O3KaKkE(9U39RvKGdlWbg2W~NaH{c9-{NOfQ0eRW1fEcE~*L$FH!fR^!2Lm z{CJh69IE|r`QJ-6FJnADFN*pK%m?r$V$Y)xd&Fc zAYdSXN$&e z$ev>uN6DsLQ{M8Q^sRLfKzKrzEo^M3( zZQ7UWpAU6m#PqM&t02+$@ol8Sh0ZNZ=YJj8Y;_GbC>O$w$x5YKE%|#%nu@yOIR|dhh9nxA^ zC<>uA7K%p5$wDy*VPg_>u?Tgr&<=#KD+yQ}LftI16QLd!ibv=I3++M(A6Z13fW)C; zHQ=s1GAEH`&{ZhEhby17Soy}ESANe=l)nf+XD%ffQ8-vA1)&xe+KW&Z3#D=ez$z4I z#O5#}PvZ)dqjxJq*E-ZfSu($$a|iT zImdYK0k3y`ehzw}5tO>hT;;9`S0#A4CVJ9Dk5}47B@q-Yq;P73mm1v>_^VutAU5Tv zsk!>b=t7r>4vxi*7aJ>yeJpQ`tg@rxk$ChC$6Y5--a|GjWORzZI8f^}a&Z)5 z2GZ!_vlL=H?HV$Y#%EkLu0f-#|E#OlHCW>sc#D--t!u#K!tCZ8>Nbe$Ttf))W{MP; z5iV8-iztUE!XjnB0_u7f4#KE!H@GlMqC&v;6cZr!$yEE#yLgDj%0Hu4m$C+u?kgqS zfew;1*Z;9%qzvpwG>t$essl|ffM_r>&`gB_gblBVUq`&u-Kc#Khz~KQUcA<>#N3AJ z5{e@iSi8%C+GKYPw2%N!+v-vds6!4&^78r+2iG%Pk$OVxQazC>HYsZ(Sy$(gTJ&*h z-@gtxT}nH|U7c@t$xkU}*d0ti)}k;{M=Rn@0s+wTeAvYbkJ5m#1=7I2_E+bj2~9PJ zP#-XmR|Yy=16?kh3>oM~Ecbaf5gZ(>Br$61A)NrC(s+g0CUv&EaRh?;+qV%|K^+3- zqtKGkU2tK{FY^Cm#5Hh{v<2k*UlyzXphMOnwKU~K&8GC?{1B=1xr^zF5fpMA-^#@? zq9^1rUZT9fAL`v-QLayWQ3RJ=16O3Km$?UgaIWVbO`$($k_LPksIS*G;OFL(XpR+p zy=dB~#+YkUrv3uI%ia9fU;M71-<8BWu*y9Y;2v1*2FoIO_m#!iK*DA!Yus=)IS>S1 zq)gMCtPuBy>%A#QqWCNgqXJqt;KP&+ z?v3tHxA;bHqT^3jeUvaauU9`^zwuEvy9asg33s#Fa~}{iqPfj(w5JFfDD^j~Tik;< z^@@Gz?L!D>`TnT)N}iO@g*aO4{`sh?&W$d|cY%M|w@$k>=c zuF?$gg{4KRdlLl{$Uo5+YVKreqNhLwhMMtq;XX7ZU*kF#to%ez(FVGVqW`ZE}P1FrAehG73iN@rU+@xxfJs zljpUcfHQs`ocZ(MEC$9HePLc8TM3)+@Buw*B?lL)^|uV`T#FcJ>4VJxpe;>mD08yi zd=}8D9&!)4@{}Am402yZ$_~2~%n*=;Lxbo=Cte9%?=G!+9w=v|a@+uBL;} z%K*Ue_z1-S!XJ*Z{PLw^PhZWhWD7F@A+LUQTKT1~IzXMsfCfY~*nYaoJ$Op_dpUK% zK|or(*#uw4@Ld1|vO1UP)tBdf=cpSKen9eB$S0+9r$NXpP~eI6<@#c5{Aq!MPe zis=ki_}sL9hm7#fiL`DUSt`H-My?9c572N2PV0QBi|NCV4SE-m-a7YyKL+7*t1;?w zlN?ALxXh2Z?37(V(hcDzEQYziVN*w)^NP1b6fH~kn{`W%V5@iUU z@ZLe*RJw=oCv?ILkEZfil!I8CI#5NvnU~R$3XkR~A_3S)U?z`dfM{-5NX>z}KsT+p z(li$g67zGqAX+y4<I_Vyw{8Rg|#!yYQ?3DYo8)McpC^|x*5&=^~ zm>*`&y6L5sUf#UrhPiainci;1*u%|cPjAjiSknTm&aIf;7B|d#ftV%J;vT5y3?iw0 zUmbV-ZmTlTfUNxRyxZ!gIrkifJXfF3G`ipZ42f-WqfRwLnL~=rJ=p99$nJ)_>t;84 zi+^;m_JEFpy$-dY(Sxv+5q?7*$Rc<%K?mAU;1!Ci+3g-|rM$JXddWp&z-uQ8istH7 zJNsSWnzIq~Q|t_g#ebdRbYq#3E&i+TIN=GCtv<7jf_68pn0L6}cDnJ6>hE+bUG8ou z8=>^L$vY?(idhHueGN zq=LBmX&ut~{E8bZk+*08vVVSAA6t)l>C=Zb+<9NT!JPk7KlVn`dfGc!jafKVMOskT zy6IWq?!Sfh`z4w;)(+9IzZ@t)ReFq8zR+J_F&gXkZrII7dxwZ(_n_8IyA5|s+=B%a z`akhp-iJ?eiTmAR@YDJ=?+{Q$DNS$t_ZeI(`d0R>=v&oSiLT_ipMs7vpl|4DJ(iNt zTfOrI7C><7RDIZm!l+jV>%m4nQa~U2s9(_v9P3xXf^QUGocOd-om$<8Wl?0~R;9m^ z5<*J`SULEC_9?L3(xjv-Dd7cSO8PGniRB_ar^5};kufczV+_lK3WoC zr!AjhXK`8P@IhR=3BiUUoroDLi0b~*Q6D>Mdq0%+80nyZc~x;Zd^nbuRaVt%Pv&V2 zhT2N4p{&GMr7J59Co0H6a(Z%VZCh$)+TOkK&k}?VU+pM3c5y=2Zm})e zd&L3#cwO<@*h3nXlo+6R3Z1#rHB(X!;5xy1y{P@x^*Gy=$>J5GAau|c19%m z;IV*FaoRISP~Kg)L;{z z_-=vs7NdaPC`!=>jC48Ln$6WBhDYXIox4UW>AV9|O6E(A#Fyp-YGEx<;-yHF5P8df zCBJ@D9AmTyrUwVQP8}K}i+S+hCnZ`5N#`Ke>buVjvjdU_xW}RM3plt$M?Y=W3oMB} zAJ~NSsoctjdK@Ul>B7PeQ=7R}z;B+dj;x+&Adu7WNe8bYJu-qle$n}p3o1v^$z67s z1Wv}Ha1Jo);D5jj1FV(9)&?DUv6w>y;@0Nyq!c&-U}8gssE1oNmhTv= zTi0qsAF};MCogByn3Ujj+PE1eYpskIyBaH;$9r zn%cawQbU!dw5rxnZK%@f(}@Bl{5;KwE1%7+6lRgRVtOQ)r|pu^4_EjTyKXAvp- zk%^N{%YsIlt>i&+n@NOJdWeuT-SyOiE3C9l0oa$OPO2u0N2sP-NYwbmT!~Bet zQA*e!8S6jFPYTPTt|lDyc^A?G9Ue}5r=^-Ki~P;h!P3B zd%#i9G{Q|n(Wzd0L#v45@!5={7P`5fM<<4HFbCC?=U0c|z1_Akw0n|{I#T34fU>iZ z2Q^8XCfWh%B|2ovHy6&d2H=oh1B$@{r&Fwckq1_D9-Q+zeVz+db|nVv9AzNUQTh{x zDn-kVg7&exW@msPy0(Pyh=AT$qsu&jLqmXu0>lm| zor%{Q#@h*`c|TkLZx^Js?47`w^Yc_9k%D__Z$nSxSRD-el-1Y697?nlaUd}@X2bz! zENyXu^mf#Ntt)u+df3kUAVI>{gaijp;6uVrI46++O48ajew)XeEIkR=Qi%|K$fFor zZSSHhu2gF+c1zpJe@Q1M6xN-$c5A!G4egGWE;?0~_-&S8eQ>I0rSG^MAGHE>Z7d6Q z=A7r$kG;@js9eEdqM??1tzK!{TVFnfgqt5c}->is&7qwRIhBIH%q& zYTePj-D&$W)A!e9W!Qv->B}o(f|1}hd7Gk@5J>6$&H`TxPU$j76ygkEhrm5Pw4-;c z#POCRD0&3NgTqb6<>QUcCCFZZoLjekUEl;e=qqfe3vQpkinuQy+mbbmo{?;(3!FdU zP`I~ZG7fG<30Uo#mX-hkoNm5yAY{^Uj&2OFIuaxhcQ2KhP(_;^bat0vLL{bSeRb-% zxd*qkt#u3&Dlm6;G;BILa?assX5erEzP&MTb=k1J!^WVI61pQG{NfnW!l2OtI(^*d z;=W0hnteQbf;{mu zaH<3+YrC7q$e#lPr%Q0>g|@vTRH+#_OMq{$$v%4rZj2c)hk&SiPx3EBqz*U2ZE>fA zUn0>n2NsM-Lqw!miDp&$u(a{PP+B4MZt1eb&R-!2?r%x5`N5M+C%UpSJI5IbM|P77mNy2#qYwK8 z{6uq6(W|AH+1_6K;9*O2okm{YwK*p`$yd{=?^cuK%q)X{U}) z(oF~;{0T^X!r{%<-tl~^r5)Z4QRu`FAQ+~WAMfZKF?(@A65e@AvwCEKK19H87G2qU zuZ4*T72+T4sSmm`!A>GC(1#29>Gqt|rg3rRm?Do9T=(`LUg?|^y#&8z5j!6(kh96D zap7!+g46d_vk5LANa;8s^iHxfaqBQ{Epb$W0N*X&Qt!`a3NTMb3`qiWyI}L4%+WS} zX1Xm^#Bx9C_DSV)bqLD)cJ0c`qorlF24h}PtzKJIRF>bv z$@CxSKj-ytmc^X;(sMYJp=Y0_4{V_xkf3nruI@r=!F+sA5wY6AF;4&hC zrD4+T%eEbB@#W;*;`Se+1=rou%g1b?+;JEop&%#!#+ z67iydA6@Ejw2WYKgIP{xSy@%B4htDbP%4*@F32AGMl_m6ob4DBS2`T+IzBMS_#sQ+ z=gj_@`IvoBw`eYP7&_|C^KLsw#IKLBjq$$KW93B}gSOU?rztJfl@?Y=X-B)# z+C3b{)^3giX_Uy-`A2I7V_uoQTnAe=h1D!WUethwjXH&edK2-WY;GKnx)49sf-c`; z_}0Q{t&;Y#E_35>`i+ZU+e8er%gRp1!CVY_S4-= z?uyvCe{$iXr;=8#ZMO(}g}6MTo#m z=ZCFn9H+UIkP=D=RJ_?;b?egPMLgbdQ%4Z^`*6WIbw12kg!@0JN308p6!6>iCEI$( zjCQQ0;6o5C#NFFhnH}csn%U?-P72B_KAX;Oy0Dd>1S#lpz{*cLgq$P+yt_X3LN59= zxuVl-P8Bgc^36G4L6<~Jmrh601?NrQhL{#yx23UWmVn*4Y}*q)jvmr%rG>DI^Ep4V zK21Y-8iH+X?JORxXLl>2_(%$5v?c?aH+es5Mb!7UhPAbhK~?y}V5V#=W}S#(YTwQ? zCviQEhF?_X+4FjVxx2qK_sVDyO-tK4*m6XLz)T-Ivc@)UT|gM-ja)CfNLkH-uZn*x z{;`HbO+t-_J8gz5= ztwQpH&1W(yhn?bbmKeQsNOiX?yK-_;kCts}uoS};V2{*i&*|nfBfN!bD{GmZCvATA zE%;LS!|9v7BK{{uNz1oPl$U6!9}ugyRYh7(?(>3`hyM%ciW-0G)UsW^ljm^z)2uVE zd=UUJbuTw}9c+k3tM<%Wb>?~O7x6L*FP+~eGkjFwTEpv8p{%3};C zLK@Cq*k|30JD5=x@>LhrEJosN^^SoKMn$}cWIV3|Uj)90CO+0S_tjmP@Qxk}KZzp* zjZwQV`)fw>j975SdX=_BtEXQ>Ir=|MZizNQDEMf-bL*vX*|s!xJ8mCQ6-^TIr}ouc zsU10owGBHW*$D$Jyw~k$~G>wXjdc`mFJag#wWZ10= z(6GfC^LEQ#e6JiWMiKMu$*%Or=$^%=%h1t6I#dhn!_Bo(+6jl8Zt@|qxkkh|b-5{e z*C<IW6@f#0GS@1TN%1Iu%o2IA*irVj!k| znA!@wRp1}&Za#i?g5O&9qHCYp2u*{clC$N>xS?t{pB``JxuJbu`+f%x+TORl-@}7G zzxDZTFAr+o*SzoZl2z}V_jBI&=fS+Oys-ct?D%cRZ-aQS=YyUPLU=IrrO3hcVQJ2B z8a#%*4Cg582i6bbKZ)co(V*jg8+Qd>&YPgV{~#?ScrW~!9Tq3+E71M5W5SO6x2+vk ztmsA6(bSjj0mic8-dy;wuOuvyueGY~zJf!QzVdB7#(| zSh}Ekbhg0x`k3LTv)hc9S)4A1=%~%_EZ&hXKd47uYB*Lau(x*|E-e|$=L-e3g)em+ z9sWW12YMc?AWG&Hv1naL$t25&1&?iv75tR(X#R5L;gyGRGRhsNiZZca!mcZeFHHP=0w+uoQg7`} zN$ zy0~VW!(-J*bbHr^cwr#}-9uE~D=i~-90^rZhyA@Sp|pc-_yTmG{tPLThlm}9OZn~!4W-#KsN|CogIhA(Dn11=I~*KEvR#rD6wH?PL8_xs+Y6) zyao&NwYaTl+wpOjIf;r-N)m)g*e{Ex+xp@?3h!)#iUvhkmX_@)+3&UgYgY9FNF0qb z4)|@0o}{Hg?|tehxyBUuI*6!H^B$~7sP!9@GYnA=LI_Fabl#AfGeIKwKJZV0RPcx5K=v?dW2X??yUGvhOsR9d2Ov`3vc!6bs0+L`h?l{2IZ zad(^JPMqPpo1Lf&$;rtXud{$7^>6JesK}B#Q8|)fo3`?!+C@vo8bTlg=Q7W4J&(n^ z=H?zTe-{;AD>=<+8q)|SW3(q&LEExAfth;i_{uJ{>;^-J%?6_Zdah%Iko?GQXh|96 z`#w}(+0r~THM}+oipP65R+S7p>URdwi-QJ$zCW3-8;-XEpYt0MM{bwW=X5mu5|CO z6DyMWBsu5wr3w0opgqIuot2LiEVh>Rp5%x(kwTx_yNnz6Bb%|>fDc3%jL<|2wH}@{ z7o8fG7?C$l=VY9~-#b(ldlHj1KEXEA)>qm`3528yzhk{UZ34TiR!g%vNpRdh6SE=! z_t|O0bDRz*U?n3}Fx;^p*?@%?hBI60jn__pk}gown~U07#)JYiImwdpcIBprj%acX zRh62&s@ejbwj@7?aMV>D+Elx8vQS$}YgoGDn2XT)mR zm6cZDW|N!ZnWb9o610xcNZ)jR&gMjH&@dww^)S4p7l^5Y7rNJx`vTEqD|o3G2@SQL z?$5kjG)gu^oKB3^ph<|nv#+PNb@+s-jrO+=M%BHs zF#M~UcWd6Ya#EX!=J}-q2g`?f1Sr<1bJ)kHK8$~zc&Q3D!h? zj(YeY=BS5f;O%|c8jlbbweqMZKoA5m0_4=QoyGb;qSJNa^Mq-kxd)#*i(P)R%)v)J zDc^>0rWc{aL`y~!TMnf8Po(eNdww!p2#l05-Z{4GvJXGXL0Pi>s3%&Gw7%E+UYrOg zc*!>gmHACr_+o%FIq|3`Nsy#^Nm}+g3Z#=vb5z<SZSpdWnCyJ2qwmHq}|1_kJ`h7#fOwvt!?`8p$rd$T&U$1}CPkMk(e|%)a!- z@xLXgmIU`V@9(ZyoI*708ZYv2$lk9=EdWJ5Ffklj*WIo4S+AvFqOnM>xpT(?n%L1Q zeD4=iSS9bB{Y}db3_Ilj!X`QI?M|&QA<;A?6>X?h@=UjtoXi@fhy#ACf!JsQ-^ZOnzvNCer)-k;fn9@S5dsG=KOM4~q@yX~j$4B&<>Rc_Bs~3~_Xs8XK zbRXJtSEh{fh4Wg9qW~-7`6s^dmjn+wL*8mbNs!KLGESXr0AZ8qSnJVA%+pjt-}wsE`B?H?8XY-1|Wj*t%(>#Lq^R?X=@K*-!GI zn($;!=ibJo_uIR$)WnP!T$<+6AlVT2yrMc)al*!Cm4hF| zip=G$vF_8lPosJG)rMRT{!V-wCzW|})_UQt7_DmTX5wr?cCoWY30~RjR<2w(EH?Pr zf@G3JO|1`TKZKlXbGGyKONqF1X;)D##=(|OzJlq6Cs$?%3~S2@YOApMR+`1&RO<>1 zIfrSHnXRKTT#m%e?udxBPqIjaZcL_z@N5;*H_m!!n2s-LWi&Uc@(l$ZcFaj9@WX|*)-7j~Rv3J0i^BYpo0ewU#lov^vY8#@!?Jxph)g)C6Nw z?DTT4gih^`KDlX}F%`C%TF(3AiMoH|P*-vE1kMe%ocCuiDy2)YwzgraWqIfwv7ljZ z56I)yEJ*TAM^&`edH7}dlzX|2xSH+(ce-bXjW$5f9rGPuDS}(3= z3;t}M^Wx34TN$@jei|)k{XX^kG){z9!ed7Q@p+xg%A;2vk8e_*hrH>oKelDu#CE;Qf&Nd9tZK@(1fuyU46!Rv{6p7cCV_OS0*7X5sbL(j(=dcyHm z!^rjbv=k;sYS-V>d4o42$<4#M%;CG&^?9BJ&%zb*#Q-e&qHR$db|#=ZKT^rt?UUW z4jp+OvLx2S1nk~F@*myHY;T^2EQu8i!;QnO@176-70>Snf54OMh~4_`<>0IS?_Mk4 zy&3urmzCc3QGT~f`OsJS-OCT**5Nx`$N27z`u1z(&CqkT|GVW2uixVp*E}WmN45X& z{>~>KS+#6a<}h4``Az4IHRO5x^Lg_M$d}F1p0hcBIO25LK3ibECrEou4nS7|t7q+pEpGM-w#;>;*G(`~W& zo;V4<9TSvW;lYUx7F~kiN@8f*Nii=kPMnN5z1NnK?@1*n>!>%cx#%;}g)SwXfvBTh zeemp_NpV*1vn(NpHb`@L_*Di-^F3kr^6nMi%ONrM_0h)YPBR>m{&-XOt zc^o5%@A`EuhyIV?2myua*LC?Ga`9sHyyYPeV?l6|Q+y9Ra0^TMK2pJ+sPslo_}5%7 zT^OgUW)wA&&ctK+Bsf^af~b3gEUlcbz*FIkCXYwc z!6N99(OkcNoBSDbV|>sTyyV)LzxU>gMlKpGxhdwZVCw!v-imoR=NkDTmT+=MEaBup zSi*a~;hbya5SY7>sr!?2jywbNw;Xt)CYQfF_PRfDui51PmqW;pb=q{ht} zw2~*zOBMWQ=M;IJUEm>4v64sY4d+}V53n+xjK|~(c7cc7x=J34H=J{g9JNZGz)|wp zy1+yJStZUnMUGk}F7XfKYE|N{jF8V&i3`6*j!t=8d0&#hQ<*06Un=3`##F+|Eh*>D z`!aZx{F6#q3D?N&sN^BEo@m%21cmxa=||vIlz?lcfmg! zxs~Mcp7?~k9rAG4zmd~JsSi0alyLHADBMI>B-NR=siV$MNYpK`YXSCexH(? zFUd>Zz9cVs#G*1ByY8bEJ#7l>g2@vWNBB!bfR+t7r$1r?&ax*#S{j*RjFUO?R`$hG zX8QVIN=nLc+>OKX1Z*g`n^MlDuyZgeusPmo0jRA#$%11{jz8E8K2zoaOPZyE@?qA< z@}znpPsm~0vZ&1A@2}Tr*>LNJA84>ePPJ41-1Q{;iHT@OEbNKAvU;g7Qd;w)$77?& zXzk^0d($d7hl688tWR{}mP1?D6S%J?#<33e1bYu>Kk7Nk22m7EuM{x7CE&z)=G2g% z+y{TezxfnU^#1G#{(u6eB;ywf1QiGcrq`54)i4FT{P18+oMWNv3I1@-U*IWV@sk7R zNXC!B^@H6dJ2tcU(IClsJ00mo8m6LI>~T_u1TLe*Q{t78o7&;ura#_wsLyyA;6u%X|6Z z#hN(BjO>YgsOJ2qJg2$k8n`B_=0A55bTsaFq|Rqv_y=54Q(^?i}P%;CY(>p^yT_-B06bUpDY^lQ0V!;mamwaAP=G&Mz04IQl$!EPh>`DA*)nPp2$(pN2t~CLm6Hu zD%G9jY;E+(N!~w8cAp3bVU;zCM8`M=qxfz)5m?S$Wkm8#NQnGaKxj%zh}d|jkuq; z($jOjgMLP?_Y|@vgI<=0|Nr`+_!P0#gL;E!kr&AR2jBNUAWx7lUJkt)dOh?8_C;aa zdkChz{gk2Q%84zS3~(sJQ)m+-pimg2pwMPzFq~}h1}R%$ppl2RDua+kZe^@>~Km*z_cYY_j6!6 z1BMF9100yK1enQzuo4dCB_+(^l1!NVUIaYEfv`KX2zZzSk1PQqGl~5u1ICEhH5{1B zfHIfzI52++JIajM;qVa=Z4vAR99YPJ+XN7njEPdWg#8!?7B2yoaG-t(5cXz?y=(~( z#xe<5u>@Gjfd(%SzUtJsQ7vG!+^GEVxYD;=nSq7zIAsPV#;cVXm=`xHGq5jyM){{2 z*lRzl^w%ozGv@KYl9gx2XO8>I(h?b5^i{qXy8A5%#6nPayw+87+8#RAG0Dq|5}f=Q{Qb zVbAsKxq&!mHnL|Zdv0P+SQR(J#~+w?D&brPEJMqTjgY~47>TY?B4Ln&s)|wB0i!Q-JIEy;Y)P~9Uj-^opx)jA(>lMD7$A`OjK_yylTZZH$+8oBAz8l!I zDi;^I92W4ESg0nDNWACK`#?T3JxFdsA1_z5 zTre4LIw71#?1cTtLC7iOrXZ1~zB%-)WYHFty+9p;JxG9VK_-lnE`lHP!i$%{VNq7l z>zBY`y`{h9z*AuorOa5_qE*4%6t1@&T65 z1wHKIErQ#Ks*yg$)_SHmu- z!D=DuBjj)(tc7tPDv=l1BY~(vULfq{b4pYnFYuxSqV9Nsmn0As#|!M0K;*6$2rF=0 zCaRJbctrwHkG#NTFrUkmMkj774?;2buUhv&R zk%zd3)`PV#i2BnI7tHz%Y;+BTy2zaU`h{1@&MfsGgJIMfZb+`KhIMHO3Z92u``xZ&7iNVfe1cW6>W}l13(SGW$XVO?`zw6gC>8}0*u9*z>j%3Ybx=2D6dmd!ZZ1y~ac>k0Q zL*>*vAeaGT-!2OJt2CsX&v7Y!Dt#sA54)883+l|n5Iw6;LfN0_esl?z=copG**~8v zGdK_VU{p}Ad6pQJd>NeAx?sDWiKmDQ7r&Wdww`^Ib@YRLJjQZ9#`RB_GgCr~Dbq_x zl%8jFDJ!xvSKphNa##PWnF?2brEA8(a^}^qXR25>z^ps6=Qw3Xg-dzG^TN20Eyovv z2G}%ZIfTehu*grkW~yENr(83qSpl7KO`UM{op8<6u-B7}=GDwu8Sng`&Wb1?p_XBT z;4;O9q5h1Cu~fSHUd^2ImR+4UWCoEkV`0yFrg8%dpLg~1>N{ga5*l5~Jeh)5nyK;6 z+m^oCm%cidyh5WORxw*xY8Gh?M~4Xo6IpV_^$H7(o|MSZ@zf z$=-XV3)aZ_{3A%QEClNpg6Czh#pRt2LZ?pG6xtjj>|*&-#xkwjMFonQ&r1=J_|u#*hXehv$BG$sJ@z6Ns+_$D~rgita5v2dI4_8g?V?V8%V)5EEwcQ1zC&W zDm7Tr{f%KsUXH+)26@MB`iAHrOeSA(sR)@@+o@o!{lRA#kk@2DHabH-JjpF1P`m}Kf$B-A%4aR@|~PqyrRyP+;M+&JIG;vOFeWj^T4iQGt8VFTs^kgo zU(X!)Ic8MC42pU$EDwu{k}87XN{H#kiry2GkY+pu|=LZL7Kow>7a7?vVv5S$}U4N(-n(? zj4iRqlO)(uWeksUZK0-du+g^cqJw*BMV^MybV+q@eRNaqnD>SdT`%bRE=yt`mXsg7 zI6**p6^g<+3{5++F()$ju`3Cp-VC}VE zXoade8O@{2o=xjVgsQdeh{DN|Rb1rJ3)%`si_My6G36I(CqYYBp>c3k{>pQSh9b{~ z=SE5WB+7U77Mvx=<{GdbFP6W8s9h3FQWAhb%xduCgtV}dgZCYxYt^)jvZJ2R0w(!qF; zsZ)ouw{08cJo#m%+8)8s>tzroXMA*2HEAEOU7xsRLLAY~gCUs)95weREH{CWl9)YQ z3P}Ns0a4s8Fm?BOj%zA)WC|k43qHBqv3#V!Sei%ni$Wy$#<})d6kiE0mxStA3waSv zIF{nJBiolv?*AxK65rf&x}szR*DnfmC!+;*oR_-ACu%J7c7i0nm0P(gV6*`D;>s$k zlLU3Dq#oOnS^t;R0}s8Na!o|7)a)5f-Iuv+q*}&u zcU33Of$Q|;B|5Ev8C}YFlQ&jFXv+PuEk3>nMy=+=k4#+Jj~2;!JAbQo_ZTyPj7r&{ z^H?S3PE?J(Xt-Qc37b|`+Defx|PvTp;zUmmrt_mgtcDE4nf)DrP{Oq@D-eF zzy-%z@3r8II!~`4^f^X)QkBTxnZ9PUURWOVJLd5}CeC$AiW>*FzXi)xWV0dgnCHlw zAVCm91nf5B`Mq_;*v4gdEphX-7ALv6%^64AhGU*kMu(_w>~owb@-WTAg^WlqZd*p= z2Ao5IzT1y^q8XUVre#@r;HWH@+7g|iDo(H_cxld@im`ZPcGl8;(!^t)Bq@1nm4ABT zD9UV{9ZmgBf7Kv_l18OJ3B)CEdOZEHK=Hm^;lcXpDF}@cy~)TJHiEHq!+)j z`)J}AE-}MeH0)SzFZM)B)Qy7bt!IZzG`ZT6IKh_S#T|@~K0DlOI%jE4EcPTZFdaMI zpMIuj_;{HvKUHe@LC6Q`lIu==;}z$qMxU!IG?p0+S%N0Vi}x*PTRCR3owGJ{;D*PM zV$aHZ8bOqOEBls~5u#k~@67kP;?ZkL3d$<=!s5J65LW-D`Zo#xJ>yyI3%^fKf1>xw z-j>-o1tZ8-luetn!VBI~+;(sT10N1I;7SxjQDV*}_e8>Um%SB=4O=zPp=n1&!{h!?H-Lgx`+JSQ7&tZb_trfvNp< z8-fF2bbjHkIvE=2@uKsu)CA+!x6W=L+emesyO+>(9qn;d#J=;0tN`^I-`z#}_>>R{ z9WaU7QI(4EMWMHETtwwhRMdYyU%emZQ2!mOT=W?yG@dK30~k4K^zFl~dovMlIco_) z5j_mD0!@sr@cvellUf_;gP}zTOFw} z$RCYpy#>@qMiQ2l-x*!LiK$JUM6?%dF5FRz=nSmyh?C%{X4|IiC<09#-7a^uC9p`4 zoI7W7I+jCB2|tRIB;nILD-Gd*d#@9wdNIc$^#>4xMmC3ZZzN}`wrxg!OPdK=+-7q)7i4qN zvNoAn!LO^O+}W7MgS}&>1@rU#OkxegQ<>#@eoygz0Q^*ymQlQk9yn+;QQS*@CeKcL8COh#QA8S9VP@^RKNE zc5_#l$v6yK9WYVWA?456Pp0LQwetdNx5X?>4$=kR9!c@=aLy4MZcZ*m_e!Z#yVtd? zrg|#sypNt*DW$nItnq)KwZp^}s*t|~PnVxg3mk@NFc`XEJzwEbPuI%;hDHg^mFzpP zm5zn|RG%Oz;6{01fPReKx(t!9sY^lirDNKvBmAE$OBJ5MGkL1G`3`i7i>7C#k~LDx>U*caP_M4ZHuIG z^C9UHeg9NsawY4Moy@w30au!{7?M6qd)~6yhY6I!Vj)m|#G1GSe;a5xJhYca3^J+F=G5dH7Yry9w{s z2)OAz{4d~f@5a4rka!*s0`aaLcQv1)z^ANxOag%xv*tIirryp zy`uN{l=5`;{MVlU>Mz0prJ{-5U6)XtGwfx6>4?BmPmrL}U(;U;5#dnAfl7G)a?+aK z1rA($wseP;dcp;r?wameqzFe#p4sG(=uM;ESQrmJR&?!5#NPVy>ezi4od zd)aIq%_*gxR6&*g9{v~MEHB3%U5M|bm&0uB%r5oh2&$U*YTna`u-40Q(9-nQLUJ(p zDlGNr1eKns*wk`a_V$l1r<9d?Dg?^N1Ry`|nK#vUFGSgnI8T&%s%1RyY2T|6VUst` z?x(H`^8TOt!G4EBdxjnnglOt%V->ZI4Bf?rq ze|KYG;PQp)1`F?nWgeZN>Up>4UA+ibcsbg$n;I61q1(}BDDxNvmCw6A?^cU&jpUeK zZ%Jxdh{MrVTjntdqMY|~-m{3XRWjT?nOd=^MmsysHI;d6f~u9MsB6D@s3GI*2%J~6 z^&~mVJRJhx!=%ucBkE#u@&j5(~)+${?96n%1iamfqA@DEFG23Dny5PkEgCytO<-XPN-prs* zFr=JUz)(g4#UK0nR7B%`K0kGDj*`WBFeKhRH*Ra!!|fY6Q~cMdp@v#f%+3oKsisu?VD9BF8qbz3iOWnbTD6 zu?bwO7uOyUxOV((jOKKeKL?o%N}`Liupuvg(pPH`%6_=Mm`(-keo7mbB|Y6sE`vb<=iUqv-^!CbZzEDx|4T^$@3W5SYQBiq`sE8=2pnyCTmO^rW%(kWi1g7ki zUygHRa|ZR9T^+fp77H5efOz3Kfh}K^|E&8}zJlvNzPDxb4ahl!!G(&DKXgv7tq6$| z2$NWc_&W#*+|p2_Fgz^nT{7i{^g>x=mT(@d#)nl=iwmy9d%}Hp#-Gb5gUx30+*5fs zq1-UEgspmOpxmytc4BoNN1P7%3Z#X(QenQkcKbGb%A%&5d@?mE_5Do?_aC{1_}ev( zIj_emI#_RH$}^^pm(* znbWa)%xOR$SGiqw?&Mu7H}olV7V$kQckoC=>J&SdjFtt_OAy4LdplxM7UGjMGX5k! zuc*3ZZS3K-H-mBR#@ZPvVcX-c)p7Vka<4{k2F-a&Gxio;qhOV0CAV53=pb>vao^g08P>;aC@qH3)ppJw|t1$^2 zfrWvR6wtHUjJUKnRF&O<9^cYXpQ>o;Uwa;Y|C5Skj3r%R`BUS#mi_?yGsi3g0Va-xtUo_4i-`;0c4nGtj zwRv^`8=wqGdcSE!%P53Rt>^G#;B9G52YD40qTf#}NZfD>`yuoxsNt@oTqI*qZ;Zi}NwmHTXSebw=6HWBoF6ZW$ADu7Ss?#!Mk!(8&o4$ciLrS49&Zky>? zeYF*?;!_P6I}j7OZLG@sY<7OpeoPGU^;Z50UUQTDcw2-57&x51@zhPwY`H(Jkvvl6 zf8J5OXAceW>>}iG#RA#+eG~&x+YV-(oe94Z>qstb{?1ZaA5K2Iy$GhUm_N7|t&$(i zkLsO0RZ*jx~k`K#lm&p0(2P9)^IKv(~j_!IrwY^e{yCRh$B-`@}OctMb0f5Tu+jcrI7b; zn|OAj^>qgoVme!84z})@x9+MeHw<;Yf*6=z-aFp<-T^DGL|@4Hpzc|HPD0^zi6Jy! zea)iC2HZ4f>CPJ=fVLPU#u5gE^gkMxaj4{0Bfd{(O%KXA2Rz*x$l05B3a1zNqiG1| zW-I?Lj{AoZ5uA2ZKP^|d?p7V!zpSsdt%!_9C8v^&T3y}QZh!E?MpDE1Xw%dA#gQ8< zuDrg()}YWl+%da*Pr$OUvJtu8H>s*WShBwBP@sip8!Za+?^BX{PN1*XHFwoF3}d*X zU7;O3AG@(g!Gy5X(WM|hA3b{QnwwU|!+hX4ztCetZPS5Fn8E=T68gxYa^XV^!LNjHJagukWI*W(zz*WxcXjlt@u3ucWlCaUcI#VM7k@YHvPluc5Z1FZ*Y zj|7l8yfF6UQyGNpRcZOc(|xTCy!7IaFsG}m4-TJg?YPcc@{Tns@xvn>Et_bk0})m0 zj%C`(X)8K<=mX2lyDUxyg9c|#pK|sp4N4=XcG;@n`xzAp$yaL;c(6WSm3n++=aLE) z1KFO8ul#n^w=44lh!?grAF;p6tb6>0S4S6>@ul;4zI5JsGiBYgk}@1rJRc~dpTL!J z`ngtF7RW^un5|d)Yv_H{%=K3vUulp<6S!#3$0_?t0!@_L*CLCybI~hj3j$Q>&a%Lj zce^O*#+4pfaxs^z_&BAyB+yGa?}zQjU%(A3G?o%?T#4AvlrG^yllxN&b_F6S=Yyy> z6`CarohS%I%W_FvuJn_X;=O@b%Iu4OQ@KQ0E=d+j=0aVcq+rdEN;w~;%R+1D=h`qb zGiAvvS#mj-EP0gDy*H3eIeq!EfemsvY*1Ay<5Y8cv3LT>IRwGBoU{|hCE6+yEo86Iq$WPKrH1W3-!oCE4a{% z$0;-R1$rr`FYEyRz9t_^gnSOLeA2m4;_p(XzKW!bfheoYtOKgu(Uc4pUH zRubI(5`uWIjQ@hG8^5{{xEWXW_YG9| z{U%w}yH{Eca@dXbgETlMQ>4zk!i78jlSQ#|u*ZatGj|>2SR1DU|NQJL&zX+`m1-Z)Rc2%&u_vU|C{1Q#awNW30nhU2X0a*r}B}bhS&SR0N_5I1J9>t35KM5vLuIf(SZGuC{y$I<{wC zop}99uWa1@oBMB4<0!^cSOpr#zODLiwDV=Jsq7XJR|jh{zUAXdKUxsD5?MjbjpDy(?yL${l*2a<+qk0%H?2-TAGdh>|02|#_-J6WWLrZiJ}rCV z%UG&m^0=8-&lCqn$5&W!iPk+w^dQEZr0}Oo{6}vdW&D#td%D6uxP9@Wd33C41BK4s z*3lrpmRDGR;>$_b*D!`mg(oWnj}?-Wp`UgGpHz2d~CTnx*Ux}q@bgB-v8D6FjfW6D+%QnHT3sG%$^!5 z*jP0&G8n70YaLk=n*Wm-D{dFts+J12RcQ}wTlK>8;G1_|`oDeW<==d#9{>C|uhiqq zzxm$t0GXFZA>fVzM14c@+2Z!hUBT=)h? z3-N+NAE>8Mc%o{M9frpcOi#Qod@nphV-cUi7#b(U%kg;V$4xWu1o6`dJiqWxq}VNp zRP+x>oGiSN0R7bzy4UnjOhve&M@WtW;J5Eg7nf%U-*?`b0?FQ4!jF(KR<)XE2F33H zB9x<2K9rgxJb%I?f;-5B9J(RkUrn(;{^qo`pqS!`3kJR8zb~2!Y$ywmTX5p6{BpSCs zbM(zDtt}i@1F=B1uxwc~7}r)s1614xDziZaHxPwicyokT${Xbn1_y=Xh_AV-P(L*+ zj|c~=R2vPnn;VFj8A#)H8EC5|vwX2la2N5*w%rylpz9v0Un*>Ym@Ir7s8@Nmi{H`H z?>za4?&KTD7s%2)Bp~W3JA`+qWC~RTB!xEfk_AFNpDtB^&BkYhh{}@Qi4*PO)GEYl50I~(ZWN4@H;pO8gD@*+I5aP()DPO`aqsK^1EW`p+NUnXTLgu z?SyAZ3ijcS@I93Q#q{FF+-65A<S6L9212W-drXptWU`N)O>rL?N*1+M1 zt?YVkYJ+3rt1Jcc`SI?w(iD_xIH7Io4-4x97sEc2)X|a|bhyV1o(YWUdD-N@b-^FWeP?V_YtSzNqa& zu-MlCrEVwe3iQ~3Yx7Rdxr%+G?AD&wFdlt%;#Dt0(#Fez=H6Nh($Wm}{-dL+?T@g- zs`~S#+s|Bk4Xdnvtb%%RxMg1o?OCC2Wf4k`)dY7h9d-Kx5eyA2xIZelV$D_Pg{3f& z3eE51cAQM2QxCh_im{i|(L}aGZW|PQf8bdZ!;!x?C4G9>Eya5@R_%{FaDz9}>tjW?_1wgWPt(IMYaZTo7*Q1#O}kG7NsVyOga_q}D)925D} zIUo8c{(i=v;i`;R)C`n%?wfHwatP0mVbZfi;`a^ z9cFv;Z7MU zAe01)esQ8GQ1;77UuP05chX}A=C|LVZ!*K+4);f79mc3ZUbSp`xR%d<@mhG8mzlim zvX*Z}Ts3iJa&>n&hxfIXT?p>WXv!3_a#`AO(_t^#Diz@arN`Ip;Q+-ju#izR3=qyo zocMCa^%}0dLDA3@g2ket#m2HZJ>wjmv)M2L3j+Ew6?4>H|I3- z0+I)X6JLf^S_|=>{?TJAc>$SrtM6Ag10DTwJD*2Xf=Blv>2DuakWrPqf}B-JJtl>| z5|q(%2@1JAE_(_;} z&e3H(cgA3rG8Ri64~3K(i`faSdOpVs=9(O3%6g5EuM@4;N%EDZngkpvUukhiuT$kK zmMQ!iC10moub5{N=9zdMi^&d_8K@p{CT4TgB&_WaQ3Vkp?_w&Xd7{O~F~T!j%oAd? zcxN`EGErb$dWc2tY=ITk*9-9!hagPC^;`Td5+V+(EU{Q%^`yD}h@{UhA($n@EEPzY zjTIAWeS%PyN#Z`LeUOasx1J<{=Enez>vv({fWW#4?lecZ%~-pdi!FEER}?awmv+sq;_KcgKes`mMLD)>ZWXTuA%=D}iTZC9g;xYyWNO^*AC7Sk=C+;k?_=f~Q?0#cA z*T1Qq>ts98$o1k9TA|uVk9W3m^X_8JH$oQ>QaMx?9z>JM{QC&X4zk<@hHn*0|AX9J zEV-LQ$RErWx$Mg!ZO;>)#gZoYu5*Q7e}YA&?^6VFv9%S^pF=79G1_Erp{_Bax1irV z3F%hQ8L0rq(|oL0uyCW;X?eUl><4w^fyqY%N`Ys1Wmrd`B=@XD?lvkB%cnf4@Up~q z;6g$n$1mQ27n!^hPo|9lXvsn#X%N};h|D;wTrF!*1U-3(xYE1DBYNz|0~|BZ9mG^M zq0KNW#ge763xbUg;sioj7b^_JM}HiojbffXY;B6MptSJsMdA-Jk{H3=p49KhYye^+ z^LiEVewZ&ZTwn&28}Fz9)S^DB$s|l8Wun@b$L+g_20~`=BB2=CXHrWo8Er!(XiG?v zm$pPu9l47BGUJ0>W?NxoAgB-d#$YWT+6LH1Jo8*+TjxQva6F^~dxqH0&nux8KSA0` zD&|H+QlNm%DHH$iLGj=AqmvDm;g7P)kQuQ8_J*wvmiVwidEye@Pzk_3hRtS1zXh-- zuu5K9W*18v&Tr_p72Z{V&9I=nvRQ#`Oi&3@nFP z$?U!qF}yET4ATh2X+<3<$1TH`*Z$CB2r%~c^qn&mXPux{Lsuh9?`>Cuo_|~h327cgzR@PCpm&ze( zB|;Y0QGa|3{H}+C5I+s_2S8|Ht?(j#H!uS)FalSj@E;X^>WZe!jFz&TjUT)6Ctkby zCtjO?CtehmOnu@7601)`#t_PO!foW#1!9yrX*~k89sJP@)CtSekGj!5NRq?|0@?_a#0aP#uxIG-pg+`z zkp=G%kDwxC4LXKrnpQ3}=SOl$>)ue7tYhfR;D;=w1Ju@s4n}bZNA5$)a(1kOVWR1E_KUCjU#d~@By(Z zOS8Lh#< zhiU)T;Rm3h6Ag4DlvTruZGR$%4@I^%8`zBsCwQ8njL~S69;t(|CPk4pG#3ECvh^U*$Do%s)m$xw($?RUO`KZ}8H#HapGQHB701DI(Kd?$W~ zcPio=Ks5mNOx7^W`|x*)<^!g|0nDHyd1OtdX+D&(*c^pe z#`X(#orF|VL!ULyhrPb)&u@U&M}4{eqV;7X`jv(XGxC25k%UMuDhO`ckO zAOfwiMzv_oH%G%bYrV#3zPbF0aW0w&di!Dw{jg2M_al&o9YZ8xACYEoD2^lya43jW z8tGv9xa3<_NkAb9Wq}w&J8Hw=MP-qSB#g*}UfRU@OpQI{#_}t=p3Ggq#M!wE`Bjd| z^Chhd6s>r`n2c1~@08WYGU*F#(t!<7pcr_NN~EKD6u}cJ2DsH43n?fzsd1ngV}M07 zR%~%hBxY7}nh?)a;T0T=lT^hks)!3n0|${r9OS7zO5hch0TXhqTCGA{E9s+@G}i8x zqFJkWsrID2mV(i$><^33Z_xEo-E4fn!|Eb~=rMSq9)c(Iiv;RkpG&e!)k+8-gpnzj zDaZ$`V=&RfJm9yUr;E{Z!+Amr(;%}GHD3|8Hc>Y7DO?+P#Ht`TbphKj-JmH(8a~uv zwO!6E@YN7}lpzi1fCyQ#`8?Y|)>4g(*dQt}KOajUxtLAf9^+{e=t$Py8Os}Mi9C^j zBWO7|nj=y)-*W0~o|mbbFGcgN(C7mkcvy+{CLwIT5UHBA+$7Gz>ZEC2xFeD@KM(fU zhl?fm;HaF;tLLua7#8~m$@9G|tu*@y+@X3IBTZdiAFT&&vKjNde#8 zpWRk+wFvQ;YK}IfT9E0SOQ(D~_BKwx(O_NJSQ!!mbya0xjl%JuwJJOF+WxjCan z-ra?}3$kQYFSAwtN1d_RCjy(~{YdQIY|gC;H(HAjs zjfyd}5Ck7bY@NULJS%m!x>eu(PsP7IbhF)}GYmmfx)>T{+&!2RSAO**q9=3~xArK0 z{B6FaT`MAC`?O z*xFZJhbt?c4b*D-$n4s+wd)$zWJWR&^y2ZRvf?$@aCe^o6e7pY=TWMf`zH@}th>p5 zjX)f;D1)Q$rj43>>=uYS7Oh|(oZHuw-=~y$JF;S{rFzB_2=CSHZ?CPpnHEU2GLr0! z#)ady-dNRFjSIuu1L4niK1;RHR?OX!)Q1pMyyafkP~3>^eH;+L#wd2v5u14qcKaC^ z4eHNXk+{Fl?rco+8qOq^Ozw_rK!BDJ7it+OQ(^d|!cqB2D_xzn@1Q!eROaBB;)uOB zhv#zfY=$6BJDglHg|7P{>_ts;zDmA7X8bI~9c^yw-qp~0wDVXYXMm>PFH7!TL}x;& z?**$Qq(!RM{*0EG*1+&uOH}4(`*(GhTpONArOOn+!^1~UR#1F~z4lG!a?T@#TphbG z>{=ZHzZ`9-uT&)lQfeYfZ=!_F0d8Xr=Y!@y?MmBL9YFqTcX}DFLBaiDPu9G#gy;3P zIhs^u50g(sHUx%AtVL?a%aV6X-ktN?cI$2I{}}r}x~%8kpY{H%N2UL4?9Y0w=k|Bo z-wit=rv|KUk5x zqUu(ijM5WwrmgJUeBSefp`UT ze|OQ!2%K%e4s{c0V%^@riCuxCI|GRd;-e`OyY^kr>w!?^jo~DVxLLoL^@~)3qFw0^ z=O>M+4VYU#=PC7R33Y2jgn)yLRMdF!T+&vuScH*?7 zjo#0@wTBVvr4b6j-6cmR%|a*w2J87nB&XAq zVei!B^MMvMW1=z}@9z8cfs(+XlEBfMQ3N*%@{wOJw+0p*)Sp$u-(>%%KK}4_LS-PD zA^$Jg-@h`WCJkfrV3jL);r^oP>0aziLyA{=( zJn>~wjdjf8^o`LaHGItC{0BFFbv;mdBW~&QGD7@*xg_r0E8)-7<(W!VV-4puNCy}g!>R0macoGGnO?9SEj_3(3fE2 z%3@q(x8v5&J1#$)jVVRXnTG73+F2og%ZWGzGXIwB8B3U=5xLI{6+8@iGqT6 z5snfrU8Zo9OB|_vsWy&E#ip%+1ykllahoKcWJIA|6v}}_Nr)XyEXW$h*PyUANvs)t z88+4y#i$qOMwPVk5u%QU;|TVJ!+a zRin4cOD%PD98%$fjv>}i{R!5yI@UA(OL|tvdbX)=lZ`F1j`R$?|2(d=s+>RN4zi$R zrw|!{`yrDR#VBSeM9~saeqX+gC{`i*?0m-NkEtKEb#>yjs_u zVkzt^WGr-pC{MHaygc10Nnr{waLryr@vajl7oZc(JSONL;<^#OTbejuGvMlD`{b!*_t8d+-Td++_~y})O2 zJ6~i{=KU=C`yS}duIEE{`Ai80 z*Z7|MH}cG#=M(GDo#zvry8CC`&$=^O+z-c2$rzlq^Nqu9$$0V31aTS14IWISC)}38 z-@n0S0s68#JgIyQM04Z#-GBrYntw80LZOi~g>QyLiJuHa(&Y-x{i*+7=+`qeU5FVt zYv-Q@7pZ2Cv{UCtO_sqxXHs2IMTOPsqvQ6Y{b@b#jFIB$oN*t9nWC4Rufv+ouDe7dk95iP{26+(n_On^ml0^uJ_fDd;9 zyth6SSt2~lfXmNtGeuA2dpXZ=L6@;yggzs+0@$Ybm@lDY^adT9YXBr zLq`O0j|dluf@xWl#)qbYX>@uJn}VYBO*L+z%rb!!O2cS14+wEE1TS{)Jf_sZa)A>( z1l*6mxbUO25HE)X>(N~ah$0M9NylBd8Qx0a1UP?Kw~G7sGv9!G0PsUJyIMSAJos81 zoeV;$YSwie4Ps?~=B*LP05SORD9%>kp-$0bGn%@bZ#MA_`*d{Q5K4{Gc({m-s7CH( z{0ac3-+;8yZH~(RH>g_r%!jt3r6~A}I=pPwF;))r!g$7gjBpbInc{m!`$-bAN%(L) z=9ut5BNR;vFjx~Hg9V!)VLIP8j206|V?;Bv+ispY)M9DK7(Qe|$68JbZ@cie31M^y zcdJ0p1R8V$2PWa+)-UyQ+EPavqBzu{Hm)DguiUC%HivI z_}65;wJ;WPs*PHDxRXAhXYWj@2S(9WA85CV;G25**JZu+*a()T9Xogf2oS)Q<9XQ+ zjNoj2pw}uA)?lroUhGM@Fp&aM^Lj__tW`JBlBc7ggG+@s1a`id_f_tV@H=B0B&#C0 zG_6Zwx)-0gNpt@=EhrnL1-ej zXvHjfL9U)YS>TwZsQZ0mbZ5)W(8Uvcz>>CT8zyE-dCyJN0;0u+q1$^apVmFwIJ9MG z%dbWoUZMI!O4_RCVb$4-zm5a15*dvA9h0*zRKYLO z)M~lVTK+El%OpE@Mf%*xTQzb9Tm+z0mGj_C!i?7Ip{+?*H9tF95`La$g9TmKC4%c1 z(?PVPxY4R%=Ma7M_BNta&@WrFtuXesS zm0~*1d39oIU?D!kHg6xctg;lX_jXU3zTvuqujF*LwH|n$ttx+*aj;|Bb-wYIQ{&); zUqV%2DRzDo1z#Ln7h6C}&5@0vD4vA`MG|=b$k?>4SK%7Ntuj^e_w$cVYoHnVaN4O{ z3B#b=-SatNb8hW#ax~$K4wJsb-OR5OzFzqCDtIZEeqF{;V9~?+@bLQKT2Zbr+&hyQ z_1!5#K!_{tB8%%Bk$s$=dknj&SoGJZyw7GX%ik*Vb~VAVU(+ljX*VcHQVwilbGf$e5*yJ??C*Ty`t87)79sR^>k0e&$HzRjWqIiB$zZi5a~%b67!m z*Wy*V`)Sj+e{U5-dZH@zIA-R|Rg%qCE`{z-QpG-M-m>t}&5)c_&Vis`#1tKUWNjuW zO(2VB@4Ia*3Z$!A9xhr|8Ff=_rr};+eZ!X9JJEY_5%$K}uPz)3gx3Vts7mi0FIk#k zi5?hFyuz1t5{d$cZe}Wo59`n6McFaDvp5+8TVGym*1D_BUA5T?@S{;1=AFG^k+}<- z?D;C^!IWc%CRvJ#N^m2uP-WdsTc5Dv8Z`!~MJn~fl;_qq{Bbm)X4tne||3zHfnUB_<0Kh{@=0^zH+oN z(B6o(d`{rqF8U?c^ul^X-|C^a8&`T8*=vx_>;LcK!j4jxrBNfJa_f@5eU2LItO)rV z#vVnH4U_dECkF5`gcgDLP6AdJb!QNlHyF3@4gJry%gT;iLGL!dY`i8-$<31kX*F zc*SkKsRHf-KvI3QD8scp@)lUj`Im^VGMiim-e?@?qw`yIk1ZB=?|k8ONe-?74>$RF4sWC9(G#mXEUMt+VE;$;OU=2=MfXf%$7`PU=Jv%9!hh}WWV%w?= zi)`_757JOiiO}~5CxUMHb_#vB_-iM-L4XD;Y>8$kIVr$LoP7jyONF-#C8vof5;Y+9 z6KC;`P#buoNE;vxXL?a&8dlbnsNQiuG!18q591;Y?lp4hDlW|#)mR9lmU{-j{X?+a zujmq_thxs0c}bP9-s1Q%pa$z@++c_&3J0Nx@jh2L@7$eO82-dji(cIn}rs?ARV_q@PG=3+954m zw|LnBzTd`e6TOYy4j%dB>kzotf`u_w#cy@;4_Xp~)hEDX1TBKGB)yAeD9-yMq{$iK ze+RAmIUS+)o)rK9KTd&hL+1p}_@75b=xD|TobC6u2yd9CpJ&IwC>&Cj2w8II4d9v- zAL;#>G^ihRd&l9F=i3);d@*9Y=EM1L zTIZwjXgiwjiJ%1kc!@ul%&tjuXrd-whSO1Rgnpqz=rJOaaS%N*0*A|)97MXAsy(87 zU;}t0=i;b0YMY8@nL0&2P1XF<@MH!P&7mk%gZ@8VJI}(Hp%KGC_)MI)h8Gv1wOHyN zNDs2d>FwD#_bmJXLi!xQkk&trK;Z~Fh7F^!31_P5%(Z1LaCAC`_%jbQhV+m(isNm` z&-oe-`T`t|9>_gc2?J>)Vs!9%n$w3HrVxeoluJ=|h|*XME*I|tiGceKlz>~Lp$T3b ziykJd5Ux{801*et!3b-Ma2_{aqZB$+49w&g)_jZ9Gmi2mYGM)cz)e6c(fss60|K~5 zr+nYSFV6P@TLTW_DqDh)5*84Lb}>i-E7{%QmHBZD*t=W<)L*C>aH9eI-m1yL735`* zks1ctuQ1|}`^#(%cjl-hxSp+%gOhFuWNX#{a~qG3bEtBh*rr4A8E~sfbfIYasS+X* zAQmN(%v_7i!Qn*32=sg=%Ep3jC)-DkP}Vtbo+k3OZ5qz5&lPTBu4%D=71*lLcv)aK zAx3Oxo5Gq-2B;9S*a0oo{M*q6V1?x^q}CKliJ~n;btsP8fcEaxyt}AV00m4cXyr6| zEwV9^1(z6$2xAdpgw)Vtmey{qSo7`S7ZfW&vAt}Qifuwls8|V#nJ@Mc>QdAQ16rn< zKwY{YuTT!+Nx#@f@m2IEbz%J3 zt$|caAg~BDGH5hh!;Fz`(Bpp1dw^R2D!Fk3yJhQ;2eSh$Kd1?6-C!{g;@=Q#4p3rH zhsJ~Ki(GLj0g08TnU_qNhhcq64-#GSy z-yDum^B!g!Da|x`rD+aT%5wfHts2eaIS2;$BVe%bqhYFp@%)iUD$J zj0_edvTC$iV5q@u9j24U-D=zn(nKwuEOym$E3KFDL=3aL;V(DjB8INlThgeBqdc;{ zVYnN~0Sx%SfiWC^j}g1gp(ag89`p;4dR+6K&@MGYeTeWD464*5bTQ~^t>$edO>fh% zrNN4Yl#`fX5F-2=YS%Dd>(IbcM+i?lLwE{ycAyfvj7NQ!CMIAfmKPyBysaz=aj#RO zihRVTd+1$VcGdlOwpxec<28R5)W;YBZVBTQ3xg(bdY6Ve5f{Q?OT{WeQXxTLg4&74 zW1?FVm=_%rmrtt(-4>^bJG3T2C*Wu1)M-thk6xy9XheHsM308`jn!t^qhzybSryW5 zTX)XLju|8@NGzBDY^*f2S9T0N_R(pyyVz!GumZ*>k$~Jr4jQJ=&(Ku*`+(~4Kx*Ul%hM>qyc4DW69~aJbGYzC=^!zjpo4YO(ia2O zbS5cChLt>nlR?o;eHI#E5+Ul5YBVsQ5*Y}sBMHGnuzJ^BBIM_uaQ)*kIs(C%cg9L; zXq9CV`vBl)wac@04|v0?uP0vc2fNu=(~tu|_fh`OVHIHk=jdo4?Vi%mC0~3HQ)qi) z#9STXo}dHDn5+8+E@*-oe$RUIM(fB0Z-}|R0lCZqx)`M42hEy`r`b9UwgZsrT;0z? zz<^*&2|aOMu$FnKB}`Yf$hW^-%e-N=%(H7@ZchTYABwZuV(rmvJoXEWPo#MoGy{*& zGyG*mvt;t70;HX9C?G}l8hVBf&XY*3UV~v06b$Pe&2{GJ-uYJZgTgoDW2}z8|4@vF z6?-&B_fF%+#FB83Bh@38SuH0>g0p@i6~`T`Qx{w)?k~{)X(6Tg!9LIPvj#eumEtUacqaaUm?Savj*p`vmI)S}?(Z zMrSnOOn}S5H!uThT>oMyjjS9m`=N+THi*LO9Lf{#NP6*Jf{jtW#z3#Cn~gxKjGydWf2QEAYh8 z+Lb0!SpLq{{8+s5@;6f_H<;$#puY@_boL`sb0IT`@5deA&sAVx#% zTg~$woIWVZPNgVu3C_~ZI;z;B1BoBhZ`823DZzR|_h##!99<0L>R8!82;Mv$nnSkL zB*1)q8~Bv3lhdz27r*sWo(HC2o344cb8;cNtLaDeCg|Fsdw0T^qJ6tS9<>j?#5Fpm z#6}T3AHEX1Js`G3!%ANB?AG7u6_<;3-+AHPqoYL^i*;{_{x<&Zr3cNkPZxM8#S_gr zePx6izx#E(`GFFvS+I!dB3fy_7($#y$^reL4px^#_P6m@u8vU~xL&UNk-%KqK{ zjyq)%!FaO-fGHgaJVabaWT-ow@lJH!CVQ}hFRrS5?*tV$Q+Wi@x}YTa_}9ME^@ zzG^a%U{tw&iHND>6rN)Ai%Pc6NE$ez6RDmWy;djRN1g7i*S!st$evYH(5M5PCLBmQ z%80zjR6#sxo=P1*-X`5A3BofM;XLvH0_SnvdxFpar@t0O;NHax`=LC8C}wWd;E2N1 zfpi9jiB`a*zL=UZrE-DL%!&ZRo2~m}3Cle=s%hnb^>Kogl%V@ywFdFuT&}n1q7_;T zH~d=Rm-64E`+G$j3kMQG9<+%iB5lD*)UB)nx?{WU>(Hq%iUIZ{D!q+FsKsz^afnZt z{Xn^BNQ`jkzLUBRU$4^>qkFNOf$BR@7z*Xk{kd?{Sqg<|Uq5K^c9NjsN%@u>(JW>5 z-gfEUQ@Z#TS+_y@<8Giht;4N>OZh!IHHLH>ac|D(zHhx}bx(otp3`}TI|>yMCGgDk za-{?;X*olhj$H%x&7maT_kw8}-GIk4`U7ki5p4qod#xu3Z-UL|!L;Sv=hJn6hK^tX zu&$aX9m!wQ4$1|cwopbn*=9(j01QEg5C@^UN2Y}g4jG#a-3LcIY$NP7dq6$OJY1be z1%u~7@KBgj5DA*GTp#W9AvfH~<HsI7qA;vEL_!0%5plVlw`08VeF z05CzCr1IpW+$ttHNs%Tyy^+rUiiaumfGQ_BFR!$!xF|PApxfb+xJ-R27qYBTjXur! znrsj}gHO;wW|+E0Ij1{$%Lc8S;q=aQim6WjG$*u`h+*wOKST|(8<7Uia-!K}_oq68 zrW~pVQLS(8TKIN&o%IhWqw9a#)sLQq$zyw=Cz4{`o zg*BOvb^76mB1EQa0}Sd;04*Twv zyV5CAo!VP?O5-Ou*0F2>+x1kZ)5H6hU`IOVSre8$x-Zp96Bg+77~Seb%nD2<5E;Ws zfxhlx+LNJbQUjeYgUex}vxg@-_-jpcl=|_M2gx+$QCbJZ067^>r&CUVFk^rgCqZ=@ z#46?+4@?bWwbQrC>0LtxBK>;TIWMgTPq<+NlZ7|a2^`)H)Tw35 z0khGG0hahShKG7-R5qcM$t|W{0n2e$whFWFVB1035IPS{i$c1zC2*u+^1?$qezTku zCvBC}|8g^0QloESwcY9zc$G@M8Fa}haFL$vq)B29nHHJ}g0^fnc$f^@qZTo}SL8Z< z*~Dd%aE+d)de9c9C)a6BQK6=JPMSyZe9{DeH-oq2v-IaX-GB6NbGm2ozFbyzGBtPU&ZQ`?=^`2CtRw(~HHNs(lYACj;pMxi|Vqc!P4ahdCR@eRAA z3!Su$4dkQTltNDJKBj+VkdrN^}AH{Au}q(CDRPxJ1ZwkMb0zmvb&fX7#X-2 z&(H)3DA2~;jChyRPbf_qjd2P9L&FK}zc$l;{?BkCAY1$w+9530k%4I+%_rnq3&xuj zk>}-D9#F@nx%s6}@v)9cTCt+7nCqq*Hi-wxa6?{TulB$JQc2IAoB#tZyzidx8q z5?CHJ=)tcU@z*SOiL%^#G3tVW!|ISmnX7oY&*>|1dP|)&x_Ck>+=oG)D`dwnU`A{^ zhr+Vgm%wSwh>&8~xGQsd_d98Yf*FqHIpF*qHJEHe&|aiE>Eyug{J_MF);tHD~tBe?N$mw}`*c#TTadHm}(=@p|e#q&= zisUe1yl3QzN~c;;S2=OiqY~!eC$=eYV z74Q+KzYaN|_z-@uTE;9-+8&xj$ayMq=mnz26dV&F93=67s(A=Qy_3sX#Z3uv*y$%^ z<`4`4O`JmZy77hzSOKfF1}3kOxO~(}%k9PBJy2ka9B`xu#K2=@p=oW~0GxoMAF>T2 z_P}vs2FrhIUV>?Q$R7BcNA=+_ph_~E^#?@22RuxL-ndoXHA2;%IB@3_<3O2Z}20{IsK zXFn2udfcf&q&RKj89WKJGX%LdsJpk3FosD5Vk375eom4g2k8V&l}17}hDms^0c;q8 z{3sOzxYLVggzVCN$XLREQ4XlvogK)pqNO*yaEUs3VUa?RpUTDbkb|(`C`Xk>Ct7g) z!=}iC7pMxI_Q2AJs5J%?VcUqNPDxXj)5j{OS_5Q>y1z>*7yS%&5v6E9tyMJx6yTY+ z35HHNIT6ctXs3bXZYOQG;Dyv3P9<5yf|tEVmOMSIBp^deVyBe&JBR|jb})x}h*nu1 zf0Ia6V#FCr47!7O^nfUhK?odPAqe)Y8m=HplKD9_2L;KC(d#595p{HOSMcg2hKPuv znVf9T7~Ol`>AN6J@E~>%%uxp3IMWqopv`EhceFvR9c6?YBXh=BIRK?H{;|e5L++Mg zLYG7?pb@S=eIf$Lyb}!1L}QZSn_ze+8>lnV@C}SI#3aKH z%)oas(vS!9j6X~nRoq|(V6 zOn#wD^O#8>eI$rE-w?4}Es9|+yTAaq-DCyLg@%aZ9MlCu;(<_3;704#)-6pmW#@$7 zdXeD^c8Jk>g5g_C!o!Gw{aqRn5)F}HU~9#XzqB``c@`PeZLs@G7M6ZszJMA+q?f@2 zVwkl#ie9(Gfb%@Tz?Rff2pHc4FzB!bkkLi~c1x?YWrplzw>j^vC5AVNxKCSzm}JmS zVzS|*pPzeB4BH&ol-rgXIFEMCU1bkMu33>?(^nyMj z7OX`X@)^mZAQ8E~rG^)~n8}8ppkaeK2~t^Zq!=RAn9B{Bi-wS^b8uj}A&}%TR~Rb| zk!FD9XlJ~gy~4l?vWL=1*E0kJQ}!CcLfCEy3r46)AeCz1(ZSwbv?Y`u|bz(9I87(lp@ z9)WW$5eP&Z4O%Q1KB5NMSyCZ38%P6((u^$xW#FVN61W8i9A%DKU^gwGMPx^=P=O(NXkLKkafPH!*fO|kZ<@YZJSYGkSg-_ zsL$#M*q)S2;0%M;lGCK~aVszyVqhEoLoc!o1iq9V$3IGU|50Onbn0Ua8vQn`Xd$%4 zF5}%1EQT%@QUc7xc?Du7=?mDtGqmIGg7MTRIcyu}998s0sIZ#V41J&e^QmK?$F5>OD7yQJ+?MOr_GT4*T1Xb9FkCEP~R?Nnp0)T9!_ zPbioObQbDSKByt~-F7)igdV)$bk4v&135q;Rg{Y030sd0t9eSf@uh}9>q6R!Chw(E zKp$+&KBJ7f3=L6bhIcQ+wl5|aO^2_QABpjq^8^FWR9G*6@4fH_gsjU~_kK>JMoQ{GAQ0_|8NM@oe zn2o1OLsan)ahpbn9n||gL)9P)W(x^N*mA+UaKs>Y!VzlS5$=Q-QjVYz^y;kvJegNH zxX@Y9S{uV^!eC%}N1I}Qq)jp0q2}QTV(<*o+yv&Zj^OcOL@nNUgnA0`1|Tw(woQu> z^@eii3^fqEM6&}dGht3?%rX46hQH46H=v-|Xo#cSG>e?qYL$h6$fRQYI~*&J95X}{ zBN=Jzq7g6j3)VlxB?IJW4t9D=nZe8DDG zX)OlIwHoZ_V>4;Ag!X%MC}jT8gG6jVn`EqE8rn!=7h8?Dr~)?lJc<3c8%{DLBMc|d zX{eiqQpzRY8X#!ov-L1WdyS;vtU93R{8X2OacH~eh|nK2q+f|59H*;<<+fFp(!84k2k+TM1< z|0x;S5Hs|t9|fhgl@HR|LQ}}v;sk83|1t=_XRJXo8Y zW8}RC`9VR!Ij;T;&o9t<;mA5i+Cz>-I?_$2PT}H1t>+Ep7v!S^aP^%sXa#+S!7mtM zlu50Xib{Kkz!ss=!3CHNKiOAc4Ku}P24fQ)ZW2XU6=1pxXt_^;j)B^+8W>~Rs{wPY zDaLU*xNBf7;hM2KMDS(2N$XPY1QVawu^}dyh%tbghfuOtM40}eiKx{-G>QLCW(p$B zDW-2A(nQ3U2(){O=>_ma(?7}dPo^42;J#aYD9tKn3#DK^9kF=v@dDNRlsI!qo6Lcz)-^~^PgwIYzEZyEr8*EPk zZWEbatTq?nyC~Ek_OS8r*$-da<0`0IthvDSEx=U>lVH(oIOgEOt8;qtL>zKVZ@dYA zotIp8xJLkaDJ{TQ3*TKF;idIsg%j)eMZ{d_K5fi^4{+Z3aS;c6iKPuM;!A>wHT`0f z#e)S&uG635OaxK5gskr z58cwRY&Q!{TuXqffpEO__qKt*sXNo$6B zb_DCeBiBQ7$eq+tNds?-U8c8)abshav^~*;o#1(;W$}1U$*E|MoQ6&lyG>DS?g1&& zM2U$WHH{ovdrbRQV88fD3D6@kP3$%InWbnDygYR71~*w7#N{#*AJxXjo-s(Sev|k3 z%%S~~F7y#tn%9Wq?m`68)*DFtiR^P|hg&`28#y%O;Ml?evYejp4w|^-Nk`CRwdJPI zsw_iPm^A9av3p2Xk0VUn)A>BiVH0n-*Wm|t56;I7R1y$o4e%{qtl%`PerRAIn+O3R zoj@@J?GKZ?H_VFe|Kk>fWga;07&6696@Uqb`$oJ-suR;3CD{8E74`2ttM`6Wc7fdW#1m>Vk0O&KO;EvQVSAuXZ$L@r@CwS)SEmjP7(&~PhL0jW~5ja0-OI%$d`9h{~89db$2 zv?@fySajSFqt=8=MT(W(&vXErYEk0ETz`|2r_yTsq$5;89ZaJ#?JNM)_8LKa4G?(TZfb_y8RxI2~`fZnK}gSe2}%`2RhaS>(*&nhcZJ*#rgm< zgHDrbvsTj*PbYO+ITZxo_zft~{iQ^^Og2W=fPKpBhJih03M7*Pqx*NG{9v~U{+~9* z%O2B@St)G^oB<#@P7ksHbp}6j{%h?t>t{^b?V)V|a2*@Rv`LK)zy}wH&Pr4<;@rOj zpa0yCq3jJo86(dB`|$eD{}}2EBT>hQQLcYGA3!(o`$xHcgdt%r<}EXXspl$U#OVJ> zJ^s-@R#o^9SA~t4|6qOoF+Wmew;1a}6vJ_hn+_?#r0$|kB)a51a9SWoqm>NONWI6q zc!vm^T_)|5PjF$*i5^RrTkR2AB8hO}!-rBGCt5WyUQ|bw;UhW<=Oh=Dak5JkIq8rz z&H%coQ%!RDs4{!yvsFa8eDS(>iVLTvX=aT>4ww;7bs?SIPlHsAi|?ZAlU#lra$t(1 zTo`3=>=gZRI;cgzio;ruFy zv-o5IO%tAdSj%jNNPI&ZYl73acfKW{u(3h&k; z7kjtjSgC5<~A9Nt~1>l3mMPDK0+si8;h@(Mziv;2Bsoe&B%dWL zEX~?hxQKx(U95#^u255(23n+UM7ezE0Z=jPJ>6yLiaI(OQ=y`mxHcMH%!omw%PZKbQMCj`dk){e^df)IJ>S^Q5~- zwdl!;@Ie#H)kLD~GVzA<|5@V_>C`N+^YvUL_d}Ar&rO@y1c4<4?`LE1;9sd*YyZQl zGC~hx#5%CW2%bp59`<_Hk9Dq}LLz9;I=o7;(h~>|DreBy@Dyj^c=hmfG3MapAjhg0 ziDlItcdDaP7o(iQ=woB5lK@zz;a8)jrtBsW{)9 z<)XQNmTUOwXuQL+pqKB}{d`6mG~$g8Ic{M25OF|=gL2>wB&VCfh6E;?}wDBKEh`uLm@u@}#{XM|*|z4nhn# zNaW9T`SV^0EJ~pF@ILKzLG|b$oo`?thEh3Hf&oa7jxQ)CRwX<)!OFK>_T_%a zMl(jZ3Spt5Y4kHoe?Y42)MK(a_<(fHf~qo_Kg{Jg1=64pMP)9l0W^DntoKU>Q#u-K z9Y!^u+6=RJ1zQnp@W@uu(%o*QG)hAU6tPN`3pa?pi>I3xmP zL0d;itrxc5I`Q3$IhZ^lPNOxtibMpfM7%6_c`ICpLb~T+m$#Ax1-JJ%zABeEgogA3 z#p7x)y4rQbQJe#+yLi_*b3i=k1Im*UEZ)V%J4wT{G#Ax=M@F7DQ=p$;2Px)QC{iQB25_zb6dmE8~Vs- zcW8f~Cc@oT8_$pI8e`mJ-FBmRX+y}zDcmEbIy5+8WD2=GJKl}H2XjP5fCe*pr*;CR zq7x5Kji6L?o#CkyDHU>qknk9SWD;da>ESUXV=`rc^TY8(QYx4-Jar1C%8B4q%0qBM zG7;011~{!G;Qrap^hq1zr4^Tb>?Z3RWUg9eNP-N{?Q(?pCCCsdYD=81VD z3dBHZh!HR6yRD@ONQ?!EPchF7UKh1ib~NvRv1dz*d!wN7=>j)`FS;>>;+4e$DjA0) z*@b`@lf!uTBj~~w81_7|Xhi+-?hu0G+_c1y^Rq>e2hb--^l`w8tOWeziN%Z~(Tyf~ zmmmdBkNIx8bBW!pxoD4knrA;P#?u0~Kf!I;Wi_nAN16xHlX--ic6I8_9wQQ_DvhJAQa#LR8ul6JUlL2sd`g9TIw9xlQ-enAqZc>mnBZ zi(UR{1`N&$_exR(MA6bOAvewKO?SV=`EbNQ0P?Oqt-;|18fLeD?itE(!zWaL@c=iY z&>4XRJN4*xGDh4g_iDF~6FkUX?e?y5(*si0qSMo)0gvmb{|7J5U}na?9ySRKz>(qr zxQy-r+RmpC@0K-zwR8{806BBzWxRApfkKFVy_=TnneGkljijh)I6y(W6O*hp`+6w4 zoCVSoq#?(+O>Ds1s4JSoy+|1VV;?QB!b!v!N!}2hePVkk%Pr`-HOm3(Mcgbw6G#m5 z%7E3@!I7K=46-&u1MxHLKDH#$zK{*F@DCS^7!&kO-Z2_NN(1#HDX}f?H`I*<2k9~H zE6@odcC&zaWNWsdWoX4D4CXJlvW3egfyIa%7z?2HtqU7_?4?V9+Q;D`XLb|1-^(xPfIYEk>9a z_HAJdskdSr@Xi8g!C{Xb?w#mLa-;_~x`c;Iq8^~aA^BZyaL%$pxXeX1XoT7Mxutm2 z<6pBPD1uvzuNe})FUy-AkQ}!SfZR3;VdyP#!xN{yjWpT%-E4i3VMS);`F_ZCwj|p4 zz#Lje0s!0_LIH%T9xSUCa+5#w=D!59O-Cr0`J&kEgLLT*Ta4HPZIV;qV#olit3A}$ z$QI?fgSTpww9%8$kDxw#NML5kzilA!?qx=31}0L3n>~)uf_-duuuBMpyi3SigqCS$ zAUO`9_EI-?T=83m-e~(Ltq>%H4_W)&;-XSCkdH^cuWw|C18!_PkzqaPM#jYh?zi8x zzy}svMaa!ELO*!v@S0+vmNzLzaD?Gz901j{aK!-woX2g(#vOjj-8dp(v7^o;KVisH z#Pb4IpE&T1x56!!>3*W9A0cKiIl-}Rgo{K(1)v-+QK0W&;~H!vuwes)oFf0oxb=nw zwOQrF?2vDxOcRGd`!QE7jFMbE;vKUSxSAI5lniTEZiAeTu)pcB(uxuD0q+bfP089- z{(sE9`&ZOimM6+nKtV-CMMVP;6%~<}iijFOAi)~~R74&D@dW~c2#5y=f)1G}Z3N4I z!0!3w{@6XU*6meouRBv&>wbA>W?7a)dl?qPwH?dxI@#6PWld&QmQ|H`KYO3=`4X7+ zoyVQK)?F*ah_fH(e9zfupMCb(XMcIzTkp5AHFoA>!LRLppq(cO-9OjI7}gG$l6K%B z6^9cTO6&cJ4$Z7*)d2-?YJzh`%KINeDZw7&QRVgMJ!P`7qy9UW_fGkgqT_|SLQ~L? z?Gv{94{^8n4&I=nMWUfVw(G5;_wDw358*AR7Is0y^TTh42OaAwPAR*jB{%sNUVCjc z0KNx$j5KIrymwPqV*3Nt^3P!OXc(Ag9K5!D!j8s?ow^+XYV4!zy-#1ZPyBV?eKco- z2j4-KNy$Wiuu1C#PmXir(_%67{g8`bIkB^x=%zd5gaYIC!S2_tSHg&eMEBmOW31l$ zn&00($^L#9gbv$6{09no^SYM}XFiL@BSUq;Qo};o3na`{vdwWfk7jt^eKO(2lMPDF zs~8qIaQna+rwRA@9O3@@j)I`jv;-Aq_l_742 zQ_Kvkj~Pr&?r$-hb^9UeV0dMS*H9Fl31 ze$B3-P76{+6vKi(#F_+q0zNQ)WYVf1-Gl9hXCp2+W{JA{5L@lBogp@=0@4X-2 zA|SeRHGAUj{Rb32g}Uyd#}SCxG)WelX7}jfR8+BlQWy|r_=;~h@uKPVTD!Il0~Z`{ z9_dFo3((iYN2M@?`pI~tROMlQdXBJ3L`tF$a55{Fdu(bE6i4amwQ*+t{z-X+8}3Il zK@j05Tb&)tAJ|LMjQ_81B-i(B=w(oiiF?kOo{b-$7PB!_4iUttRrf)hQ_D$lp z5=yWDfzpi0#7D!R)FN`_;qr}7qWogtBUY~{KgFN%HPm3YKI=?gj)dcjoX|8~@dxiG zL~0`c0~sptX~V&J%hkogodGqq=v*{zu0#z-IHGdg1W-_ zzXR?8lA{}+;B-%%3y^=$NJ9YqMd=P$j~+&I^m9LG0F1Am`{KqYasD9Mtr%O4mZFiE zJPyrWLvy8>CPIfan~ivj*fSgr2X&yXPd@n{O!9`nDmH%fNF@xj?8JvSlb6fzHXdVf zz>gV#R;0_oY6=C&(0>HUh&BjNkTOV`j*l=(x-Wji`0()l%KvV=HcT`|(0_>FL<=c` z^PY&aqkcu49SUiZ=?0vL!p6{|j3Sc7fKo)VHrDGxBadrAi?16;Ssy{|$k4yEbAZ_%~wg_D}5Dh=J=<8+h#XPb6-{VDpKNQqX#>qJ92}{fvax zf|?Fp)*D4eR4_UW^#lGS|AUtY{0QQw(Mj?LxU;1-ir*sk>!5$)(8kK;PGr*HpGaoV z##_)Ix&kdPK12JY_{G`k!&Fi|tSq#%R?L>Shy4?2%u@_K#aK)p@lR~$+YF$F#d%cI zU4R_(Po%$xEp)mm$SH))~tL_m{eJi zc_sBGJ+oAD_+MPz&&HB8No|KL|0Oc2kp~5SDBKu9aym`#;6ZR*|D}nOJ<_NK=MU3+fvFC)*Ui)>|aH`^FLTdHMQKRrn_=88TVrpQx5N*pSw&KLNmwPQgIzGL<|{86NE z!#>77SfpY`D03U_T_Af9N0Cko+u}_S9~wTsl_nMl+N=r)XJVeL_fJ^3zW*21#C*7# z&ynnz+Jx$WA*u`e7|9VD#G@nej&#-uOYP79lHE3eM(Pe|f!8mQHye&HM54S5L(YupA4E9Z2_iMLq|JdYzfMxgwM3e1fGEidu6HR^`MQBOK-nVksb>g~R_2G*H zG?%YKfPw=5>6QTcm1Bl=`xlQIj5a*^kkGEPF^7F7uh#hgI;rY?6MWZ~4g%5c=HPaV z%tunV}^o z#a(8rf1*Rk8ZUO8>(k8UpHLXmfYb`q)JEDjlz;r9`=dX{-!7Oa;7+Wepu#`yW}B=W zjbEGShH_^~?e>$T0w)-^XQBt9B2t^v<<-xDAF!jfJX?hrcjIU4N4L!Gu)mkY1Ft{Wn8YJ{qR;=}IHd!@+XK*ik?Z1~$iuUaO>LzF z4pf5s$31>6e+T`BV+8%t>wiBj1De74Odb-_Bz|Gl!WV~sqM!Sg#peHuMZxL+uP+M7 z+$k0XED-wl$wT*D7#^`ikn_idp%AB01CZT^F8{-q-vtK!&^iIPU%L!}BMZcs;lVuq z2`@jG5InLJ#5W&F{WU5lSmiMp3Q-Ts*5+I30xE}b+eK_iu zr#!;_Hp;ULrC1t+GtS)hPmKBzw@czR=BEqo^gax{RU4;o&zDCNnSdk@F39AhIycY2{B&_+ESL-quES9CPOIq&)( zmQ4J0NPuHI#Sc6_Uf~!3_w0$V01l~EC&B{}0ZN)M3ejGLb0jkG_Yr}>rT`hVk%1R? zQGC}Q6?hOC5ME|T1k$lb2PUEd7$Uw8jcnR78!ilSQ4Y6MOdwFpmx@r+Kl*=3eyq>{ zEh1_l(x2#6bF z2z4d_+!dhRz)q?zC@B(4Ki(GrX~d^w;(tj9JkY-VEfww#{PkX3BgcP#i?P~6ZF;vi zpaoxGsZ0!D;P(Y6q5FQ+DZY`>gV{hU>u)Fa2LcDEb1=RS@I5IYWFI1N5LMy7zeVGM zlHvU`gU?45q6_^812h@vjH30ELjkgt#p!~7XF$6tcnFVqC=f6(B0cl203~{$?0J)u z30!&*rAS%>=|St(!FWXUBxVcYVp3>p2c?#QasfIeM$**wUue%pVRQlQ$Qd-%Tik&o$_*R=<)DQNm0Ja9bs$2yFj z51TsH9RCV$nc=tp(1ZTZ)g`Cyr9euMKZ+7NZ4W$*C-K>iwxA?6Fp&~?m=Jil7t=XF zNrn*dg1_r)np80K-VM0Y0uv+@q#!Vj`HT<%JRg1?hLlkX?zF%WnyTV8ct-*OTy3HI zFJu&v7l_aKi3AQ#I@J0I3^=zx9NVvt29T!}y^=kFZ&V8Z=&`_qbSi!b(Wf^ofc6;a z5>)jX-}ubi z41MT`o|Dz)1NZG=D0;C@m|d5mT{DbX4ijA%|;dVSbEncw4h|FoZD24>30teBj{vMiU@8KXNfo; z5O>!pEd<5Ovspn;`#CHIEy$Vjkz!l~D|D)#U(msC-dOJfEG28}B=o|MACm-OQ|3~T zd=QN!MAX4{3tB-}j!sg=_)Ja~1h7c2`#dfXpfE6Tmkla0N*Af@5+srF{vF2xc9O~Kx zV)39A_@*gnkg^L*=tJG4HwD2 zR0qUcsS#u62cMVPyY!&!^RWEwo2Utp56bZIgFL@x+1BE5AHvTh!mc|f>jJ`|TO0_0 z0nmhuiT9(RJuoWY)(0jmfdiTgz9ySHwa?=|FSLqg-$MnRSb4*@eL37Z?JU>0Z?ik~ePaf7`TqegSZUpEt zZUlgOgZoMBCz?0(I<6gSy{>b)5%}ccO=1BlLQg}rP(~PZI2>D~k+kJ}qk}L2rz$(X z)+Z~5iX5x!DPtxrffo53`osfP3soW$%SeGO3l>;NO-;ZLxj;Wa{voYoJX{gdJDG8> zmBg1-M;kxsMyWM#kXkcp6Z4}D-(+>=!PTLnw!j0LUZNtiR#EFm?dz$g!WhFz850teI}qF!`M)FyRgOcv$|@7tqWc(mnp6z;NJ!FM#7<-eV8L zwv`D7#2y>eNgqwz3XCxI2oEY#;$95?4`6_a_1}-cpa#nCAN)Qr7MMVVs7~BVgV*%; zfpJVdER4SgyF2JIo>b5%I8?YB2!ucwWB?)bR>FNf1g+OXv#MKSs-kRW-UnYpzF~i{V z=O^eO3IKbPJ%Ja|(93Kd652=ic7C#D3U7Y<1nI!w1ev0vxo!0F8JeZ|xp|6>X;}Cl z>eaV;x2~bOkM5x^U@jrIq3n&xx5&0eu@w^d7Wv3F{6_SkcmzBYsG6}_>FvsvyS@2kJmi?170-*35@&%e>Tyd2>KI+vHP6fVTC;NG)8e# z_g2UT^{)JOi!WHu8Y zY|YS@V1*v9y9X^7mY zJExF4S0+l`C4OX2?45d$NVPviWA;sv@gaWh;-7m3r)1c8*rtLf_Un`?7+XQ0#ZB#} zCpa+m5Fx4irXW8(6G>AK_EXi6;R92Fty5%$tpWJaJ^uM1Y06J-oh9w3=2S>RJ+zmV zm`LZZ;t@uH&o$FlAHGGkD@%pg|e!{cz!5 zdk&sU!Yn1`{=@6^^mHOaAR5*BNg6|hp$uNstp`D1<+tfsKMLR##J>`kB@RzLOq;?$ z0|R~=8^#_=5I;m~M2G^?367HnetU#QQa%!gq8*;(10Xax4gK*Kc-X%}#M#lQ`}lio z3UhIMB7N!sev}2hauGjwPSTcw-;|`ig=l=@_>^YGqQ3MgI&#IA<5LfhOwkS%J)-?b zB8+YZ9PNbWMzF{_4o!9p%8!OjlRcXLIQHd5i?qP{_gz8X4f=P?yRC9Aqc&amcs_pQ zhgw0eizy6kf)PG3^#Fp88I5F(5K71*3Quw&=5?%ID8%%0;G^44;t!3^`U|49(K#{o z@Fb58$vTZr2V_7W9r^+1UB=V{Rxr>;$O6sk1z$ZZM?6W$kw&`*Ok)Gu1CKfv`zQ4j z&EyTkBPC;a$vp^m@+;WkD`~n@QxEv5{imk1%+2Jv%ea@xZnmNE%D$-= z7AP#tWh8V45fVC2PX3F3k=U&kOoxP=n;w1kv!Ct!+VxdZw|aIaB06{G)tF~@&7#@q zRotD*fLF1Z97@09^d>XHrrepQ$5Udf=C#u6X|ni>TF=R&i)9TX14_Cqdva!b=-{-+ zW2MrwUuVejrO~p4CB@n4aCz*`Zr3+i-Rgsoh~K=cs8aDc`$L~u9sZ`fLT&ri@ZW`Z ztNXvnV@kB{`KDp(?}}YE#nf;2qKOSwPhY-d`{dmATl=RK0on7}w(koWvN?C=%gdY3 z#7uYjoSj~~%jqe$Ivk44?5uRT2JAoW{@$qL-ApxZpY|vYrPJ$jZ@>$GXZfypF8miJ zt*vHR>vH{mL)47LVl;I4><*jauIN^)B=B+X{_CdMvMX-uKu?`4{K8Rka_=l-cYj^; zjq7c}*L(iH=3S$NFAaoPF3#I+#V#KWLzAfS4=n@_iO8LqxjNX<*hlx=t&F8)oo_g zQm(d@t9Hq8xnoP|-*YRg6mO5qwx?X}myF(AEa})h&A>|Q=!n^A_u8!v`>5h}y&Kee z$j8X&{ogn*?eNa{oXV{M^s8bspnplE41IQ|%`n`pjtGiBHUGT%=i?f6%cm`$hW3z9 zLgt=CkNU>v?VhWs!`Aapn)lqA8Tl*CfDafiPo{`XFFTHyXC(PTFF00SsMfE;~;d{RC zyR07i{-{MY{+o*B0*iX|`{FV+fv959-jxf!zL=R{szk}~`GH-X%Az&|SFLWlwZoy@ z{w7Jb>NnL!Ti0yAb%2M*>hw~54K55UeMy!zJRUn-*t_5xuvxuI8q=cMrSe_210_zd7x~!}bGW@7(yf8Ir@Z8`m78Gw85JXP?tG?5z4p>i1c)&Myvx zn@&y-P)F_m{;Zf9MdMea>$#Utm zH|^$3r^D*;RHOT^{II`By;iRl_OL?uQ$df4k5T-3m;WgLkx7F937@D+)vOhS|04Vs zRT^TQe4Hn>Rk{C{;aG>uHj-}XQHP7voUiW|sf`*Q<_JxbZ_hTXO_F3KqM*_^!;)QU zrMWTE!XU`P>W$H)l*LLH1jJObn$5C;kiz?0;vNelFL!Y!o>@_eKQ@dTQL&zspEJ?^lVuvs<3xE z*=b2F88NQu=^Vs(2hUN<@--Yyn^1j%r&VQ0~;x!dD=>}n#F(WH7ZX4+OX z*H~RqU30Cvc|J*2_3ZTd`lHj(CyLwB=^9W9-d=i}EP*Q>DNzN}+9NVJO@fw=UC&Bg z@DK@gYC4yrjnBJ|H}9G5v<`TE)HWvaWJs!&eLWRt=B+lHF6CJg^sFbVv1%T@pbbGD z1JL7N?KSRbo);Vyo1FBT5EOjBvr@IZ%a>f1x~fz1F`8BzOHrXLUp_E?(ljq-JVT5O zL4#j}L>9Vd1f-aoO@lM?QdzW7W(MBh~CPB)1()DUQ7X&^#+%m3p87rzp_e}&%M3L>|S*ScGg?Rb3p zP-)!)>nYDAi)xotFD~DVXcXn7=ryu_0+W0#?>_5vLo++QR=d+vp^OYl!soRKXVd1O zlg-X9#f?crsyIhceG=jI&1BoVF1Mn*H@2<{b%b%i{tKt+PX1h3Lyz4vF6o{Rq*%&l zy(ltkYcZ5VXsM4t};|>Y9v9Zsb7TkHXJ6AcaOwK>}Q8}+}uZkY4n;15XS(B z$J)S&_@YHkeQ2D9E7b5xHR{`VNxgEXu^@J?tOzEDO*~(M1U>2KxsWpBbUCYi4o9No z`f5}EolOhWaC9YEiY_+~R+1!JIdC}p=e-`I+ie|TNU|hdYd0UST&Oo0ON>R8rZ!`J zy|F1x0#`Sica6>ULOapqW5RUF@^N+jt*~iJL%q5BN`_8wurB?=Os}Wf*WaOtHe~4p zH&ZV4tJRHFMW*^ZS@`VuG3$Z(UTQhB&zFEFhGW(vi{|PE(-l*Fq0WEokmc}fucyT2 z!%EE*MoICwV=Oarx}>HN&tZ~<%SU2To4}vCi_y&tftviRqh$QvRICfe&Q8VSQEURoF{0=QrGr;17JL9n zF-IRZxJT>~VXfDA`Ovgnh#IW!ZpGU#fv?Nr&&SOMFE*Mi4l)K!!00p45wmtrBWhsA zmbboMu`rBCF0YQ&ol2Z8wmO|Iuc3?V$02$o!|h&mT;Tqx;Ol~~L;JLu^0XoD{OuXp zBRnO1F|v3mEG^Z8g-a{97}af+YMf;Ec%bBN@4Un1>hlf6 zOOoYV7cTVAma$Ui4o;AeFDk=YysCpNhD2HR^gxDh?;LuD@w?4xl7uW3nUgbS1Rpdh znafGPM-Lk+W;(5%Jz{3obdeHClVnc^stm`_>sA{Tr&BSl`wLr*rXf7JCqwdieR9Xv zhG`fz_93>$^ij-gRLyE1D%V;W=T}kYk zDXZZ>Yc_{QC(jbmpd@&8`dH5&d7a(RNj`={ ze4m^e=p36avUKHKmnUy}0P?QTB*q@L?$hx;okqD z@T@g@XXJF3%WZ;);encsY7E2hOs`5ZZF}bjTpoz4YoJ8Il*3lf<&2ZWPg4}TLOsD9 z6fcXH_8zXcP4_EM0oUx_o&;IGTsyF}U3GbT6gSJiCs9I{B6bXq%=EhKP9D`HS-vtB zUl_SSg;dnYr9y6u9wd~Bv4kTi+?Y;Wt~`1yXT6MIq&m?xb9G&oyRLnCInK~>nw z*#{g-h9r6%k`i}m8nOiDS+Z~`BL13ThSp}D(|NM|=~(lneOL|!|9r{+#YNAFThryf z{sC=kz%p1UslGUp>HIHc1U5>F=ev*IX`JpwQPARVk_1np&3k_d`y2DR&nycc?=wc# zEtGL#l`MRAG2YTVXZK*k=(Rh2N}U8O?K-u^HdiLg8)f-&ZrrWTxuB-*Y*hC&s!a?> zSG{y(7lup=^&N!OBH^psw_Yk(>{gsG;;|2)7DhFyCn{8{#CXx_iO89i7_tG1Y{1F^ zQ^A!PP2iZ`Ql#1?@$=IMM!l*Am9+g5x_0o|K-DZ1h|xJBfrFCqa!2wl(~JNz-#t%q z8daa9eSW8G(6T`5a?oRqIK&b&&RD#g-R@N?H$I=i)8q0vYzA6&Iurv01rDUU4mGr2 z5J&dYT-usDQ?A}ha);=|vV|X{kLp)_|F-y-F-(hj_{FjD_S8iZ2v3|WTW%kZw$4(S z7!OZ8LoiA&$}0!m3mq&U>&9b(Bz#giwku)Q>#DSND2_x)w0ft|p)3eV*W@%w!d?&U zuO4Lkm!v32CUZV(`%SXMdY*DF{={s(;&C~kPp{fNc36HEMNN|kZ%UKmZ>es$jNGna za&DMzk$DK6kJ--lcXjh4-W&i?fW8G3Hp|4_hWE-YE zE*~sd%^I-~gXZ6LFB>J&>W;8uXW?XWK=oleNN|%5Go%dL=gjLc>|MS#OX#c1owk_S zD&zGwb9JS;8dhstiK(`stV&4DAL8taQq zZ5HzlQ61-Mhe0glfPWBrf z$;%Eoy97QDyLkBELa?6@j;P^!wfVb#iSqiw=|lan|Ka|#JAxd5wn4(6;aDkgXAGm? z*fq#H^x9>$twOa_s6OUGOV7F9+Xd5hkLec7Re0e>Bw(dA=U~h{w&qT`XB^k2TGOXZ zpN2YVx`F=b4NF)D3|n%>n7dLx+w<=u8AOXiwsN#;VZi6L-DRJLVD?@5{OHe*wyGHf;ki&+e<^Zdh^A+1tC}SV7RD}?D=Mu8LupAYX;tyi`B?UTZWS*DrltCo5Pvm7aTa-ixid2J~$C_UOCeA95Phqrdf3 zs;)|P=QmfrX_WX&A=|u0GJlH{=p>~{w&m4<=!@GH*ns2NuAo8W6mO9Y`t^a9@_n;V zO4N&TxU@R0lKb+FYki~Boj&*wAY?ekP$d7Cr^jp3XS5k-GrGHdI&p)GyuhBhz6$l)*Uio9psZ}QB)g<;x>^}F z-GZs;@jy49EmM86t|!%}Qb!js*dzrbvUJ6L#ddziiy~>U_tmSeR&`u*eI0h#9kpPk zSv<;WT{a}tC53WpYSHKdW|&urbcuzt?Kf?|*2!)TIa&J^dPdgMC>l^wxju9YC z%idU9JW23?!q2S4OYYA`qb%3qgzk3@DV0$82|CGGd{^}>{8CZ`5@l^KoED{L5fCxC zHF%N)ueRr;9wR~lrtV0VOrLIUwixCBL&taarAdxGase_Q-bx+Hvd zy)MQyE7lwfPE2zCaOm62KV(P-uMQ05?_bbeTlxTHN&4l1$TMM!aLdW3$A(zKvR<~d= zm8f4fPfzj$p*}13^!W1buP7^H9W`6AV2CRchX+aT2g{ zbsY;MYU-mW(cH($22rk7eQP#11q&~fQ8cNNg zh|}F9f{>%+v#mFyQeg?%y~RHyOY~>8r%rY*u!OJ=uhXLLX;AN4)ZsEUP1gJC)^p=g zi?kWQ#+VKWgBnotLtBHIJ|MJQ22)^faancbgh!$e4BFwKp>zEd)?gj7+8tyvXAY>t z4QgM5nkCz_T$vSGM<T5d*_q~3kES0~ficugLU-WFhvt>rP1qW1}EL--5c^>cwg+S{)j;{(Jt%xwk+3Sro~Y^zA1INs}-Dj+T@zw&zN}LXYEn_*>kKE zcrFe~7Qf!K)mpiJ4&fmO35E!pcg3;golnv~*;HO!IFFj>WOV1-5iX~PupQ{yKV4>a z4!Qc2k`br1-`+V+KpNV|qrQ{Mtes(@gL2vqRCS-7-$)@=rn^5xN~BlqH>(B~JIVUP z?heANi9rk_fc4YfBc84EJj5`O<7DyD!OgLo7RbxMrgOY3U%p;)pprC_gd`Az+W0gg zJ27g;qdjpV6X3aCxZ5|S%*u|CR7sNG)2(NGweuEJb%VLuRGBP`pF}6#%Ak#c8`?gN zOKDEGCLA7~E~|EV(1xXe^sVJc~>swpwG@sH+zGE2V8v1cwFrI`j=w$Q31WXZXu zc?Y&+#txSo0$C@^pWfMT42K}18L&4RW%0_s@=XRFIIr8~D0R4oYkuCuKRyG5=~>IdLV8IANWyCo>_c7vyM(SCH*SZi z?S=iM1N$ZLMORB*+zjpBf@udCjOHzP&t)r%Hs64cOCVG$2e-D>&cQCEU6&oR&bH@w zBMgLc_|3t6M`LHT!<4q-n(C{jdW*THT6FHX`=wj zlYlRZ8t?vri}GaAv&#+FV&{3|z}uqRZ}T;#g^YqO{WkW%#b_+7&J2ZinFjm1Ps{+q z&b~`-)hPKrZ4Ylw#Y`aukK=GI#<;CrPb|GO&zojm^vU)yOPZz2QG2r&_3|qDR1AlR zl&a@4>oeb@{-EE_;#M1(iX`*$p}g`%u5x`dhAo9~$TZ28y*^x1l05$bj279vXS<_X z(iRb7rETb}l63WET+QeliihkjpNIQZkwnXfv(KMeAn&5)475wCmltxnuPum3k057h zKf}@PFVCNE9;JOJbOD_Vh(j9YKB#lQb@M{#Tn7|$XAeVsIwW^%;)OXjHfSa}JR`E+ z)z-F*ZFImkV8ZDWQ;kciXSXt&lEogFk2XR*G&>=5%cUbBmrxNL(SlthL(t6M#zvHe z%$Hew9UOwk{DQoVF^oquwjr+HGCPbgJ-zRlAWl~CH2T7ku35<^o{KT)f6Q$$&+ z`q;c*NrJ58b#42uf*Go09hqxMlo+e^WsdTh4cwAs3!ZKEbX80XR~PkoG6UFt^(L0$ z^ujfeMnyE|pOp`uje`{+jqrkZ>9UF!*ZU03Z0%|Rd)$Hyy_VXFA240qqU*DCD(7&> zz}z}RH$P;a4n8sx+BJ=nOBaPmNMCNgWcu`EM!sc+T1e1BN%cJYWOM|Nbr6wJLf1}) z?Y)9nMW>CI4X)56fo~3M&fY2a9KvGk_z4fxnjV@vt zt&^m$+jm{*2_}`3s!?LBja7A2E%Fg1`LnojO_K3a*on48!~xkxxNlqZ@>{#czNBJC zx60z>6l2`hSrNX&1d0xEM45``1cYhq3?WVBJ#w**o}W*AgwXdhkaUVIen-+`8GHUU zk9SZ94@&M!H}~}wO=G{Iz!JuuPL{9i+fz|E3;oVjl3kG6sE$bJ(<4Qtu`~Am0f)WQ z?j7f1YEbl0f2|gSgk_0IL%lRVNmQ=P`^$%gcex?R#fPlE@ho$Hl1q%}JkU8;&)w`oBjTFDaLHxeBh&DGraqC<%JG zw`-_?%f(dWa%;Ydk!T=E07#-i$u*m)nv#vSZ zCdp^{{DHpM;ElamLRJnOi{G{g_pZM9R7sv|7i)TPNJSyZ#d5ONNxJ3pyQ5NtBSIXA zH%hXX9eJmYYSI)mx0@v8(!t|rPYX(#el4>6nIm60ESBJFJ+MKn67a>vqj#^&ahW2^ zR`#A4J2lH4Q{rNfU4ovJj}<3QYo+~MN-}<@(mTqE)ea<@MLhQL4 zS{Ey`$=0pJOZaN-u7pb)r&$8SFvq`Y4?E+X6Jn-C`K`-CqU7-Mc&WQ^&dD3*pxt$n zgum`;D@CY_2>)YSOWTDeGX`nxTw|h>W4blS-EX!vsA-bH%dLl^_QEAY!J9#GN+*6u zml&(gn(9-CrV$vdIx{5fO-WBzg(_jCN~Z&U5i+pz zB(K-yA&oWj(kl>zkuNcxUpRT7a+j0w!JU;E(Y=;-UL2cR-#<&*m5N)qm}bn8-pKY@o88*&gz~p{ zl0j|Nfko%{{U6*qT{S3@-_q_ao4sOgHlQxMUVfneSb|VyCRElhdA+#Nw$~FXHqV!h?}tyH@BfG4L*4IR?V)Gu0{Iom0~&S{Gr65|%bFzRSqZn<&s z$dN5WYVuFxxD@^O{G93FHu1Px+u+fNm;4@|-ZT8GS~q-xw-O|uh0S-OQl=56kSMX9 z9oRI!O^9ow)9M~clFVNW-Te9c9_3`6YRGe6~WWt9x>|NLX!85&O0z5m#S1xm1@%Z-t@@y z$gF*@s8#W)(tg!3QyP_BcRY7zPMoj|sg1HFYa!S6>|5yII;2!IX@rH33Lmv--z~K& zK346QqO+_&Te_H$(LJQvHIVs{`BA_29es|^p!O^8QQjk;_C2Ro#b-qORa33vGp_wA zu2t~~9Tqax@~Guer1rg_R>dbq`_)&i;uEL+YOYoBiPwJJu2%6$(0+B+s`w;oze0Z! z`jaGySF7HxRg<+}O^=!$rD@;$YE^vFwO=DY8Tm!5 zuYEsSqvBJj{Tlh3k-sr&-?!AL_?Wa`=3$yqWW?OOm9l%Tu&}UVSgq24bvp2PpJV@g za$%umSZ$Pra|c?tR?aDdO&WF!10Y~;wqI>bnr&Raq$H#m$3$(zs#ReBDZEz2N70$x zx!vYi=y2G%087Jnt%>eCGcxXpEEi}Th1~D;h{vD%3%(PJG43r?q$R(cq&I4MJz9%dc>6g9;#9cD%B*}faT32+fLDrXq1Cp?48LH^5n?1vx75bwClHbrb(8o z?QKzeVd}FRUBFVS)N}@8n7?&g$-SvxcS4OdT>?q2_BBQ|Ln8k+yEJx_-s24v6;kz-QeOvo??LId1oQOky7%$9wjcny)u#lcS|~UY=#rGU_R07>{Y}uDk&pNr-s7 zb*cO3#x%83H8L@cY0>V|a@=^7gKCn1rJ}}MFNJbg7#4n0HOt1l*)bFr_TF)Vz^alc zYd7~sTJ!)w0j!g(SH`;S2NtwG=UYfetJFrVADRe@`=NEyg<6~vc$;sjEt2#}_FY@Y z93N6BHmfXMJ{expimiz*fQp2yghU$8V;3huaK}r~a!<(7MjRw*L~vR#1oHgya7<{W zO1@_y0A-2;E86KnN%?q3>b9ZT9xnA!A-P+R>?u=)5X#)gPaGpI6p6 z!)b+yFd2M{*3+RQazgCOZ#^;lK0$UQ!-?Q#Pu+zXYgUX-aNuNT{QUa_IdM9{j`rgx z=B(NAI>GL=+VI8q333y3g6iF=d*`h=i8{gNE%`M+>W{o6oxq(FZke~{CQE|Va%0%} zMd3We4wG(x-Tir*gue1-@2*rK;P0K%r85Gi*Ox=CiUv|;m|5@PWH1hFughv~nes>8 zSdu3|o;KDiiF(S@8=<_Qb8X5kqa=D07O^jb+D|Q$!7_|uk|=9q(N~Ko%m}70EfS=) z%_Bk+R522U`m?dp#D;~`=G2y&5w%W|KEHmY`QV~^tR0RTtw-cwa93I-@TD_z?;c^2O6!N#H$^u7 z)2dJH68FWidXqsox3zOhLhaX32N{h-`bf5=V{vTEGot!5SpTE`M(-4V6;vKl(7&6u@O%dJr!IwRc*;gOG&z!$l>rfI8(WkiR@FchQyMOostVQ_?1 z9Vg2k-|5f41QKfMmR5C(Rjv3o%BsdQ9Fq60a__cyUE5Y!9pvvsERwH-q{rlTWA&1? zzv)egbLnbOlVwIp9Oc3O)9r6?_g8#>mn_MimmY18nK9Ts2v*V9pEsnAs%aV#+JsNK zM1QgUX2`FgsreygxXWEEK^otJc2#_`m;%rJIBfG-!#t}|Pv>$}o0`WE^zp*=U3V|e zm6<(TewnY4@0{B?S12i$44pe-$tz2zkBZGG+weTQ;`H!*vC-1d*3eX|om!e0N`2-Y zOW;eBL1G|hlShQ9nF*JT|AV93{<8ytYgLl>tK92(1B*E4Mt$1j@zwY`oqbAsQ6n8j zT%k4agHdpejEgDo)z#})GN<8-WUn$dd%+x=7z-V=5^*$XH%uCFK^|KiPYZ!!u{x~f zp1V;zv!OK}D+58dv^VOsotw(@UD5g7D5!6nak&vyrP#=L^4J-K2K{E#RayfX@z$(E z_2=~TGa9OW-fkRe!Rb3+-It9Xl+d-X(1Yg?_)c-KQlm*BUIL0NyT79Lld)|g!y|7oP@u9bA>fv#h_wC(XkH8zuAJd=@=QxoP^5j-& zL1-UJhoB31or*RVL+4jgYT?^S9kp z6C~;5eMN=QxGP5GiIV2|j`m@%s(Fv}I3FGAxRCobb%W-3qG+{b30xa>M{SlCpN5bo zfiF%)xHLVcfk-)|OYrIrr{k=2S_wmbf0ddc!Ox?+`Zwdo0*#Hf8kT#qwEoCrEKHH_ zL(X66nlpM0@LGDj2Jf&dD0EHHGAEq9ty;dxmyFkLX6?AMPz{AQq!cMVRvhrT3w7Wb zcSF{Ka|1B^2crbPJ`=sOkXC|?#CpA}a% z$tbPT0j+)2+o2|GiU8KhdY`xNDaeFNlP$6V7gi@su11~AaWLn=ED4(+YtnkDg%Nn! zug%_GJYQ}>+>}i*^!w0NxQ}7Q5w9B#QiDU>EN{E5S|!Q5lD&-;Fl0A$f3XO`H6tt% z>!YGoV`mze)V1oqGlq=m2A2yO*f|n35?+^~+|pLuSYMAols2=arqbA8>btF8`$Iny z(@fpmKHy$>4`q;{)MZ7cw8;7OtM5Jn(31URd)k4E3)%w=f9v|gh}Noco$=njn2v=u zEqqvO``z1W=$M#Wkz+J1(fo3CY;&no)iBU0@O-)P6bOgZ6o}h^G!PsK88xQHFdc^c zyX)si2H%%dnqg+A#w(6dY3A2&bJcU`n>HijwMHZ`2I3+(6YUT}c{4`y>0KWbJzz6Vq(zimp;-Qw(lp(1)w69s`G!IY!{!gfwU> z{pgsQP6XV$o7+zfax6WRNruKQi`k*Ws@2?9@5p=*KawcOW~TuT0IzEx!=Vf*4nsf3 zeCLd*c^Zd&=CD0?#-_S&oV+=cVz9Z;8kivl@o0ul;mbA*V{>hw{2P>P#Gr?mc?!nV zLS{n!zq`uh(j1DKrXWGldbq(=?lCed_3qZfYqzmL4QX*5q?S`Hs!3~?neoVGYS@`%pGDn50Lfv)^xX;NG*&L)E92li_lCrG2lwn-Ab+f>ybGh?DyzV|v^ z4vDRKOl@Iwl9sZ|wRKnxc(zefty){!##DS1jlEsA`m+n;JGadl3Js}!V`@K_qIHpTNyAOnaVsyRWPwjxv9Yoe7mTDbN*!0z zG~9F@x8Bj>SST^pH?$R3)>sdgz%ooDt4k9nh3Ta!e4dAYDlfn%HV-cWovZClS9n#a`^M!{5hcgnccth;hJBstiX zL&JTgK^^{X%kQjAg5Lb?)q&0qIw@gCsX!B|HYJJ%YNlevyA_+v+ z;!3n7Dr+Vf7Q-NFT1W1kYQ0V(lTC|b6!he8LXP$<2NMUkEMBkUW>@3(X-X_586-&1 zQbTp?o_XN|fccTA*S&p9%7GwNT4kp_Tm@JpNz#>+OfRX-Q6Q7pYg^QDhWa&yGiX9k^d5TzJ>qE@!tfcw@qQ z#*8~c?|9}sb6Mj1yFAI^oniZ~vFQzaM#R7wG*_9SALi28J8I3hIqhmb(@>X2_aw*7 zVCIlfCmj`7dW8?GP`3Z|(7Dd!1#J}yK9t?|JEO!|b)TthS`_P}b})ObQoUQDnivD4 z|K#+R633!8ugqHWs~F1bRM}|NVrp~E9kot-%!=iTv zB3ryvGKiy8T<8*k;&z=pI<+@U_Z11}ruNMJI!=c#(?PpBfZy|B4Kfadeyw(R#JxbR zB%hPuJ*OP26n;|?7eO2SjZ zWMo9-*4ZyFXtj{UCJ}^;yX8CP_DytkM*6;!Wh0+=G<)L2si5%CQ%8nZshO4PmEU#$ z)7f`v67%(LcWDM~Sov}&tX64?R{SnqJj@?52$!$x#>Um{o5`NbVq&)bc6aXDgM&|* zUTyut7)SR*Gw{ZppQ^@5jW34ybt`8})I4Sasr>bhu8=O355x&UpNG`soFA@zcWpjj zqJBMeq{oBsFLnmd#*2<_O4cujTzOZh4VRHgpkYeXp|FcBGhKE!woILLFeK&&CgjG6 zQhZl6F%cx?d1co=EjslI9|KTZw*0{?;j0HiQ^xdxHWtWC$71E_u2>G&h zD|M3W&6(J9+t@)$ry;>^u|`H=8L^#7|3}OpJ}~*Oh)Bh|Cdqp3jNSI{(_09AB_2A{b`>tx2C@&mo2R6^UwL?9*)bukL z&;G>?*NMVK4n!W5Wh=R(G2a8MYJB%bFEH|V;qR?SJg#fM;D5X)zvi(ZKdYw5{j`$o~FiPSZ(nk)12K?=zNc7iPXol(oKeh919)2v&BELmP<9H=ZO3Up;){@V7zPrxe?j zE_F|pT3o3nOPp6*dxsO}*Y8D1H&dFdeA(8U*rZ=B!$vh-YwGyljQ>rB_C1TSX|>$f zFgn5>IEQjeTpl#6GtUW`&Ld3HM~`nDyjX}_BXmJHU$*8=$+_D9NDtaSK=UWnsGx@f*zk6%T3Yld6{J4({r_D+t5)sfWQi3)*4;K zU{Wfjo)GeIXGr@OO*YUh269cvbRFZ-Ouw+(a&!^)C+=Dy5sXmf+q%@9U20U9x}{5P zl$bAD4gE*SV<@FwB*wa{sNr2|r-FkWby!hvD{7OZc;j_t$IS^5EE3T-S4G~47KySt zddvGuECUqeCNPe6sVBNrtB!D_;8=;a975mG^iUXv?tVHjbo}p03sR};vTD~lpT2CDRwaq_!HWXnq0^B}goj)yHx6vmZWmZR zok)okBSBAWIR!Bb#iBHhOVN@yTl4QEi-Q^sgjI-R5aK1u^JC?u)zj}m6D0IaNZa9^ znw2Edals?PW>%uC`Dx1btc>Y06CxLpMaIg)mt{9(SZSN6d@Dv{0cJ zEZ-b>mlJ|zkiTn_kLZ^+A@8jN_>&KD&n3gk> zEL>`htvLAv$^rrL<7Y;0b03)?s-P5J+?^0VN>WkyOCvrrq zG4Vy%z13l5FvN5DROHF>`5=fQAy01(WHxHIKyWxC`Mtb(ZM$>*#6mLcm+-X%XH4D& z3N&1wi-S7w%)wKw9B#M{bSbI_DH$1sCU$kvrh`IZ2+gy><4jSCBNFwi8*x|qG%xm# zu*S7P2@Rv!3tR7oI&e7FC#LuZIKL+;y0{S-rvsfy$I`@eRk>_*(ar6tsZis?#AHffJUstzukJY3zd#X@LZ6B_P@QB? z*mR;~`MP&^f25c$oHR*qQIf=1Yu{bHyb+cxEBf`;^i!dlp3$P4bp~mY!AnP~{XB^W zM?dTEbcREGmTtxse5n^7s!%f|%1iPmS95tWUE(H8D8`=hfxtvP`TLFpzOjw;p_J zW(0KWmrhG`0oLFT%$X|5SQaIcjVRGpyUyfO{0W78!C^g6BC#3 zZ)`zcF0G+0dK-?rQnt*#KeUYA7B+3w(ay!+8WSsqb`?bLV1;q;yf4n%jh>nJ!FCCL zK2(&tZQcUkb!U%G+%KW4N9@;<=5SdEhNNaO3^I(CtL*4AxpN(yM1^ClJ%^BpOG2M@ zl3y-jRvVCOt zT%R(+)_$!O8=Odj@%iM%@v<#+%7rNIDr=wOj$sI>o}ViWIX>NmBfy}Prv<-@lkhhe z&-PsrVyAhzz`{gf|M3##`Gt{@uEqDDncpTz_^Yzcn^j^cNe=ZB)!~2y7St$Py_@mNwOAcP?Oqs9gb*<xoTuhIEV|T)Itf^gh-HEx15G6XGHYt3o9 zFvk#z(I%KDe4&ZKv}rPwoCujkhm#3U7hHe5?`~mS49>S#``SBH+6@`86{@PGs}>FB zD#S(5zQ|bJV3l0IkI3E=O7~W4-AI#8N4fOWH3+Y8+Y#j_j@1>3ja__e#@M3enz^C4 z44!|BsSVe68%=FBxY1m1E-_i!Om&UMN{gK-Xd;H~$=D;#HxOn-y!-V=mY&<(I2+81 zLN_dJ)NJN57%mnSpQQLM_o%UYvC--A4Gi!d^YaGQ9Ui(>&`JF@96sIm^$6qBEGsQZ z$;HN&B8ljpJ9LQKJ1(KCvHLqO1wl_ia`zX zOUFp~bLWW*RY4qCBI6|V1rp|E<3t z5+6JIyGEKg!N$2@tG-IDX-rfVu9fW^$rm*tOZhxT5drnJ9BBZWF6n*-_p?Xn+et{5e3 z?acnFg4y*^r;ao+5WW1>zKEoH+{opL$>S5$&t}Qu^-$00=bBf59J7i|qyeeY5ezm< z>ms*Rz-6=Rbc{Qd<=2It*@)37oATR>*XypZ??j(&q82gfni!7;eJnM+c}{MiO@l46 zinXD`JF1Z0m##I)jodaw+&8FJiSni>q`P_!A)(@39*D_CPth#&D3bk?vcBf4&=njw zWtXK-qqmJD%_12!7x&BJ<=SS;fjN-^n<0a;rq!Ol(=Ch8FQt{{VnO927@vM0R%%n| zy&t%?po~afUtF#&_p$#uYg{jj$gIrfEMM87ku(=+$hM!l)TW(ZNMn^0BwtI5j1+<$ z!`M)hrRw3-v&R>z?3nS`v+%8KgVk$*&%xm8GKk5`%3@38bR1JrA9pk!t)KVw*$0F| ziPzXBFamGRDtC9C7+mId%+bIqNk1|a}`+tT;ZTBes=X(R2q~jYU5kEJ_%Sp9p3N7 zRYTbf&xkgfnZL~Zvs=qDM~;FGIa5P7X_N5Fu8>0)$duL7sWv((l>j2s zBN+z*{c7`;+dcC>tsG7)E%9oML|AP-eEbKB{D!s^JMK6J(FELWx;+SAkZfzb4rtFm zeoQkkw5Qp;!O#|h-^&UBosjs!|A!oig#UBYn)=bzIQHnXw!7EWb|Z5 zRCNNbdm#m^!;_}5NtbNCc3ioEccW0lKFI0Brs)jH>GAfW6NhHWJ5$q_^UEyB;>(ax z-(im8#yOKhaesQ(M)H#tUA5m{Gk&$^BviZlF91}<bhlt`n^ zthPwd@Gi!eC7klch7c1k_0@pDc46- zS)8mFM}$Pz&vac5p{T*0dgMN}XYpyq1gFE|?ZAcy|~wmJq}wAy0c!+vdwX!e}$g(kI&wUJPGE ztQ@_0q)OKC@^r+uJF{MpbKD5_LVT*!tf+O`05wX~=eccF#}EMFc??P(XwuW-_gOUSd6Mg2QjLkU1=G|d!@;`8H|^S|P#5(Ir~nZ_ccH5*VO zy*YM1rD?`0@{m!AL&`8_=t6pnEz#3)66?8RsH%+;j*F#8T-}amDD}yZtvm;(u!>Uv z5_Ev0<>3Fxi9Gl|ccLs`?%Fz3ARb0gB*}`_488W2^;9zKC`y)Oj~zWB9kYm#MV6v8 zE`$+#Sv~GcxU`;NMlcjzftQaI;dGuhMCp>r^0}R3Tc-K#0s<*R*8M8iYupUU9$^Yv zdjvw3Bz;!Y(p{>uCyLvUCjn1ehq^l#@YW_S&X>i{2F~a0XU~;jR44(<7rPIPiZ_=D z5~B_oI&t&~8-fC0l7MHYTGNJS`JD?~iCF?xdQ*#TV0*VN-c<}j*L;6IJSBd%O+?R> z*pa{#>0X#RDEmkqW7DJ`IGRZBi_&tk(Y5$S30u2!qBj|-FEm&^<)dtpEqJ;s?Am$w zMZ9JYwi+$6_}NLgBB`hr9UPfy;e*`Hhq2&72j1t%DIR$J4*=YDvsp zmm(P2T4JSfiLrLsb+?k&7qlY=|+WE}P%&RU3a^89}@p3?GW=AG(eKI``U}$!;F(+Hld2D^By!`{22q@2Nvuh!SnOYc%J5vwC;xLy? zkH<>?P7&Lmg~v?C#Y`7WhkX`L|9zG~f8RX%+0PEy)So>{q+fGCONyC(6tefT;?EZT z_jz^RkW7z9I_Hgdhq;r~S25bgcYMeXLht@eBf@9t^mpcGkA9XBGkq^4`nxP9JefOl zVP>eTZQDGhqx=6?zfdP5{)zi0FJ{^w67luW(9D1O`Wyc*zJ2z)dn@1l-v8~ycfbGW z{yzkM|KRrz|MSG}1OGQ`@A?&0lI@F%ih}q;L`9w|ps4spMMdS23IZYmDk@3>38Xv{ zGLx4Ip0)BpX6}>!K$c(5xc8H7kI|m$sxi*kb@pAUC|%XI+vBZaJ8Z7)wQ6u{_wLiX z=v!6YRo3phzd2*Y%0#X1K6~6zHE?CjN6eTpW5$dbu~vMi`d|O%`|*GG|K&G7jDH)L zqXd5N$0&ih$~;Axuf!^$15URi|8loOQ2N-$OlEKveW6~Tpxmf^x@ zeo%l7ME`Onkr7uYw?F$ytl|UB3dNtKtW<)DN+20G{RUPkfz=9b8D6Xe*3j=1bTM6O^ ziZ&>r??O2W-O)Xr4Lp?QDjOALlai-|HY;0zJcY*qB;QKxJk$9KfdHV}il?_lehFFw z?3v!KfWq@#V22XesVKVy(QaiAWZVt4h4R2-j}jd7ECf;yM_(7#vN$33$jB0~FUULl@t?o$E>=--1%;E+Q7zfTGMfL8wyC{hB4 zsYLZ3QI4VoM~HBd68s@h%!n*7i5*8`Kk)#>MbJNN37ig7p&zQJT5+9J+(Ei$y-GQy^EH&`vA{I{8lin5%$>KDIiQvR$rl!C(8yMLZ)r^FQlUtoVCH`B|58r`vey!BemDv*k+YqH@O;{i6>*qMU^= zw0=eW`A2`1;PU;%`<)SnkcrRmFcz zxvuy}l`+NNq$u!XA(ECnS{_+==wAa?B}mOA=Rs+jaC~CPHT&f0^(}-TKzTcwUT(161X;c17qDnl;MyG#G3|9z-A*dxuy|WU) z=cqVY{Vmw9!mlYjzf9tXTPszPB$+ti5u*T);13qbxVIg}%=ZbC+*+;P1m&$YK#r;| zC0hQMYXMxVrn2-?)V5SLkfy5diKlcH6;gVqQAHfCEf+$f&3^AphDuJ{q-3fg7ir3^ zdOlX{G>S;3IE*aSlcf@vRo5?FK_*kLGpJRM0IkQx>q@9i2{FP3hUa1~GPt8cGLmc+ zg*wxmx3QwC1}#?&p`a@e=?**>z2=!l=|(*Gy_=*;AT0{>bl#|Hy@DeCce5H2*0320 zQqbA>>!02%IIdT=M9|WeG$oX-WTFaz3q(9%Hq6v1wyH|LimA`PO;xt@!wyy9`OUvm zKI~GJ-Rd4_sz5&Ml@BxfbR^HK{VF^IMuI9Qu2+S62}XobctAa<245XemG21Ef6xFN zQi}jML;w^XR_X5%m63`JqyZ&}A4gR`TmnA?iq$)CSo&1_^45Gso%U~00wrpoR3(?G zl&L1zRhN>d22CpT>HipyFORA3;qP&_Sn=TbHc+mThrpC%Ra|*$ph9)EC~h~@`Xm~7 zLJd@s)zJJfQ$@W8|2xE{h9^9vD!1E|o5GG#mG|W{F?-1-qk3L{9|YnV_JY}JrW#~y z!a!3p)xctfFsmUr|8r6eav^}XPO5<#^(OwFQVFM4;MA(8)gTuF=d>F5{`U7wTc_4T ztH7&MmG61VYf#UKk_J_&S3{s#j@B6UGcv52XwIsN>kNhxW)n2m>lJRgLA`TMWtWJj z5B+EdsrfbPJI8K-D8bvPH1rv1OZ9)*DCz~*b11=>BNSBCs1kw~;f{!_U{VzKTvdWw z6#t0w5B@Ru%ux!40_Rl(Sm<2UU#4DAgD_RXyr?P{P(*z}M#~}COp~gNDwm+0QRT84 z#Jm75ml$^pGb7OP`!ecnR+?4CthNC2W2HrX*$Pmr+NK6uRLmz9HQ1&G+R29^h_xuF zw?hqDRB-VB4_MU&CD@@-NB$9yKgDAn_)OtB&$d7U3P?c*64a_P0ed)x5Nev9B%D)2m#M7!`9br=a=tBhe?L{zTCoXBj7-vdU2r-W-`aoT#yo?7j_$>n}D{~rBlc>4MHPX&vP=1UyuvJwl z*U_Jf9}(66jD$R+YS4&!5Lb+ep+ymfAJN7b8RHF1Ie^5QKF3`K?yWlRdIKvD_^BQW?O>BWndA-mDp@unr!Q*>y z9I!(?=?aTuln~bimjugL&K@ovWGIYxEpi1B!Msjz1sAyj2`)4}!F7AF>plEk!he_Y z-(~!FIsZ-Mzbp7}68KChAWdVoD_u9m55$8?V5N(ECP_G@M6*?If*_pw0U>JfKbcBQ|gtXE{x(Jao_Rgff zrHc+R&p?mBQaw-}bj->_*a6iobP*|N;U#VjDmQN#ie_Z2F9;chIvHw=o*M{=qBkZ7 z5(cpki3hJuW&oG3PYD>NsA+aBQY6qsi{zb2bLsJGm6Gm)^;2B*G6TAMo#_e=D1j^& z3IgjO#1uk)@l(B>2?U^1%*O=ljo(vXbMqER$jjaY#Rg&ky=3#he(29&6B3q)DQpUh z83P6K=Llr4cLvtmrR0MB6ouM&U^M*8`pk8`+~~TQ>(YJtM%N}+FxTY=(IyuQO}vDf z(2JsC;z50!N1Q3OutAaj4Kj+NSq=e+6o!dW?S*Ip=VRp-g!b!DXj}xfBDxhLow`UL zZQhy9E@g{rD_WulsQE7BVYayf+g%Du*|>JNl%4NF*%d+A{Ua!Q3>4UcNhxrNsCyab zgSNQt>_x@TA;3P@Z|QML+2;!E$HVJFSCGXx;6f3(2#zxjx|Bj!h@2WltV71 z$kn2%*81OfH%s*w;N*j=7epzvV)9d7k4g zWf^okd*$dabJ1v{(Npf?9>UU+dSD7p^tNRAzau`xa|OizQSuWmMNolHH_ zcZIIeN*5X!Dno10|L7j{449)YFh60LSEW?BlxkL~Sn!>6DK-2PvkMT%pcsL7%B8e& zP$ckbUCQYwvO1Si&p%N&rHz7^m_bgNDU>w0&bWe~31yz8rzrxSbI~Gfy{plMSRruU ze(AB@Z1aIQ<(LYx4&lQ*Nnu5?A2wg6O zo87J;0=6j>k=pPJaoHJk;}jsb5uA|(^cy6Y`Jst67uit{lyOsxlpeI1X3kutS9F44 z)vVZDwEBQGqa5Q!aHUDZ=K2`}RF4rO^wB)q>!PK`9LU}03ii4JD1-{|tVaf}UYDDU ztDk)PbMgtaR{m`t2&xo2d2q_izPY49Of#hz3mMGEcy_pMqqQKRoCJA0umIOz`#<5| z{rD+F!$jm1_46u(E3H;fs$A*o0as9Pj!f=kVe6GaVM2APGJtMz8UqvymO+=k7*c6X zg9(!`yik!o{WQa#17A0Uy zt%1L6b+NPLKD;KxL2Pr~rB9GS}_n@!G{41*?T@1!|sJPr@$z%3<}U=1^hC?h2#Zn8=QykWDFj@BmCBt7kRA;_d0`dDP}@e8jJu+BcU;YKyvDb zzcILktWmwI>kX{_quuHC{yp8svU1PqencpNi&acO9Du$lg}@D+` z*lM@3#!bnIYw%dK;zy56Dae1csG;kK6c7?!Q&QYUtN@p>mc5KfU?4_Fp*-GNXs}F8 zbrXkFDoAycJ4CiBm6D3y>9y#m8T>4xyeqAykeR}g7(bB2oWj2dKhPNvhaVvj7BOk` z3*J6&9Cp|MSg(&X8giGA42cz-CNwh-2i!}lr8Q+ zru(P(n}MI3-4qCK;Z|?Juyse{=DJ;s><;oj{*C-Q!!3Vr(us+a-#e=kK?+m~8uDi0 zc`-sjNh1VLfYB556~rTF+M%}~ssge9^%i#!tTOQk9YK*KsVE3$a7NaBdyD%fuz^Bd zD5`><(TlHQs=1u!$X9n}#fer3JwW6@TDgUtEt*<&+gK9{G)k68;uKHmMumTis zcVm`Fhv2#%!D|P^GeU7eLves$MJws=^{F31!uW)cwEqxPdS*-ZnuyZ@__Fiw^AS(je{$Af~9{ihB!u@;2$ZTQm`dO z<4>;V!E&GA$D=FRrR>M(60E(2ZsmaFO-oHka}cW4rQwWFv?zz7(Lt+=^1oK+OpzOz zfi&6yLd!jr4Lj_n-{~s0Eog>F;1CJoyh~v}sx7GM36cs&+<~L++r?-_gh9NkL=Tr- zN@3M@(s|aGCx#UNqZd2KsTGtw>|&3M_XkCGL>P9XiGi&0FDCPC(tN2P$BD zAd92Ay_IgIiXeIiFGdGfyOonsMN3gs<5o^Z6)i(ity?)ARkQ>}b#7%X`Qvez2zqD* zit63UYA!+#qNoAdIpbE&GNHgiZqa|vtu#gzC8FrOTe%QbbPF7y#9H*{w8l z5o{Ig&A@7LE3HvQZ78z1mG-Ej4it5|6>C({6%=*3m2N6RS3)_QpT%aB94lf+%dk)w zw^Tf@1$4XaaVx#d!)tRZ$9Qg{O*9(r$Zmg(yg6cNEFYOMCWJEeP9INl{p2hA*wxbf zg{M^IPCJC*K-BL3Coa(gQ3y$^1TMPWuG}=KKfOvDJUX_6qg6)w7)X?Hzs(iX6K~^) zLNIZ{`$gJpQg34##nuNTpmd)Ym$K!wSRX|Dc&gsV!;T^kQq(>QrEZ}n8;;7*I{b}voQNQ$BYEQC5Dau2 zg6VXWuZyDlOk#Vlx|M7H0a5y9i-Pq=R7y@Ve}^cbix!2~DY^&rUUw^_EDFLHh(Cs| z@!oJNbCB-TJ2J*Y%MR|yDrK%mna3a~rzaBZ{Mw(S&i9bsVm*Nc9-38f=D`!hbTQvU z9eHb^hqAm#6XW-7`9wRkZLkJRfyfavmWr8$NQ5}Cc%r_Wi4*mPIJNkON${K!?-6r~ zQ(ffY?2pIqR1^3y!9&i?q%8K(DjhkdMIPbrOv(~jlt4v#7mvA=cu#1tC$z+)D|Kn4 z)ldLbf!VwZ3vFt!FibxQMn9K%M6iiv+Hw!gLka3KHHd7|6cUU$%MnQAQaxTp^NipX zA|Q@<;z3j4L@2r7EmLE4rGh-xgQ$dNYN&XHhg>HjT#iObOSCB@#xVl|7|MDA6o-J2 z0OesSvY${bhMq`3(xNuWv(h6{KFOZY>s228XdpI>R(mKoVHD1SuJHs?JPNQ@K{EiN zW7c||?qocn0o;?Q)ksUx zbb^>pRUl!OC-gm4DlB(BD&u-14467ww}Twd_tYJ^p1}A1*Bd>vxX18Jk3BOSW5j9` zXtA_DpoJ3!KUh0$;!fJ=34R~Q!y`Hg#tFR5PzWL1{x+}$k3iV!VIUXfi=u5&McYNu z4v#-p4eazN-vxG|sl<*cb^{!VeHOby*&Z~JJtQ}ix9z8tjUI}RQ5&mm59>J>dJ*m+K*=iRubH3e{zFLR1~meg19WfIvhH z?Fk%Y1s>AnLvvA9#ASzNnMX?R-S0W#QI0~?(0)%bG{WvU7AH$@(h;f>luQ>xr_D+! z3%A+xlQzYNy#5Kb%oD^>4|EJhVW|hpfA${iRj7Nv zrw#*bzb8;HdIBZ~(mD)*P#rZ?nmEcEJP8mSV8%WUvT(tH0m4?h0cP+s0HRCYiX2b2=?baN+YosT4^LtjZz*= z7ox~7dX%O&Ji>m@CCNv+Bn+NQAQIM0?gdW87!zXTNDIB}QJUXkXb2X?5F-6mhH@B~ zJxYt-$&rrIdpY99qC}I!9liIakfNU=ecS3$+L$+6m<2=ab{9@wu%&gd{m|mVE2<&; zL^Zl+XDz&;18=8Cu`<8q$`#fjT;y`4%M-lf3Ft?Q@zmkE-R%kVpq9N#uSc9jmf&7b zz{VrK&(rS-_Imt4==TKdI=vS&4N3zJ4=PZ9!cBGZvyYz#_*tJH^f-a$@im@7F^h=F zh=s>=gs~1y7C{oxMB4e*n^=uW-xIx#_LlpX|&Qc zV$5AbqG*lhYP29Dp3pT3xE>9-N^V&i$aPO>)bnOvdeNCFmvGsP?zDNf20mfxO|pRpNmmt z{Et@oEy2XeKNg10|KVXdj=n*hh$cBgNY*2Sci9Pz2%#hdIZzQ`n42E4iB2cn1P}vysig_@l^&PY6+2 zg+5M_Avevlx3IvZyc)0XR(V%@#U29>3_TiN<5jZJvt0BfvaOinl|dTi4f#}&*lB5Dj7^g3&uu-<#rX0H<1p%3uK|gs1nX zaD7ZiUC=M8AKaiBI>5kVjGqnD;a@K|LdH#ABqqNLW>WceO%@DZodJ*946fj3m(^&v@i-0cIG-CiFOEM~Xw ziaXTi_I0>J9d2KnJ9NeEGrPT_ZQH#5z21FZYTG_9`f5LFLn3IKH?+s=+UE^?A1L&a zLGxi25=D2$QJb=WdJlM&gSy%&%gX%<_bHajr0O_6`bEaIP{C1Pc@_)@Exd=k%6d3F zauDyP6{pPzwaBX^vGtUxhrQ$yOmHV2B!$Fk{;-!*!04iF-XQ2?@FykExEB@d^WH9nA#pEn#RKYz>Ru=iw#I!K z(WTgwg=Ntwjsy!_yRK)-0DD77u<}5U?g$>qGI3xuifI{C=6!=L1_=#1q5~ z;ks9;;$qf%wa}nZv60_6=@pJx)>z|JPI>Xe7)V}ktyej%8{@CRxX!E8>py|k;QeX6 zH*f})%=L($XHgp~?VML6`5UQ64eaxVFc&~~&MS8BU5Ys>9j)i)X_y;y7eV*s1-6(A zUg}>W=X#oEbq?Ms^fww@F*Z{HoNglkxL`qJ=y248VuE)E-G zYfHtXh-X7%uuq5$vzxhs356Boo$2-}J^Tm-;RrsxvCyUTLQ^>0c?1n z52yh<9&Yw|Z*xiq&WeMvJ}{zm3^g@M4a`=w$>CL;+>m$H_j*7Nb_Ts1Pcj?isC0e< zLeojS&==aoGG4|!?3?QJbR~hDtL~DcyGNK+OVxUlKsuS@7Hf%h=nJzB@u3@!Lpn4ey+B1X1+X-1MArECI+1BM9`)paMyV*2aueprJ3bSgz&D+;L&v zk6_;O#v!fH+}v?C;diMGAbr*`<^>oylqJvAk=J(X=K{3~Le@#A<` zA`~~S3pcZ9TuBhJaWF|4S=dT0vc1uhAUQg{f7Wm&IBHs&Sv;;R`TJNfXSj8u)@_bF zmChOVM)oc$K#gMN)=dwdktWp8>!ss*grEy54GCJHAc3n`HZCWcB%uHqoYN+-tPXyE z={OnMGU`n%!p8p>YCt5*jnG9GYnsGy!vMe*ku39F5#bwpo%DwED}|&83X{i0duNNc zay*0<%^6of$lO*%xT)h{4V9o3t6{4%YobBGV-U(yqRS1EwM3HoPEZNJ)wK zl`V0NCnY*1M7p6p6v<621xQx$l!6>5Sz@l>hD;B4;zrc(o!KLk?bc zKYS{x;JKC;xa4hglYCdFaO$xTL=;d@iIw*cfdisIZq>lWr+DND|Bw-PjjMVyW-sfE z_evri3%0j@4Bb0(FshZjo}&#vTA7Dd*_`?5g7bKqE?`8}a{(v`2r`luG}Kd}ZTrDV3ExOTHhm zY50<8C7b?=sSA}w6l#h#^<(48ah^zJI;VX6-Nwv^8WD?PYYTb!3OFI8@=lB^mE&U1 zo?o`{R*hFvWK%tkaP}lUog5bne8d=hhM#Oq37s5Q-xE*Y1!{;DESfVY`m%*SCW2@e z3i3qyN9KVL@GhW)#*NltWKkz$q@nE?hi}&e3>s?!14jNfhSVQ+dEe7Vs=$WHy^W2Y zYZ#{}6t35pC(rzKt2rYLC0Yf@mu>U9182ePJ^lF{GyV}neV6kU7D;~ISf@tNq`G-& z-pH)ak6#!^idtVHkw2o7CVtK#t*9mBVQ{z=c@_=3g z6-kH4dm*#3AxJvV%MR3^*v8;IbF7cHwiZB)98ZVd(uI&0DF^DKH~=6meqpc2$8$Qj z4g^$a9(Q$kDbI=({(NPTPpso8FCmt2i$MO)T>?Y{+-B7uy&2G~B=~}he1XM2oFWJ$ z_%LNB_-?n01S@pD2Qkj;B|iOp1bGBW53M0aU*dx)Q>p<}OCcfc+X$j%zTk3SAW^hH zR4GUj+g6arJFxO5}HepazI{&wk=p)k=9{T?pM2&Pn#6a zZvBHxK71vNKU7tw$`6KkruhpEi0$N;Lp+pUH7P56_{K<*FZ6mP8nazRJ|f8%NcO#3 zrJ?~O4H9|@?tcR6Dj&`aLZn$f0LK=h_KLcc)xIDX(a9vnT*Cz^J}y|}`zcoOYkdD% z^~|b(7jm@4w;IY25Zz}iSFjDsn}Yg6*5_aA+a_LPMTcJZ@Q1N*0yotc#QU{rJ`}0` zWFM~>MbuA?pxkOGRhS?80c8BSFo_6_gE*aup>q&KCufp<)EAWELRuyXOn4gz`CdMc zF(bd-g6~iu-3~*{@L>lhlME}%7rlWawncdKViwVJf+htCYIF^bimU_cStE=cVVRL} z9(f2b{QOPZ_9 zlGJDuH)59F(HCz^y6taEx?PuamR;p4vllytN&F2d|K$2%iG?Sx0XbH-nJ!Xw@`(;vOR zDr2_~C0Dxk_~fVDDqVL9j34+2WV!gr+iQFP0Ix3Z_0i|#o?*ts_wx4n{QG@{KCBoH z`21yW%pRcvA15Wj@54~JA`+DJLAAYjw-@glh4ujsCLj7=AM|1Ve2vr9hkXCE*Y^*Y z5&U~yfg&Hj_&1I7*522L31u3k8!0d7eSJj!##f)nwXSp>Maj56(_w407@CWkzY5U@ z`!Q$ZMH$s!=#$^EDv#bd6;vD!yuNc3n-XXxz90we5-@wNye{?q7C*{-_++2zX~u?A zkbk;LeQs=%9#vfzymYwSFP?&>K6Hx%1N;!+zR(!1kh@6m}C5B#zc1=;;CQPiy z&^EuEf}h~cGaT6m=s2LN|9~%O!bv;D&woX^e;NRB3W}+kt?P*nWkvPSmS2xR$coqh7J29uerAUh zKNls;Dw_V604Q20+KGCHpAk>`bUFCfTdy70_^5)D=yC{%Cn2CXmQ62Gdn0cYGK06^ zT>93V#HW}oy}Urj)*)VtA)fr?0)H4oe#HSlB2j=ou%QbyUpESJj!LcWTO#|A<*w7B ztlY(cp9r;}Xc20KYSFMguws5c{xp6d94vcs|pRI=wvur{AC?jVl>c45K+4r z4iuUE7JTUoLs5ePhJ+JOa7S)`osULD5XT^}BNRvxJet9SsW2Ak+gtVQQJR$o{&&`R zsq-1*g~K55qX>{r0nnMJ2H!uBIWlWkz0VzN@c9K76rJ&bbBMfRJ)yzE>wUK)^?GN{ z`V_QiJHB}43%SHoqc8aALu9Sd@B4f>?QruP3Glx)(%<(f#)qmnEYP)WsF9;V{X-tS zMFVn-^nE_ph>DMZ@TWqbc+UHjbI>>bX%uP~^m+UeDttEpLXHa<@`fTp_=?p9T@gQ% z`#I?QWY=C&5XbIbs{@D`Z?_i&ckDid_HNk&?veHG)g^meS zy0Fm4Yy^6U#NHk<^1na_EkE)XWo9r^#U}t?U-AWM5`k&Jg$O5u{>AfUROp>mV{`{b zp|gu^_LM}(p23e!&ub8E$qpO{o1 z#A);Sn|*j4q{SEDI`FTMmVGNb4z5$Y-wo%(=f$BGURdM!fUY*F2ZrdG5qwxd_W*(?0`%2l^6#j*QRqT=Mbz_9!Tb`k0J-jU3-U^y%+jiM66U-i0c2alVBI zLR3cE2p&nl_?!Vv&~L0fqGRDVUfX&1yE#=#I~Lo9TgQt-9ll`1&Et2cFWByTuY^E zUGc%(A}iS+8@MZ7UA`dIA^3Lx_$Y=Z<6S+`qILN~-Dp8X7DSXiz7S7KYzu$uT*?$w zsOBxMb4)%r(PdEK7&8e;{_wImvr3`;X&^vaRKuHK8&;%A_7&shEMc( zZ>rUW(b@kUoKInkGt^_L%%O*^U=vf zJlpvhySPw6zb`bw3X=_j>=+ ze=4L;(9fB(KRoyGfB)}EU)lb!AwHb?^}O%u{yg-5mQKw3RZ+aSBBNM-)Bf}83vG!H z==x^*7JL>y1}|>+D~%7EB+m2eC(aa4h&!-NxEvDKaa9H;PCa8_l^zp)^Z75$aJRn$i4^sZT}PIL9@7z2MBy+*RsDH{A%PM8yFW? z4?H{0EqU0eOz%az*YFZX?Qp;)t3X|0n9f81TH z&HLxNUzHYXncvhrs{QD4v9{2z9WT}zrNFO_wpaY$xLK>tHeheH;Cg#pc4_Nvk`;cs zbY9`V)A#*L2HM-93R(ViS3&);`?x^T=^VionZ3?~Ge>4_{L;d3x;rRi(aG-nxXH^l zByZfQ#&vGEEQN0&m{+G+C97v;`-jVMg%)etLUna~Rr7UChc>TN+wwz?$jCBmz|vg+saw>fQeXHIFSrL_xpEt|Ues_G?74ekGK*Xs0ALsuoZoYgkq6mldk z5*D8<<#};+aBTepvUXIK-y(2VFAAqfX5VCN%{oUHBQ^Be-l?Q5xCz*A8M-e$qg&UF^&x}V$b_58Puzes%F zX4NWxvHKSre|hjPjqf`qSO2>9AM-!TBRaay=hMa6$1gm<)g4X?U+9^yw`-?FczSf> z+H((1;yOa19LMfO;hi597&2{5UvOeVS6nfVwL%Gdnbn>4&u`vqQN&Qx{CG$8p(B&! zXRH`1Hal*S5!W$(xuP+~akrdr8i~4!OQQWZljJ^iI%Qwk1G>}oGVYwMH&vTX)>jDW zUsn^-94PVbnf{j6`#ZE+N%f-4)@-}aRHk#MPMhlLDr!zP=wyus*@}4=AMCu_B&l9* zUeZ4pW~wq%X;ty*;(Cyobux=Vws=YH+Pk|ZtV{<3`hE8$^S1j|hdvN+CsgFhnI1+W zMJ#GO;kaK;cR%44=9+I-|Dxu*ep&M?`^mi{_oV$6I~yF9I;+z%AmLwRWo75kEkkq% z(J{CQc~hsu`i~>BhNqi{uhu+hv0KbtqKif)`P7~b1^-Ia*4_b&fW;(`X}|}(yxI_V zD%?6?x8thBQsCesNnFQAV7@9Tt8M;B)IF%Mq1LW=34PVQ@KhgNjc5=ue2IiV-;=d8 z`=0b^bWOA)(SXf&R<%7CG~2CoSs=rb83v7hb?KPtYS__f8|ZF3j(cePz@m&VG-rqC zNMS@=%pwD^x4&=XA05d z)?PF25Tq)47(@T9z=}(v8!*8`e9T#NU39;se^j>q^!f+H6?Z`m$kK;v zujS9Qa9Qb~cBD@mVF(81^V5gM&fTMnE-|Li@i5HHN^Mkvr-rMRertAOIChdLQydbr zmCuUQ!JSh@ms;XjX>;rKLEZpbu0kgf$_EowBf9`BM=M>i&>XUc9*^Wy_*GYt_ zti)}*pm{M{3t$cdA=9V1skW{6TPz*cUNOQY;PKKUtL$`*Di`NVW-pE{sM-TNweTIk z7I9&`4Fg&tyxd$d*K(I{VAr8E!4*jGtFbE^)=xU9E223RO8ASxdG$-~q1tF@k%YdQ zQ+;Fg1Zvmk8jFZiiY5Gu!}huz6n;bv!DbmMNfax)el);7y1gD~b^Wn>&1(PW!W zTd?H7CEb>oCFBMOekSJ3n!AJg3~Rt+C4Ac6lxLcd^igwWJY$f5TxnX-tI?FXgp0^J zc9dD>+?Pbgs)8L*qU1a^oVqy!zC~YhFfdti_(S>9lB5%DHHGq8p1Vr=v)+e|~gZWg4!!ZHe$GmIR+JS~_%T5|@|i zZ8RA~i${A;;KD$Rav3!4>(WX~v>SGz_KU*Ub*23FwXfF#D+{xFJw#}X?j0{A=V$yZCbS@jLItB8Tk(wZ0z~F_Y+=( z2Lx#C8j%Pum)dr9O*#-h(D2n)S&mT&{`~x zxLc>w$8s@-|J2?D)0kL;7u!2MqwDJ630^uYNy~5lBz<2UE{ZAYe+CB%ha89XdK;b<7^mmbMgG)3d>WhKu(H zqvHH@22scETF`dhNM=OT6LFF2B$tQV4%f|_5W!cDQMf5Krgox*BeHp?w7jKawQl;r z^g+J%cKj!@k zS?|+1)y0W-Ws4DMRx<=Tf1XiZ(s!>4Yd5>O`y|ggwK|9V5Ar`q8q^x}pLK&8{%_QO zbqs3wze!f`_)2p{CN6swbAefMpE{JjVaWvUKE?zhYqv<6hus;Ax56vpvTlo2QoLAj zwfh*736?fTM5(3yT92f9ay4OeB|LTCfRmPBLw#Dm1U;+jDcL{C*W_B-DhMN~OB;~T zM+Y*FrQWB28)O$2hDT(TQwPQ_riVFNGsc-?VpP&S?Cq+nnkeUF%#9zm_i8cA^y%uw zTysUmM5|n;I!b6IyRbo9-Jr!X4ov-%th(WWFhdf)iD-x-PiEm5jM5p0#^|y5xWdUHySWd67Q*Eg+zGt9LEBQPr8a2mB-YET)y118cxBHh z;{N3v37$HCDt0Yy|Ao4Y8an2N^B4}LJw0RV$r1O>R zKHotL$wCHGII(_d4*C5$UU0PTwA_=VOYU~7SG#KpJRrWw>UZLO&@xYwpoX&FB`3EPFD%W@Ymvg67svWC)#BS^o4=+AjM9MnCe%BLqoW5Z?aJ zJq#w#bm?5fhPzBAT}SDHa6f~|vA0ykoV^PX2Dk*x|6Oml^Rj3@rBW+8dTsim$Fb0< zjYz&z+q(MBVv1+W(-%CW3`FZ5l^q&tmg`pc{7~NdV}dzd@=fhN_+2bR zsp{+-4duAG*$5gUQ-iHb+u+bH{4$<#QN`nyk&demj@6vLTvbzZYKbiVH23Oo;>6kV ziYgNW5+&g2aQoh^6NF!Jk|D>PS~3Hnq|Z;p9qkU!j$0|1OOe!%wp!y;!UKJ{mDu7) zmt-#wU0hs*b?xkEcpZbOInCwgM<2A!ULl}y14uPV#!u_#T#EWUCFJRfqX%QPW4GlIC}7_iwZd*AhKAX{?>C)M66#q4?_1`L-5g^3-=? zERLFPZ9{{$40A6blGKkDEx0m=cCq@9oycs^_BCi5E@>MtY4MWPi~Z-Ze9-8I?4Cxg3^kTa2u=+j+2sT>;5rE5>A)rFRZ&K z;&OfOtkl+L#iiu6S{@_OoYppcxFL-4k$lPXVfk?74n%K+x>FWD-m_z3ryj2f|Z1%2hEP#t;@h1m* z8cUJEFt?4EWZAPlmWuz;hkMd#%Z}u#APB8|ez80yaR`^U_l;CbN4 z-93`x>EhaD@!|3aq+gPKao*D2ILUO#(28Y|sBR!pc%uz_WSx%Nj+h*=L*`busTO5X@vEa!`7Q9k-qHh-E(2mfxW11%iCzlhAgMIUoQ-w z9O&+bH{xI|MS>n4Tei7r;)H{M=_`Z|))A1cW?OY*{$wve8&-sKn1rmO<;Ka5dz7m( z^$(c4nKVx)ZTzV5qb==PzQlduTogAnVYha4Mj`K%l|Sy=oqa=d+O2)v7E^zL&ZBZj z!~d-@;X=vf`RS8s`$hCBTbamagh6%z93*tvn^2j!x)noG&OXYI_c7S6RAaVz8;sg)E@?9RCQ`{lB{!JtVS zUDc$AuLCVkySdfbXwbw}44LkhV{OmnO|tyy`OcC#dUBh03CsrMO4Hu61_V157F|mt z|8?ZQS|t|k7_Zi&IP6*2dnDlDnX%S#cw;K;mxWJP)UMbf!dU>$+3W)n^5|I2m2U|f zg(F-@{e7T0eMl&wuG8A?q=7gpDL(Dpu%azoZUADEgbj6_)bM|75)BrNhNml+W_1W2 zybEQCmyk#2M-yL_i^3(c@JU+oqQgRmRhV%TW%(z2ZmisJznsdF^@?IXi1{Fe;UsEW z|K4wScoPbnXS$^Qv~zXgf_qHMfOR^9jWrtn&yi@)R$bksEBrXTE0*|q67+c0=`~lv z<$7_xPCxSVk)Q9B0=#k_Sx_qk*U`AH0ttVF(RtU2&4O^WiS^mlXyy0w0L&9yyvM1R&$1??|<2WQyZIlnU z6jk4Ev-QxNXlbkEawze|_1uIVk#e4{9SypajS_3Rcah18DNU!|hOAnLvootG>q1hv zNk=v_GVgKDU9kS*{a)+{ZEL|EL08tgqO9m0*KKfd_L0`4utoBJ(Na_qD_Y3G9cL_S z9GX=kJkOq!cxn<bkW7 zN&0MYf_aPZ?4(F6mr`TGBa--K_Q2)N`z;6+0Wdm=HzM?3cXA+m>@ z!yF;P!;m60nglzVFE~h4BHSf84pSpD?7tz4pe7r60-W3Oq z`SfACll#1~S}TzJpJW_9b1>X)wRE>p;C1BVLW8pH$f>5glCGE|_{@#f-sAv-*QsgLma-Q5|A)am1?axCJo;JcOW07}iXZ>*wu9t1ctw z9StYJDkQ=e^(`wC!?O{@F5i)twrg57m)HZcA-8*EzQIt#-(+G$G2+;D!oS$nKelE<_Jh!X2)#xag$B!| zxNCW0Y}NEy>N;)F9WW{xO|M8YkQkjW|bDtXk@)(2Wm461A^7SW;Rr5OAL70!41d5vSB#70C=LT;}6$UhH}VK zjN^E9IKG8Gwqvn-txrpqTwXYr>`lVS7f!KBoa5i7Nci*Y<0HdRU#kV%uE=4vS%%XM z*x==iEe4FwFAaUYj-jxOXUoevx*?ySq*O^qrFOPSJ6@^f7#vF45A@z67>=w7`SK)e zs%2g39HZkzmo4nn@+EwF{_ec032cjW1#_odMD zfZej;n${q3Uai=lyFtW+0*BJ8*4B-~S|g*N{g21u%T0HkXgZw-ZIW{QzGUC!l)Dut z&(u_k(|Z-Am+5p}X-%2QERnvvbiOt5p46}49q9;*l;Y*)qw7vbd%L;;xckg8%_@a> zy7%zv%{WMfV&MQb^lAHkSbak4kr>Y|n(HlcL5&nP5;i!HI;L&8u9c2y8|$@xS=+0@ zEo~R?IXca4wjoQ~F*$h*81RfeOJ3vX46@s;76UvYD|*;|bXCkfGtVdoJGD_s_WV-E z`AXz9ZCIG%93G{NVpfq?VNZ~qXbQKIRFR!}29klLRqnlj)Qf(SHGY){92Tw$R>;utPn z!ln-Om^WiW5dK&I*Xc6UepLHW4kJ=Tv-aHjtb3Pv`js;+`N^X@hIjAUv!}g%-=1q< z=SjBH9d6jOoJhMwHG{9*W*A$5H!(dc#uuChF;OY4Xchnksg{!q-JC4UaW6*e* zo9+Bv8&dF8nrhWd67Vcz@WPlF#yVYsRjZJUpU34FHql;sf2d*FB`R&1OmQYX7O#&vb%F65hfT)X>9=4vyWYy30Uil~gaBX=$s)T&p(|8})U4 zT8~6{d0^GGeF)lk5E|y*&tS^1H1{7&}~|kS{9ba{bkBx-U)3) zV!T?jKW;AN32AIP4c4O){(O~f<3iE(lDuYq`0SrA{5fW|KEHo+s@ieDn3E{CV(qrn zTYKQ#1lL%`q~_Z<57`wL&kw#0BasDVHmpbd;960O3LTUGhD{> z9hJwCj6PjoId?sbO{TJvCE)R~;!`#p7{Z8dALvez#ZR`Lwj9L4PnraA>MMP<@Dz1J z8vb9$Sa3Eka!)j6;D8{VQMAaj#5p?dhEp2;&ogjWRGJ@%RG6{2*fOYX|JqTlt|>_JbOjkDUn`e&n?dpNIb3>p@5;J%#(+%H0vxnts9(Lp+OgSWN!i9 z5|H{ohSZX!NJ6JBEp07{g3>~wSOQ2j!(GdU7eB~$5?br( zu!YOflV2QN(7#CxH9jur9K1E(FDrg_ zDdolr9EOd61`N=lOP1<;vH=?2rNggiT8i+iF2 zjultcnPS$^xPvM`X-m!+oqz;5e$Hj2!;(G4sbPBmBc6-U;#X%<7pI4H4#g!kXH1vR zn@-nMK3yU)rcXPTCf?IAN=#KXXD?ToP9Co>Pn1m>A(Gb{F;zHha`C2v?0&f#Jbvn^&-j4)??EV7F7 zB>ee~lXLqfkp<>)%CLON^68;ttK#pOyNApp4mS3k67o2s$IloH<6l=v2{P=ut;@JHw4qy6i*M8IX1mAvdrj9nl`uBf063nxq6ux639JYAaEbnL;PesT$dS|#nP?Bk~| z=$Tw3se3yx>}YF!VYo-4JU_Xsa~b@zsi)7077$XugiWQzpWpUi07(}v$BSN;wgCxz zd4BH2BREhcU6#NZk>IK9{H}xvQ7BRNm1(04CpWrlS)Bt8OJKyL=;7sys|(W1bU+`5 z?X0&6Xl#nG<(!}A{Csu0c4Jh-=C%k&zi2+$SZ*B66EUFJwq3#~sOi~>E&U^t4ARf6 zUHC(yq<^`>cJYeFKx0?jVo#Q|pDtds)g;$5SZ=0Bz{3SiD-P0r53NBBW|vB;^|TuU z#Aew#N%L%d*_KM3Mu2h{L<7QZTU0aA+S8X8DJid77q;1VAnX+vICQBhIWhJ7jFLW9lvd0Tf2HWc>Nv=FgS~b*HvpRVtTcug!?7nY21~KG2#?i4QHSRbke$&Vf;TL z@g6z5mv6s|Tqn)2qq6j2cG=bA_bvTY7_(NNC_e35l-({g%?5V1PK#YDEE$jZKVJW} zL{faR{!GrC2}WMFHk`=C=+~E3Yu2t3YT?BKa=H$uBUyr{w-qfI5xS}9=^1d+drHhB zMdzO`F{kYP%{T5@B~mu+$7Ze#uaodE`gV`Z6`V}NPHdkWta5Z#d5lV<>U8$p?Ks9W zY-!6Dne#1c!}*fYi(TioRf}#oMkp-KPD%S@?fPY(>BiCBT_B5}?Vq>Os)=&E>jPC5 zN`B9`ExJ%J!5|q`7wHlqs>c7t29vatr3v(+BES7d$23e5^y#J*F}-(1ev!{_NWhbo zEve0-Vj2?FRL%q9zzJ&=P9=($j*fqoa7nBCWvyOs15+bTKB^rW>^*w77&$Na&@87d zi{;GfF?VDA(_?ANqT!Ksc5S#(kpBC%udjXG#N;%I#0}o)Hp2J{5!RTcXrG=ejXfNu zlrg=PP~osJ08`cPR+XNpM8GZH%>+mS(!H6c_rblIOrfwdDhs)@Zq!s$yG{o zbR}LTuGNxiwH~A1&W5pt`uhl^t9}Ep>r{JT*ufJ50S2T%UtMi#P0`;ncvl?@Yqb#r zH*MkG)=4239g2rkln*iF+4!eXS>KbRbFj)WQ%0Ani%Hc7*Q2&oXEupxvBI(Msus%t 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

=K7;-s*~U;5lLur%flw!>wZXIK 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

    VNhrC$clHt7fmjaTca6e6e(PU~0 z1PKSgR1?2mu6=+UEZ0s00*7Itq@qLevQsdKk`TFx;1g91ZDfUZBLmB%l~~a@##U2B zI-d#638-wWHEP*R?flDC3+v~5(DXso%Jft%)FptTZp|^O8Y&`^p;fW6uBg$<4UqmymuAB~(aDEQDbuzE^YXp`|ptghD?Lf87qg4~I%h5TIvJvu7pq-~x*IObhC>gv_Unk98m+!zt@n!W|?5sMhap)sN(5$N16L2nMt5iw{=)M6x6Do<2g>H)^_ zq{b@SU-#>h=3av7QM2UcE$IOoxYU2hhR(#i+d7{!UVR%44w@iiP(ON`+EE3yqeEbb zo91DtPncms^DwAiCCvwn7H@0kNk2%uSu{r_4P{lReGwe$7( zKxZ;}z5y?jus$|6*#t$VX0YAez-cuiyAx!Vtd_D;XLJ~>zEm$h(Vu`8GS`4_CWrS% zt`xNj#BJK2VGCZR%fSo_yD?XwPaXy`IfF(=xQ;^Ya@ z8(_n%PK-ftd3jQ`>xp`EN*MAG^MJb3v_%C3h$P{MJ)h>v>R`antJYDzXd0%|;+`bSq^u}sElfiav60Rq^MPKd+wL>Kb9Y3^rsNANJ z{m|a&cGwNIqt5~vbk{((cY8-3*23-oia!6d!$ek;Ej&=#yRnPvXXkN+h~YH?pW1<{&pfUC7me|3|pwdn7=dPMQTE@XrzEGA<9NSQsGW|o_eS%c^+Xh9 zasu0slQ9Yfd?0-_se(EX)O-J}8^5u3noA)$2ne+?7FrHHKOn1T|8N@GG?;HGSx(Hd z1HYdoNR(t^z=SCIvlsdqamXlAzuwI~H#iohL1P5V4zL&)L4(9bOnW6ViFY0} z!Tak=Qm{-jKID#tbRP~jg^VWAq>Xswp^iBiht1iK)@rArhygX2Ix0|<4qR^H6??#T z8kG1I)7`mtubGLM?!IOs;+eCE<}jD^y?9~*i^FpEOH&l8@O^iwv+<|-&_oiWOq7_FJ03pjTV(Hj$po^(B&8xc6eo-07CO8&O44{J1D5zFq@P{O+p7yKc&wz~BB=(+7 z@pQ(RO>xpK!%&3KcWaG3<7lbu(jCCbwuBB#O^0iWq6+JARo}#(s_!YWe;s&AWm0H?LA526f=GGf&WhG|54c}e!KvKTV3#!v&E^GR_nw-k%{Df3Y$qo@tTl%bFQ=@3>4G(oAztxe_F7;-`t97Dwsn zQTjnpWKhyMd1vFguwj&DUPEI?vSQ||s^Am3WX?<6b7#*rGLbZe`Nf_ci~a&Ew21Yl z^i!@3nGO=bU#0}lQY3G`mAvjK?n}Z^A(VsI4#0X-_9<76L<3X!iwM(q?9y{p8e@%x zHI*ZkU~Rj!r<89*`K_I49>ʷqyt1qfTAiu&Tn%H1nmwl;I)URbS4hr(BsUc`|o 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

    %@_sG+=cE~A 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

    ObeLnm#t8|8IP4|Uv2^= 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

    DT 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 RZC4jAmn1qVz{Akz{{ukXV@&`6 literal 0 HcmV?d00001 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 000000000..9b683f22c --- /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 000000000..6a3b0007f --- /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] 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; + + AggregatedTreeElements() : 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 builder = new LinkBuilder(); + 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 tasks; + final bool forceStripTypes; + final bool stripAsserts; + // TODO(antonm): make available from command-line options. + final bool outputAst = false; + + Map 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> classMembers) { + Set processedTypes = new Set(); + List workQueue = new List(); + 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 strips) + : tasks = [], + 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 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(); + 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(); + + parse(element) => element.parseNode(compiler); + + Set topLevelElements = new Set(); + Map> classMembers = + new Map>(); + + // 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(), + const Link(), + const Link(), + const Link() + ); + constructor.cachedNode = new FunctionExpression( + new Send(classNode.name, synthesizedIdentifier), + new NodeList(new StringToken(OPEN_PAREN_INFO, '(', -1), + const Link(), + 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 renames = new Map(); + Map imports = new Map(); + bool shouldCutDeclarationTypes = forceStripTypes + || (compiler.enableMinification + && isSafeToRemoveTypeDeclarations(classMembers)); + renamePlaceholders( + compiler, collector, renames, imports, + fixedMemberNames, shouldCutDeclarationTypes); + + // Sort elements. + final sortedTopLevels = sortElements(topLevelElements); + final sortedClassMembers = new Map>(); + 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 = '\n$sb\n'; + return; + } + + final topLevelNodes = []; + final memberNodes = new Map>(); + for (final element in sortedTopLevels) { + topLevelNodes.add(elementAsts[element].ast); + if (element.isClass() && !element.isMixinApplication) { + final members = []; + 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 topLevelElements) { + Iterable 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 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 sortElements(Iterable 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 000000000..8a8abe41f --- /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 000000000..c074af482 --- /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 imports, + Collection topLevelNodes, + Map> 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 000000000..d184663b6 --- /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 nodes; + LocalPlaceholder(this.identifier) : nodes = new Set(); + int get hashCode => identifier.hashCode; + String toString() => + 'local_placeholder[id($identifier), nodes($nodes)]'; +} + +class FunctionScope { + final Set parameterIdentifiers; + final Set localPlaceholders; + FunctionScope() + : parameterIdentifiers = new Set(), + localPlaceholders = new Set(); + 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: . 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 fixedMemberNames; // member names which cannot be renamed. + final Map elementAsts; + final Set nullNodes; // Nodes that should not be in output. + final Set unresolvedNodes; + final Map> elementNodes; + final Map functionScopes; + final Map> privateNodes; + final List declarationTypePlaceholders; + final Map> memberPlaceholders; + final Map> constructorPlaceholders; + Map 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(), + unresolvedNodes = new Set(), + elementNodes = new Map>(), + functionScopes = new Map(), + privateNodes = new Map>(), + declarationTypePlaceholders = new List(), + memberPlaceholders = new Map>(), + constructorPlaceholders = + new Map>(); + + 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(); + 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()).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()).add(node); + } + + void makePrivateIdentifier(Identifier node) { + assert(node != null); + privateNodes.putIfAbsent( + currentElement.getLibrary(), () => new Set()).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, () => []) + .add(new ConstructorPlaceholder(node, type)); + } + void makeRedirectingConstructorPlaceholder(Node node, Element element) { + constructorPlaceholders + .putIfAbsent(element, () => []) + .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 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 default C {...} + // lib2: class C {...} + 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 000000000..886d39e2f --- /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 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 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 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 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 renames, + Map imports, + Set fixedMemberNames, + bool cutDeclarationTypes) { + final Map> renamed + = new Map>(); + + renameNodes(Collection nodes, renamer) { + for (Node node in sorted(nodes, _compareNodes)) { + renames[node] = renamer(node); + } + } + + sortedForEach(Map 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 argumentsLink = type.typeArguments; + result.add(renameType(argumentsLink.head, renameElement)); + for (Link 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 forbiddenIdentifiers = new Set.from(['main']); + forbiddenIdentifiers.addAll(Keyword.keywords.keys); + forbiddenIdentifiers.addAll(fixedMemberNames); + generateUniqueName = (_) => + generator.generate(forbiddenIdentifiers.contains); + rename = makeRenamer(generateUniqueName); + renameElement = makeElementRenamer(rename, generateUniqueName); + + Set allParameterIdentifiers = new Set(); + 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> allSortedLocals = new List>(); + 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 currentSortedPlaceholders = + sorted(functionScope.localPlaceholders, + compareBy((LocalPlaceholder ph) => -ph.nodes.length)); + List> 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()); + } + 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 renamables = []; + placeholderCollector.elementNodes.forEach( + (Element element, Set nodes) { + renamables.add(new ElementRenamable(element, nodes, elementRenamer)); + }); + placeholderCollector.memberPlaceholders.forEach( + (String memberName, Set identifiers) { + renamables.add( + new MemberRenamable(memberName, identifiers, memberRenamer)); + }); + for (Set 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(); + 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 nodes) { + renameNodes(nodes, (_) => renameElement(element)); + }); + + // Rename locals. + sortedForEach(placeholderCollector.functionScopes, + (functionElement, functionScope) { + Set placeholders = functionScope.localPlaceholders; + Set memberIdentifiers = new Set(); + if (functionElement.getEnclosingClass() != null) { + functionElement.getEnclosingClass().forEachMember( + (enclosingClass, member) { + memberIdentifiers.add(member.name.slowToString()); + }); + } + Set usedLocalIdentifiers = new Set(); + 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.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 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 000000000..c30a2cfab --- /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 { + 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 builder = new LinkBuilder(); + 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 000000000..7c78bd5a5 --- /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 arguments, Link 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(B b) :] is the + * function type [: (B) -> A :] and the unaliased type of + * [: Func :] 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 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 value; } :], the + * type of the expression [: new Box().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 arguments, Link parameters) { + if (parameters.isEmpty) { + assert(arguments.isEmpty); + // Return fast on empty substitutions. + return this; + } + Link parameterLink = parameters; + Link 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(''); + static const NOT_RETURNING = const StatementType(''); + static const MAYBE_RETURNING = const StatementType(''); + + /** 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 arguments, Link 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 arguments, Link 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 :] the + * [declaredType] is [: Map :] 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 :] and [: T :] + * where [: T :] is a type variable, in which case [declaredType] holds + * [: dynamic :] and [: T :], respectively, or for [: X :] where [: X :] + * is not resolved or does not imply a type. + */ + final Link typeArguments; + + MalformedType(this.element, this.userProvidedBadType, + [this.typeArguments = null]); + + TypeKind get kind => TypeKind.MALFORMED_TYPE; + + SourceString get name => element.name; + + DartType subst(Link arguments, Link 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 types) { + for (DartType typeArgument in types) { + if (typeArgument.isMalformed) { + return true; + } + } + return false; +} + +abstract class GenericType extends DartType { + final Link typeArguments; + final bool isMalformed; + + GenericType(Link this.typeArguments, bool this.isMalformed); + + TypeDeclarationElement get element; + + /// Creates a new instance of this type using the provided type arguments. + GenericType _createType(Link newTypeArguments); + + DartType subst(Link arguments, Link 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 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 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 typeArguments = const Link()]) + : super(typeArguments, hasMalformed(typeArguments)) { + assert(invariant(element, element.isDeclaration)); + } + + TypeKind get kind => TypeKind.INTERFACE; + + SourceString get name => element.name; + + InterfaceType _createType(Link 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 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 parameterTypes; + final Link optionalParameterTypes; + + /** + * The names of the named parameters ordered lexicographically. + */ + final Link namedParameters; + + /** + * The types of the named parameters in the order corresponding to the + * [namedParameters]. + */ + final Link namedParameterTypes; + final bool isMalformed; + + factory FunctionType(Element element, + DartType returnType, + Link parameterTypes, + Link optionalParameterTypes, + Link namedParameters, + Link 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 this.parameterTypes, + Link this.optionalParameterTypes, + Link this.namedParameters, + Link 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 namedParameter = namedParameters; + Link 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 arguments, Link 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 namedParameter = namedParameters; + Link 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 typeArguments = const Link()]) + : super(typeArguments, hasMalformed(typeArguments)); + + TypedefType _createType(Link 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 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 tps = tf.parameterTypes; + Link 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 tNames = tf.namedParameters; + Link tTypes = tf.namedParameterTypes; + Link sNames = sf.namedParameters; + Link 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 tOptionalParameterType = tf.optionalParameterTypes; + Link 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 substTypes(Link types, + Link arguments, + Link parameters) { + bool changed = false; + var builder = new LinkBuilder(); + Link 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(); + 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 000000000..1d0c2b980 --- /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 000000000..2798f9554 --- /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 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 sortedByPosition(Iterable 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 get compilationUnits; + Link get tags; + LibraryName get libraryTag; + Link 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 getNonPrivateElementsInScope(); + + void setExports(Iterable 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 get imported; + Element lookupLocalMember(SourceString memberName); +} + +abstract class TypedefElement extends Element + implements TypeDeclarationElement { + TypedefType get rawType; + DartType get alias; + FunctionSignature get functionSignature; + Link 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 get requiredParameters; + Link get optionalParameters; + + int get requiredParameterCount; + int get optionalParameterCount; + bool get optionalParametersAreNamed; + + int get parameterCount; + List 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 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 get allSupertypes; + Link get interfaces; + + bool get hasConstructor; + Link 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 value); + void set interfaces(Link 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 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 000000000..f00794640 --- /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 metadata = const Link(); + + 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 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 contents = new Map(); + + bool get isEmpty => contents.isEmpty; + Iterable 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 localMembers = const Link(); + + 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 compilationUnits = + const Link(); + Link tags = const Link(); + LibraryName libraryTag; + bool canUseNative = false; + Link localMembers = const Link(); + 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 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 slotForExports; + + LibraryElementX(Script script, [Uri canonicalUri, LibraryElement this.origin]) + : this.canonicalUri = ((canonicalUri == null) ? script.uri : canonicalUri), + importScope = new Map(), + 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 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 exportedElements) { + assert(invariant(this, !exportsHandled, + message: 'Exports already set to $slotForExports on $this')); + assert(invariant(this, exportedElements != null)); + var builder = new LinkBuilder(); + 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 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 imported; + Token firstPosition; + + PrefixElementX(SourceString prefix, Element enclosing, this.firstPosition) + : imported = new Map(), + 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 parameters = + TypeDeclarationElementX.createTypeVariables(this, node.typeParameters); + cachedType = new TypedefType(this, parameters); + if (parameters.isEmpty) { + rawType = cachedType; + } else { + var dynamicParameters = const Link(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new TypedefType(this, dynamicParameters); + } + compiler.resolveTypedef(this); + return cachedType; + } + + Link 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 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 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 requiredParameters; + final Link optionalParameters; + final DartType returnType; + final int requiredParameterCount; + final int optionalParameterCount; + final bool optionalParametersAreNamed; + + List _orderedOptionalParameters; + + FunctionSignatureX(this.requiredParameters, + this.optionalParameters, + this.requiredParameterCount, + this.optionalParameterCount, + this.optionalParametersAreNamed, + this.returnType); + + void forEachRequiredParameter(void function(Element parameter)) { + for (Link link = requiredParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + void forEachOptionalParameter(void function(Element parameter)) { + for (Link link = optionalParameters; + !link.isEmpty; + link = link.tail) { + function(link.head); + } + } + + List get orderedOptionalParameters { + if (_orderedOptionalParameters != null) return _orderedOptionalParameters; + List list = new List.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 seen = new Set(); + 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(), + const Link(), + const Link(), + const Link()); + 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 createTypeVariables(TypeDeclarationElement element, + NodeList parameters) { + if (parameters == null) return const Link(); + + // Create types and elements for type variable. + var arguments = new LinkBuilder(); + 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:] for [:List:] and [:Map:] 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:] for [:List:] + * and [:Map:] 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:] 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 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 backendMembers = const Link(); + + Link 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 parameters = computeTypeParameters(compiler); + thisType = new InterfaceType(this, parameters); + if (parameters.isEmpty) { + rawType = thisType; + } else { + var dynamicParameters = const Link(); + parameters.forEach((_) { + dynamicParameters = + dynamicParameters.prepend(compiler.types.dynamicType); + }); + rawType = new InterfaceType(this, dynamicParameters); + } + } else { + thisType = origin.computeType(compiler); + rawType = origin.rawType; + } + } + return thisType; + } + + Link computeTypeParameters(Compiler compiler); + + /** + * Return [:true:] if this element is the [:Object:] class for the [compiler]. + */ + bool isObject(Compiler compiler) => + identical(declaration, compiler.objectClass); + + Link 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 get constructors { + // TODO(ajohnsen): See if we can avoid this method at some point. + Link result = const Link(); + // 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 seen = new Set(); + 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 localMembers = const Link(); + 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 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 computeTypeParameters(Compiler compiler) { + NamedMixinApplication named = node.asNamedMixinApplication(); + if (named == null) return const Link(); + 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 labels = const Link(); + 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 000000000..4a4fea81e --- /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> instanceMembersByName; + final Set 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>(), + universe = new Universe(), + seenClasses = new Set(); + + /// 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 members = instanceMembersByName.putIfAbsent( + memberName, () => const Link()); + 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 supertypes = cls.allSupertypes; + !supertypes.isEmpty; supertypes = supertypes.tail) { + processClass(supertypes.head.element); + } + }); + } + + void registerNewSelector(SourceString name, + Selector selector, + Map> selectorsMap) { + if (name != selector.name) { + String message = "$name != ${selector.name} (${selector.kind})"; + compiler.internalError("Wrong selector name: $message."); + } + Set selectors = + selectorsMap.putIfAbsent(name, () => new Set()); + 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 members = instanceMembersByName[memberName]; + if (members != null) { + LinkBuilder remaining = new LinkBuilder(); + 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 selectors = universe.invokedNames.putIfAbsent( + element.name, () => new Set()); + 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 resolvedElements; + + final Queue queue; + + ResolutionEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('resolution enqueuer', compiler, itemCompilationContextCreator), + resolvedElements = new Map(), + queue = new Queue(); + + 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 queue; + final Map generatedCode = + new Map(); + + CodegenEnqueuer(Compiler compiler, + ItemCompilationContext itemCompilationContextCreator()) + : super('codegen enqueuer', compiler, itemCompilationContextCreator), + queue = new Queue(); + + 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 000000000..198a28f38 --- /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 000000000..b7df642c7 --- /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 000000000..da8cd4451 --- /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 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 implements NodeVisitor { + 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 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 statements; + Block(this.statements); + Block.empty() : this.statements = []; + + 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 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 arguments) => new Call(this, arguments); +} + +class LiteralExpression extends Expression { + final String template; + final List 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 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 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 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 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), [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), [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), [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 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 elements; + + ArrayInitializer(this.length, this.elements); + + factory ArrayInitializer.from(Iterable 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 _convert(Iterable 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 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]); + +Block block2(Statement s1, Statement s2) => new Block([s1, s2]); + +Call call(Expression target, List arguments) { + return new Call(target, arguments); +} + +Fun fun(List 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 000000000..6d66f1fca --- /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 000000000..504933d80 --- /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 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 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([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 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 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 inputs = node.inputs; + + List 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 { + final Set set; + final List list; + + OrderedSet() : set = new Set(), list = []; + + 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 vars; + final OrderedSet params; + + VarCollector() : nested = false, + vars = new OrderedSet(), + params = new OrderedSet(); + + 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 { + 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> maps = []; + final List parameterNumberStack = []; + final List variableNumberStack = []; + int parameterNumber = 0; + int variableNumber = 0; + + MinifyRenamer(); + + void enterScope(VarCollector vars) { + maps.add(new Map()); + 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 = []; + 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 000000000..2a681d892 --- /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 compiledFunctions; + + ReturnInfo(HType this.returnType) + : compiledFunctions = new List(); + + 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 names; + final List types; + + OptionalParameterTypes(int optionalArgumentsCount) + : names = new List.fixedLength(optionalArgumentsCount), + types = new List.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 types; + final List namedArguments; + + HTypeList(int length) + : types = new List.fixedLength(length), + namedArguments = null; + HTypeList.withNamedArguments(int length, this.namedArguments) + : types = new List.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> 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 fieldInitializerTypeMap; + final Map fieldConstructorTypeMap; + final Map 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 setterSelectorsUsed; + + final Map> optimizedStaticFunctions; + final Map optimizedFunctions; + + FieldTypesRegistry(JavaScriptBackend backend) + : constructors = new Map>(), + fieldInitializerTypeMap = new Map(), + fieldConstructorTypeMap = new Map(), + fieldTypeMap = new Map(), + setterSelectorsUsed = new Set(), + optimizedStaticFunctions = new Map>(), + optimizedFunctions = new Map(), + 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 ctors = constructors[element]; + return ctors == null ? 0 : ctors.length; + } + + void registerFieldType(Map 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()); + Set 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()); + optimizedStaticFunctions[field].add(element); + } else { + optimizedFunctions.putIfAbsent( + field, () => new FunctionSet(backend.compiler)); + optimizedFunctions[field].add(element); + } + } + + void dump() { + Set allFields = new Set(); + 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 staticTypeMap; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final Set optimizedStaticFunctions; + final SelectorMap selectorTypeMap; + final FunctionSet optimizedFunctions; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Keys must be declaration elements. + */ + final Map optimizedTypes; + final Map optimizedDefaultValueTypes; + + ArgumentTypesRegistry(JavaScriptBackend backend) + : staticTypeMap = new Map(), + optimizedStaticFunctions = new Set(), + selectorTypeMap = new SelectorMap(backend.compiler), + optimizedFunctions = new FunctionSet(backend.compiler), + optimizedTypes = new Map(), + optimizedDefaultValueTypes = + new Map(), + 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 boundsChecked; + + JavaScriptItemCompilationContext() + : types = new HTypeMap(), + boundsChecked = new Set(); +} + +class JavaScriptBackend extends Backend { + SsaBuilderTask builder; + SsaOptimizerTask optimizer; + SsaCodeGeneratorTask generator; + CodeEmitterTask emitter; + + /** + * The generated code as a js AST for compiled methods. + */ + Map get generatedCode { + return compiler.enqueuer.codegen.generatedCode; + } + + /** + * The generated code as a js AST for compiled bailout methods. + */ + final Map generatedBailoutCode = + new Map(); + + 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 returnInfo; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements must be declaration elements. + */ + final List 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 usedInterceptors; + + /** + * A collection of selectors that must have a one shot interceptor + * generated. + */ + final Set 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> 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> 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 interceptedClasses; + + List get tasks { + return [builder, optimizer, generator, emitter]; + } + + final RuntimeTypeInformation rti; + + JavaScriptBackend(Compiler compiler, bool generateSourceMap, bool disableEval) + : namer = determineNamer(compiler), + returnInfo = new Map(), + invalidateAfterCodegen = new List(), + usedInterceptors = new Set(), + oneShotInterceptors = new Set(), + interceptedElements = new Map>(), + rti = new RuntimeTypeInformation(compiler), + specializedGetInterceptors = + new Map>(), + interceptedClasses = new LinkedHashMap(), + 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 getInterceptedClassesOn(Selector selector) { + Set intercepted = interceptedElements[selector.name]; + if (intercepted == null) return null; + Set result = new Set(); + for (Element element in intercepted) { + if (selector.applies(element, compiler)) { + result.add(element.getEnclosingClass()); + } + } + if (result.isEmpty) return null; + return result; + } + + List getListOfInterceptedClasses() { + return [jsStringClass, jsArrayClass, jsIntClass, + jsDoubleClass, jsNumberClass, jsNullClass, + jsFunctionClass, jsBoolClass]; + } + + void initializeInterceptorElements() { + objectInterceptorClass = + compiler.findInterceptor(const SourceString('ObjectInterceptor')); + getInterceptorMethod = + compiler.findInterceptor(const SourceString('getInterceptor')); + List 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 set = interceptedElements.putIfAbsent( + member.name, () => new Set()); + set.add(member); + }, + includeSuperMembers: true); + } + enqueuer.registerInstantiatedClass(cls); + } + + void registerSpecializedGetInterceptor(Set 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 keys = []; + 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 000000000..86717aed7 --- /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 { + 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 { + 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 properties = []; + 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 arguments = []; + + // 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 _array(List values) { + List valueList = []; + 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 000000000..30c8bc5d2 --- /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 000000000..f40123572 --- /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 properties = []; + + // 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 neededClasses; + // TODO(ngeoffray): remove this field. + Set 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 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 interceptorClosureCache; + + /** + * Raw ClassElement symbols occuring in is-checks and type assertions. If the + * program contains parameterized checks `x is Set` and + * `x is Set` then the ClassElement `Set` will occur once in + * [checkedClasses]. + */ + Set checkedClasses; + + /** + * Raw Typedef symbols occuring in is-checks and type assertions. If the + * program contains `x is F` and `x is F` then the TypedefElement + * `F` will occur once in [checkedTypedefs]. + */ + Set checkedTypedefs; + + final bool generateSourceMap; + + CodeEmitterTask(Compiler compiler, Namer namer, this.generateSourceMap) + : boundClosureBuffer = new CodeBuffer(), + mainBuffer = new CodeBuffer(), + this.namer = namer, + boundClosureCache = new Map(), + interceptorClosureCache = new Map(), + constantEmitter = new ConstantEmitter(compiler, namer), + super(compiler) { + nativeEmitter = new NativeEmitter(this); + } + + void computeRequiredTypeChecks() { + assert(checkedClasses == null); + checkedClasses = new Set(); + checkedTypedefs = new Set(); + 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 :. 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 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 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 parametersBuffer = + new List.fixedLength( + selector.argumentCount + extraArgumentCount); + // The arguments that will be passed to the real method. + List argumentsBuffer = + new List.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 body; + if (member.hasFixedBackendName()) { + body = nativeEmitter.generateParameterStubStatements( + member, invocationName, parametersBuffer, argumentsBuffer, + indexOfLastOptionalArgumentInParameters); + } else { + body = [ + 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 generatedStubNames = new Set(); + 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 selectors = signature.optionalParametersAreNamed + ? computeNamedSelectors(signature, member) + : computeOptionalSelectors(signature, member); + for (Selector selector in selectors) { + addParameterStub(member, selector, defineStub, generatedStubNames); + } + } else { + Set 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 computeNamedSelectors(FunctionSignature signature, + FunctionElement element) { + Set selectors = new Set(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + [])); + + // 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 newSet = new Set(); + selectors.forEach((Selector other) { + List 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 computeOptionalSelectors(FunctionSignature signature, + FunctionElement element) { + Set selectors = new Set(); + // Add the selector that does not have any optional argument. + selectors.add(new Selector(SelectorKind.CALL, + element.name, + element.getLibrary(), + signature.requiredParameterCount, + [])); + + // 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, + [])); + } + 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 arguments = [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 parameters = []; + List arguments = []; + 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( + [ + new js.Return( + new js.VariableUse('receiver') + .dot(name) + .callWith(arguments))])); + builder.addProperty(name, function); + } + } + + Iterable 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 generated = new Set(); + // 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 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 unneededClasses = new Set(); + // 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 needed = new Set(); + backend.specializedGetInterceptors.forEach( + (_, Collection elements) { + needed.addAll(elements); + } + ); + + ConstantHandler handler = compiler.constantHandler; + List 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 sortedClasses = + new List.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 elements = + backend.generatedCode.keys.where(isStaticFunction); + Set 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 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 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 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 fieldNames = compiler.enableMinification + ? inInterceptor ? const ['a', 'b', 'c'] + : const ['a', 'b'] + : inInterceptor ? const ['self', 'target', 'receiver'] + : const ['self', 'target']; + + Iterable 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 parameters = []; + List arguments = []; + 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 parameters = []; + List arguments = []; + 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 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 + ? [new js.VariableUse(receiverArgumentName)] + : []); + } 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 generatedSelectors = new Set(); + + 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 parameters = []; + List arguments = []; + 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( + [ + new js.Return( + buildGetter().dot(closureCallName) + .callWith(arguments))])); + + defineStub(invocationName, function); + } + } + } + + void emitStaticNonFinalFieldInitializations(CodeBuffer buffer) { + ConstantHandler handler = compiler.constantHandler; + Iterable 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 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 arguments = []; + 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 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 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 addedJsNames = new Set(); + + // Keep track of the noSuchMethod holders for each possible + // receiver type. + Map> noSuchMethodHolders = + new Map>(); + Set noSuchMethodHoldersFor(DartType type) { + ClassElement element = type.element; + Set 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 parameters = []; + CodeBuffer args = new CodeBuffer(); + for (int i = 0; i < selector.argumentCount; i++) { + parameters.add(new js.Parameter('\$$i')); + } + + List 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( + [ + new js.VariableUse(namer.CURRENT_ISOLATE) + .dot(createInvocationMirror) + .callWith( + [ + 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([new js.Return(expression)])); + return function; + } + + void addNoSuchMethodHandlers(SourceString ignore, Set 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 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 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 classes = specializedGetInterceptors[name]; + emitGetInterceptorMethod(buffer, objectName, name, classes); + } + } + + void computeNeededClasses() { + instantiatedClasses = + compiler.codegenWorld.instantiatedClasses.where(computeClassFilter()) + .toSet(); + neededClasses = new Set.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 classes1 = backend.getInterceptedClassesOn(selector1); + Set 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 classes = backend.getInterceptedClassesOn(selector); + String oneShotInterceptorName = namer.oneShotInterceptorName(selector); + String getInterceptorName = + namer.getInterceptorName(backend.getInterceptorMethod, classes); + + List parameters = []; + List arguments = []; + 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 000000000..06d5d01a1 --- /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 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 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 fields = []; + 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 000000000..52e6ee0f6 --- /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 000000000..98344a1e0 --- /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 usedNames, + Map 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 [ + '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/(?[ + 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 [ + 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 suggestionMap, + Set used, + List 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 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 = [_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 000000000..e5bb9670d --- /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 [ + // 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 ["__proto__", "prototype", "constructor", "call"]; + + // Symbols that we might be using in our JS snippets. + static const reservedGlobalSymbols = const [ + // 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 _jsReserved = null; + /// Names that cannot be used by members, top level and static + /// methods. + Set get jsReserved { + if (_jsReserved == null) { + _jsReserved = new Set(); + _jsReserved.addAll(javaScriptKeywords); + _jsReserved.addAll(reservedPropertySymbols); + } + return _jsReserved; + } + + Set _jsVariableReserved = null; + /// Names that cannot be used by local variables and parameters. + Set get jsVariableReserved { + if (_jsVariableReserved == null) { + _jsVariableReserved = new Set(); + _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 globals; + final Map oneShotInterceptorNames; + final Map shortPrivateNameOwners; + + final Set usedGlobalNames; + final Set usedInstanceNames; + final Map globalNameMap; + final Map suggestedGlobalNames; + final Map instanceNameMap; + final Map suggestedInstanceNames; + + final Map operatorNameMap; + final Map popularNameCounters; + + final Map bailoutNames; + + final Map constantNames; + + Namer(this.compiler) + : globals = new Map(), + oneShotInterceptorNames = new Map(), + shortPrivateNameOwners = new Map(), + bailoutNames = new Map(), + usedGlobalNames = new Set(), + usedInstanceNames = new Set(), + instanceNameMap = new Map(), + operatorNameMap = new Map(), + globalNameMap = new Map(), + suggestedGlobalNames = new Map(), + suggestedInstanceNames = new Map(), + constantNames = new Map(), + popularNameCounters = new Map(); + + 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 usedNames, + Map 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 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 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 000000000..9400f5f42 --- /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 classesWithDynamicDispatch; + + // Native classes found in the application. + Set nativeClasses; + + // Caches the native subtypes of a native class. + Map> subtypes; + + // Caches the direct native subtypes of a native class. + Map> 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 overriddenMethods; + + // Caches the methods that have a native body. + Set 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(), + nativeClasses = new Set(), + subtypes = new Map>(), + directSubtypes = new Map>(), + overriddenMethods = new Set(), + nativeMethods = new Set(), + 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 getDirectSubclasses(ClassElement cls) { + List result = directSubtypes[cls]; + return result == null ? const[] : result; + } + + void potentiallyConvertDartClosuresToJs(List statements, + FunctionElement member, + List stubParameters) { + FunctionSignature parameters = member.computeSignature(compiler); + Element converter = + compiler.findHelper(const SourceString('convertDartClosureToJS')); + String closureConverter = backend.namer.isolateAccess(converter); + Set stubParameterNames = new Set.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 generateParameterStubStatements( + Element member, + String invocationName, + List stubParameters, + List 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 statements = []; + potentiallyConvertDartClosuresToJs(statements, member, stubParameters); + + String target; + List 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 [ + 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 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.use('this')]..addAll( + parameters.map((param) => js.use(param.name)))))); + } + + js.Block generateMethodBodyWithPrototypeCheckForElement( + FunctionElement element, + js.Block body, + List 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 seen = new Set(); + List classes = []; + void visit(ClassElement cls) { + if (seen.contains(cls)) return; + seen.add(cls); + getDirectSubclasses(cls).forEach(visit); + classes.add(cls); + } + classesWithDynamicDispatch.forEach(visit); + + List preorderDispatchClasses = classes.where( + (cls) => !getDirectSubclasses(cls).isEmpty && + classesWithDynamicDispatch.contains(cls)).toList(); + + if (!compiler.enableMinification) { + nativeBuffer.add('// ${classes.length} classes\n'); + } + Iterable 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 varNames = []; + // Values of temporary variables. + Map varDefns = new Map(); + + // 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 tagDefns = + new Map(); + + js.Expression makeExpression(ClassElement classElement) { + // Expression fragments for this set of cls keys. + List expressions = []; + // TODO: Remove if cls is abstract. + List 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 statements = []; + + List initializations = + []; + 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 objectProperties = []; + + 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 000000000..a3a0339b9 --- /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 operator[](ClassElement element); + // Get the iterator for all classes that need type checks. + Iterator 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 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 instantiatedArguments = new Set(); + for (DartType type in compiler.codegenWorld.instantiatedTypes) { + addAllInterfaceTypeArguments(type, instantiatedArguments); + } + + Set checkedArguments = new Set(); + for (DartType type in compiler.enqueuer.codegen.universe.isChecks) { + addAllInterfaceTypeArguments(type, checkedArguments); + } + + allArguments = new Set.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 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 variables = interface.element.typeVariables; + if (variables.isEmpty) return name; + List 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 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> map = + new Map>(); + + Iterable operator[](ClassElement element) { + Set result = map[element]; + return result != null ? result : const []; + } + + void add(ClassElement cls, ClassElement check) { + map.putIfAbsent(cls, () => new Set()); + map[cls].add(check); + } + + Iterator 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 000000000..0fccce2a4 --- /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 000000000..75446c7b9 --- /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 implements Map { + final int length; + // A constant map is backed by a JavaScript object. + final _jsObject; + final List _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 get keys { + return new _ConstantMapKeyIterable(this); + } + + Iterable 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 extends ConstantMap { + 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 { + ConstantMap _map; + _ConstantMapKeyIterable(this._map); + + Iterator 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 000000000..09e31da11 --- /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 namedArguments]) { + return Primitives.applyFunction( + function, positionalArguments, namedArguments); + } +} + +// Patch for Expando implementation. +patch class Expando { + 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 { + 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 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 strings, String separator) { + checkNull(strings); + if (separator is !String) throw new ArgumentError(separator); + return stringJoinUnchecked(_toJsStringArray(strings), separator); + } + + patch static String concatAll(Iterable strings) { + return stringJoinUnchecked(_toJsStringArray(strings), ""); + } + + static List _toJsStringArray(Iterable 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 000000000..2ad4a1e88 --- /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 000000000..6a32f1720 --- /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 000000000..0374de5b9 --- /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 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 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 start(String executable, + List arguments, + [ProcessOptions options]) { + throw new UnsupportedError("Process.start"); + } + + patch static Future run(String executable, + List 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 bytes) { + throw new UnsupportedError("_WindowsCodePageDecoder._decodeBytes"); + } +} + +patch class _WindowsCodePageEncoder { + patch static List _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 000000000..82a80fec9 --- /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 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 managers; + + _Manager() { + _nativeDetectEnvironment(); + topEventLoop = new _EventLoop(); + isolates = new Map(); + managers = new Map(); + 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 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(); + 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 completer = new Completer(); + 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 _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> 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 _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() + _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 000000000..aab847a4d --- /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 000000000..e33ae3f6b --- /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 implements List { + 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 where(bool f(E element)) { + return IterableMixinWorkaround.where(this, f); + } + + Iterable expand(Iterable f(E element)) { + return IterableMixinWorkaround.expand(this, f); + } + + void addAll(Collection 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 take(int n) { + return IterableMixinWorkaround.takeList(this, n); + } + + Iterable takeWhile(bool test(E value)) { + return IterableMixinWorkaround.takeWhile(this, test); + } + + Iterable skip(int n) { + return IterableMixinWorkaround.skipList(this, n); + } + + Iterable 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 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 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 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 toList() => new List.from(this); + + Set toSet() => new Set.from(this); + + Iterator get iterator => new ListIterator(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 000000000..0fbff9c6a --- /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 _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 get namedArguments { + if (isAccessor) return null; + var map = {}; + 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 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 a = []; + 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 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 listOfNamedArguments = + new List.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, '', [], {}); + } 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('', '', [], {}); + } + } + + // 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 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 000000000..fa5677fc6 --- /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 000000000..381a69056 --- /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 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 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 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 get charCodes { + List result = new List.fixedLength(length); + for (int i = 0; i < length; i++) { + result[i] = JS('int', '#.charCodeAt(#)', this, i); + } + return result; + } + + Iterable get codeUnits { + throw new UnimplementedError("String.codeUnits"); + } + + Iterable 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 000000000..3bccb2cf5 --- /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 000000000..12365076c --- /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 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 classMirrors = new Expando(); + + 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 invoke(String memberName, + List positionalArguments, + [Map 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(); + // 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 000000000..c75e554e5 --- /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 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 = []; + } + return JS('var', '\$dynamicMetadata'); +} + +void set _dynamicMetadata(List 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 buildDynamicMetadata(List> inputTable) { + List result = []; + 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 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> 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 000000000..b62b0b5c5 --- /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 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 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 _groups; + + const _MatchImplementation( + String this.pattern, + String this.str, + int this.start, + int this.end, + List this._groups); + + String group(int index) => _groups[index]; + String operator [](int index) => group(index); + int get groupCount => _groups.length - 1; + + List groups(List groups) { + List out = []; + for (int i in groups) { + out.add(group(i)); + } + return out; + } +} + +class _AllMatchesIterable extends Iterable { + final JSSyntaxRegExp _re; + final String _str; + + const _AllMatchesIterable(this._re, this._str); + + Iterator get iterator => new _AllMatchesIterator(_re, _str); +} + +class _AllMatchesIterator implements Iterator { + 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 000000000..326b892f5 --- /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 000000000..8ac1fa4b3 --- /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 groups(List groups_) { + List result = new List(); + for (int g in groups_) { + result.add(group(g)); + } + return result; + } + + final int start; + final String str; + final String pattern; +} + +List allMatchesInStringUnchecked(String needle, String haystack) { + // Copied from StringBase.allMatches in + // /runtime/lib/string_base.dart + List result = new List(); + 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 000000000..074cecec6 --- /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 nameSet; + for (Combinator combinator in tag.combinators) { + if (combinator.isShow) { + show = true; + var set = new Set(); + 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(); + } + 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 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 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 libraryNames = + new LinkedHashMap(); + + 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(); + 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 NEXT = + const [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 imports = const Link(); + + /** + * 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 dependencies = const Link(); + + /** + * The export scope for [library] which is gradually computed by the work-list + * computation in [LibraryDependencyHandler.computeExports]. + */ + Map exportScope = + new LinkedHashMap(); + + /** + * 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 pendingExportSet = new Set(); + + 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 pullPendingExports() { + List 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 nodeMap = + new LinkedHashMap(); + + 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> tasks = + new LinkedHashMap>(); + + // 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 pendingExports = node.pullPendingExports(); + tasks[node] = pendingExports; + }); + tasks.forEach((LibraryDependencyNode node, List 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 000000000..13a15f820 --- /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 _parametersFromFunctionSignature( + Dart2JsMirrorSystem system, + Dart2JsMethodMirror method, + FunctionSignature signature) { + var parameters = []; + Link 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 _convertElementMemberToMemberMirrors( + Dart2JsContainerMirror library, Element element) { + if (element.isSynthesized) { + return const []; + } else if (element is VariableElement) { + return [new Dart2JsFieldMirror(library, element)]; + } else if (element is FunctionElement) { + return [new Dart2JsMethodMirror(library, element)]; + } else if (element is AbstractFieldElement) { + var members = []; + 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 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 opts = const []]) + : 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 libraries, Path libraryRoot, + [Path packageRoot, List opts = const []]) + : 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.from(opts); + opts.add('--analyze-only'); + opts.add('--analyze-all'); + _compiler = new Compiler(provider.readStringFromUri, + null, + silentDiagnosticHandler, + libraryUri, packageUri, opts); + var librariesUri = []; + 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 compileToJavaScript() => + new Future.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 _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 get metadata { + if (_metadata == null) { + _metadata = []; + 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.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 _libraries; + Map _libraryMap; + + Dart2JsMirrorSystem(this.compiler) + : _libraryMap = new Map(); + + void _ensureLibraries() { + if (_libraries == null) { + _libraries = {}; + compiler.libraries.forEach((_, LibraryElement v) { + var mirror = new Dart2JsLibraryMirror(mirrors, v); + _libraries[mirror.simpleName] = mirror; + _libraryMap[v] = mirror; + }); + } + } + + Map get libraries { + _ensureLibraries(); + return new ImmutableMapWrapper(_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 _members; + + Dart2JsContainerMirror(Dart2JsMirrorSystem system, Element element) + : super(system, element); + + void _ensureMembers(); + + Map get members { + _ensureMembers(); + return new ImmutableMapWrapper(_members); + } + + Map get functions { + _ensureMembers(); + return new AsFilteredImmutableMap( + _members, + (MemberMirror member) => member is MethodMirror ? member : null); + } + + Map get getters { + _ensureMembers(); + return new AsFilteredImmutableMap( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isGetter ? + member : null); + } + + Map get setters { + _ensureMembers(); + return new AsFilteredImmutableMap( + _members, + (MemberMirror member) => + member is MethodMirror && (member as MethodMirror).isSetter ? + member : null); + } + + Map get variables { + _ensureMembers(); + return new AsFilteredImmutableMap( + _members, + (MemberMirror member) => member is VariableMirror ? member : null); + } +} + +class Dart2JsLibraryMirror extends Dart2JsContainerMirror + implements LibraryMirror { + Map _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 = {}; + _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 = {}; + _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 get classes { + _ensureClasses(); + return new ImmutableMapWrapper(_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 _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 = {}; + _class.forEachMember((_, e) { + for (var member in _convertElementMemberToMemberMirrors(this, e)) { + assert(!_members.containsKey(member.simpleName)); + _members[member.simpleName] = member; + } + }); + } + } + + Map get methods => functions; + + Map get constructors { + _ensureMembers(); + return new AsFilteredImmutableMap( + _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 get superinterfaces { + var list = []; + Link 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 get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List get typeVariables { + if (_typeVariables == null) { + _typeVariables = []; + _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 _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 get typeArguments { + throw new UnsupportedError( + 'Declarations do not have type arguments'); + } + + List get typeVariables { + if (_typeVariables == null) { + _typeVariables = []; + 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 get superinterfaces => const []; + + 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 get members => const {}; + + Map get constructors => const {}; + + Map get methods => const {}; + + Map get getters => const {}; + + Map get setters => const {}; + + Map get variables => const {}; + + ClassMirror get defaultFactory => null; +} + +class Dart2JsInterfaceTypeMirror extends Dart2JsTypeElementMirror + implements ClassMirror { + List _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 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 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 get typeArguments { + if (_typeArguments == null) { + _typeArguments = []; + if (!_interfaceType.isRaw) { + Link 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 get typeVariables => + originalDeclaration.typeVariables; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map get constructors => + originalDeclaration.constructors; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map get methods => originalDeclaration.methods; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map get setters => originalDeclaration.setters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map get getters => originalDeclaration.getters; + + // TODO(johnniwinther): Substitute type arguments for type variables. + Map 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 _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 get members { + var method = callMethod; + if (method != null) { + var map = new Map.from( + originalDeclaration.members); + var name = method.qualifiedName; + assert(!map.containsKey(name)); + map[name] = method; + return new ImmutableMapWrapper(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 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 get typeArguments => const []; + + List get typeVariables => + originalDeclaration.typeVariables; + + TypeMirror get returnType { + return _convertTypeToTypeMirror(mirrors, _functionType.returnType, + mirrors.compiler.types.dynamicType); + } + + List 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 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 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 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.immediate( + _convertConstantToInstanceMirror(mirrors, _constant.entries[index])); + } +} + +class Dart2JsMapConstantMirror extends Dart2JsConstantMirror + implements MapInstanceMirror { + List _listCache; + + Dart2JsMapConstantMirror(Dart2JsMirrorSystem mirrors, + MapConstant constant) + : super(mirrors, constant); + + MapConstant get _constant => super._constant; + + List get _list { + if (_listCache == null) { + _listCache = new List(_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 get keys { + // TODO(johnniwinther): Return an unmodifiable list instead. + return new List.from(_list); + } + + Future operator[](String key) { + int index = _list.indexOf(key); + if (index == -1) return null; + return new Future.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 _fieldMapCache; + + Dart2JsConstructedConstantMirror(Dart2JsMirrorSystem mirrors, + ConstructedConstant constant) + : super(mirrors, constant); + + ConstructedConstant get _constant => super._constant; + + Map get _fieldMap { + if (_fieldMapCache == null) { + _fieldMapCache = new LinkedHashMap(); + 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 getField(String fieldName) { + Constant fieldConstant = _fieldMap[fieldName]; + if (fieldConstant != null) { + return new Future.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 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 000000000..6f5189e97 --- /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 opts = const []]) { + 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 libraries, + Path libraryRoot, + [Path packageRoot, + List 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 compileToJavaScript(); +} + +/** + * The main interface for the whole mirror system. + */ +abstract class MirrorSystem { + /** + * Returns an unmodifiable map of all libraries in this mirror system. + */ + Map 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 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 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 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 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 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 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 get members; + + /** + * An immutable map from names to mirrors for all class + * declarations in this library. + */ + Map get classes; + + /** + * An immutable map from names to mirrors for all function, getter, + * and setter declarations in this library. + */ + Map get functions; + + /** + * An immutable map from names to mirrors for all getter + * declarations in this library. + */ + Map get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations in this library. + */ + Map get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations in this library. + */ + Map 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 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 get typeArguments; + + /** + * Returns the list of type variables for this type. + */ + List 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 get members; + + /** + * An immutable map from names to mirrors for all method, + * declarations for this type. This does not include getters and + * setters. + */ + Map get methods; + + /** + * An immutable map from names to mirrors for all getter + * declarations for this type. + */ + Map get getters; + + /** + * An immutable map from names to mirrors for all setter + * declarations for this type. + */ + Map get setters; + + /** + * An immutable map from names to mirrors for all variable + * declarations for this type. + */ + Map get variables; + + /** + * An immutable map from names to mirrors for all constructor + * declarations for this type. + */ + Map 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 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 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 000000000..d9040897b --- /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 computeSubdeclarations(ClassMirror type) { + type = type.originalDeclaration; + var subtypes = []; + 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 { + final bool includeType; + final ClassMirror type; + + HierarchyIterable(this.type, {bool includeType}) + : this.includeType = includeType; + + Iterator 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 { + final Queue queue = new Queue(); + 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 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 000000000..347021155 --- /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 implements Map { + AbstractMap(); + + AbstractMap.from(Map 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 get keys { + var keys = []; + forEach((k,_) => keys.add(k)); + return keys; + } + + Collection get values { + var values = []; + 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 extends AbstractMap { + final Map _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 value); + +/** + * An immutable map wrapper capable of filtering the input map. + */ +class FilteredImmutableMap extends ImmutableMapWrapper { + final Filter _filter; + + FilteredImmutableMap(Map 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 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 extends AbstractMap { + final Map _map; + final AsFilter _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 000000000..373f16e01 --- /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 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 nativeClasses = new Set(); + + final Set registeredClasses = new Set(); + final Set pendingClasses = new Set(); + final Set unusedClasses = new Set(); + + /** + * 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 nativeBehaviors = + new Map(); + + 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 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 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 doneAddSubtypes = new Set(); + + NativeCodegenEnqueuer(Enqueuer world, Compiler compiler, this.emitter) + : super(world, compiler, compiler.enableNativeLiveTypeAnalysis); + + void processNativeClasses(Iterable 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 subtypes = emitter.subtypes.putIfAbsent( + type.element, + () => []); + 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 directSubtypes = emitter.directSubtypes.putIfAbsent( + superclass, + () => []); + 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 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 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 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 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 arguments = []; + List inputs = []; + 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, [])); + } +} 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 000000000..5a0da01bb --- /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 imports = new LinkBuilder(); + 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 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 patches = element.localMembers; + applyContainerPatch(element.origin, patches); + })); + } + + void applyContainerPatch(ClassElement originClass, + Link 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 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 000000000..e62aa7019 --- /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 get superUses; +} + +class TreeElementMapping implements TreeElements { + final Element currentElement; + final Map selectors = new LinkedHashMap(); + final Map types = new LinkedHashMap(); + final Set checkedParameters = new Set(); + final Set superUses = new Set(); + + 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 seen = new Set(); + 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 originParameters, + Link 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 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().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 pendingClassesToBeResolved = new Queue(); + + /** + * 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 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(); + for (Element parameter in signature.requiredParameters) { + parameterTypes.addLast(parameter.computeType(compiler)); + } + var optionalParameterTypes = const Link(); + var namedParameters = const Link(); + var namedParameterTypes = const Link(); + if (signature.optionalParametersAreNamed) { + var namedParametersBuilder = new LinkBuilder(); + var namedParameterTypesBuilder = new LinkBuilder(); + 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(); + 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 initialized; + Link initializers; + bool hasSuper; + + InitializerResolver(this.visitor) + : initialized = new Map(), 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 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(); + } else { + initializers = functionNode.initializers.nodes; + } + FunctionElement result; + bool resolvedSuper = false; + for (Link 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 extends Visitor { + 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 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 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 breakTargetStack; + Link 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(), + continueTargetStack = const Link(); + + 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 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 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(); + resolveTypeArguments( + node, null, enclosingElement, + scope, onFailure, whenResolved, arguments); + return new MalformedType(erroneousElement, null, arguments.toLink()); + } + + DartType checkNoTypeArguments(DartType type) { + var arguments = new LinkBuilder(); + bool hashTypeArgumentMismatch = resolveTypeArguments( + node, const Link(), 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(); + 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(); + 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 typeVariables, + Element enclosingElement, + Scope scope, + onFailure, whenResolved, + LinkBuilder arguments) { + if (node.typeArguments == null) { + return false; + } + bool typeArgumentCountMismatch = false; + for (Link 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 { + 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 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 parameterNodes = (node.parameters == null) + ? const Link() : 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 named = []; + for (Link 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 seenNamedArguments = []; + for (Link 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 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 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 labelElements = {}; + 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 continueLabels = {}; + Link 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 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 { + 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(); + // Resolve the bounds of type variables. + Link typeLink = element.typeVariables; + Link 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 + // [: :]. + 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 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 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 interfaces = (namedMixinApplication != null) + ? resolveInterfaces(namedMixinApplication.interfaces, + namedMixinApplication.superclass) + : const Link(); + + // 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 resolveInterfaces(NodeList interfaces, Node superclass) { + Link result = const Link(); + if (interfaces == null) return result; + for (Link 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(); + addAllSupertypes(allSupertypes, supertype); + for (Link 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(); + } + } + + /** + * Adds [type] and all supertypes of [type] to [builder] while substituting + * type variables. + */ + void addAllSupertypes(LinkBuilder builder, InterfaceType type) { + builder.addLast(type); + Link typeArguments = type.typeArguments; + ClassElement classElement = type.element; + Link typeVariables = classElement.typeVariables; + Link 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 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 { + 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 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 { + final Element enclosingElement; + Link optionalParameters = const Link(); + 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 elements = analyzeNodes(node.nodes); + optionalParameterCount = elements.length; + optionalParameters = elements.toLink(); + return null; + } + + Element visitVariableDefinitions(VariableDefinitions node) { + Link 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 analyzeNodes(Link link) { + LinkBuilder elements = new LinkBuilder(); + 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 parameters = const Link(); + 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 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 { + 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 000000000..130486ce7 --- /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 000000000..92e7840bb --- /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 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 elements; + + MutableScope(Scope parent) + : super(parent), + this.elements = new Map() { + 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 000000000..e8ee0f122 --- /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 000000000..3bfadb20d --- /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 extends Visitor { + 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 000000000..01f8e9e81 --- /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 extends AbstractScanner { + 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 groupingStack = const Link(); + + 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 000000000..4cf9eeb21 --- /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 { + final List bytes; + + ByteArrayScanner(List 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 000000000..4d7989872 --- /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 implements SourceString { + final List bytes; + final int offset; + final int length; + int _hashCode; + + ByteString(List 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 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 bytes, int offset, int length) + : super(bytes, offset, length); + + static AsciiString of(List bytes, int offset, int length) { + AsciiString string = new AsciiString(bytes, offset, length); + return string; + } + + Iterator 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 bytes = string.charCodes; + return AsciiString.of(bytes, 0, bytes.length); + } +} + + +class AsciiStringIterator implements Iterator { + final List bytes; + int offset; + final int end; + int _current; + + AsciiStringIterator(List bytes) + : this.bytes = bytes, offset = 0, end = bytes.length; + AsciiStringIterator.range(List 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 bytes, int offset, int length) + : super(bytes, offset, length); + + static Utf8String of(List bytes, int offset, int length) { + return new Utf8String(bytes, offset, length); + } + + static Utf8String fromString(String string) { + throw "not implemented yet"; + } + + Iterator 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 000000000..1e359aa49 --- /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(); + 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 000000000..5d3c9a81c --- /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 implements SourceString { + static const List values = const [ + 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 _keywords; + static Map 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 computeKeywordMap() { + Map result = new LinkedHashMap(); + 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 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 strings = + new List.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 strings, + int offset, int length) { + List result = new List.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 table; + final Keyword keyword; + + ArrayKeywordState(List 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 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 000000000..e535f6259 --- /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 expectedDeclaration(Token token) { + error("expected a declaration, but got '${token.slowToString()}'", token); + return const Link(); + } + + 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 interpolationScope; + + Link nodes = const Link(); + + Link metadata = const Link(); + + ElementListener(DiagnosticListener listener, + this.compilationUnitElement, + this.idGenerator) + : this.listener = listener, + stringValidator = new StringValidator(listener), + interpolationScope = const Link(); + + 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(); + } + } + + 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 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 expectedDeclaration(Token token) { + listener.cancel("expected a declaration, but got '${token.slowToString()}'", + token: token); + return const Link(); + } + + 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 popMetadata(Element element) { + var result = metadata; + for (Link link = metadata; !link.isEmpty; link = link.tail) { + element.addMetadata(link.head); + } + metadata = const Link(); + 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 poppedNodes = const Link(); + 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 parts = + const Link(); + // 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.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 link = const Link().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 caseNodes = const Link(); + 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 000000000..5a85e0032 --- /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 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 findMemberName(Token token) { + Token start = token; + Link identifiers = const Link(); + 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 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 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 000000000..eca572b5d --- /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 000000000..02f409aac --- /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 = {}; + // } + 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 000000000..123b650d6 --- /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 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 000000000..1bd83272d --- /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 000000000..073835575 --- /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 000000000..e3cd59100 --- /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 000000000..0fa3489ee --- /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 { + 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 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 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 000000000..a236eae79 --- /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 { + 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 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 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 { + 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 000000000..b1306167e --- /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 000000000..14a417562 --- /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 _lineStarts; + + SourceFile(this.filename, this.text); + + List 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 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 000000000..c100694e0 --- /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.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 sourceFiles = {}; + int dartCharactersRead = 0; + + Future 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 000000000..a2cd6fcc3 --- /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 entries; + + Map sourceUrlMap; + List sourceUrlList; + Map sourceNameMap; + List sourceNameList; + + int previousTargetLine; + int previousTargetColumn; + int previousSourceUrlIndex; + int previousSourceLine; + int previousSourceColumn; + int previousSourceNameIndex; + bool firstEntryInLine; + + SourceMapBuilder() { + entries = new List(); + + sourceUrlMap = new Map(); + sourceUrlList = new List(); + sourceNameMap = new Map(); + sourceNameList = new List(); + + 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 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 list, String value, Map 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 000000000..0e10ecfc4 --- /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 lives; + final List loopMarkers; + Environment() : lives = new Set(), + loopMarkers = new List(); + Environment.from(Environment other) + : lives = new Set.from(other.lives), + loopMarkers = new List.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 = []; + 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 capturedEnvironments; + final Map liveInstructions; + Environment environment; + /** + * The set of current loop headers that dominate the current block. + */ + Set loopMarkers; + + SsaEnvironmentBuilder(Compiler this.compiler) + : capturedEnvironments = new Map(), + liveInstructions = new Map(), + loopMarkers = new Set(); + + + 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 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 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 = [], + bailoutArity = 0, + parameterNames = new Map(); + + 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 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 000000000..ac19fea18 --- /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 functionsCalledInLoop; + final Map selectorsCalledInLoop; + final JavaScriptBackend backend; + + String get name => 'SSA builder'; + + SsaBuilderTask(JavaScriptBackend backend) + : emitter = backend.emitter, + functionsCalledInLoop = new Set(), + selectorsCalledInLoop = new Map(), + 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 directLocals; + Map redirectionMapping; + SsaBuilder builder; + ClosureClassMap closureData; + + LocalsHandler(this.builder) + : directLocals = new LinkedHashMap(), + redirectionMapping = new Map(); + + 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.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, + []); + 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 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): + * <== move the first box creation outside the loop. + * ; + * loop-entry: + * if (!) goto loop-exit; + * + * // create a new box and copy the captured loop-variables. + * + * 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 saved = + new LinkedHashMap.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 joinedLocals = + new LinkedHashMap(); + 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, [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 locals, + HBasicBlock joinBlock) { + assert(locals.length > 0); + if (locals.length == 1) return locals[0]; + Map joinedLocals = + new LinkedHashMap(); + 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 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 labels() => const []; + 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 jumps; + + TargetJumpHandler(SsaBuilder builder, this.target) + : this.builder = builder, + jumps = [] { + 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 labels() { + List result = null; + for (LabelElement element in target.labels) { + if (result == null) result = []; + result.add(element); + } + return (result == null) ? const [] : 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 parameters; + final RuntimeTypeInformation rti; + HParameterValue lastAddedParameter; + + Map 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 activationVariables; + + // We build the Ssa graph by simulating a stack machine. + List 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 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(), + activationVariables = new Map(), + jumpTargets = new Map(), + parameters = new Map(), + sourceElementStack = [work.element], + inliningStack = [], + 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 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 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 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 compiledArguments = new List(); + 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 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 = []; + 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 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 arguments, + List constructors, + Map fieldValues, + FunctionElement inlinedFromElement) { + compiler.withCurrentElement(constructor, () { + assert(invariant(constructor, constructor.isImplementation)); + constructors.addLast(constructor); + + List compiledArguments = new List(); + 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 constructors, + Map fieldValues) { + assert(invariant(constructor, constructor.isImplementation)); + FunctionExpression functionNode = constructor.parseNode(compiler); + + bool foundSuperOrRedirect = false; + + if (functionNode.initializers != null) { + Link initializers = functionNode.initializers.nodes; + for (Link 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 arguments = call.arguments; + inlineSuperOrRedirect(target, selector, arguments, constructors, + fieldValues, constructor); + foundSuperOrRedirect = true; + } else { + // A field initializer. + SendSet init = link.head; + Link 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(), + 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 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 fieldValues = new Map(); + + // 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 constructors = [functionElement]; + buildInitializers(functionElement, constructors, fieldValues); + + // Call the JavaScript constructor with the fields as argument. + List constructorArguments = []; + 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 inputs = []; + 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 = []; + 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 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 breakLocals = []; + 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: + // + // loop-entry: + // if (!) goto loop-exit; + // + // + // 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 continueLocals = []; + 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 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), + [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 continueLocals = []; + 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 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), [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 capturedVariables = []; + 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 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 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(), 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([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()); + } 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 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([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 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 inputs = [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 inputs = [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 inputs = [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 inputs = [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 inputs = [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 inputs = [reference, a0, a1, a2, a3, a4]; + HInstruction result = new HInvokeStatic(inputs, type); + push(result); + } + + HForeign createForeign(String code, HType type, List 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 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('#[#]', [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 [getTypeArgument(type)]; + } else { + assert(type.element.isClass()); + List arguments = []; + InterfaceType interface = type; + for (DartType argument in interface.typeArguments) { + List inputs = []; + 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 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 checks = []; + 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 inputs = + [isSubtype, typeArgument, representation]; + HInstruction call = new HInvokeStatic(inputs, HType.BOOLEAN); + add(call); + checks.add(call); + index++; + }); + instruction = new HIs(type, [expression]..addAll(checks)); + } else { + instruction = new HIs(type, [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 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 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 instructions = + new Map(); + List 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 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 arguments, + FunctionElement element, + List 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 link, List 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 inputs = []; + 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 = []; + inputs.add(closureTarget); + addDynamicSendArgumentsToList(node, inputs); + Selector closureSelector = new Selector.callClosureFrom(selector); + pushWithPosition(new HInvokeClosure(closureSelector, inputs), node); + } + + void handleForeignJs(Send node) { + Link 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 inputs = []; + 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, + [])); + } 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 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, [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 inputs = [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 inputs = [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, + [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, + [])); + } + + 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(); + if (node.argumentsNode != null) { + addGenericSendArgumentsToList(node.arguments, arguments); + } + var argumentsInstruction = new HLiteralList(arguments); + add(argumentsInstruction); + + var argumentNames = new List(); + 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 = [ + 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 = [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 = [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 inputs = []; + + /** + * 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, + [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 inputs = []; + if (!type.isRaw) { + type.typeArguments.forEach((DartType argument) { + inputs.add(analyzeTypeArgument(argument, currentNode)); + }); + } + callSetRuntimeTypeInfo(type.element, inputs, newObject); + } + + void callSetRuntimeTypeInfo(ClassElement element, + List 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( + [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 = []; + 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 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 = [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 inputs = [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 inputs = [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 argumentNodes, + List argumentValues, + List 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 = []; + argumentNodes.forEach((argumentNode) { + visit(argumentNode); + HInstruction value = pop(); + argumentValues.add(value); + }); + } + HInstruction arguments = new HLiteralList(argumentValues); + add(arguments); + HInstruction existingNamesList; + if (existingArguments != null) { + List existingNames = []; + 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 argumentNodes) { + List existingArguments = []; + 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 arguments) { + Set interceptedClasses = getInterceptedClassesOn(selector); + List inputs = []; + 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 = [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 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 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 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 inputs = []; + for (Link 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 $iter = .iterator; + // while ($iter.moveNext()) { + // E = $iter.current; + // + // } + + // 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 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, [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: [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 breakLocals = []; + 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 inputs = []; + for (Link 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 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 caseLocals = []; + 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 constants = new Map(); + // 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([expression]); + HBasicBlock expressionBlock = close(switchInstruction); + JumpHandler jumpHandler = createJumpHandler(node); + LocalsHandler savedLocals = localsHandler; + + List> matchExpressions = >[]; + List statements = []; + bool hasDefault = false; + Element getFallThroughErrorElement = + compiler.findHelper(const SourceString("getFallThroughError")); + HasNextIterator caseIterator = + new HasNextIterator(node.cases.iterator); + while (caseIterator.hasNext) { + SwitchCase switchCase = caseIterator.next(); + List caseConstants = []; + 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 caseLocals = []; + 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 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 skipLabels(Link labelsAndCases) { + while (!labelsAndCases.isEmpty && labelsAndCases.head is Label) { + labelsAndCases = labelsAndCases.tail; + } + return labelsAndCases; + } + + Link 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 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 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 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, [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, [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 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, + [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 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, [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 000000000..e5caac120 --- /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 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([]); + 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 { + final LinkedHashMap map = new LinkedHashMap(); + + 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 generateAtUseSite; + final Set controlFlowOperators; + final Map breakAction; + final Map continueAction; + final List parameters; + + js.Block currentContainer; + js.Block get body => currentContainer; + List expressionStack; + List 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 collectedVariableDeclarations; + + /** + * Set of variables and parameters that have already been declared. + */ + final Set 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(), + collectedVariableDeclarations = new OrderedSet(), + currentContainer = new js.Block.empty(), + parameters = [], + expressionStack = [], + oldContainerStack = [], + generateAtUseSite = new Set(), + controlFlowOperators = new Set(), + breakAction = new Map(), + continueAction = new Map(); + + 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 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()); + } + + /** + * 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 declarations = + []; + 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 oldExpressionStack = expressionStack; + List sequenceElements = []; + 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 visitArguments(List inputs) { + assert(inputs.length >= HInvoke.ARGUMENTS_OFFSET); + List result = []; + 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( + [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 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 cases = []; + + 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 expressions; + if (jsInitialization is js.Sequence) { + expressions = jsInitialization.expressions; + } else { + expressions = [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 inits = + []; + 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 continueOverrides = const Link(); + + 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 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 currentLocation = new Map(); + + // Map to keep track of the initial value of a variable. + Map initialValue = new Map(); + + // List of variables to assign a value. + List worklist = []; + + // List of variables that we can assign a value to (ie are not + // being used anymore). + List ready = []; + + // Prune [copies] by removing self-copies. + List prunedCopies = []; + 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 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 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 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 dominated = node.block.dominatedBlocks; + for (int i = 2; i < dominated.length; i++) { + visitBasicBlock(dominated[i]); + } + } + + js.Call jsPropertyCall(js.Expression receiver, + String fieldName, + List 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 arguments = [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 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 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 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 data = []; + 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 inputs = node.inputs; + // We can't use 'visitArguments', since our arguments start at input[0]. + List arguments = []; + 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 inverseOperator = const { + "==" : "!=", + "!=" : "==", + "===": "!==", + "!==": "===", + "<" : ">=", + "<=" : ">", + ">" : "<=", + ">=" : "<" + }; + 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, []); + 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, [pop()]), node); + } + } + + void visitLiteralList(HLiteralList node) { + world.registerInstantiatedClass(compiler.listClass); + generateArrayLiteral(node); + } + + void generateArrayLiteral(HLiteralList node) { + int len = node.inputs.length; + List elements = []; + 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(#) === #', + [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, [])); + 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 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 castNames = const { + "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 arguments = []; + 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 arguments = []; + 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 oldBailoutSwitches; + final List newParameters; + final List 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 defaultClauseUsedInBailoutStack; + + SsaBailoutPropagator propagator; + HInstruction savedFirstInstruction; + + SsaUnoptimizedCodeGenerator(backend, work) + : super(backend, work), + oldBailoutSwitches = [], + newParameters = [], + labels = [], + defaultClauseUsedInBailoutStack = []; + + 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 names = new List(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(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(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 bailouts1, + [List 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 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 cases = []; + 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 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 000000000..625eafb13 --- /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 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 pureInputs; + Set 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 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(); + if (pureInputs == null) pureInputs = new Set(); + + // 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 generateAtUseSite; + Set 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 000000000..375f61727 --- /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 000000000..afbd2e172 --- /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 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 blocks; + + // We canonicalize all constants used within a graph so we do not + // have to worry about them for global value numbering. + Map constants; + + HGraph() + : blocks = new List(), + constants = new Map() { + 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 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 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 bailoutTargets; + + final List predecessors; + List successors; + + HBasicBlock dominator = null; + final List dominatedBlocks; + + HBasicBlock() : this.withId(null); + HBasicBlock.withId(this.id) + : phis = new HInstructionList(), + predecessors = [], + successors = const [], + dominatedBlocks = [], + bailoutTargets = []; + + 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 better = const Link(); + 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 inputs; + final List 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 = []; + + 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 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 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 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 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 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 users = new Set(); + 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([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 padding; + HBailoutTarget(this.state) : super([]) { + 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([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([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([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 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 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 inputs) + : super(selector, null, inputs) { + assert(selector.isClosureCall()); + } + accept(HVisitor visitor) => visitor.visitInvokeClosure(this); +} + +class HInvokeDynamicMethod extends HInvokeDynamic { + HInvokeDynamicMethod(Selector selector, + List 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 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 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, [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, [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, [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, [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 inputs, + {this.isStatement: false}) + : super(inputs) { + setAllSideEffects(); + setDependsOnSomething(); + } + + HForeign.statement(code, List 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 inputs) + : super(const LiteralDartString("new"), type, inputs); + accept(HVisitor visitor) => visitor.visitForeignNew(this); +} + +abstract class HInvokeBinary extends HInstruction { + HInvokeBinary(HInstruction left, HInstruction right) + : super([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 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([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 []); + toString() => 'exit'; + accept(HVisitor visitor) => visitor.visitExit(this); +} + +class HGoto extends HControlFlow { + HGoto() : super(const []); + 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 []); + HJump.toLabel(LabelElement label) + : label = label, target = label.target, super(const []); +} + +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 []); + 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 []); + 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([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([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([]); + + 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([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([]) { + 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 inputs) : super(inputs) { + sourceElement = element; + } + HPhi.noInputs(Element element) : this(element, []); + HPhi.singleInput(Element element, HInstruction input) + : this(element, [input]); + HPhi.manyInputs(Element element, List 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([value]); + toString() => 'return'; + accept(HVisitor visitor) => visitor.visitReturn(this); +} + +class HThrow extends HControlFlow { + final bool isRethrow; + HThrow(value, {this.isRethrow: false}) : super([value]); + toString() => 'throw'; + accept(HVisitor visitor) => visitor.visitThrow(this); +} + +class HStatic extends HInstruction { + final Element element; + HStatic(this.element) : super([]) { + 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 interceptedClasses; + HInterceptor(this.interceptedClasses, HInstruction receiver) + : super([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 interceptedClasses; + HOneShotInterceptor(Selector selector, + List 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([]) { + // 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([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([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([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 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([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([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([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 blocks; + final List backEdges; + final List labels; + final TargetElement target; + + /** Corresponding block information for the loop. */ + HLoopBlockInformation loopBlockInformation; + + HLoopInformation(this.header, this.target, this.labels) + : blocks = new List(), + backEdges = new List(); + + 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 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 labels; + final TargetElement target; + final bool isContinue; + + HLabeledBlockInformation(this.body, + List labels, + {this.isContinue: false}) : + this.labels = labels, this.target = labels[0].target; + + HLabeledBlockInformation.implicit(this.body, + this.target, + {this.isContinue: false}) + : this.labels = const[]; + + 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 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> matchExpressions; + final List 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 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 000000000..35ba2cbac --- /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 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 phases = [ + // 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 phases = [ + 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, [ + 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 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 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 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 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 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), []); + 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 inputs = new List.from(node.inputs); + inputs[0] = constant; + return new HInvokeDynamicMethod(selector, inputs, true); + } + } +} + +class SsaCheckInserter extends HBaseVisitor implements OptimizationPhase { + final HTypeMap types; + final Set 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 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 worklist = []; + // A set to keep track of the live phis that we found. + final Set livePhis = new Set(); + + // 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 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 worklist = []; + + // 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 visited; + + List blockChangesFlags; + List loopChangesFlags; + + SsaGlobalValueNumberer(this.compiler, this.types) : visited = new Set(); + + 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 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 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.fixedLength(length); + loopChangesFlags = new List.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 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 values; + + void visitGraph(HGraph graph) { + values = new List.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 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 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 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 ifUsers = []; + List notIfUsers = []; + + 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 thisUsers; + final Set allSetters; + final Map> blockFieldSetters; + bool thisExposed = false; + HGraph currentGraph; + Map currentFieldSetters; + + SsaConstructionFieldTypes(JavaScriptBackend this.backend, + CodegenWorkItem this.work, + HTypeMap this.types) + : thisUsers = new Set(), + allSetters = new Set(), + blockFieldSetters = new Map>(); + + 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(); + } 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 predecessorsFieldSetters = + blockFieldSetters[block.predecessors[i]]; + Map newFieldSetters = new Map(); + 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 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 inputs = new List.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 000000000..13de8b620 --- /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 000000000..7a8f6bcf7 --- /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 { + 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 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 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 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 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 000000000..b6d4746d7 --- /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 _list = new List()..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 000000000..ee0f1a15a --- /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 workmap; + final List worklist; + final Map pendingOptimizations; + final HTypeMap types; + + final Compiler compiler; + String get name => 'type propagator'; + + SsaTypePropagator(this.compiler, this.types) + : workmap = new Map(), + worklist = new List(), + pendingOptimizations = new Map(); + + 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 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 000000000..5e9598e95 --- /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 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 instructions, Function f) { + var copy = new List.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 000000000..c28d5c4b9 --- /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 conversions = []; + + /** + * Value ranges for integer instructions. This map gets populated by + * the dominator tree visit. + */ + final Map ranges = new Map(); + + 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 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 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 000000000..d565de77e --- /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 table; + ValueSetNode collisions; + ValueSet() : table = new List.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 toList() { + return copyTo([], 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 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.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 000000000..34f960d2d --- /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 ranges; + LiveInterval() : ranges = []; + + /** + * 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 res = new List(); + 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 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 liveInstructions; + + /** + * Map containing the live intervals of instructions. + */ + final Map liveIntervals; + + LiveEnvironment(this.liveIntervals, this.endId) + : liveInstructions = new Map(), + loopMarkers = new Map(); + + /** + * 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 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 liveInstructions; + + /** + * The live intervals of instructions. + */ + final Map liveIntervals; + + SsaLiveIntervalBuilder(this.compiler, this.generateAtUseSite) + : liveInstructions = new Map(), + liveIntervals = new Map(); + + 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 copies; + + /** + * Assignments from an instruction that does not need a name (e.g. a + * constant) to the phi of a successor. + */ + final List assignments; + + CopyHandler() + : copies = new List(), + assignments = new List(); + + 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 ownName; + final Map copyHandlers; + + // Used to control heuristic that determines how local variables are declared. + final Set 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(), + copyHandlers = new Map(), + allUsedNames = new Set(), + 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 usedNames; + final List freeTemporaryNames; + int temporaryIndex = 0; + static final RegExp regexp = new RegExp('t[0-9]+'); + + VariableNamer(LiveEnvironment environment, + this.names, + this.compiler) + : usedNames = new Set(), + freeTemporaryNames = new List() { + // [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 liveInstructions; + final Map liveIntervals; + final Set 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 000000000..ef294bda5 --- /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 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 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 000000000..ea5677109 --- /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 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 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 read(String filename) { + RandomAccessFile file = new File(filename).openSync(); + bool threw = true; + try { + int size = file.lengthSync(); + List 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 000000000..5a3494d6e --- /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 { + 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 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 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 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 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 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 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 { + HasNextIterator currentIterator; + DartString right; + bool hasNextLookAhead; + int _current = null; + + ConsDartStringIterator(ConsDartString cons) + : currentIterator = new HasNextIterator(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(right.iterator); + right = null; + hasNextLookAhead = currentIterator.hasNext; + } + } +} + +/** + *Iterator that returns the actual string contents of a string with escapes. + */ +class StringEscapeIterator implements Iterator{ + final Iterator 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 000000000..1b3b4555b --- /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 { + 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 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()); + Postfix.singleton(Node argument) : super.singleton(argument); +} + +class Prefix extends NodeList { + Prefix() : super(null, const Link()); + 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 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 get iterator => nodes.iterator; + + NodeList.singleton(Node node) : this(null, const Link().prepend(node)); + NodeList.empty() : this(null, const Link()); + + NodeList asNodeList() => this; + + int slowLength() { + int result = 0; + for (Link 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 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 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 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 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 { + 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 { + 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 { + 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 mapping = const [ + 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 { + 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 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 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 builder = new LinkBuilder(); + 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 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 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 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 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 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 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 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 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 { + const GetDartStringVisitor(); + DartString visitNode(Node node) => null; + DartString visitStringJuxtaposition(StringJuxtaposition node) + => node.dartString; + DartString visitLiteralString(LiteralString node) => node.dartString; +} + +class IsInterpolationVisitor extends Visitor { + 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 000000000..129c637b9 --- /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 tagStack; + + PrettyPrinter() : + sb = new StringBuffer(), + tagStack = const Link(); + + 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("\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("\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 000000000..3faa36c40 --- /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 000000000..9c20f2d34 --- /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 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 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 000000000..9096cff5b --- /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 000000000..6a3b93f37 --- /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 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 000000000..01fa6070d --- /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 { + final Compiler compiler; + final TreeElements elements; + final Types types; + + Node lastSeenNode; + DartType expectedReturnType; + ClassElement currentClass; + + Link cascadeTypes = const Link(); + + 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 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 arguments = send.arguments; + if (type == null || identical(type, types.dynamicType)) { + while(!arguments.isEmpty) { + analyze(arguments.head); + arguments = arguments.tail; + } + } else { + FunctionType funType = type; + Link parameterTypes = funType.parameterTypes; + Link 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 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 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 000000000..6c05203f9 --- /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()); + } + + /** + * 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 singletonSet = new Set(); + singletonSet.add(baseType); + return new UnionType(singletonSet); + } + + factory ConcreteType.unknown() { + return const UnknownConcreteType(); + } + + ConcreteType union(int maxConcreteTypeSize, ConcreteType other); + bool isUnkown(); + bool isEmpty(); + Set 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 get baseTypes => + new Set.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 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 newBaseTypes = new Set.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 { + final ConcreteTypesInferrer inferrer; + final ClassElement typeOfThis; + final Map 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 cartesianProduct = + new List.from(this); + return cartesianProduct.toString(); + } +} + +/** + * An helper class for [ConcreteTypeCartesianProduct]. + */ +class ConcreteTypeCartesianProductIterator + implements Iterator { + final ConcreteTypesInferrer inferrer; + final BaseType baseTypeOfThis; + final Map concreteTypes; + final Map nextValues; + final Map state; + int size = 1; + int counter = 0; + ConcreteTypesEnvironment _current; + + ConcreteTypeCartesianProductIterator(this.inferrer, this.baseTypeOfThis, + Map concreteTypes) + : this.concreteTypes = concreteTypes, + nextValues = new Map(), + state = new Map() { + 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 result = new Map(); + 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 environment; + final BaseType typeOfThis; + + ConcreteTypesEnvironment(this.inferrer, [this.typeOfThis]) : + this.environment = new Map(); + 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 newMap = + new Map.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 newMap = + new Map.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> cache; + + /** A map from expressions to their inferred concrete types. */ + final Map inferredTypes; + + /** A map from fields to their inferred concrete types. */ + final Map inferredFieldTypes; + + /** The work queue consumed by [analyzeMain]. */ + final Queue workQueue; + + /** [: callers[f] :] is the list of [: f :]'s possible callers. */ + final Map> callers; + + /** [: readers[field] :] is the list of [: field :]'s possible readers. */ + final Map> 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 inferredParameterTypes; + + ConcreteTypesInferrer(Compiler compiler) + : this.compiler = compiler, + cache = new Map>(), + inferredTypes = new Map(), + inferredFieldTypes = new Map(), + inferredParameterTypes = new Map(), + workQueue = new Queue(), + callers = new Map>(), + readers = new Map>(), + 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 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 map = + cache.containsKey(methodElement) + ? cache[methodElement] + : new Map(); + 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 getMembersByName(SourceString methodName) { + // TODO(polux): memoize? + var result = new List(); + 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 current = callers[callee]; + if (current != null) { + current.add(caller); + } else { + Set newSet = new Set(); + newSet.add(caller); + callers[callee] = newSet; + } + } + + /** + * Add [reader] to the set of [field]'s readers. + */ + void addReader(Element field, FunctionElement reader) { + Set current = readers[field]; + if (current != null) { + current.add(reader); + } else { + Set newSet = new Set(); + newSet.add(reader); + readers[field] = newSet; + } + } + + /** + * Add callers of [function] to the workqueue. + */ + void invalidateCallers(FunctionElement function) { + Set methodCallers = callers[function]; + if (methodCallers == null) return; + for (FunctionElement caller in methodCallers) { + Map 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 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 associateArguments(FunctionElement function, + ArgumentsTypes argumentsTypes) { + final Map result = new Map(); + 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 remainingPositionalArguments = + argumentsTypes.positional.iterator; + // we attach each positional parameter to its corresponding positional + // argument + for (Link 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 remainingOptionalParameters = signature.optionalParameters; + final Map leftOverNamedParameters = + new Map(); + 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 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 template = cache[function]; + if (template == null) { + template = new Map(); + 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 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 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(); + 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 positional; + final Map 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 { + final ConcreteTypesInferrer inferrer; + + final FunctionElement currentMethod; + ConcreteTypesEnvironment environment; + Node lastSeenNode; + + TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer, + this.environment) + : super(elements); + + ArgumentsTypes analyzeArguments(Link arguments) { + final positional = new List(); + final named = new Map(); + for(Link 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 positionalArguments = [ + 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 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 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 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 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 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 000000000..fdce42731 --- /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 untypedElements; + final Map> typedSends; + ConcreteTypesInferrer concreteTypesInferrer; + + TypesTask(Compiler compiler) + : untypedElements = new Set(), + typedSends = new Map>(), + 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 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 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(); + + 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 computeConcreteSendArguments(Send node) { + if (node.argumentsNode == null) return null; + if (node.arguments.isEmpty) return const Link(); + if (node.receiver != null && concreteTypes[node.receiver] == null) { + return null; + } + LinkBuilder types = new LinkBuilder(); + 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 types = computeConcreteSendArguments(node); + if (types != null) { + Link existing = task.typedSends[element]; + if (existing == null) { + task.typedSends[element] = types; + } else { + // interest(node, 'multiple invocations'); + Link 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 computeLubs(Link a, Link b) { + LinkBuilder lubs = new LinkBuilder(); + 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 000000000..23bc78287 --- /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 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 filterAllBySelector(Selector selector) { + Set result = new Set(); + 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 filterHierarchyBySelector(Selector selector) { + Set result = new Set(); + 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 membersByName; + + FunctionSetNode(ClassElement type) : super(type), + membersByName = new Map(); + +} 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 000000000..f3101745e --- /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 nodes = + new Map(); + + // 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 unseenInterfaceSubtypes = + new Set(); + + 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 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 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 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 000000000..cf98a10cd --- /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 extends PartialTypeTree { + + SelectorMap(Compiler compiler) : super(compiler); + + SelectorMapNode newSpecializedNode(ClassElement type) + => new SelectorMapNode(type); + + T operator [](Selector selector) { + SelectorMapNode node = findNode(selectorType(selector), false); + if (node == null) return null; + Link> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return null; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue existing = link.head; + if (existing.selector.equalsUntyped(selector)) return existing.value; + } + return null; + } + + void operator []=(Selector selector, T value) { + SelectorMapNode node = findNode(selectorType(selector), true); + Link> selectors = node.selectorsByName[selector.name]; + if (selectors == null) { + // No existing selectors with the given name. Create a new + // linked list. + SelectorValue head = new SelectorValue(selector, value); + node.selectorsByName[selector.name] = + new Link>().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 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 head = new SelectorValue(selector, value); + node.selectorsByName[selector.name] = selectors.prepend(head); + } + } + + // TODO(kasperl): Share code with the [] operator? + bool containsKey(Selector selector) { + SelectorMapNode node = findNode(selectorType(selector), false); + if (node == null) return false; + Link> selectors = node.selectorsByName[selector.name]; + if (selectors == null) return false; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue 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 node) { + Link> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue 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 node) { + Link> selectors = node.selectorsByName[member.name]; + if (selectors == null) return true; + for (Link link = selectors; !link.isEmpty; link = link.tail) { + SelectorValue existing = link.head; + Selector selector = existing.selector; + if (selector.appliesUntyped(member, compiler)) { + if (!visit(selector, existing.value)) return false; + } + } + return true; + }); + } + +} + +class SelectorMapNode extends PartialTypeTreeNode { + + final Map>> selectorsByName; + + SelectorMapNode(ClassElement type) : super(type), + selectorsByName = new Map>>(); + +} + +class SelectorValue { + 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 000000000..6b6438299 --- /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 instantiatedClasses; + final Set instantiatedTypes; + + /** + * Documentation wanted -- johnniwinther + * + * Invariant: Elements are declaration elements. + */ + final Set staticFunctionsNeedingGetter; + final Map> invokedNames; + final Map> invokedGetters; + final Map> 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> fieldGetters; + + /** + * Fields set. See comment in [fieldGetters]. + */ + final Map> fieldSetters; + final Set isChecks; + + Universe() : instantiatedClasses = new Set(), + instantiatedTypes = new Set(), + staticFunctionsNeedingGetter = new Set(), + invokedNames = new Map>(), + invokedGetters = new Map>(), + fieldGetters = new Map>(), + fieldSetters = new Map>(), + invokedSetters = new Map>(), + isChecks = new Set(); + + bool hasMatchingSelector(Set 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 namedArguments; + final List orderedNamedArguments; + + Selector( + this.kind, + SourceString name, + LibraryElement library, + this.argumentCount, + [List namedArguments = const []]) + : this.name = name, + this.library = name.isPrivate() ? library : null, + this.namedArguments = namedArguments, + this.orderedNamedArguments = namedArguments.isEmpty + ? namedArguments + : [] { + 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 named = const []]) + : this(SelectorKind.CALL, name, library, arity, named); + + Selector.callClosure(int arity, [List 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 nameSet = new Set(); + 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 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 first, List 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 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 000000000..5961d0764 --- /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 000000000..9cc81cc99 --- /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 extends Iterable { + T get head => null; + Link get tail => null; + + factory Link.fromList(List list) { + switch (list.length) { + case 0: + return new Link(); + case 1: + return new LinkEntry(list[0]); + case 2: + return new LinkEntry(list[0], new LinkEntry(list[1])); + case 3: + return new LinkEntry( + list[0], new LinkEntry(list[1], new LinkEntry(list[2]))); + } + Link link = new Link(); + for (int i = list.length ; i > 0; i--) { + link = link.prepend(list[i - 1]); + } + return link; + } + + const Link(); + + Link prepend(T element) { + return new LinkEntry(element, this); + } + + Iterator get iterator => new LinkIterator(this); + + void printOn(StringBuffer buffer, [separatedBy]) { + } + + List toList() => new List.fixedLength(0); + + bool get isEmpty => true; + + Link reverse() => this; + + Link reversePrependAll(Link from) { + if (from.isEmpty) return this; + return this.prepend(from.head).reversePrependAll(from.tail); + } + + Link 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) return false; + return other.isEmpty; + } + + String toString() => "[]"; + + get length { + throw new UnsupportedError('get:length'); + } + + int slowLength() => 0; +} + +abstract class LinkBuilder { + factory LinkBuilder() = LinkBuilderImplementation; + + Link 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 000000000..d00ec8675 --- /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 implements Iterator { + T _current; + Link _link; + + LinkIterator(Link 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 extends Link { + final T head; + Link tail; + + LinkEntry(T this.head, [Link tail]) + : this.tail = ((tail == null) ? new Link() : tail); + + Link prepend(T element) { + // TODO(ahe): Use new Link, but this cost 8% performance on VM. + return new LinkEntry(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 reverse() { + Link result = const Link(); + for (Link link = this; !link.isEmpty; link = link.tail) { + result = result.prepend(link.head); + } + return result; + } + + Link reversePrependAll(Link from) { + Link result; + for (result = this; !from.isEmpty; from = from.tail) { + result = result.prepend(from.head); + } + return result; + } + + Link skip(int n) { + Link 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 toList() { + List list = new List(); + for (Link link = this; !link.isEmpty; link = link.tail) { + list.addLast(link.head); + } + return list; + } + + void forEach(void f(T element)) { + for (Link link = this; !link.isEmpty; link = link.tail) { + f(link.head); + } + } + + bool operator ==(other) { + if (other is !Link) return false; + Link 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 implements LinkBuilder { + LinkEntry head = null; + LinkEntry lastLink = null; + int length = 0; + + LinkBuilderImplementation(); + + Link toLink() { + if (head == null) return const Link(); + lastLink.tail = const Link(); + Link link = head; + lastLink = null; + head = null; + return link; + } + + void addLast(T t) { + length++; + LinkEntry entry = new LinkEntry(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 000000000..fed5f154c --- /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 uriParts = uri.path.split('/'); + List 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 000000000..8a07687d3 --- /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 000000000..bbb852ab8 --- /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 000000000..56f369824 --- /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 000000000..a40050ee8 --- /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> subtypes; + final Map> mixinUses; + final Map> typesImplementedBySubclasses; + final Set classesNeedingRti; + final Map> rtiDependencies; + final FunctionSet userDefinedGetters; + final FunctionSet userDefinedSetters; + + World(Compiler compiler) + : subtypes = new Map>(), + mixinUses = new Map>(), + typesImplementedBySubclasses = + new Map>(), + userDefinedGetters = new FunctionSet(compiler), + userDefinedSetters = new FunctionSet(compiler), + classesNeedingRti = new Set(), + rtiDependencies = new Map>(), + 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 subtypesOfCls = + subtypes.putIfAbsent(type.element, () => new Set()); + 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 typesImplementedBySubclassesOfCls = + typesImplementedBySubclasses.putIfAbsent( + type.element, () => new Set()); + 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 classes = subtypes[cls]; + if (classes != null) { + classes.forEach((ClassElement sub) { + potentiallyAddForRti(sub); + }); + } + + Set 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 users = + mixinUses.putIfAbsent(mixin, () => + new Set()); + users.add(mixinApplication); + } + + void registerRtiDependency(Element element, Element dependency) { + // We're not dealing with typedef for now. + if (!element.isClass() || !dependency.isClass()) return; + Set classes = + rtiDependencies.putIfAbsent(element, () => new Set()); + 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 subclasses= typesImplementedBySubclasses[superclass]; + if (subclasses == null) return false; + return subclasses.contains(type.element); + } + + bool hasNoOverridingMember(Element element) { + ClassElement cls = element.getEnclosingClass(); + Set 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 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 findNoSuchMethodHolders(DartType type) { + Set result = new Set(); + 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 elements; + final SourceString name; + + MemberSet(SourceString this.name) : elements = new Set(); + + 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 000000000..5c354839e --- /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 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 000000000..c966cea00 --- /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 children; + final Map attributes; + + Element(this.tag, this.children) + : attributes = {}; + + Element.empty(this.tag) + : children = null, + attributes = {}; + + Element.withTag(this.tag) + : children = [], + attributes = {}; + + Element.text(this.tag, String text) + : children = [new Text(text)], + attributes = {}; + + 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 000000000..67109a45f --- /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 +/// "]'); + +/// 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 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 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 _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 parseChildLines(BlockParser parser) { + // Grab all of the lines that form the blockquote, stripping off the ">". + final childLines = []; + + 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 parseChildLines(BlockParser parser) { + final childLines = []; + + 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 ``, it will not wrap it in a `

    ` 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 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 = []; + var childLines = []; + + endItem() { + if (childLines.length > 0) { + items.add(new ListItem(childLines)); + childLines = []; + } + } + + 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

  1. , like: + //
  2. ATc2!!%RM@R@>-7 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