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

Side by Side Diff: frog/leg/tree/nodes.dart

Issue 9642001: Make string juxtaposition combine properly with string interpolations. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Refactor juxtaposition handling. 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 | Annotate | Revision Log
« no previous file with comments | « frog/leg/tree/dartstring.dart ('k') | frog/leg/tree/tree.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) 2011, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2011, 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 interface Visitor<R> { 5 interface Visitor<R> {
6 R visitBlock(Block node); 6 R visitBlock(Block node);
7 R visitBreakStatement(BreakStatement node); 7 R visitBreakStatement(BreakStatement node);
8 R visitCatchBlock(CatchBlock node); 8 R visitCatchBlock(CatchBlock node);
9 R visitClassNode(ClassNode node); 9 R visitClassNode(ClassNode node);
10 R visitConditional(Conditional node); 10 R visitConditional(Conditional node);
11 R visitContinueStatement(ContinueStatement node); 11 R visitContinueStatement(ContinueStatement node);
12 R visitDoWhile(DoWhile node); 12 R visitDoWhile(DoWhile node);
13 R visitEmptyStatement(EmptyStatement node); 13 R visitEmptyStatement(EmptyStatement node);
14 R visitExpressionStatement(ExpressionStatement node); 14 R visitExpressionStatement(ExpressionStatement node);
15 R visitFor(For node); 15 R visitFor(For node);
16 R visitForInStatement(ForInStatement node); 16 R visitForInStatement(ForInStatement node);
17 R visitFunctionDeclaration(FunctionDeclaration node); 17 R visitFunctionDeclaration(FunctionDeclaration node);
18 R visitFunctionExpression(FunctionExpression node); 18 R visitFunctionExpression(FunctionExpression node);
19 R visitIdentifier(Identifier node); 19 R visitIdentifier(Identifier node);
20 R visitIf(If node); 20 R visitIf(If node);
21 R visitLabeledStatement(LabeledStatement node); 21 R visitLabeledStatement(LabeledStatement node);
22 R visitLiteralBool(LiteralBool node); 22 R visitLiteralBool(LiteralBool node);
23 R visitLiteralDouble(LiteralDouble node); 23 R visitLiteralDouble(LiteralDouble node);
24 R visitLiteralInt(LiteralInt node); 24 R visitLiteralInt(LiteralInt node);
25 R visitLiteralList(LiteralList node); 25 R visitLiteralList(LiteralList node);
26 R visitLiteralMap(LiteralMap node); 26 R visitLiteralMap(LiteralMap node);
27 R visitLiteralMapEntry(LiteralMapEntry node); 27 R visitLiteralMapEntry(LiteralMapEntry node);
28 R visitLiteralNull(LiteralNull node); 28 R visitLiteralNull(LiteralNull node);
29 R visitLiteralString(LiteralString node); 29 R visitLiteralString(LiteralString node);
30 R visitLiteralStringJuxtaposition(LiteralStringJuxtaposition node); 30 R visitStringJuxtaposition(StringJuxtaposition node);
31 R visitModifiers(Modifiers node); 31 R visitModifiers(Modifiers node);
32 R visitNamedArgument(NamedArgument node); 32 R visitNamedArgument(NamedArgument node);
33 R visitNewExpression(NewExpression node); 33 R visitNewExpression(NewExpression node);
34 R visitNodeList(NodeList node); 34 R visitNodeList(NodeList node);
35 R visitOperator(Operator node); 35 R visitOperator(Operator node);
36 R visitParenthesizedExpression(ParenthesizedExpression node); 36 R visitParenthesizedExpression(ParenthesizedExpression node);
37 R visitReturn(Return node); 37 R visitReturn(Return node);
38 R visitScriptTag(ScriptTag node); 38 R visitScriptTag(ScriptTag node);
39 R visitSend(Send node); 39 R visitSend(Send node);
40 R visitSendSet(SendSet node); 40 R visitSendSet(SendSet node);
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 NodeList asNodeList() => null; 129 NodeList asNodeList() => null;
130 Operator asOperator() => null; 130 Operator asOperator() => null;
131 ParenthesizedExpression asParenthesizedExpression() => null; 131 ParenthesizedExpression asParenthesizedExpression() => null;
132 Return asReturn() => null; 132 Return asReturn() => null;
133 ScriptTag asScriptTag() => null; 133 ScriptTag asScriptTag() => null;
134 Send asSend() => null; 134 Send asSend() => null;
135 SendSet asSendSet() => null; 135 SendSet asSendSet() => null;
136 Statement asStatement() => null; 136 Statement asStatement() => null;
137 StringInterpolation asStringInterpolation() => null; 137 StringInterpolation asStringInterpolation() => null;
138 StringInterpolationPart asStringInterpolationPart() => null; 138 StringInterpolationPart asStringInterpolationPart() => null;
139 StringJuxtaposition asStringJuxtaposition() => null;
139 SwitchCase asSwitchCase() => null; 140 SwitchCase asSwitchCase() => null;
140 SwitchStatement asSwitchStatement() => null; 141 SwitchStatement asSwitchStatement() => null;
141 Throw asThrow() => null; 142 Throw asThrow() => null;
142 TryStatement asTryStatement() => null; 143 TryStatement asTryStatement() => null;
143 TypeAnnotation asTypeAnnotation() => null; 144 TypeAnnotation asTypeAnnotation() => null;
144 Typedef asTypedef() => null; 145 Typedef asTypedef() => null;
145 VariableDefinitions asVariableDefinitions() => null; 146 VariableDefinitions asVariableDefinitions() => null;
146 While asWhile() => null; 147 While asWhile() => null;
147 148
148 bool isValidBreakTarget() => false; 149 bool isValidBreakTarget() => false;
(...skipping 572 matching lines...) Expand 10 before | Expand all | Expand 10 after
721 722
722 int get leftQuoteLength() => (raw ? 1 : 0) + leftQuoteCharCount; 723 int get leftQuoteLength() => (raw ? 1 : 0) + leftQuoteCharCount;
723 int get rightQuoteLength() => (leftQuoteCharCount > 2) ? 3 : 1; 724 int get rightQuoteLength() => (leftQuoteCharCount > 2) ? 3 : 1;
724 static StringQuoting getQuoting(int quote, bool raw, int quoteLength) { 725 static StringQuoting getQuoting(int quote, bool raw, int quoteLength) {
725 int index = quoteLength - 1; 726 int index = quoteLength - 1;
726 if (quoteLength > 2) index -= 1; 727 if (quoteLength > 2) index -= 1;
727 return mapping[(raw ? 1 : 0) + index * 2 + (quote === $SQ ? 8 : 0)]; 728 return mapping[(raw ? 1 : 0) + index * 2 + (quote === $SQ ? 8 : 0)];
728 } 729 }
729 } 730 }
730 731
731 class DartString implements Iterable<int> {
732 // This is a convenience constructor. If you need a const literal DartString,
733 // use [const LiteralDartString(string)] directly.
734 factory DartString.literal(String string) => new LiteralDartString(string);
735 factory DartString.rawString(SourceString source, int length) =>
736 new RawSourceDartString(source, length);
737 factory DartString.escapedString(SourceString source, int length) =>
738 new EscapedSourceDartString(source, length);
739 const DartString();
740 abstract int get length();
741 bool isEmpty() => length == 0;
742 abstract Iterator<int> iterator();
743 abstract String slowToString();
744
745 bool operator ==(var other) {
746 if (other is !DartString) return false;
747 DartString otherString = other;
748 if (length != otherString.length) return false;
749 Iterator it1 = iterator();
750 Iterator it2 = otherString.iterator();
751 while (it1.hasNext()) {
752 if (it1.next() != it2.next()) return false;
753 }
754 return true;
755 }
756 String toString() => "DartString#${length}:${slowToString()}";
757 abstract SourceString get source();
758 }
759
760 class LiteralDartString extends DartString {
761 final String string;
762 const LiteralDartString(this.string);
763 int get length() => string.length;
764 Iterator<int> iterator() => new StringCodeIterator(string);
765 String slowToString() => string;
766 SourceString get source() => new StringWrapper(string);
767 }
768
769 class SourceBasedDartString extends DartString {
770 String toStringCache = null;
771 final SourceString source;
772 final int length;
773 SourceBasedDartString(this.source, this.length);
774 abstract Iterator<int> iterator();
775 }
776
777 class RawSourceDartString extends SourceBasedDartString {
778 RawSourceDartString(source, length) : super(source, length);
779 Iterator<int> iterator() => source.iterator();
780 String slowToString() {
781 if (toStringCache !== null) return toStringCache;
782 toStringCache = source.slowToString();
783 return toStringCache;
784 }
785 }
786
787 class EscapedSourceDartString extends SourceBasedDartString {
788 EscapedSourceDartString(source, length) : super(source, length);
789 Iterator<int> iterator() {
790 if (toStringCache !== null) return new StringCodeIterator(toStringCache);
791 return new StringEscapeIterator(source);
792 }
793 String slowToString() {
794 if (toStringCache !== null) return toStringCache;
795 StringBuffer buffer = new StringBuffer();
796 StringEscapeIterator it = new StringEscapeIterator(source);
797 while (it.hasNext()) {
798 buffer.addCharCode(it.next());
799 }
800 toStringCache = buffer.toString();
801 return toStringCache;
802 }
803 }
804
805 class ConsDartString extends DartString {
806 final DartString left;
807 final DartString right;
808 final int length;
809 int hashCache = null;
810 String toStringCache;
811 ConsDartString(DartString left, DartString right)
812 : this.left = left,
813 this.right = right,
814 length = left.length + right.length;
815
816 Iterator<int> iterator() => new ConsDartStringIterator(this);
817
818 String slowToString() {
819 if (toStringCache !== null) return toStringCache;
820 toStringCache = left.slowToString().concat(right.slowToString());
821 return toStringCache;
822 }
823 SourceString get source() => new StringWrapper(slowToString());
824 }
825
826 class ConsDartStringIterator implements Iterator<int> {
827 Iterator<int> current;
828 DartString right;
829 bool hasNextLookAhead;
830 ConsDartStringIterator(ConsDartString cons)
831 : current = cons.left.iterator(),
832 right = cons.right {
833 hasNextLookAhead = current.hasNext();
834 if (!hasNextLookAhead) {
835 nextPart();
836 }
837 }
838 bool hasNext() {
839 return hasNextLookAhead;
840 }
841 int next() {
842 assert(hasNextLookAhead);
843 int result = current.next();
844 hasNextLookAhead = current.hasNext();
845 if (!hasNextLookAhead) {
846 nextPart();
847 }
848 return result;
849 }
850 void nextPart() {
851 if (right !== null) {
852 current = right.iterator();
853 right = null;
854 hasNextLookAhead = current.hasNext();
855 }
856 }
857 }
858
859 /** 732 /**
860 *Iterator that returns the actual string contents of a string with escapes. 733 * Marker interface uniting the classes that can be the result of
861 */ 734 * a string literal.
862 class StringEscapeIterator implements Iterator<int>{ 735 */
863 final Iterator<int> source; 736 interface StringNode /* extends Node - when supported */ {
ahe 2012/03/19 08:13:08 LiteralString currently extends Literal. That's no
Lasse Reichstein Nielsen 2012/03/19 09:57:48 I'll try. I'm not sure StringNode makes a good sub
864 StringEscapeIterator(SourceString source) : this.source = source.iterator(); 737 final DartString dartString;
865 bool hasNext() => source.hasNext(); 738 final bool isInterpolation;
866 int next() { 739 // Use until StringNode can extend Node.
867 int code = source.next(); 740 Node asNode();
868 if (code !== $BACKSLASH) {
869 return code;
870 }
871 code = source.next();
872 if (code === $n) return $LF;
873 if (code === $r) return $CR;
874 if (code === $t) return $TAB;
875 if (code === $b) return $BS;
876 if (code === $f) return $FF;
877 if (code === $v) return $VTAB;
878 if (code === $x) {
879 int value = hexDigitValue(source.next());
880 value = value * 16 + hexDigitValue(source.next());
881 return value;
882 }
883 if (code === $u) {
884 int value = 0;
885 code = source.next();
886 if (code === $OPEN_CURLY_BRACKET) {
887 for (code = source.next();
888 code != $CLOSE_CURLY_BRACKET;
889 code = source.next()) {
890 value = value * 16 + hexDigitValue(code);
891 }
892 return value;
893 }
894 // Four digit hex value.
895 value = hexDigitValue(code);
896 for (int i = 0; i < 3; i++) {
897 code = source.next();
898 value = value * 16 + hexDigitValue(code);
899 }
900 return value;
901 }
902 return code;
903 }
904 } 741 }
905 742
906 743 class LiteralString extends Literal<SourceString> implements StringNode {
907 class LiteralString extends Literal<SourceString> { 744 /** Non-null on validated string literals. */
908 /** Set on validated string literals. */ 745 final DartString dartString;
909 final DartString dartString = null;
910 746
911 LiteralString(Token token, this.dartString) : super(token, null); 747 LiteralString(Token token, this.dartString) : super(token, null);
912 748
913 LiteralString asLiteralString() => this; 749 LiteralString asLiteralString() => this;
914 750
751 Node asNode() => this;
752 bool get isInterpolation() => false;
915 bool isValidated() => dartString !== null; 753 bool isValidated() => dartString !== null;
916 754
917 SourceString get value() => token.value; 755 SourceString get value() => token.value;
918 756
919 accept(Visitor visitor) => visitor.visitLiteralString(this); 757 accept(Visitor visitor) => visitor.visitLiteralString(this);
920 } 758 }
921 759
922 class LiteralNull extends Literal<SourceString> { 760 class LiteralNull extends Literal<SourceString> {
923 LiteralNull(Token token) : super(token, null); 761 LiteralNull(Token token) : super(token, null);
924 762
(...skipping 290 matching lines...) Expand 10 before | Expand all | Expand 10 after
1215 visitChildren(Visitor visitor) => nodes.accept(visitor); 1053 visitChildren(Visitor visitor) => nodes.accept(visitor);
1216 1054
1217 bool isStatic() => (flags & FLAG_STATIC) != 0; 1055 bool isStatic() => (flags & FLAG_STATIC) != 0;
1218 bool isAbstract() => (flags & FLAG_ABSTRACT) != 0; 1056 bool isAbstract() => (flags & FLAG_ABSTRACT) != 0;
1219 bool isFinal() => (flags & FLAG_FINAL) != 0; 1057 bool isFinal() => (flags & FLAG_FINAL) != 0;
1220 bool isVar() => (flags & FLAG_VAR) != 0; 1058 bool isVar() => (flags & FLAG_VAR) != 0;
1221 bool isConst() => (flags & FLAG_CONST) != 0; 1059 bool isConst() => (flags & FLAG_CONST) != 0;
1222 bool isFactory() => (flags & FLAG_FACTORY) != 0; 1060 bool isFactory() => (flags & FLAG_FACTORY) != 0;
1223 } 1061 }
1224 1062
1225 class StringInterpolation extends Expression { 1063 class StringInterpolation extends Expression implements StringNode {
1226 final LiteralString string; 1064 final LiteralString string;
1227 final NodeList parts; 1065 final NodeList parts;
1228 1066
1229 StringInterpolation(this.string, this.parts); 1067 StringInterpolation(this.string, this.parts);
1230 1068
1231 StringInterpolation asStringInterpolation() => this; 1069 StringInterpolation asStringInterpolation() => this;
1232 1070
1071 DartString get dartString() => null;
1072 Node asNode() => this;
1073 bool get isInterpolation() => true;
1074
1233 accept(Visitor visitor) => visitor.visitStringInterpolation(this); 1075 accept(Visitor visitor) => visitor.visitStringInterpolation(this);
1234 1076
1235 visitChildren(Visitor visitor) { 1077 visitChildren(Visitor visitor) {
1236 string.accept(visitor); 1078 string.accept(visitor);
1237 parts.accept(visitor); 1079 parts.accept(visitor);
1238 } 1080 }
1239 1081
1240 Token getBeginToken() => string.getBeginToken(); 1082 Token getBeginToken() => string.getBeginToken();
1241 1083
1242 Token getEndToken() => parts.getEndToken(); 1084 Token getEndToken() => parts.getEndToken();
(...skipping 12 matching lines...) Expand all
1255 visitChildren(Visitor visitor) { 1097 visitChildren(Visitor visitor) {
1256 expression.accept(visitor); 1098 expression.accept(visitor);
1257 string.accept(visitor); 1099 string.accept(visitor);
1258 } 1100 }
1259 1101
1260 Token getBeginToken() => expression.getBeginToken(); 1102 Token getBeginToken() => expression.getBeginToken();
1261 1103
1262 Token getEndToken() => string.getEndToken(); 1104 Token getEndToken() => string.getEndToken();
1263 } 1105 }
1264 1106
1265 class LiteralStringJuxtaposition extends LiteralString { 1107 /**
1266 // List of either StringLiteral or StringInterpolation. 1108 * A class representing juxtaposed string literals.
1267 final Link<Expression> literals; 1109 * The string literals can be both plain literals and string interpolations.
1110 */
1111 class StringJuxtaposition extends Expression implements StringNode {
1112 final Expression first;
1113 final Expression second;
1114 // Caches the check for whether this juxtaposition contains a string
1115 // interpolation
1116 bool _isInterpolation = null;
1117 // Caches a Dart string representation of the entire juxtaposition's
1118 // content. Only juxtapositions that don't (transitively) contains
1119 // interpolations have a static representation.
1120 DartString _dartString = null;
1268 1121
1269 LiteralStringJuxtaposition(Link<Expression> literals) 1122 StringJuxtaposition(this.first, this.second);
1270 : this.literals = literals,
1271 super(literals.head.getBeginToken(), concatenateLiterals(literals));
1272 1123
1273 static DartString concatenateLiterals(Link<Expression> literals) { 1124 StringJuxtaposition asStringJuxtaposition() => this;
1274 assert(!literals.isEmpty()); 1125
1275 LiteralString literal = literals.head; 1126 bool get isInterpolation() {
1276 if (literals.tail.isEmpty()) { 1127 if (_isInterpolation === null) {
1277 return literal.dartString; 1128 _isInterpolation = (first.accept(const IsInterpolationVisitor()) ||
1129 second.accept(const IsInterpolationVisitor()));
1278 } 1130 }
1279 return new ConsDartString(literal.dartString, 1131 return _isInterpolation;
1280 concatenateLiterals(literals.tail));
1281 } 1132 }
1282 1133
1283 SourceString get value() => null; 1134 /**
1284 1135 * Retrieve a single DartString that represents this entire juxtaposition
1285 accept(Visitor visitor) => visitor.visitLiteralStringJuxtaposition(this); 1136 * of string literals.
1286 1137 * Should only be called if [isInterpolation] returns false.
1287 visitChildren(Visitor visitor) { 1138 */
1288 for (Expression literal in literals) { 1139 DartString get dartString() {
1289 literal.accept(visitor); 1140 if (_dartString === null) {
1141 DartString firstString = first.accept(const GetDartStringVisitor());
1142 DartString secondString = second.accept(const GetDartStringVisitor());
1143 if (firstString === null || secondString === null) {
1144 return null;
1145 }
1146 _dartString = new DartString.concat(firstString, secondString);
1290 } 1147 }
1148 return _dartString;
1291 } 1149 }
1292 1150
1293 Token getBeginToken() => literals.head.getBeginToken(); 1151 Node asNode() => this;
1294 1152
1295 Token getEndToken() { 1153 accept(Visitor visitor) => visitor.visitStringJuxtaposition(this);
1296 Link<Expression> current = literals; 1154
1297 Expression lastExpression = null; 1155 void visitChildren(Visitor visitor) {
1298 while (!current.isEmpty()) { 1156 first.accept(visitor);
1299 lastExpression = current.head; 1157 second.accept(visitor);
1300 current = current.tail;
1301 }
1302 return lastExpression.getEndToken();
1303 } 1158 }
1159
1160 Token getBeginToken() => first.getBeginToken();
1161
1162 Token getEndToken() => second.getEndToken();
1304 } 1163 }
1305 1164
1306 class EmptyStatement extends Statement { 1165 class EmptyStatement extends Statement {
1307 final Token semicolonToken; 1166 final Token semicolonToken;
1308 1167
1309 EmptyStatement(this.semicolonToken); 1168 EmptyStatement(this.semicolonToken);
1310 1169
1311 EmptyStatement asEmptyStatement() => this; 1170 EmptyStatement asEmptyStatement() => this;
1312 1171
1313 accept(Visitor visitor) => visitor.visitEmptyStatement(this); 1172 accept(Visitor visitor) => visitor.visitEmptyStatement(this);
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after
1563 1422
1564 Node getBody() { 1423 Node getBody() {
1565 if (statement is! LabeledStatement) return statement; 1424 if (statement is! LabeledStatement) return statement;
1566 LabeledStatement labeled = statement; 1425 LabeledStatement labeled = statement;
1567 return labeled.getBody(); 1426 return labeled.getBody();
1568 } 1427 }
1569 } 1428 }
1570 1429
1571 class ScriptTag extends Node { 1430 class ScriptTag extends Node {
1572 final Identifier tag; 1431 final Identifier tag;
1573 final LiteralString argument; 1432 final StringNode argument;
1574 final Identifier prefixIdentifier; 1433 final Identifier prefixIdentifier;
1575 final LiteralString prefix; 1434 final StringNode prefix;
1576 1435
1577 final Token beginToken; 1436 final Token beginToken;
1578 final Token endToken; 1437 final Token endToken;
1579 1438
1580 ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix, 1439 ScriptTag(this.tag, this.argument, this.prefixIdentifier, this.prefix,
1581 this.beginToken, this.endToken); 1440 this.beginToken, this.endToken);
1582 1441
1583 bool isImport() => tag.source == const SourceString("import"); 1442 bool isImport() => tag.source == const SourceString("import");
1584 bool isSource() => tag.source == const SourceString("source"); 1443 bool isSource() => tag.source == const SourceString("source");
1585 bool isLibrary() => tag.source == const SourceString("library"); 1444 bool isLibrary() => tag.source == const SourceString("library");
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
1708 static bool isConstructorRedirect(Send node) { 1567 static bool isConstructorRedirect(Send node) {
1709 return (node.receiver === null && 1568 return (node.receiver === null &&
1710 node.selector.asIdentifier() !== null && 1569 node.selector.asIdentifier() !== null &&
1711 node.selector.asIdentifier().isThis()) || 1570 node.selector.asIdentifier().isThis()) ||
1712 (node.receiver !== null && 1571 (node.receiver !== null &&
1713 node.receiver.asIdentifier() !== null && 1572 node.receiver.asIdentifier() !== null &&
1714 node.receiver.asIdentifier().isThis() && 1573 node.receiver.asIdentifier().isThis() &&
1715 node.selector.asIdentifier() !== null); 1574 node.selector.asIdentifier() !== null);
1716 } 1575 }
1717 } 1576 }
1577
1578 class GetDartStringVisitor extends AbstractVisitor<DartString> {
1579 const GetDartStringVisitor();
1580 DartString visitNode(Node node) => null;
1581 DartString visitStringJuxtaposition(StringJuxtaposition node)
1582 => node.dartString;
1583 DartString visitLiteralString(LiteralString node) => node.dartString;
1584 }
1585
1586 class IsInterpolationVisitor extends AbstractVisitor<bool> {
1587 const IsInterpolationVisitor();
1588 bool visitNode(Node node) => false;
1589 bool visitStringInterpolation(StringInterpolation node) => true;
1590 bool visitStringJuxtaposition(StringJuxtaposition node)
1591 => node.isInterpolation;
1592 }
OLDNEW
« no previous file with comments | « frog/leg/tree/dartstring.dart ('k') | frog/leg/tree/tree.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698