Thu, 11 Jul 2013 18:33:33 +0200
8013925: Remove symbol fields from nodes that don't need them
Reviewed-by: jlaskey, lagergren
1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package jdk.nashorn.internal.ir.debug;
28 import java.util.Arrays;
29 import java.util.List;
30 import jdk.nashorn.internal.codegen.CompilerConstants;
31 import jdk.nashorn.internal.ir.AccessNode;
32 import jdk.nashorn.internal.ir.BinaryNode;
33 import jdk.nashorn.internal.ir.Block;
34 import jdk.nashorn.internal.ir.BlockStatement;
35 import jdk.nashorn.internal.ir.BreakNode;
36 import jdk.nashorn.internal.ir.CallNode;
37 import jdk.nashorn.internal.ir.CaseNode;
38 import jdk.nashorn.internal.ir.CatchNode;
39 import jdk.nashorn.internal.ir.ContinueNode;
40 import jdk.nashorn.internal.ir.EmptyNode;
41 import jdk.nashorn.internal.ir.ExpressionStatement;
42 import jdk.nashorn.internal.ir.ForNode;
43 import jdk.nashorn.internal.ir.FunctionNode;
44 import jdk.nashorn.internal.ir.IdentNode;
45 import jdk.nashorn.internal.ir.IfNode;
46 import jdk.nashorn.internal.ir.IndexNode;
47 import jdk.nashorn.internal.ir.LabelNode;
48 import jdk.nashorn.internal.ir.LexicalContext;
49 import jdk.nashorn.internal.ir.LiteralNode;
50 import jdk.nashorn.internal.ir.Node;
51 import jdk.nashorn.internal.ir.ObjectNode;
52 import jdk.nashorn.internal.ir.PropertyNode;
53 import jdk.nashorn.internal.ir.ReturnNode;
54 import jdk.nashorn.internal.ir.RuntimeNode;
55 import jdk.nashorn.internal.ir.SplitNode;
56 import jdk.nashorn.internal.ir.Statement;
57 import jdk.nashorn.internal.ir.SwitchNode;
58 import jdk.nashorn.internal.ir.TernaryNode;
59 import jdk.nashorn.internal.ir.ThrowNode;
60 import jdk.nashorn.internal.ir.TryNode;
61 import jdk.nashorn.internal.ir.UnaryNode;
62 import jdk.nashorn.internal.ir.VarNode;
63 import jdk.nashorn.internal.ir.WhileNode;
64 import jdk.nashorn.internal.ir.WithNode;
65 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
66 import jdk.nashorn.internal.parser.JSONParser;
67 import jdk.nashorn.internal.parser.Lexer.RegexToken;
68 import jdk.nashorn.internal.parser.Parser;
69 import jdk.nashorn.internal.parser.TokenType;
70 import jdk.nashorn.internal.runtime.Context;
71 import jdk.nashorn.internal.runtime.ParserException;
72 import jdk.nashorn.internal.runtime.ScriptEnvironment;
73 import jdk.nashorn.internal.runtime.Source;
75 /**
76 * This IR writer produces a JSON string that represents AST as a JSON string.
77 */
78 public final class JSONWriter extends NodeVisitor<LexicalContext> {
80 /**
81 * Returns AST as JSON compatible string.
82 *
83 * @param env script environment to use
84 * @param code code to be parsed
85 * @param name name of the code source (used for location)
86 * @param includeLoc tells whether to include location information for nodes or not
87 * @return JSON string representation of AST of the supplied code
88 */
89 public static String parse(final ScriptEnvironment env, final String code, final String name, final boolean includeLoc) {
90 final Parser parser = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
91 final JSONWriter jsonWriter = new JSONWriter(includeLoc);
92 try {
93 final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
94 functionNode.accept(jsonWriter);
95 return jsonWriter.getString();
96 } catch (final ParserException e) {
97 e.throwAsEcmaException();
98 return null;
99 }
100 }
102 @Override
103 protected boolean enterDefault(final Node node) {
104 objectStart();
105 location(node);
107 return true;
108 }
110 private boolean leave() {
111 objectEnd();
112 return false;
113 }
115 @Override
116 protected Node leaveDefault(final Node node) {
117 objectEnd();
118 return null;
119 }
121 @Override
122 public boolean enterAccessNode(final AccessNode accessNode) {
123 enterDefault(accessNode);
125 type("MemberExpression");
126 comma();
128 property("object");
129 accessNode.getBase().accept(this);
130 comma();
132 property("property");
133 accessNode.getProperty().accept(this);
134 comma();
136 property("computed", false);
138 return leave();
139 }
141 @Override
142 public boolean enterBlock(final Block block) {
143 enterDefault(block);
145 type("BlockStatement");
146 comma();
148 array("body", block.getStatements());
150 return leave();
151 }
153 private static boolean isLogical(final TokenType tt) {
154 switch (tt) {
155 case AND:
156 case OR:
157 return true;
158 default:
159 return false;
160 }
161 }
163 @Override
164 public boolean enterBinaryNode(final BinaryNode binaryNode) {
165 enterDefault(binaryNode);
167 final String name;
168 if (binaryNode.isAssignment()) {
169 name = "AssignmentExpression";
170 } else if (isLogical(binaryNode.tokenType())) {
171 name = "LogicalExpression";
172 } else {
173 name = "BinaryExpression";
174 }
176 type(name);
177 comma();
179 property("operator", binaryNode.tokenType().getName());
180 comma();
182 property("left");
183 binaryNode.lhs().accept(this);
184 comma();
186 property("right");
187 binaryNode.rhs().accept(this);
189 return leave();
190 }
192 @Override
193 public boolean enterBreakNode(final BreakNode breakNode) {
194 enterDefault(breakNode);
196 type("BreakStatement");
197 comma();
199 final IdentNode label = breakNode.getLabel();
200 if (label != null) {
201 property("label", label.getName());
202 } else {
203 property("label");
204 nullValue();
205 }
207 return leave();
208 }
210 @Override
211 public boolean enterCallNode(final CallNode callNode) {
212 enterDefault(callNode);
214 type("CallExpression");
215 comma();
217 property("callee");
218 callNode.getFunction().accept(this);
219 comma();
221 array("arguments", callNode.getArgs());
223 return leave();
224 }
226 @Override
227 public boolean enterCaseNode(final CaseNode caseNode) {
228 enterDefault(caseNode);
230 type("SwitchCase");
231 comma();
233 final Node test = caseNode.getTest();
234 property("test");
235 if (test != null) {
236 test.accept(this);
237 } else {
238 nullValue();
239 }
240 comma();
242 array("consequent", caseNode.getBody().getStatements());
244 return leave();
245 }
247 @Override
248 public boolean enterCatchNode(final CatchNode catchNode) {
249 enterDefault(catchNode);
251 type("CatchClause");
252 comma();
254 property("param");
255 catchNode.getException().accept(this);
256 comma();
258 final Node guard = catchNode.getExceptionCondition();
259 property("guard");
260 if (guard != null) {
261 guard.accept(this);
262 } else {
263 nullValue();
264 }
265 comma();
267 property("body");
268 catchNode.getBody().accept(this);
270 return leave();
271 }
273 @Override
274 public boolean enterContinueNode(final ContinueNode continueNode) {
275 enterDefault(continueNode);
277 type("ContinueStatement");
278 comma();
280 final IdentNode label = continueNode.getLabel();
281 if (label != null) {
282 property("label", label.getName());
283 } else {
284 property("label");
285 nullValue();
286 }
288 return leave();
289 }
291 @Override
292 public boolean enterEmptyNode(final EmptyNode emptyNode) {
293 enterDefault(emptyNode);
295 type("EmptyStatement");
297 return leave();
298 }
300 @Override
301 public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
302 enterDefault(expressionStatement);
304 type("ExpressionStatement");
305 comma();
307 property("expression");
308 expressionStatement.getExpression().accept(this);
310 return leave();
311 }
313 @Override
314 public boolean enterBlockStatement(BlockStatement blockStatement) {
315 enterDefault(blockStatement);
317 type("BlockStatement");
318 comma();
320 property("block");
321 blockStatement.getBlock().accept(this);
323 return leave();
324 }
326 @Override
327 public boolean enterForNode(final ForNode forNode) {
328 enterDefault(forNode);
330 if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
331 type("ForInStatement");
332 comma();
334 Node init = forNode.getInit();
335 assert init != null;
336 property("left");
337 init.accept(this);
338 comma();
340 Node modify = forNode.getModify();
341 assert modify != null;
342 property("right");
343 modify.accept(this);
344 comma();
346 property("body");
347 forNode.getBody().accept(this);
348 comma();
350 property("each", forNode.isForEach());
351 } else {
352 type("ForStatement");
353 comma();
355 final Node init = forNode.getInit();
356 property("init");
357 if (init != null) {
358 init.accept(this);
359 } else {
360 nullValue();
361 }
362 comma();
364 final Node test = forNode.getTest();
365 property("test");
366 if (test != null) {
367 test.accept(this);
368 } else {
369 nullValue();
370 }
371 comma();
373 final Node update = forNode.getModify();
374 property("update");
375 if (update != null) {
376 update.accept(this);
377 } else {
378 nullValue();
379 }
380 comma();
382 property("body");
383 forNode.getBody().accept(this);
384 }
386 return leave();
387 }
389 @Override
390 public boolean enterFunctionNode(final FunctionNode functionNode) {
391 enterDefault(functionNode);
393 final boolean program = functionNode.isProgram();
394 final String name;
395 if (program) {
396 name = "Program";
397 } else if (functionNode.isDeclared()) {
398 name = "FunctionDeclaration";
399 } else {
400 name = "FunctionExpression";
401 }
402 type(name);
403 comma();
405 if (! program) {
406 property("id");
407 if (functionNode.isAnonymous()) {
408 nullValue();
409 } else {
410 functionNode.getIdent().accept(this);
411 }
412 comma();
413 }
415 property("rest");
416 nullValue();
417 comma();
419 if (!program) {
420 array("params", functionNode.getParameters());
421 comma();
422 }
424 // body consists of nested functions and statements
425 final List<Statement> stats = functionNode.getBody().getStatements();
426 final int size = stats.size();
427 int idx = 0;
428 arrayStart("body");
430 for (final Node stat : stats) {
431 stat.accept(this);
432 if (idx != (size - 1)) {
433 comma();
434 }
435 idx++;
436 }
437 arrayEnd();
439 return leave();
440 }
442 @Override
443 public boolean enterIdentNode(final IdentNode identNode) {
444 enterDefault(identNode);
446 final String name = identNode.getName();
447 if ("this".equals(name)) {
448 type("ThisExpression");
449 } else {
450 type("Identifier");
451 comma();
452 property("name", identNode.getName());
453 }
455 return leave();
456 }
458 @Override
459 public boolean enterIfNode(final IfNode ifNode) {
460 enterDefault(ifNode);
462 type("IfStatement");
463 comma();
465 property("test");
466 ifNode.getTest().accept(this);
467 comma();
469 property("consequent");
470 ifNode.getPass().accept(this);
471 final Node elsePart = ifNode.getFail();
472 comma();
474 property("alternate");
475 if (elsePart != null) {
476 elsePart.accept(this);
477 } else {
478 nullValue();
479 }
481 return leave();
482 }
484 @Override
485 public boolean enterIndexNode(final IndexNode indexNode) {
486 enterDefault(indexNode);
488 type("MemberExpression");
489 comma();
491 property("object");
492 indexNode.getBase().accept(this);
493 comma();
495 property("property");
496 indexNode.getIndex().accept(this);
497 comma();
499 property("computed", true);
501 return leave();
502 }
504 @Override
505 public boolean enterLabelNode(final LabelNode labelNode) {
506 enterDefault(labelNode);
508 type("LabeledStatement");
509 comma();
511 property("label");
512 labelNode.getLabel().accept(this);
513 comma();
515 property("body");
516 labelNode.getBody().accept(this);
518 return leave();
519 }
521 @SuppressWarnings("rawtypes")
522 @Override
523 public boolean enterLiteralNode(final LiteralNode literalNode) {
524 enterDefault(literalNode);
526 if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
527 type("ArrayExpression");
528 comma();
530 final Node[] value = literalNode.getArray();
531 array("elements", Arrays.asList(value));
532 } else {
533 type("Literal");
534 comma();
536 property("value");
537 final Object value = literalNode.getValue();
538 if (value instanceof RegexToken) {
539 // encode RegExp literals as Strings of the form /.../<flags>
540 final RegexToken regex = (RegexToken)value;
541 final StringBuilder regexBuf = new StringBuilder();
542 regexBuf.append('/');
543 regexBuf.append(regex.getExpression());
544 regexBuf.append('/');
545 regexBuf.append(regex.getOptions());
546 buf.append(quote(regexBuf.toString()));
547 } else {
548 final String str = literalNode.getString();
549 // encode every String literal with prefix '$' so that script
550 // can differentiate b/w RegExps as Strings and Strings.
551 buf.append(literalNode.isString()? quote("$" + str) : str);
552 }
553 }
555 return leave();
556 }
558 @Override
559 public boolean enterObjectNode(final ObjectNode objectNode) {
560 enterDefault(objectNode);
562 type("ObjectExpression");
563 comma();
565 array("properties", objectNode.getElements());
567 return leave();
568 }
570 @Override
571 public boolean enterPropertyNode(final PropertyNode propertyNode) {
572 final Node key = propertyNode.getKey();
574 final Node value = propertyNode.getValue();
575 if (value != null) {
576 objectStart();
577 location(propertyNode);
579 property("key");
580 key.accept(this);
581 comma();
583 property("value");
584 value.accept(this);
585 comma();
587 property("kind", "init");
589 objectEnd();
590 } else {
591 // getter
592 final Node getter = propertyNode.getGetter();
593 if (getter != null) {
594 objectStart();
595 location(propertyNode);
597 property("key");
598 key.accept(this);
599 comma();
601 property("value");
602 getter.accept(this);
603 comma();
605 property("kind", "get");
607 objectEnd();
608 }
610 // setter
611 final Node setter = propertyNode.getSetter();
612 if (setter != null) {
613 if (getter != null) {
614 comma();
615 }
616 objectStart();
617 location(propertyNode);
619 property("key");
620 key.accept(this);
621 comma();
623 property("value");
624 setter.accept(this);
625 comma();
627 property("kind", "set");
629 objectEnd();
630 }
631 }
633 return false;
634 }
636 @Override
637 public boolean enterReturnNode(final ReturnNode returnNode) {
638 enterDefault(returnNode);
640 type("ReturnStatement");
641 comma();
643 final Node arg = returnNode.getExpression();
644 property("argument");
645 if (arg != null) {
646 arg.accept(this);
647 } else {
648 nullValue();
649 }
651 return leave();
652 }
654 @Override
655 public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
656 final RuntimeNode.Request req = runtimeNode.getRequest();
658 if (req == RuntimeNode.Request.DEBUGGER) {
659 enterDefault(runtimeNode);
660 type("DebuggerStatement");
661 return leave();
662 }
664 return false;
665 }
667 @Override
668 public boolean enterSplitNode(final SplitNode splitNode) {
669 return false;
670 }
672 @Override
673 public boolean enterSwitchNode(final SwitchNode switchNode) {
674 enterDefault(switchNode);
676 type("SwitchStatement");
677 comma();
679 property("discriminant");
680 switchNode.getExpression().accept(this);
681 comma();
683 array("cases", switchNode.getCases());
685 return leave();
686 }
688 @Override
689 public boolean enterTernaryNode(final TernaryNode ternaryNode) {
690 enterDefault(ternaryNode);
692 type("ConditionalExpression");
693 comma();
695 property("test");
696 ternaryNode.getTest().accept(this);
697 comma();
699 property("consequent");
700 ternaryNode.getTrueExpression().accept(this);
701 comma();
703 property("alternate");
704 ternaryNode.getFalseExpression().accept(this);
706 return leave();
707 }
709 @Override
710 public boolean enterThrowNode(final ThrowNode throwNode) {
711 enterDefault(throwNode);
713 type("ThrowStatement");
714 comma();
716 property("argument");
717 throwNode.getExpression().accept(this);
719 return leave();
720 }
722 @Override
723 public boolean enterTryNode(final TryNode tryNode) {
724 enterDefault(tryNode);
726 type("TryStatement");
727 comma();
729 property("block");
730 tryNode.getBody().accept(this);
731 comma();
733 array("handlers", tryNode.getCatches());
734 comma();
736 property("finalizer");
737 final Node finallyNode = tryNode.getFinallyBody();
738 if (finallyNode != null) {
739 finallyNode.accept(this);
740 } else {
741 nullValue();
742 }
744 return leave();
745 }
747 @Override
748 public boolean enterUnaryNode(final UnaryNode unaryNode) {
749 enterDefault(unaryNode);
751 final TokenType tokenType = unaryNode.tokenType();
752 if (tokenType == TokenType.NEW) {
753 type("NewExpression");
754 comma();
756 final CallNode callNode = (CallNode)unaryNode.rhs();
757 property("callee");
758 callNode.getFunction().accept(this);
759 comma();
761 array("arguments", callNode.getArgs());
762 } else {
763 final boolean prefix;
764 final String operator;
765 switch (tokenType) {
766 case INCPOSTFIX:
767 prefix = false;
768 operator = "++";
769 break;
770 case DECPOSTFIX:
771 prefix = false;
772 operator = "--";
773 break;
774 case INCPREFIX:
775 operator = "++";
776 prefix = true;
777 break;
778 case DECPREFIX:
779 operator = "--";
780 prefix = true;
781 break;
782 default:
783 prefix = false;
784 operator = tokenType.getName();
785 }
787 type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
788 comma();
790 property("operator", operator);
791 comma();
793 property("prefix", prefix);
794 comma();
796 property("argument");
797 unaryNode.rhs().accept(this);
798 }
800 return leave();
801 }
803 @Override
804 public boolean enterVarNode(final VarNode varNode) {
805 enterDefault(varNode);
807 type("VariableDeclaration");
808 comma();
810 arrayStart("declarations");
812 // VariableDeclarator
813 objectStart();
814 location(varNode.getName());
816 type("VariableDeclarator");
817 comma();
819 property("id", varNode.getName().toString());
820 comma();
822 property("init");
823 final Node init = varNode.getInit();
824 if (init != null) {
825 init.accept(this);
826 } else {
827 nullValue();
828 }
830 // VariableDeclarator
831 objectEnd();
833 // declarations
834 arrayEnd();
836 return leave();
837 }
839 @Override
840 public boolean enterWhileNode(final WhileNode whileNode) {
841 enterDefault(whileNode);
843 type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
844 comma();
846 if (whileNode.isDoWhile()) {
847 property("body");
848 whileNode.getBody().accept(this);
849 comma();
851 property("test");
852 whileNode.getTest().accept(this);
853 } else {
854 property("test");
855 whileNode.getTest().accept(this);
856 comma();
858 property("block");
859 whileNode.getBody().accept(this);
860 }
862 return leave();
863 }
865 @Override
866 public boolean enterWithNode(final WithNode withNode) {
867 enterDefault(withNode);
869 type("WithStatement");
870 comma();
872 property("object");
873 withNode.getExpression().accept(this);
874 comma();
876 property("body");
877 withNode.getBody().accept(this);
879 return leave();
880 }
882 // Internals below
884 private JSONWriter(final boolean includeLocation) {
885 super(new LexicalContext());
886 this.buf = new StringBuilder();
887 this.includeLocation = includeLocation;
888 }
890 private final StringBuilder buf;
891 private final boolean includeLocation;
893 private String getString() {
894 return buf.toString();
895 }
897 private void property(final String key, final String value) {
898 buf.append('"');
899 buf.append(key);
900 buf.append("\":");
901 if (value != null) {
902 buf.append('"');
903 buf.append(value);
904 buf.append('"');
905 }
906 }
908 private void property(final String key, final boolean value) {
909 property(key, Boolean.toString(value));
910 }
912 private void property(final String key, final int value) {
913 property(key, Integer.toString(value));
914 }
916 private void property(final String key) {
917 property(key, null);
918 }
920 private void type(final String value) {
921 property("type", value);
922 }
924 private void objectStart(final String name) {
925 buf.append('"');
926 buf.append(name);
927 buf.append("\":{");
928 }
930 private void objectStart() {
931 buf.append('{');
932 }
934 private void objectEnd() {
935 buf.append('}');
936 }
938 private void array(final String name, final List<? extends Node> nodes) {
939 // The size, idx comparison is just to avoid trailing comma..
940 final int size = nodes.size();
941 int idx = 0;
942 arrayStart(name);
943 for (final Node node : nodes) {
944 if (node != null) {
945 node.accept(this);
946 } else {
947 nullValue();
948 }
949 if (idx != (size - 1)) {
950 comma();
951 }
952 idx++;
953 }
954 arrayEnd();
955 }
957 private void arrayStart(final String name) {
958 buf.append('"');
959 buf.append(name);
960 buf.append('"');
961 buf.append(':');
962 buf.append('[');
963 }
965 private void arrayEnd() {
966 buf.append(']');
967 }
969 private void comma() {
970 buf.append(',');
971 }
973 private void nullValue() {
974 buf.append("null");
975 }
977 private void location(final Node node) {
978 if (includeLocation) {
979 objectStart("loc");
981 // source name
982 final Source src = lc.getCurrentFunction().getSource();
983 property("source", src.getName());
984 comma();
986 // start position
987 objectStart("start");
988 final int start = node.getStart();
989 property("line", src.getLine(start));
990 comma();
991 property("column", src.getColumn(start));
992 objectEnd();
993 comma();
995 // end position
996 objectStart("end");
997 final int end = node.getFinish();
998 property("line", src.getLine(end));
999 comma();
1000 property("column", src.getColumn(end));
1001 objectEnd();
1003 // end 'loc'
1004 objectEnd();
1006 comma();
1007 }
1008 }
1010 private static String quote(final String str) {
1011 return JSONParser.quote(str);
1012 }
1013 }