Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 } | |
| OLD | NEW |