Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(295)

Side by Side Diff: frog/leg/compile_time_constants.dart

Issue 9753001: super and this in compile-time constants. (Closed) Base URL: /home/flo/programming/dart2/../dart/dart@master
Patch Set: Address comments Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | frog/leg/compiler.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 class Constant implements Hashable { 5 class Constant implements Hashable {
6 const Constant(); 6 const Constant();
7 7
8 bool isNull() => false; 8 bool isNull() => false;
9 bool isBool() => false; 9 bool isBool() => false;
10 bool isTrue() => false; 10 bool isTrue() => false;
(...skipping 514 matching lines...) Expand 10 before | Expand all | Expand 10 after
525 initialVariableValues[element] = value; 525 initialVariableValues[element] = value;
526 pendingVariables.remove(element); 526 pendingVariables.remove(element);
527 return value; 527 return value;
528 }); 528 });
529 } 529 }
530 530
531 Constant compileNodeWithDefinitions(Node node, TreeElements definitions) { 531 Constant compileNodeWithDefinitions(Node node, TreeElements definitions) {
532 return measure(() { 532 return measure(() {
533 assert(node !== null); 533 assert(node !== null);
534 CompileTimeConstantEvaluator evaluator = 534 CompileTimeConstantEvaluator evaluator =
535 new CompileTimeConstantEvaluator(this, definitions, compiler); 535 new CompileTimeConstantEvaluator(definitions, compiler);
536 return evaluator.evaluate(node); 536 return evaluator.evaluate(node);
537 }); 537 });
538 } 538 }
539 539
540 /** 540 /**
541 * Returns a [List] of static non final fields that need to be initialized. 541 * Returns a [List] of static non final fields that need to be initialized.
542 * The list must be evaluated in order since the fields might depend on each 542 * The list must be evaluated in order since the fields might depend on each
543 * other. 543 * other.
544 */ 544 */
545 List<VariableElement> getStaticNonFinalFieldsForEmission() { 545 List<VariableElement> getStaticNonFinalFieldsForEmission() {
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
659 } 659 }
660 } 660 }
661 } 661 }
662 662
663 String getJsConstructor(ClassElement element) { 663 String getJsConstructor(ClassElement element) {
664 return compiler.namer.isolatePropertyAccess(element); 664 return compiler.namer.isolatePropertyAccess(element);
665 } 665 }
666 } 666 }
667 667
668 class CompileTimeConstantEvaluator extends AbstractVisitor { 668 class CompileTimeConstantEvaluator extends AbstractVisitor {
669 final ConstantHandler constantHandler;
670 final TreeElements elements; 669 final TreeElements elements;
671 final Compiler compiler; 670 final Compiler compiler;
672 final Map<Element, Constant> definitions = null;
673 671
674 CompileTimeConstantEvaluator(this.constantHandler, 672 CompileTimeConstantEvaluator(this.elements, this.compiler);
675 this.elements,
676 this.compiler);
677
678 CompileTimeConstantEvaluator.insideConstructor(this.constantHandler,
679 this.elements,
680 this.compiler,
681 this.definitions);
682
683 bool insideConstructor() => definitions !== null;
684 673
685 Constant evaluate(Node node) { 674 Constant evaluate(Node node) {
686 return node.accept(this); 675 return node.accept(this);
687 } 676 }
688 677
689 visitNode(Node node) { 678 visitNode(Node node) {
690 error(node); 679 error(node);
691 } 680 }
692 681
693 Constant visitLiteralBool(LiteralBool node) { 682 Constant visitLiteralBool(LiteralBool node) {
(...skipping 12 matching lines...) Expand all
706 if (!node.isConst()) error(node); 695 if (!node.isConst()) error(node);
707 List<Constant> arguments = <Constant>[]; 696 List<Constant> arguments = <Constant>[];
708 for (Link<Node> link = node.elements.nodes; 697 for (Link<Node> link = node.elements.nodes;
709 !link.isEmpty(); 698 !link.isEmpty();
710 link = link.tail) { 699 link = link.tail) {
711 arguments.add(evaluate(link.head)); 700 arguments.add(evaluate(link.head));
712 } 701 }
713 // TODO(floitsch): get type from somewhere. 702 // TODO(floitsch): get type from somewhere.
714 Type type = null; 703 Type type = null;
715 Constant constant = new ListConstant(type, arguments); 704 Constant constant = new ListConstant(type, arguments);
716 constantHandler.registerCompileTimeConstant(constant); 705 compiler.constantHandler.registerCompileTimeConstant(constant);
717 return constant; 706 return constant;
718 } 707 }
719 708
720 Constant visitLiteralMap(LiteralMap node) { 709 Constant visitLiteralMap(LiteralMap node) {
721 // TODO(floitsch): check for isConst, once the parser adds it into the node. 710 // TODO(floitsch): check for isConst, once the parser adds it into the node.
722 // if (!node.isConst()) error(node); 711 // if (!node.isConst()) error(node);
723 List<StringConstant> keys = <StringConstant>[]; 712 List<StringConstant> keys = <StringConstant>[];
724 List<Constant> values = <Constant>[]; 713 List<Constant> values = <Constant>[];
725 bool hasProtoKey = false; 714 bool hasProtoKey = false;
726 for (Link<Node> link = node.entries.nodes; 715 for (Link<Node> link = node.entries.nodes;
(...skipping 13 matching lines...) Expand all
740 keys.add(key); 729 keys.add(key);
741 values.add(evaluate(entry.value)); 730 values.add(evaluate(entry.value));
742 } 731 }
743 if (hasProtoKey) { 732 if (hasProtoKey) {
744 compiler.unimplemented("visitLiteralMap with __proto__ key", 733 compiler.unimplemented("visitLiteralMap with __proto__ key",
745 node: node); 734 node: node);
746 } 735 }
747 // TODO(floitsch): this should be a List<String> type. 736 // TODO(floitsch): this should be a List<String> type.
748 Type keysType = null; 737 Type keysType = null;
749 ListConstant keysList = new ListConstant(keysType, keys); 738 ListConstant keysList = new ListConstant(keysType, keys);
750 constantHandler.registerCompileTimeConstant(keysList); 739 compiler.constantHandler.registerCompileTimeConstant(keysList);
751 ClassElement classElement = 740 ClassElement classElement =
752 compiler.jsHelperLibrary.find(MapConstant.DART_CLASS); 741 compiler.jsHelperLibrary.find(MapConstant.DART_CLASS);
753 classElement.ensureResolved(compiler); 742 classElement.ensureResolved(compiler);
754 // TODO(floitsch): copy over the generic type. 743 // TODO(floitsch): copy over the generic type.
755 Type type = new SimpleType(classElement.name, classElement); 744 Type type = new SimpleType(classElement.name, classElement);
756 compiler.registerInstantiatedClass(classElement); 745 compiler.registerInstantiatedClass(classElement);
757 Constant constant = new MapConstant(type, keysList, values); 746 Constant constant = new MapConstant(type, keysList, values);
758 constantHandler.registerCompileTimeConstant(constant); 747 compiler.constantHandler.registerCompileTimeConstant(constant);
759 return constant; 748 return constant;
760 } 749 }
761 750
762 Constant visitLiteralNull(LiteralNull node) { 751 Constant visitLiteralNull(LiteralNull node) {
763 return new NullConstant(); 752 return new NullConstant();
764 } 753 }
765 754
766 Constant visitLiteralString(LiteralString node) { 755 Constant visitLiteralString(LiteralString node) {
767 return new StringConstant(node.dartString); 756 return new StringConstant(node.dartString);
768 } 757 }
(...skipping 26 matching lines...) Expand all
795 } 784 }
796 785
797 // TODO(floitsch): provide better error-messages. 786 // TODO(floitsch): provide better error-messages.
798 Constant visitSend(Send send) { 787 Constant visitSend(Send send) {
799 Element element = elements[send]; 788 Element element = elements[send];
800 if (Elements.isStaticOrTopLevelField(element)) { 789 if (Elements.isStaticOrTopLevelField(element)) {
801 if (element.modifiers === null || 790 if (element.modifiers === null ||
802 !element.modifiers.isFinal()) { 791 !element.modifiers.isFinal()) {
803 error(send); 792 error(send);
804 } 793 }
805 return constantHandler.compileVariable(element); 794 return compiler.compileVariable(element);
806 } else if (send.isPrefix) { 795 } else if (send.isPrefix) {
807 assert(send.isOperator); 796 assert(send.isOperator);
808 Constant receiverConstant = evaluate(send.receiver); 797 Constant receiverConstant = evaluate(send.receiver);
809 Operator op = send.selector; 798 Operator op = send.selector;
810 Constant folded; 799 Constant folded;
811 switch (op.source.stringValue) { 800 switch (op.source.stringValue) {
812 case "!": 801 case "!":
813 folded = const NotOperation().fold(receiverConstant); 802 folded = const NotOperation().fold(receiverConstant);
814 break; 803 break;
815 case "-": 804 case "-":
816 folded = const NegateOperation().fold(receiverConstant); 805 folded = const NegateOperation().fold(receiverConstant);
817 break; 806 break;
818 case "~": 807 case "~":
819 folded = const BitNotOperation().fold(receiverConstant); 808 folded = const BitNotOperation().fold(receiverConstant);
820 break; 809 break;
821 default: 810 default:
822 compiler.internalError("Unexpected operator.", node: op); 811 compiler.internalError("Unexpected operator.", node: op);
823 break; 812 break;
824 } 813 }
825 if (folded === null) error(send); 814 if (folded === null) error(send);
826 return folded; 815 return folded;
827 } else if (Elements.isLocal(element)) {
828 if (!insideConstructor()) error(send);
829 Constant constant = definitions[element];
830 if (constant === null) {
831 compiler.internalError("Local variable without value", node: send);
832 }
833 return constant;
834 } else if (send.isOperator && !send.isPostfix) { 816 } else if (send.isOperator && !send.isPostfix) {
835 assert(send.argumentCount() == 1); 817 assert(send.argumentCount() == 1);
836 Constant left = evaluate(send.receiver); 818 Constant left = evaluate(send.receiver);
837 Constant right = evaluate(send.argumentsNode.nodes.head); 819 Constant right = evaluate(send.argumentsNode.nodes.head);
838 Operator op = send.selector.asOperator(); 820 Operator op = send.selector.asOperator();
839 Constant folded; 821 Constant folded;
840 switch (op.source.stringValue) { 822 switch (op.source.stringValue) {
841 case "+": 823 case "+":
842 if (left.isString() && !right.isString()) { 824 if (left.isString() && !right.isString()) {
843 // At the moment only compile-time concatenation of two strings is 825 // At the moment only compile-time concatenation of two strings is
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
924 if (folded === null) error(send); 906 if (folded === null) error(send);
925 return folded; 907 return folded;
926 } 908 }
927 return super.visitSend(send); 909 return super.visitSend(send);
928 } 910 }
929 911
930 visitSendSet(SendSet node) { 912 visitSendSet(SendSet node) {
931 error(node); 913 error(node);
932 } 914 }
933 915
916 /** Returns the list of constants that are passed to the static function. */
917 List<Constant> evaluateArgumentsToConstructor(Send send,
918 FunctionElement target) {
919 FunctionParameters parameters = target.computeParameters(compiler);
920 List<Constant> arguments = <Constant>[];
921 Selector selector = elements.getSelector(send);
922
923 Function compileArgument = evaluate;
924 Function compileConstant = compiler.compileVariable;
925 bool succeeded = selector.addSendArgumentsToList(
926 send, arguments, parameters, compileArgument, compileConstant);
927 if (!succeeded) error(send);
928 return arguments;
929 }
930
934 Constant visitNewExpression(NewExpression node) { 931 Constant visitNewExpression(NewExpression node) {
935 Element currentElement = compiler.currentElement;
936
937 void assignArgumentsToParameters(
938 FunctionParameters parameters,
939 Map<Element, Constant> constructorDefinitions,
940 Map<Element, Constant> fieldValues) {
941 Send send = node.send;
942 if (send.arguments.isEmpty() && parameters.parameterCount == 0) return;
943 List<Constant> arguments = <Constant>[];
944 Selector selector = elements.getSelector(send);
945
946 Function compileArgument = evaluate;
947 Function compileConstant = constantHandler.compileVariable;
948 bool succeeded = selector.addSendArgumentsToList(
949 send, arguments, parameters, compileArgument, compileConstant);
950 if (!succeeded) error(node);
951
952 int index = 0;
953 parameters.forEachParameter((Element parameter) {
954 Constant argument = arguments[index++];
955 constructorDefinitions[parameter] = argument;
956 if (parameter.kind == ElementKind.FIELD_PARAMETER) {
957 FieldParameterElement fieldParameterElement = parameter;
958 fieldValues[fieldParameterElement.fieldElement] = argument;
959 }
960 });
961 }
962
963 void compileInitializers(Link<Node> initializers,
964 CompileTimeConstantEvaluator evaluator,
965 TreeElements constructorElements,
966 Map<Element, Constant> constructorDefinitions,
967 Map<Element, Constant> fieldValues) {
968 for (Link<Node> link = initializers; !link.isEmpty(); link = link.tail) {
969 assert(link.head is Send);
970 if (link.head is !SendSet) {
971 // A super initializer or constructor redirection.
972 Send call = link.head;
973 assert(Initializers.isSuperConstructorCall(call) ||
974 Initializers.isConstructorRedirect(call));
975 compiler.unimplemented("ConstantHandler with this or super",
976 node: call);
977 } else {
978 // A field initializer.
979 SendSet init = link.head;
980 Link<Node> arguments = init.arguments;
981 assert(!arguments.isEmpty() && arguments.tail.isEmpty());
982 Constant fieldValue = evaluator.evaluate(arguments.head);
983 fieldValues[constructorElements[init]] = fieldValue;
984 }
985 }
986 }
987
988 List<Constant> buildJsNewArguments(ClassElement classElement,
989 Map<Element, Constant> fieldValues) {
990 List<Constant> jsNewArguments = <Constant>[];
991 for (Element member in classElement.members) {
992 if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
993 Constant fieldValue = fieldValues[member];
994 if (fieldValue === null) {
995 // Use the default value.
996 fieldValue = constantHandler.compileVariable(member);
997 }
998 jsNewArguments.add(fieldValue);
999 }
1000 }
1001 if (classElement.superclass != compiler.coreLibrary.find(Types.OBJECT)) {
1002 compiler.withCurrentElement(currentElement, () {
1003 compiler.unimplemented("ConstantHandler with super", node: node);
1004 });
1005 }
1006 return jsNewArguments;
1007 }
1008
1009 if (!node.isConst()) error(node); 932 if (!node.isConst()) error(node);
1010 933
1011 FunctionElement constructor = elements[node.send]; 934 FunctionElement constructor = elements[node.send];
1012 TreeElements constructorElements = 935 ClassElement classElement = constructor.enclosingElement;
1013 compiler.resolver.resolveMethodElement(constructor); 936 if (classElement.isInterface()) {
1014 if (constructor != constructor.defaultImplementation) { 937 compiler.resolver.resolveMethodElement(constructor);
1015 constructor = constructor.defaultImplementation; 938 constructor = constructor.defaultImplementation;
1016 constructorElements = 939 classElement = constructor.enclosingElement;
1017 compiler.resolver.resolveMethodElement(constructor);
1018 } 940 }
1019 941
1020 List<Constant> jsNewArguments; 942 List<Constant> arguments =
1021 ClassElement classElement = constructor.enclosingElement; 943 evaluateArgumentsToConstructor(node.send, constructor);
1022 compiler.withCurrentElement(constructor, () { 944 ConstructorEvaluator evaluator =
1023 FunctionExpression functionNode = constructor.parseNode(compiler); 945 new ConstructorEvaluator(constructor, compiler);
1024 NodeList initializerList = functionNode.initializers; 946 evaluator.evaluateConstructorFieldValues(arguments);
1025 FunctionParameters parameters = constructor.computeParameters(compiler); 947 List<Constant>jsNewArguments = evaluator.buildJsNewArguments(classElement);
1026
1027 Map<Element, Constant> fieldValues = new Map<Element, Constant>();
1028 Map<Element, Constant> constructorDefinitions =
1029 new Map<Element, Constant>();
1030
1031 assignArgumentsToParameters(parameters, constructorDefinitions,
1032 fieldValues);
1033 CompileTimeConstantEvaluator initializerEvaluator =
1034 new CompileTimeConstantEvaluator.insideConstructor(
1035 constantHandler, constructorElements, compiler,
1036 constructorDefinitions);
1037 if (initializerList !== null) {
1038 Link<Node> initializers = functionNode.initializers.nodes;
1039 compileInitializers(initializers,
1040 initializerEvaluator,
1041 constructorElements,
1042 constructorDefinitions,
1043 fieldValues);
1044 }
1045 jsNewArguments = buildJsNewArguments(classElement, fieldValues);
1046 });
1047
1048 948
1049 compiler.registerInstantiatedClass(classElement); 949 compiler.registerInstantiatedClass(classElement);
1050 // TODO(floitsch): take generic types into account. 950 // TODO(floitsch): take generic types into account.
1051 Type type = classElement.computeType(compiler); 951 Type type = classElement.computeType(compiler);
1052 Constant constant = new ConstructedConstant(type, jsNewArguments); 952 Constant constant = new ConstructedConstant(type, jsNewArguments);
1053 constantHandler.registerCompileTimeConstant(constant); 953 compiler.constantHandler.registerCompileTimeConstant(constant);
1054 return constant; 954 return constant;
1055 } 955 }
1056 956
1057 Constant visitParenthesizedExpression(ParenthesizedExpression node) { 957 Constant visitParenthesizedExpression(ParenthesizedExpression node) {
1058 return node.expression.accept(this); 958 return node.expression.accept(this);
1059 } 959 }
1060 960
1061 error(Node node) { 961 error(Node node) {
1062 // TODO(floitsch): get the list of constants that are currently compiled 962 // TODO(floitsch): get the list of constants that are currently compiled
1063 // and present some kind of stack-trace. 963 // and present some kind of stack-trace.
1064 MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT; 964 MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT;
1065 compiler.reportError(node, new CompileTimeConstantError(kind, const [])); 965 compiler.reportError(node, new CompileTimeConstantError(kind, const []));
1066 } 966 }
1067 } 967 }
968
969 class ConstructorEvaluator extends CompileTimeConstantEvaluator {
970 FunctionElement constructor;
971 final Map<Element, Constant> definitions;
972 final Map<Element, Constant> fieldValues;
973
974 ConstructorEvaluator(FunctionElement constructor, Compiler compiler)
975 : this.constructor = constructor,
976 this.definitions = new Map<Element, Constant>(),
977 this.fieldValues = new Map<Element, Constant>(),
978 super(compiler.resolver.resolveMethodElement(constructor),
979 compiler);
980
981 Constant visitSend(Send send) {
982 Element element = elements[send];
983 if (Elements.isLocal(element)) {
984 Constant constant = definitions[element];
985 if (constant === null) {
986 compiler.internalError("Local variable without value", node: send);
987 }
988 return constant;
989 }
990 return super.visitSend(send);
991 }
992
993 /**
994 * Given the arguments (a list of constants) assigns them to the parameters,
995 * updating the definitions map. If the constructor has field-initializer
996 * parameters (like [:this.x:]), also updates the [fieldValues] map.
997 */
998 void assignArgumentsToParameters(List<Constant> arguments) {
999 // Assign arguments to parameters.
1000 FunctionParameters parameters = constructor.computeParameters(compiler);
1001 int index = 0;
1002 parameters.forEachParameter((Element parameter) {
1003 Constant argument = arguments[index++];
1004 definitions[parameter] = argument;
1005 if (parameter.kind == ElementKind.FIELD_PARAMETER) {
1006 FieldParameterElement fieldParameterElement = parameter;
1007 fieldValues[fieldParameterElement.fieldElement] = argument;
1008 }
1009 });
1010 }
1011
1012 void evaluateSuperOrRedirectSend(FunctionElement targetConstructor,
1013 List<Constant> targetArguments) {
1014 ConstructorEvaluator evaluator =
1015 new ConstructorEvaluator(targetConstructor, compiler);
1016 evaluator.evaluateConstructorFieldValues(targetArguments);
1017 // Copy over the fieldValues from the super/redirect-constructor.
1018 evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value);
1019 }
1020
1021 /**
1022 * Runs through the initializers of the given [constructor] and updates
1023 * the [fieldValues] map.
1024 */
1025 void evaluateConstructorInitializers() {
1026 FunctionExpression functionNode = constructor.parseNode(compiler);
1027 NodeList initializerList = functionNode.initializers;
1028
1029 bool foundSuperOrRedirect = false;
1030
1031 if (initializerList !== null) {
1032 for (Link<Node> link = initializerList.nodes;
1033 !link.isEmpty();
1034 link = link.tail) {
1035 assert(link.head is Send);
1036 if (link.head is !SendSet) {
1037 // A super initializer or constructor redirection.
1038 Send call = link.head;
1039 FunctionElement targetConstructor = elements[call];
1040 List<Constant> targetArguments =
1041 evaluateArgumentsToConstructor(call, targetConstructor);
1042 evaluateSuperOrRedirectSend(targetConstructor, targetArguments);
1043 foundSuperOrRedirect = true;
1044 } else {
1045 // A field initializer.
1046 SendSet init = link.head;
1047 Link<Node> initArguments = init.arguments;
1048 assert(!initArguments.isEmpty() && initArguments.tail.isEmpty());
1049 Constant fieldValue = evaluate(initArguments.head);
1050 fieldValues[elements[init]] = fieldValue;
1051 }
1052 }
1053 }
1054
1055 if (!foundSuperOrRedirect) {
1056 // No super initializer found. Try to find the default constructor if
1057 // the class is not Object.
1058 ClassElement enclosingClass = constructor.enclosingElement;
1059 ClassElement superClass = enclosingClass.superclass;
1060 if (enclosingClass != compiler.objectClass) {
1061 assert(superClass !== null);
1062 assert(superClass.isResolved);
1063 FunctionElement targetConstructor =
1064 superClass.lookupConstructor(superClass.name);
1065 if (targetConstructor === null) {
1066 compiler.internalError("no default constructor available");
1067 }
1068 evaluateSuperOrRedirectSend(targetConstructor, const <Constant>[]);
1069 }
1070 }
1071 }
1072
1073 /**
1074 * Simulates the execution of the [constructor] with the given
1075 * [arguments] to obtain the field values that need to be passed to the
1076 * native JavaScript constructor.
1077 */
1078 void evaluateConstructorFieldValues(List<Constant> arguments) {
1079 compiler.withCurrentElement(constructor, () {
1080 assignArgumentsToParameters(arguments);
1081 evaluateConstructorInitializers();
1082 });
1083 }
1084
1085 List<Constant> buildJsNewArguments(ClassElement classElement) {
1086 List<Constant> jsNewArguments = <Constant>[];
1087 // TODO(floitsch): share this code with the emitter, so that we don't
1088 // need to care about the order of fields here.
1089 while (classElement != compiler.objectClass) {
1090 for (Element member in classElement.members) {
1091 if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
1092 Constant fieldValue = fieldValues[member];
1093 if (fieldValue === null) {
1094 // Use the default value.
1095 fieldValue = compiler.compileVariable(member);
1096 }
1097 jsNewArguments.add(fieldValue);
1098 }
1099 }
1100 classElement = classElement.superclass;
1101 }
1102 return jsNewArguments;
1103 }
1104 }
OLDNEW
« no previous file with comments | « no previous file | frog/leg/compiler.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698