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

Side by Side Diff: sdk/lib/_internal/compiler/implementation/js/nodes.dart

Issue 12276002: Add a small JS parser to ease the building of ASTs (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Streamline support for var Created 7 years, 10 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
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 part of js; 5 part of js;
6 6
7 abstract class NodeVisitor<T> { 7 abstract class NodeVisitor<T> {
8 T visitProgram(Program node); 8 T visitProgram(Program node);
9 9
10 T visitBlock(Block node); 10 T visitBlock(Block node);
(...skipping 911 matching lines...) Expand 10 before | Expand all | Expand 10 after
922 String pattern; 922 String pattern;
923 923
924 RegExpLiteral(this.pattern); 924 RegExpLiteral(this.pattern);
925 925
926 accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this); 926 accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this);
927 void visitChildren(NodeVisitor visitor) {} 927 void visitChildren(NodeVisitor visitor) {}
928 928
929 int get precedenceLevel => PRIMARY; 929 int get precedenceLevel => PRIMARY;
930 } 930 }
931 931
932 class JsBuilder { 932 class JsBuilder {
ahe 2013/02/19 10:05:25 How about moving JsBuilder and MiniJsParser to as
erikcorry 2013/02/19 10:38:29 I'd like to move them to a different file (a separ
ahe 2013/02/19 10:47:34 Good point.
933 const JsBuilder(); 933 const JsBuilder();
934 934
935 VariableUse operator [](String name) => new VariableUse(name); 935 Expression operator [](String source) {
936 return new MiniJsParser(source).expression();
937 }
936 938
937 // TODO(ahe): Remove this method. 939 // TODO(ahe): Remove this method.
938 Binary equals(Expression left, Expression right) { 940 Binary equals(Expression left, Expression right) {
939 return new Binary('==', left, right); 941 return new Binary('==', left, right);
940 } 942 }
941 943
942 // TODO(ahe): Remove this method. 944 // TODO(ahe): Remove this method.
943 Binary strictEquals(Expression left, Expression right) { 945 Binary strictEquals(Expression left, Expression right) {
944 return new Binary('===', left, right); 946 return new Binary('===', left, right);
945 } 947 }
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
1043 Try try_(body, {catchPart, finallyPart}) { 1045 Try try_(body, {catchPart, finallyPart}) {
1044 if (catchPart != null) catchPart = toStatement(catchPart); 1046 if (catchPart != null) catchPart = toStatement(catchPart);
1045 if (finallyPart != null) finallyPart = toStatement(finallyPart); 1047 if (finallyPart != null) finallyPart = toStatement(finallyPart);
1046 return new Try(toStatement(body), catchPart, finallyPart); 1048 return new Try(toStatement(body), catchPart, finallyPart);
1047 } 1049 }
1048 } 1050 }
1049 1051
1050 const JsBuilder js = const JsBuilder(); 1052 const JsBuilder js = const JsBuilder();
1051 1053
1052 LiteralString string(String value) => js.string(value); 1054 LiteralString string(String value) => js.string(value);
1055
1056 class MiniJsParserError {
1057 MiniJsParserError(this.parser, this.message) { }
1058
1059 MiniJsParser parser;
1060 String message;
1061
1062 String toString() {
1063 var codes =
1064 new List.fixedLength(parser.lastPosition, fill: charCodes.$SPACE);
1065 var spaces = new String.fromCharCodes(codes);
1066 return "Error in MiniJsParser:\n${parser.src}\n$spaces^\n$spaces$message\n";
1067 }
1068 }
1069
1070 /// Mini JavaScript parser for tiny snippets of code that we want to make into
1071 /// AST nodes. Handles:
1072 /// * identifiers.
1073 /// * dot access.
1074 /// * method calls.
1075 /// * [] access.
1076 /// * string, boolean, null and numeric literals (no backslash escapes, no hex).
sra1 2013/02/20 01:50:24 see comment below about backslashes. I'd revise to
1077 /// * most operators.
1078 /// * brackets.
1079 /// * var declarations.
1080 /// Notable things it can't do yet include:
1081 /// * operator precedence.
1082 /// * array and object literals.
1083 /// * new, throw, return, typeof.
1084 /// * statements, including any flow control (if, while, for, etc.)
1085 /// It's a fairly standard recursive descent parser.
1086 class MiniJsParser {
ahe 2013/02/19 10:05:25 I would call this "JsExpressionParser" or somethin
sra1 2013/02/20 01:50:24 I think the urge to extend to statements will be i
1087 MiniJsParser(this.src)
1088 : lastCategory = NONE, lastToken = null, lastPosition = 0, position = 0 {
ahe 2013/02/19 10:05:25 One line per initializer.
1089 getSymbol();
1090 }
1091
1092 int lastCategory;
1093 String lastToken;
1094 int lastPosition;
1095 int position;
1096 String src;
1097
1098 static const NONE = -1;
1099 static const ALPHA = 0;
1100 static const NUMERIC = 1;
1101 static const STRING = 2;
1102 static const SYMBOL = 3;
1103 static const RELATION = 4;
1104 static const DOT = 5;
1105 static const LPAREN = 6;
1106 static const RPAREN = 7;
1107 static const LSQUARE = 8;
1108 static const RSQUARE = 9;
1109 static const COMMA = 10;
1110 static const OTHER = 11;
1111
1112 // Make sure that ]] is two symbols.
1113 bool singleCharCategory(int category) => category >= DOT;
1114
1115 static String categoryToString(int cat) {
1116 switch (cat) {
1117 case NONE: return "NONE";
1118 case ALPHA: return "ALPHA";
1119 case NUMERIC: return "NUMERIC";
1120 case SYMBOL: return "SYMBOL";
1121 case RELATION: return "RELATION";
1122 case DOT: return "DOT";
1123 case LPAREN: return "LPAREN";
1124 case RPAREN: return "RPAREN";
1125 case LSQUARE: return "LSQUARE";
1126 case RSQUARE: return "RSQUARE";
1127 case STRING: return "STRING";
1128 case COMMA: return "COMMA";
1129 case OTHER: return "OTHER";
1130 }
1131 return "Unknown: $cat";
1132 }
1133
1134 static const CATEGORIES = const <int>[
1135 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
1136 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 8-15
1137 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 16-23
1138 OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 24-31
1139 OTHER, RELATION, OTHER, OTHER, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&ยด
1140 LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./
1141 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234
1142 NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789
1143 OTHER, OTHER, RELATION, RELATION, RELATION, OTHER, OTHER, // :;<=>?@
1144 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH
1145 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP
1146 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX
1147 ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_'
1148 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh
1149 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop
1150 ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx
1151 ALPHA, ALPHA, OTHER, SYMBOL, OTHER, SYMBOL]; // yz{|}~
1152
1153 static int category(int code) {
1154 if (code >= CATEGORIES.length) return OTHER;
1155 return CATEGORIES[code];
1156 }
1157
1158 void getSymbol() {
1159 while (position < src.length &&
1160 src.codeUnitAt(position) == charCodes.$SPACE) {
1161 position++;
1162 }
1163 if (position == src.length) {
1164 lastCategory = NONE;
1165 lastToken = null;
1166 lastPosition = position;
1167 return;
1168 }
1169 int code = src.codeUnitAt(position);
1170 lastPosition = position;
1171 if (code == charCodes.$SQ || code == charCodes.$DQ) {
1172 do {
1173 position++;
1174 } while (src.codeUnitAt(position) != code);
ahe 2013/02/19 10:05:25 This doesn't handle \ in strings.
erikcorry 2013/02/19 10:38:29 That's right, that's noted in the description of t
ahe 2013/02/19 10:47:34 You could throw an exception if you see a backslas
sra1 2013/02/20 01:50:24 Noooo! It works pretty well (e.g. is actually used
1175 lastCategory = STRING;
1176 position++;
1177 lastToken = src.substring(lastPosition, position);
1178 } else {
1179 int cat = category(src.codeUnitAt(position));
1180 int newCat;
1181 do {
1182 position++;
1183 if (position == src.length) break;
1184 newCat = category(src.codeUnitAt(position));
1185 } while (!singleCharCategory(cat) &&
1186 (cat == newCat ||
1187 (cat == ALPHA && newCat == NUMERIC) || // eg. level42.
1188 (cat == NUMERIC && newCat == DOT) || // eg. 3.1415
ahe 2013/02/19 10:05:25 I think this will allow things like: 1.1.1.
erikcorry 2013/02/19 10:38:29 Yes, it will. I think my preferred solution is "d
ahe 2013/02/19 10:47:34 You could add code like this after line 1191: if
sra1 2013/02/20 01:50:24 We should avoid putting checks in the printer that
erikcorry 2013/02/20 14:39:09 The printer already performs this check (at least
1189 (cat == SYMBOL && newCat == RELATION))); // eg. +=.
1190 lastCategory = cat;
1191 lastToken = src.substring(lastPosition, position);
1192 }
1193 }
1194
1195 void expectCategory(int cat) {
1196 if (cat != lastCategory) {
1197 throw new MiniJsParserError(this, "Expected ${categoryToString(cat)}");
1198 }
1199 getSymbol();
1200 }
1201
1202 bool acceptCategory(int cat) {
1203 if (cat == lastCategory) {
1204 getSymbol();
1205 return true;
1206 }
1207 return false;
1208 }
1209
1210 bool acceptString(String string) {
1211 if (lastToken == string) {
1212 getSymbol();
1213 return true;
1214 }
1215 return false;
1216 }
1217
1218 Expression parsePrimary() {
1219 String last = lastToken;
1220 if (acceptCategory(ALPHA)) {
1221 if (last == "true") {
1222 return new LiteralBool(true);
1223 } else if (last == "false") {
1224 return new LiteralBool(false);
1225 } else if (last == "null") {
1226 return new LiteralNull();
1227 } else {
1228 return new VariableUse(last);
1229 }
1230 } else if (acceptCategory(LPAREN)) {
1231 Expression expression = parseExpression();
1232 expectCategory(RPAREN);
1233 return expression;
1234 } else if (acceptCategory(STRING)) {
1235 return new LiteralString(last);
1236 } else if (acceptCategory(NUMERIC)) {
1237 return new LiteralNumber(last);
1238 } else {
1239 throw new MiniJsParserError(this, "Expected primary expression");
1240 }
1241 }
1242
1243 Expression parseMember() {
1244 Expression receiver = parsePrimary();
1245 while (true) {
1246 if (acceptCategory(DOT)) {
1247 String identifier = lastToken;
1248 expectCategory(ALPHA);
1249 receiver = new PropertyAccess.field(receiver, identifier);
1250 } else if (acceptCategory(LSQUARE)) {
1251 Expression inBraces = parseExpression();
1252 expectCategory(RSQUARE);
1253 receiver = new PropertyAccess(receiver, inBraces);
1254 } else {
1255 return receiver;
1256 }
1257 }
1258 }
1259
1260 Expression parseCall() {
1261 Expression receiver = parseMember();
1262 if (acceptCategory(LPAREN)) {
1263 final arguments = <Expression>[];
1264 if (!acceptCategory(RPAREN)) {
1265 while (true) {
1266 Expression argument = parseExpression();
1267 arguments.add(argument);
1268 if (acceptCategory(RPAREN)) break;
1269 expectCategory(COMMA);
1270 }
1271 }
1272 return new Call(receiver, arguments);
1273 } else {
1274 return receiver;
1275 }
1276 }
1277
1278 Expression parseBinary() {
1279 // Since we don't handle precedence we don't allow two different symbols
1280 // without brackets.
ahe 2013/02/19 10:05:25 Should be "without parentheses". "brackets" is amb
1281 Expression lhs = parseCall();
1282 String firstSymbol = lastToken;
1283 while (true) {
1284 String symbol = lastToken;
1285 if (!acceptCategory(SYMBOL)) return lhs;
1286 if (symbol != firstSymbol) {
1287 throw new MiniJsParserError(
1288 this, "Mixed $firstSymbol and $symbol operators without ()");
1289 }
1290 if (symbol == '++' || symbol == '--') {
1291 lhs = new Postfix(symbol, lhs);
1292 } else {
1293 Expression rhs = parseCall();
1294 if (symbol.endsWith("=")) {
1295 // +=, -=, *= etc.
1296 lhs = new Assignment.compound(lhs,
1297 symbol.substring(0, symbol.length - 1),
1298 rhs);
1299 } else {
1300 lhs = new Binary(symbol, lhs, rhs);
1301 }
1302 }
1303 }
1304 }
1305
1306 Expression parseRelation() {
1307 if (acceptString("!")) {
1308 Expression expression = parseBinary();
1309 return new Prefix("!", expression);
sra1 2013/02/20 01:50:24 Looks like this silently gets "!a == true" wron
erikcorry 2013/02/20 14:39:09 Done.
1310 }
1311 Expression lhs = parseBinary();
1312 String relation = lastToken;
1313 if (!acceptCategory(RELATION)) return lhs;
1314 Expression rhs = parseBinary();
1315 if (relation == "=") {
1316 return new Assignment(lhs, rhs);
1317 } else {
1318 // Regular binary operation.
1319 return new Binary(relation, lhs, rhs);
1320 }
1321 }
1322
1323 Expression parseExpression() => parseRelation();
1324
1325 Expression parseVarDeclarationOrExpression() {
1326 if (acceptString("var")) {
1327 var initialization = [];
1328 do {
1329 String variable = lastToken;
1330 expectCategory(ALPHA);
1331 Expression initializer = null;
1332 if (acceptString("=")) {
1333 initializer = parseExpression();
1334 }
1335 var declaration = new VariableDeclaration(variable);
1336 initialization.add(
1337 new VariableInitialization(declaration, initializer));
1338 } while (acceptCategory(COMMA));
1339 return new VariableDeclarationList(initialization);
1340 } else {
1341 return parseExpression();
1342 }
1343 }
1344
1345 Expression expression() {
1346 Expression expression = parseVarDeclarationOrExpression();
1347 if (lastCategory != NONE && position != src.length) {
1348 throw new MiniJsParserError(
1349 this, "Unparsed junk: ${categoryToString(lastCategory)}");
1350 }
1351 return expression;
1352 }
1353 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698