flexijson.js revision 1252:be5c4e5da0c1
1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 *   - Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 *
11 *   - Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 *   - Neither the name of Oracle nor the names of its
16 *     contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * Hjson - "the Human JSON - A configuration file format that
34 * caters to humans and helps reduce the errors they make"
35 * See also: http://hjson.org/
36 *
37 * I wanted to see if we can use Nashorn Parser API (jdk9) to support
38 * similar flexible JSON extension with #nashorn. In this FlexiJSON.parse
39 * implementation, Nashorn Parser API is used to validate that the
40 * extendable flexi JSON is "data only" (i.e., no executable code) and
41 * then 'eval'ed to make an object out of it.
42 *
43 * FlexiJSON allows the following:
44 *
45 *   * single and mutliple line comments anywhere
46 *   * non-quoted property names and values
47 *   * regexp literal values
48 *   * omitting trailing comma
49 *
50 * When nashorn -scripting mode is enabled, FlexiJSON supports these
51 * as well:
52 *
53 *   * shell style # comments
54 *   * multiple line (Unix heredoc style) string values
55 */
56
57"use strict";
58
59function FlexiJSON() {}
60
61// helper to locate Nashorn Parser API classes
62FlexiJSON.treeType = function(name) {
63    return Java.type("jdk.nashorn.api.tree." + name);
64}
65
66// Nashorn Parser API classes used
67FlexiJSON.ArrayLiteral = FlexiJSON.treeType("ArrayLiteralTree");
68FlexiJSON.ExpressionStatement = FlexiJSON.treeType("ExpressionStatementTree");
69FlexiJSON.ObjectLiteral = FlexiJSON.treeType("ObjectLiteralTree");
70FlexiJSON.RegExpLiteral = FlexiJSON.treeType("RegExpLiteralTree");
71FlexiJSON.Literal = FlexiJSON.treeType("LiteralTree");
72FlexiJSON.Parser = FlexiJSON.treeType("Parser");
73FlexiJSON.SimpleTreeVisitor = FlexiJSON.treeType("SimpleTreeVisitorES5_1");
74
75// FlexiJSON.parse API
76
77FlexiJSON.parse = function(str) {
78    var parser = (typeof $OPTIONS == "undefined")?
79        FlexiJSON.Parser.create() :
80        FlexiJSON.Parser.create("-scripting");
81
82    // force the string to be an expression by putting it inside (, )
83    str = "(" + str + ")";
84    var ast = parser.parse("<flexijsondoc>", str, null);
85    // Should not happen. parse would have thrown syntax error
86    if (!ast) {
87        return undefined;
88    }
89
90    // allowed 'literal' values in flexi JSON
91    function isLiteral(node) {
92        return node instanceof FlexiJSON.ArrayLiteral ||
93            node instanceof FlexiJSON.Literal ||
94            node instanceof FlexiJSON.ObjectLiteral ||
95            node instanceof FlexiJSON.RegExpLiteral;
96    }
97
98    var visitor;
99    ast.accept(visitor = new (Java.extend(FlexiJSON.SimpleTreeVisitor)) {
100         lineMap: null,
101
102         throwError: function(msg, node) {
103             if (this.lineMap) {
104                 var pos = node.startPosition;
105                 var line = this.lineMap.getLineNumber(pos);
106                 var column = this.lineMap.getColumnNumber(pos);
107                 // we introduced extra '(' at start. So, adjust column number
108                 msg = msg + " @ " + line + ":" + (column - 1);
109             }
110             throw new TypeError(msg);
111         },
112
113         visitLiteral: function(node, extra) {
114             print(node.value);
115         },
116
117         visitExpressionStatement: function(node, extra) {
118             var expr = node.expression;
119             if (isLiteral(expr)) {
120                 expr.accept(visitor, extra);
121             } else {
122                 this.throwError("only literals can occur", expr);
123             }
124         },
125
126         visitArrayLiteral: function(node, extra) {
127             for each (var elem in node.elements) {
128                 if (isLiteral(elem)) {
129                     elem.accept(visitor, extra);
130                 } else {
131                     this.throwError("only literal array element value allowed", elem);
132                 }
133             }
134         },
135
136         visitObjectLiteral: function(node, extra) {
137             for each (var prop in node.properties) {
138                 if (prop.getter != null || prop.setter != null) {
139                     this.throwError("getter/setter property not allowed", node);
140                 }
141
142                 var value = prop.value;
143                 if (isLiteral(value)) {
144                     value.accept(visitor, extra);
145                 } else {
146                     this.throwError("only literal property value allowed", value);
147                 }
148             }
149         },
150
151         visitCompilationUnit: function(node, extra) {
152             this.lineMap = node.lineMap;
153             var elements = node.sourceElements;
154             if (elements.length > 1) {
155                 this.throwError("more than one top level expression", node.sourceElements[1]);
156             }
157             var stat = node.sourceElements[0];
158             if (! (stat instanceof FlexiJSON.ExpressionStatement)) {
159                 this.throwError("only one top level expresion allowed", stat);
160             }
161             stat.accept(visitor, extra);
162         },
163    }, null);
164
165    // safe to eval given string as flexi JSON!
166    return eval(str);
167}
168