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 */
25
26 package jdk.nashorn.internal.ir.debug;
27
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.ArrayList;
31 import jdk.nashorn.internal.codegen.CompilerConstants;
32 import jdk.nashorn.internal.ir.AccessNode;
33 import jdk.nashorn.internal.ir.BinaryNode;
34 import jdk.nashorn.internal.ir.Block;
35 import jdk.nashorn.internal.ir.BlockStatement;
36 import jdk.nashorn.internal.ir.BreakNode;
37 import jdk.nashorn.internal.ir.CallNode;
38 import jdk.nashorn.internal.ir.CaseNode;
39 import jdk.nashorn.internal.ir.CatchNode;
40 import jdk.nashorn.internal.ir.ContinueNode;
41 import jdk.nashorn.internal.ir.EmptyNode;
42 import jdk.nashorn.internal.ir.ExpressionStatement;
43 import jdk.nashorn.internal.ir.ForNode;
44 import jdk.nashorn.internal.ir.FunctionNode;
45 import jdk.nashorn.internal.ir.IdentNode;
46 import jdk.nashorn.internal.ir.IfNode;
47 import jdk.nashorn.internal.ir.IndexNode;
48 import jdk.nashorn.internal.ir.LabelNode;
49 import jdk.nashorn.internal.ir.LexicalContext;
50 import jdk.nashorn.internal.ir.LiteralNode;
51 import jdk.nashorn.internal.ir.Node;
52 import jdk.nashorn.internal.ir.ObjectNode;
53 import jdk.nashorn.internal.ir.PropertyNode;
54 import jdk.nashorn.internal.ir.ReturnNode;
55 import jdk.nashorn.internal.ir.RuntimeNode;
56 import jdk.nashorn.internal.ir.SplitNode;
57 import jdk.nashorn.internal.ir.Statement;
58 import jdk.nashorn.internal.ir.SwitchNode;
59 import jdk.nashorn.internal.ir.TernaryNode;
60 import jdk.nashorn.internal.ir.ThrowNode;
61 import jdk.nashorn.internal.ir.TryNode;
62 import jdk.nashorn.internal.ir.UnaryNode;
63 import jdk.nashorn.internal.ir.VarNode;
64 import jdk.nashorn.internal.ir.WhileNode;
65 import jdk.nashorn.internal.ir.WithNode;
66 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
67 import jdk.nashorn.internal.parser.JSONParser;
68 import jdk.nashorn.internal.parser.Lexer.RegexToken;
69 import jdk.nashorn.internal.parser.Parser;
70 import jdk.nashorn.internal.parser.TokenType;
71 import jdk.nashorn.internal.runtime.Context;
72 import jdk.nashorn.internal.runtime.ParserException;
73 import jdk.nashorn.internal.runtime.ScriptEnvironment;
74 import jdk.nashorn.internal.runtime.Source;
75
76 /**
77 * This IR writer produces a JSON string that represents AST as a JSON string.
78 */
79 public final class JSONWriter extends NodeVisitor<LexicalContext> {
80
81 /**
82 * Returns AST as JSON compatible string.
83 *
84 * @param env script environment to use
85 * @param code code to be parsed
86 * @param name name of the code source (used for location)
87 * @param includeLoc tells whether to include location information for nodes or not
88 * @return JSON string representation of AST of the supplied code
89 */
90 public static String parse(final ScriptEnvironment env, final String code, final String name, final boolean includeLoc) {
91 final Parser parser = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
92 final JSONWriter jsonWriter = new JSONWriter(includeLoc);
93 try {
94 final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
95 functionNode.accept(jsonWriter);
96 return jsonWriter.getString();
97 } catch (final ParserException e) {
98 e.throwAsEcmaException();
99 return null;
100 }
101 }
102
103 @Override
104 protected boolean enterDefault(final Node node) {
105 objectStart();
106 location(node);
107
108 return true;
109 }
110
111 private boolean leave() {
112 objectEnd();
113 return false;
114 }
115
116 @Override
117 protected Node leaveDefault(final Node node) {
118 objectEnd();
119 return null;
120 }
121
122 @Override
123 public boolean enterAccessNode(final AccessNode accessNode) {
124 enterDefault(accessNode);
125
126 type("MemberExpression");
127 comma();
128
129 property("object");
130 accessNode.getBase().accept(this);
131 comma();
132
133 property("property");
134 accessNode.getProperty().accept(this);
135 comma();
136
137 property("computed", false);
138
139 return leave();
140 }
141
142 @Override
143 public boolean enterBlock(final Block block) {
144 enterDefault(block);
145
146 type("BlockStatement");
147 comma();
148
149 array("body", block.getStatements());
150
151 return leave();
152 }
153
154 private static boolean isLogical(final TokenType tt) {
155 switch (tt) {
156 case AND:
157 case OR:
158 return true;
159 default:
160 return false;
161 }
162 }
163
164 @Override
165 public boolean enterBinaryNode(final BinaryNode binaryNode) {
166 enterDefault(binaryNode);
167
168 final String name;
169 if (binaryNode.isAssignment()) {
170 name = "AssignmentExpression";
171 } else if (isLogical(binaryNode.tokenType())) {
172 name = "LogicalExpression";
173 } else {
174 name = "BinaryExpression";
175 }
176
177 type(name);
178 comma();
179
180 property("operator", binaryNode.tokenType().getName());
181 comma();
182
183 property("left");
184 binaryNode.lhs().accept(this);
185 comma();
186
187 property("right");
188 binaryNode.rhs().accept(this);
189
190 return leave();
191 }
192
193 @Override
194 public boolean enterBreakNode(final BreakNode breakNode) {
195 enterDefault(breakNode);
196
197 type("BreakStatement");
198 comma();
199
200 final IdentNode label = breakNode.getLabel();
201 property("label");
202 if (label != null) {
203 label.accept(this);
204 } else {
205 nullValue();
206 }
207
208 return leave();
209 }
210
211 @Override
212 public boolean enterCallNode(final CallNode callNode) {
213 enterDefault(callNode);
214
215 type("CallExpression");
216 comma();
217
218 property("callee");
219 callNode.getFunction().accept(this);
220 comma();
221
222 array("arguments", callNode.getArgs());
223
224 return leave();
225 }
226
227 @Override
228 public boolean enterCaseNode(final CaseNode caseNode) {
229 enterDefault(caseNode);
230
231 type("SwitchCase");
232 comma();
233
234 final Node test = caseNode.getTest();
235 property("test");
236 if (test != null) {
237 test.accept(this);
238 } else {
239 nullValue();
240 }
241 comma();
242
243 array("consequent", caseNode.getBody().getStatements());
244
245 return leave();
246 }
247
248 @Override
249 public boolean enterCatchNode(final CatchNode catchNode) {
250 enterDefault(catchNode);
251
252 type("CatchClause");
253 comma();
254
255 property("param");
256 catchNode.getException().accept(this);
257 comma();
258
259 final Node guard = catchNode.getExceptionCondition();
260 if (guard != null) {
261 property("guard");
262 guard.accept(this);
263 comma();
264 }
265
266 property("body");
267 catchNode.getBody().accept(this);
268
269 return leave();
270 }
271
272 @Override
273 public boolean enterContinueNode(final ContinueNode continueNode) {
274 enterDefault(continueNode);
275
276 type("ContinueStatement");
277 comma();
278
279 final IdentNode label = continueNode.getLabel();
280 property("label");
281 if (label != null) {
282 label.accept(this);
283 } else {
284 nullValue();
285 }
286
287 return leave();
288 }
289
290 @Override
291 public boolean enterEmptyNode(final EmptyNode emptyNode) {
292 enterDefault(emptyNode);
293
294 type("EmptyStatement");
295
296 return leave();
297 }
298
299 @Override
300 public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
301 // handle debugger statement
302 final Node expression = expressionStatement.getExpression();
303 if (expression instanceof RuntimeNode) {
304 expression.accept(this);
305 return false;
306 }
307
308 enterDefault(expressionStatement);
309
310 type("ExpressionStatement");
311 comma();
312
313 property("expression");
314 expression.accept(this);
315
316 return leave();
317 }
318
319 @Override
320 public boolean enterBlockStatement(BlockStatement blockStatement) {
321 enterDefault(blockStatement);
322
323 type("BlockStatement");
324 comma();
325
326 property("block");
327 blockStatement.getBlock().accept(this);
328
329 return leave();
330 }
331
332 @Override
333 public boolean enterForNode(final ForNode forNode) {
334 enterDefault(forNode);
335
336 if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
337 type("ForInStatement");
338 comma();
339
340 Node init = forNode.getInit();
341 assert init != null;
342 property("left");
343 init.accept(this);
344 comma();
345
346 Node modify = forNode.getModify();
347 assert modify != null;
348 property("right");
349 modify.accept(this);
350 comma();
351
352 property("body");
353 forNode.getBody().accept(this);
354 comma();
355
356 property("each", forNode.isForEach());
357 } else {
358 type("ForStatement");
359 comma();
360
361 final Node init = forNode.getInit();
362 property("init");
363 if (init != null) {
364 init.accept(this);
365 } else {
366 nullValue();
367 }
368 comma();
369
370 final Node test = forNode.getTest();
371 property("test");
372 if (test != null) {
373 test.accept(this);
374 } else {
375 nullValue();
376 }
377 comma();
378
379 final Node update = forNode.getModify();
380 property("update");
381 if (update != null) {
382 update.accept(this);
383 } else {
384 nullValue();
385 }
386 comma();
387
388 property("body");
389 forNode.getBody().accept(this);
390 }
391
392 return leave();
393 }
394
395 @Override
396 public boolean enterFunctionNode(final FunctionNode functionNode) {
397 final boolean program = functionNode.isProgram();
398 if (program) {
399 return emitProgram(functionNode);
400 }
401
402 enterDefault(functionNode);
403 final String name;
404 if (functionNode.isDeclared()) {
405 name = "FunctionDeclaration";
406 } else {
407 name = "FunctionExpression";
408 }
409 type(name);
410 comma();
411
412 property("id");
413 if (functionNode.isAnonymous()) {
414 nullValue();
415 } else {
416 functionNode.getIdent().accept(this);
417 }
418 comma();
419
420 array("params", functionNode.getParameters());
421 comma();
422
423 arrayStart("defaults");
424 arrayEnd();
425 comma();
426
427 property("rest");
428 nullValue();
429 comma();
430
431 property("body");
432 functionNode.getBody().accept(this);
433 comma();
434
435 property("generator", false);
436 comma();
437
438 property("expression", false);
439
440 return leave();
441 }
442
443 private boolean emitProgram(final FunctionNode functionNode) {
444 enterDefault(functionNode);
445 type("Program");
446 comma();
447
448 // body consists of nested functions and statements
449 final List<Statement> stats = functionNode.getBody().getStatements();
450 final int size = stats.size();
451 int idx = 0;
452 arrayStart("body");
453
454 for (final Node stat : stats) {
455 stat.accept(this);
456 if (idx != (size - 1)) {
457 comma();
458 }
459 idx++;
460 }
461 arrayEnd();
462
463 return leave();
464 }
465
466 @Override
467 public boolean enterIdentNode(final IdentNode identNode) {
468 enterDefault(identNode);
469
470 final String name = identNode.getName();
471 if ("this".equals(name)) {
472 type("ThisExpression");
473 } else {
474 type("Identifier");
475 comma();
476 property("name", identNode.getName());
477 }
478
479 return leave();
480 }
481
482 @Override
483 public boolean enterIfNode(final IfNode ifNode) {
484 enterDefault(ifNode);
485
486 type("IfStatement");
487 comma();
488
489 property("test");
490 ifNode.getTest().accept(this);
491 comma();
492
493 property("consequent");
494 ifNode.getPass().accept(this);
495 final Node elsePart = ifNode.getFail();
496 comma();
497
498 property("alternate");
499 if (elsePart != null) {
500 elsePart.accept(this);
501 } else {
502 nullValue();
503 }
504
505 return leave();
506 }
507
508 @Override
509 public boolean enterIndexNode(final IndexNode indexNode) {
510 enterDefault(indexNode);
511
512 type("MemberExpression");
513 comma();
514
515 property("object");
516 indexNode.getBase().accept(this);
517 comma();
518
519 property("property");
520 indexNode.getIndex().accept(this);
521 comma();
522
523 property("computed", true);
524
525 return leave();
526 }
527
528 @Override
529 public boolean enterLabelNode(final LabelNode labelNode) {
530 enterDefault(labelNode);
531
532 type("LabeledStatement");
533 comma();
534
535 property("label");
536 labelNode.getLabel().accept(this);
537 comma();
538
539 property("body");
540 labelNode.getBody().accept(this);
541
542 return leave();
543 }
544
545 @SuppressWarnings("rawtypes")
546 @Override
547 public boolean enterLiteralNode(final LiteralNode literalNode) {
548 enterDefault(literalNode);
549
550 if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
551 type("ArrayExpression");
552 comma();
553
554 final Node[] value = literalNode.getArray();
555 array("elements", Arrays.asList(value));
556 } else {
557 type("Literal");
558 comma();
559
560 property("value");
561 final Object value = literalNode.getValue();
562 if (value instanceof RegexToken) {
563 // encode RegExp literals as Strings of the form /.../<flags>
564 final RegexToken regex = (RegexToken)value;
565 final StringBuilder regexBuf = new StringBuilder();
566 regexBuf.append('/');
567 regexBuf.append(regex.getExpression());
568 regexBuf.append('/');
569 regexBuf.append(regex.getOptions());
570 buf.append(quote(regexBuf.toString()));
571 } else {
572 final String str = literalNode.getString();
573 // encode every String literal with prefix '$' so that script
574 // can differentiate b/w RegExps as Strings and Strings.
575 buf.append(literalNode.isString()? quote("$" + str) : str);
576 }
577 }
578
579 return leave();
580 }
581
582 @Override
583 public boolean enterObjectNode(final ObjectNode objectNode) {
584 enterDefault(objectNode);
585
586 type("ObjectExpression");
587 comma();
588
589 array("properties", objectNode.getElements());
590
591 return leave();
592 }
593
594 @Override
595 public boolean enterPropertyNode(final PropertyNode propertyNode) {
596 final Node key = propertyNode.getKey();
597
598 final Node value = propertyNode.getValue();
599 if (value != null) {
600 objectStart();
601 location(propertyNode);
602
603 property("key");
604 key.accept(this);
605 comma();
606
607 property("value");
608 value.accept(this);
609 comma();
610
611 property("kind", "init");
612
613 objectEnd();
614 } else {
615 // getter
616 final Node getter = propertyNode.getGetter();
617 if (getter != null) {
618 objectStart();
619 location(propertyNode);
620
621 property("key");
622 key.accept(this);
623 comma();
624
625 property("value");
626 getter.accept(this);
627 comma();
628
629 property("kind", "get");
630
631 objectEnd();
632 }
633
634 // setter
635 final Node setter = propertyNode.getSetter();
636 if (setter != null) {
637 if (getter != null) {
638 comma();
639 }
640 objectStart();
641 location(propertyNode);
642
643 property("key");
644 key.accept(this);
645 comma();
646
647 property("value");
648 setter.accept(this);
649 comma();
650
651 property("kind", "set");
652
653 objectEnd();
654 }
655 }
656
657 return false;
658 }
659
660 @Override
661 public boolean enterReturnNode(final ReturnNode returnNode) {
662 enterDefault(returnNode);
663
664 type("ReturnStatement");
665 comma();
666
667 final Node arg = returnNode.getExpression();
668 property("argument");
669 if (arg != null) {
670 arg.accept(this);
671 } else {
672 nullValue();
673 }
674
675 return leave();
676 }
677
678 @Override
679 public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
680 final RuntimeNode.Request req = runtimeNode.getRequest();
681
682 if (req == RuntimeNode.Request.DEBUGGER) {
683 enterDefault(runtimeNode);
684 type("DebuggerStatement");
685 return leave();
686 }
687
688 return false;
689 }
690
691 @Override
692 public boolean enterSplitNode(final SplitNode splitNode) {
693 return false;
694 }
695
696 @Override
697 public boolean enterSwitchNode(final SwitchNode switchNode) {
698 enterDefault(switchNode);
699
700 type("SwitchStatement");
701 comma();
702
703 property("discriminant");
704 switchNode.getExpression().accept(this);
705 comma();
706
707 array("cases", switchNode.getCases());
708
709 return leave();
710 }
711
712 @Override
713 public boolean enterTernaryNode(final TernaryNode ternaryNode) {
714 enterDefault(ternaryNode);
715
716 type("ConditionalExpression");
717 comma();
718
719 property("test");
720 ternaryNode.getTest().accept(this);
721 comma();
722
723 property("consequent");
724 ternaryNode.getTrueExpression().accept(this);
725 comma();
726
727 property("alternate");
728 ternaryNode.getFalseExpression().accept(this);
729
730 return leave();
731 }
732
733 @Override
734 public boolean enterThrowNode(final ThrowNode throwNode) {
735 enterDefault(throwNode);
736
737 type("ThrowStatement");
738 comma();
739
740 property("argument");
741 throwNode.getExpression().accept(this);
742
743 return leave();
744 }
745
746 @Override
747 public boolean enterTryNode(final TryNode tryNode) {
748 enterDefault(tryNode);
749
750 type("TryStatement");
751 comma();
752
753 property("block");
754 tryNode.getBody().accept(this);
755 comma();
756
757
758 final List<? extends Node> catches = tryNode.getCatches();
759 final List<CatchNode> guarded = new ArrayList<>();
760 CatchNode unguarded = null;
761 if (catches != null) {
762 for (Node n : catches) {
763 CatchNode cn = (CatchNode)n;
764 if (cn.getExceptionCondition() != null) {
765 guarded.add(cn);
766 } else {
767 assert unguarded == null: "too many unguarded?";
768 unguarded = cn;
769 }
770 }
771 }
772
773 array("guardedHandlers", guarded);
774 comma();
775
776 property("handler");
777 if (unguarded != null) {
778 unguarded.accept(this);
779 } else {
780 nullValue();
781 }
782 comma();
783
784 property("finalizer");
785 final Node finallyNode = tryNode.getFinallyBody();
786 if (finallyNode != null) {
787 finallyNode.accept(this);
788 } else {
789 nullValue();
790 }
791
792 return leave();
793 }
794
795 @Override
796 public boolean enterUnaryNode(final UnaryNode unaryNode) {
797 enterDefault(unaryNode);
798
799 final TokenType tokenType = unaryNode.tokenType();
800 if (tokenType == TokenType.NEW) {
801 type("NewExpression");
802 comma();
803
804 final CallNode callNode = (CallNode)unaryNode.rhs();
805 property("callee");
806 callNode.getFunction().accept(this);
807 comma();
808
809 array("arguments", callNode.getArgs());
810 } else {
811 final String operator;
812 final boolean prefix;
813 switch (tokenType) {
814 case INCPOSTFIX:
815 prefix = false;
816 operator = "++";
817 break;
818 case DECPOSTFIX:
819 prefix = false;
820 operator = "--";
821 break;
822 case INCPREFIX:
823 operator = "++";
824 prefix = true;
825 break;
826 case DECPREFIX:
827 operator = "--";
828 prefix = true;
829 break;
830 default:
831 prefix = true;
832 operator = tokenType.getName();
833 break;
834 }
835
836 type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
837 comma();
838
839 property("operator", operator);
840 comma();
841
842 property("prefix", prefix);
843 comma();
844
845 property("argument");
846 unaryNode.rhs().accept(this);
847 }
848
849 return leave();
850 }
851
852 @Override
853 public boolean enterVarNode(final VarNode varNode) {
854 final Node init = varNode.getInit();
855 if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) {
856 // function declaration - don't emit VariableDeclaration instead
857 // just emit FunctionDeclaration using 'init' Node.
858 init.accept(this);
859 return false;
860 }
861
862 enterDefault(varNode);
863
864 type("VariableDeclaration");
865 comma();
866
867 arrayStart("declarations");
868
869 // VariableDeclarator
870 objectStart();
871 location(varNode.getName());
872
873 type("VariableDeclarator");
874 comma();
875
876 property("id");
877 varNode.getName().accept(this);
878 comma();
879
880 property("init");
881 if (init != null) {
882 init.accept(this);
883 } else {
884 nullValue();
885 }
886
887 // VariableDeclarator
888 objectEnd();
889
890 // declarations
891 arrayEnd();
892
893 return leave();
894 }
895
896 @Override
897 public boolean enterWhileNode(final WhileNode whileNode) {
898 enterDefault(whileNode);
899
900 type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
901 comma();
902
903 if (whileNode.isDoWhile()) {
904 property("body");
905 whileNode.getBody().accept(this);
906 comma();
907
908 property("test");
909 whileNode.getTest().accept(this);
910 } else {
911 property("test");
912 whileNode.getTest().accept(this);
913 comma();
914
915 property("body");
916 whileNode.getBody().accept(this);
917 }
918
919 return leave();
920 }
921
922 @Override
923 public boolean enterWithNode(final WithNode withNode) {
924 enterDefault(withNode);
925
926 type("WithStatement");
927 comma();
928
929 property("object");
930 withNode.getExpression().accept(this);
931 comma();
932
933 property("body");
934 withNode.getBody().accept(this);
935
936 return leave();
937 }
938
939 // Internals below
940
941 private JSONWriter(final boolean includeLocation) {
942 super(new LexicalContext());
943 this.buf = new StringBuilder();
944 this.includeLocation = includeLocation;
945 }
946
947 private final StringBuilder buf;
948 private final boolean includeLocation;
949
950 private String getString() {
951 return buf.toString();
952 }
953
954 private void property(final String key, final String value, final boolean escape) {
955 buf.append('"');
956 buf.append(key);
957 buf.append("\":");
958 if (value != null) {
959 if (escape) buf.append('"');
960 buf.append(value);
961 if (escape) buf.append('"');
962 }
963 }
964
965 private void property(final String key, final String value) {
966 property(key, value, true);
967 }
968
969 private void property(final String key, final boolean value) {
970 property(key, Boolean.toString(value), false);
971 }
972
973 private void property(final String key, final int value) {
974 property(key, Integer.toString(value), false);
975 }
976
977 private void property(final String key) {
978 property(key, null);
979 }
980
981 private void type(final String value) {
982 property("type", value);
983 }
984
985 private void objectStart(final String name) {
986 buf.append('"');
987 buf.append(name);
988 buf.append("\":{");
989 }
990
991 private void objectStart() {
992 buf.append('{');
993 }
994
995 private void objectEnd() {
996 buf.append('}');
997 }
998
999 private void array(final String name, final List<? extends Node> nodes) {
1000 // The size, idx comparison is just to avoid trailing comma..
1001 final int size = nodes.size();
1002 int idx = 0;
1003 arrayStart(name);
1004 for (final Node node : nodes) {
1005 if (node != null) {
1006 node.accept(this);
1007 } else {
1008 nullValue();
1009 }
1010 if (idx != (size - 1)) {
1011 comma();
1012 }
1013 idx++;
1014 }
1015 arrayEnd();
1016 }
1017
1018 private void arrayStart(final String name) {
1019 buf.append('"');
1020 buf.append(name);
1021 buf.append('"');
1022 buf.append(':');
1023 buf.append('[');
1024 }
1025
1026 private void arrayEnd() {
1027 buf.append(']');
1028 }
1029
1030 private void comma() {
1031 buf.append(',');
1032 }
1033
1034 private void nullValue() {
1035 buf.append("null");
1036 }
1037
1038 private void location(final Node node) {
1039 if (includeLocation) {
1040 objectStart("loc");
1041
1042 // source name
1043 final Source src = lc.getCurrentFunction().getSource();
1044 property("source", src.getName());
1045 comma();
1046
1047 // start position
1048 objectStart("start");
1049 final int start = node.getStart();
1050 property("line", src.getLine(start));
1051 comma();
1052 property("column", src.getColumn(start));
1053 objectEnd();
1054 comma();
1055
1056 // end position
1057 objectStart("end");
1058 final int end = node.getFinish();
1059 property("line", src.getLine(end));
1060 comma();
1061 property("column", src.getColumn(end));
1062 objectEnd();
1063
1064 // end 'loc'
1065 objectEnd();
1066
1067 comma();
1068 }
1069 }
1070
1071 private static String quote(final String str) {
1072 return JSONParser.quote(str);
1073 }
1074 }
--- EOF ---