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 |