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 59 function FlexiJSON() {} 60 61 // helper to locate Nashorn Parser API classes 62 FlexiJSON.treeType = function(name) { 63 return Java.type("jdk.nashorn.api.tree." + name); 64 } 65 66 // Nashorn Parser API classes used 67 FlexiJSON.ArrayLiteral = FlexiJSON.treeType("ArrayLiteralTree"); 68 FlexiJSON.ExpressionStatement = FlexiJSON.treeType("ExpressionStatementTree"); 69 FlexiJSON.ObjectLiteral = FlexiJSON.treeType("ObjectLiteralTree"); 70 FlexiJSON.RegExpLiteral = FlexiJSON.treeType("RegExpLiteralTree"); 71 FlexiJSON.Literal = FlexiJSON.treeType("LiteralTree"); 72 FlexiJSON.Parser = FlexiJSON.treeType("Parser"); 73 FlexiJSON.SimpleTreeVisitor = FlexiJSON.treeType("SimpleTreeVisitorES5_1"); 74 75 // FlexiJSON.parse API 76 77 FlexiJSON.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 }