From e7a40ce8509a2e3fd39f4f50f2cba8ea7638cf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Mon, 20 Mar 2023 17:09:09 +0100 Subject: [PATCH] JS: Import graaljs fork into NetBeans codebase UPL licensed code can be directly imported and this simplifies development. The code is identical with: https://github.com/matthiasblaesing/graaljs-nb Tag: graaljs-parser-1.2 --- webcommon/libs.nashorn/external/binaries-list | 17 - .../external/graaljs-parser-1.2-license.txt | 24 - webcommon/libs.nashorn/licenseinfo.xml | 129 + .../libs.nashorn/nbproject/project.properties | 4 +- webcommon/libs.nashorn/nbproject/project.xml | 13 +- .../com/oracle/js/parser/AbstractParser.java | 582 ++ .../src/com/oracle/js/parser/ECMAErrors.java | 81 + .../com/oracle/js/parser/ErrorManager.java | 315 + .../src/com/oracle/js/parser/JSErrorType.java | 55 + .../src/com/oracle/js/parser/JSType.java | 576 ++ .../src/com/oracle/js/parser/Lexer.java | 2333 +++++++ .../src/com/oracle/js/parser/Namespace.java | 112 + .../src/com/oracle/js/parser/Parser.java | 6077 +++++++++++++++++ .../com/oracle/js/parser/ParserContext.java | 368 + .../js/parser/ParserContextBaseNode.java | 129 + .../js/parser/ParserContextBlockNode.java | 73 + .../js/parser/ParserContextBreakableNode.java | 57 + .../js/parser/ParserContextFunctionNode.java | 287 + .../js/parser/ParserContextLabelNode.java | 68 + .../js/parser/ParserContextLoopNode.java | 53 + .../js/parser/ParserContextModuleNode.java | 118 + .../oracle/js/parser/ParserContextNode.java | 86 + .../js/parser/ParserContextSwitchNode.java | 52 + .../com/oracle/js/parser/ParserException.java | 183 + .../RecompilableScriptFunctionData.java | 52 + .../src/com/oracle/js/parser/Scanner.java | 199 + .../oracle/js/parser/ScriptEnvironment.java | 250 + .../src/com/oracle/js/parser/Source.java | 891 +++ .../src/com/oracle/js/parser/Token.java | 198 + .../src/com/oracle/js/parser/TokenKind.java | 68 + .../src/com/oracle/js/parser/TokenLookup.java | 235 + .../src/com/oracle/js/parser/TokenStream.java | 229 + .../src/com/oracle/js/parser/TokenType.java | 393 ++ .../com/oracle/js/parser/ir/AccessNode.java | 160 + .../com/oracle/js/parser/ir/Assignment.java | 64 + .../src/com/oracle/js/parser/ir/BaseNode.java | 131 + .../com/oracle/js/parser/ir/BinaryNode.java | 290 + .../src/com/oracle/js/parser/ir/Block.java | 419 ++ .../oracle/js/parser/ir/BlockStatement.java | 132 + .../com/oracle/js/parser/ir/BreakNode.java | 96 + .../oracle/js/parser/ir/BreakableNode.java | 62 + .../js/parser/ir/BreakableStatement.java | 105 + .../src/com/oracle/js/parser/ir/CallNode.java | 260 + .../src/com/oracle/js/parser/ir/CaseNode.java | 183 + .../com/oracle/js/parser/ir/CatchNode.java | 195 + .../com/oracle/js/parser/ir/ClassNode.java | 205 + .../com/oracle/js/parser/ir/ContinueNode.java | 95 + .../com/oracle/js/parser/ir/DebuggerNode.java | 73 + .../com/oracle/js/parser/ir/EmptyNode.java | 72 + .../com/oracle/js/parser/ir/ErrorNode.java | 72 + .../oracle/js/parser/ir/ExportClauseNode.java | 100 + .../com/oracle/js/parser/ir/ExportNode.java | 225 + .../js/parser/ir/ExportSpecifierNode.java | 113 + .../com/oracle/js/parser/ir/Expression.java | 90 + .../oracle/js/parser/ir/ExpressionList.java | 99 + .../js/parser/ir/ExpressionStatement.java | 113 + .../src/com/oracle/js/parser/ir/Flags.java | 84 + .../src/com/oracle/js/parser/ir/ForNode.java | 294 + .../src/com/oracle/js/parser/ir/FromNode.java | 95 + .../com/oracle/js/parser/ir/FunctionCall.java | 60 + .../com/oracle/js/parser/ir/FunctionNode.java | 781 +++ .../com/oracle/js/parser/ir/IdentNode.java | 270 + .../src/com/oracle/js/parser/ir/IfNode.java | 163 + .../oracle/js/parser/ir/ImportClauseNode.java | 159 + .../com/oracle/js/parser/ir/ImportNode.java | 146 + .../js/parser/ir/ImportSpecifierNode.java | 113 + .../com/oracle/js/parser/ir/IndexNode.java | 182 + .../parser/ir/JoinPredecessorExpression.java | 125 + .../oracle/js/parser/ir/JsxAttributeNode.java | 105 + .../oracle/js/parser/ir/JsxElementNode.java | 130 + .../oracle/js/parser/ir/JumpStatement.java | 115 + .../src/com/oracle/js/parser/ir/Label.java | 72 + .../com/oracle/js/parser/ir/LabelNode.java | 134 + .../src/com/oracle/js/parser/ir/Labels.java | 59 + .../oracle/js/parser/ir/LexicalContext.java | 595 ++ .../parser/ir/LexicalContextExpression.java | 70 + .../js/parser/ir/LexicalContextNode.java | 88 + .../js/parser/ir/LexicalContextStatement.java | 70 + .../com/oracle/js/parser/ir/LiteralNode.java | 697 ++ .../src/com/oracle/js/parser/ir/LoopNode.java | 185 + .../src/com/oracle/js/parser/ir/Module.java | 221 + .../js/parser/ir/NameSpaceImportNode.java | 93 + .../oracle/js/parser/ir/NamedImportsNode.java | 100 + .../src/com/oracle/js/parser/ir/Node.java | 321 + .../com/oracle/js/parser/ir/ObjectNode.java | 126 + .../com/oracle/js/parser/ir/PropertyKey.java | 54 + .../com/oracle/js/parser/ir/PropertyNode.java | 256 + .../com/oracle/js/parser/ir/ReturnNode.java | 134 + .../com/oracle/js/parser/ir/RuntimeNode.java | 234 + .../com/oracle/js/parser/ir/Statement.java | 124 + .../com/oracle/js/parser/ir/SwitchNode.java | 191 + .../src/com/oracle/js/parser/ir/Symbol.java | 389 ++ .../src/com/oracle/js/parser/ir/Terminal.java | 54 + .../com/oracle/js/parser/ir/TernaryNode.java | 193 + .../com/oracle/js/parser/ir/ThrowNode.java | 139 + .../src/com/oracle/js/parser/ir/TryNode.java | 218 + .../com/oracle/js/parser/ir/UnaryNode.java | 217 + .../src/com/oracle/js/parser/ir/VarNode.java | 321 + .../com/oracle/js/parser/ir/WhileNode.java | 166 + .../src/com/oracle/js/parser/ir/WithNode.java | 152 + .../js/parser/ir/visitor/NodeVisitor.java | 929 +++ .../ir/visitor/TranslatorNodeVisitor.java | 523 ++ .../com/oracle/js/parser/package-info.java | 48 + .../js/parser/resources/Messages.properties | 227 + .../com/oracle/js/parser/DumpingVisitor.java | 117 + .../src/com/oracle/js/parser/ManualTest.java | 76 + .../src/com/oracle/js/parser/ParserTest.java | 367 + 107 files changed, 28395 insertions(+), 48 deletions(-) delete mode 100644 webcommon/libs.nashorn/external/binaries-list delete mode 100644 webcommon/libs.nashorn/external/graaljs-parser-1.2-license.txt create mode 100644 webcommon/libs.nashorn/licenseinfo.xml create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/AbstractParser.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ECMAErrors.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ErrorManager.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/JSErrorType.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/JSType.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Lexer.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Namespace.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Parser.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContext.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBaseNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBlockNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBreakableNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextFunctionNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLabelNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLoopNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextModuleNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextSwitchNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ParserException.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/RecompilableScriptFunctionData.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Scanner.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ScriptEnvironment.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Source.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/Token.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/TokenKind.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/TokenLookup.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/TokenStream.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/TokenType.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/AccessNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Assignment.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BaseNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BinaryNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Block.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BlockStatement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BreakNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BreakableNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BreakableStatement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/CallNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/CaseNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/CatchNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ClassNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ContinueNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/DebuggerNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/EmptyNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ErrorNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ExportClauseNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ExportNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ExportSpecifierNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Expression.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ExpressionList.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ExpressionStatement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Flags.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ForNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/FromNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/FunctionCall.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/FunctionNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/IdentNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/IfNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ImportClauseNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ImportNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ImportSpecifierNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/IndexNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/JoinPredecessorExpression.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/JsxAttributeNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/JsxElementNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/JumpStatement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Label.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LabelNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Labels.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LexicalContext.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LexicalContextExpression.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LexicalContextNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LexicalContextStatement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LiteralNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/LoopNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Module.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/NameSpaceImportNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/NamedImportsNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Node.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ObjectNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/PropertyKey.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/PropertyNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ReturnNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/RuntimeNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Statement.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/SwitchNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Symbol.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Terminal.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/TernaryNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/ThrowNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/TryNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/UnaryNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/VarNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/WhileNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/WithNode.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/visitor/NodeVisitor.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/package-info.java create mode 100644 webcommon/libs.nashorn/src/com/oracle/js/parser/resources/Messages.properties create mode 100644 webcommon/libs.nashorn/test/unit/src/com/oracle/js/parser/DumpingVisitor.java create mode 100644 webcommon/libs.nashorn/test/unit/src/com/oracle/js/parser/ManualTest.java create mode 100644 webcommon/libs.nashorn/test/unit/src/com/oracle/js/parser/ParserTest.java diff --git a/webcommon/libs.nashorn/external/binaries-list b/webcommon/libs.nashorn/external/binaries-list deleted file mode 100644 index af28d52156d4..000000000000 --- a/webcommon/libs.nashorn/external/binaries-list +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -188B87174403746227AEBD41B1A9E131FD77D4DA eu.doppel-helix.netbeans.lib:graaljs-parser:1.2 diff --git a/webcommon/libs.nashorn/external/graaljs-parser-1.2-license.txt b/webcommon/libs.nashorn/external/graaljs-parser-1.2-license.txt deleted file mode 100644 index 2077e44fde47..000000000000 --- a/webcommon/libs.nashorn/external/graaljs-parser-1.2-license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Name: Graal.js Parser -Version: 1.2 -Description: An ECMAScript 13 (ECMAScript 2022) compatible parser for JavaScript source code. Based on the parser of Graal.js (https://github.com/graalvm/graaljs). -License: UPL -Origin: https://github.com/matthiasblaesing/graaljs-nb - -Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - -The Universal Permissive License (UPL), Version 1.0 - -Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this software, associated documentation and/or data (collectively the "Software"), free of charge and under any and all copyright rights in the Software, and any and all patent rights owned or freely licensable by each licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, or (ii) the Larger Works (as defined below), to deal in both - -(a) the Software, and - -(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software each a "Larger Work" to which the Software is contributed by such licensors), - -without restriction, including without limitation the rights to copy, create derivative works of, display, perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other terms. - -This license is subject to the following condition: - -The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL must be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/webcommon/libs.nashorn/licenseinfo.xml b/webcommon/libs.nashorn/licenseinfo.xml new file mode 100644 index 000000000000..1ca07e6f2ffc --- /dev/null +++ b/webcommon/libs.nashorn/licenseinfo.xml @@ -0,0 +1,129 @@ + + + + + src/com/oracle/js/parser/AbstractParser.java + src/com/oracle/js/parser/ECMAErrors.java + src/com/oracle/js/parser/ErrorManager.java + src/com/oracle/js/parser/JSErrorType.java + src/com/oracle/js/parser/JSType.java + src/com/oracle/js/parser/Lexer.java + src/com/oracle/js/parser/Namespace.java + src/com/oracle/js/parser/Parser.java + src/com/oracle/js/parser/ParserContext.java + src/com/oracle/js/parser/ParserContextBaseNode.java + src/com/oracle/js/parser/ParserContextBlockNode.java + src/com/oracle/js/parser/ParserContextBreakableNode.java + src/com/oracle/js/parser/ParserContextFunctionNode.java + src/com/oracle/js/parser/ParserContextLabelNode.java + src/com/oracle/js/parser/ParserContextLoopNode.java + src/com/oracle/js/parser/ParserContextModuleNode.java + src/com/oracle/js/parser/ParserContextNode.java + src/com/oracle/js/parser/ParserContextSwitchNode.java + src/com/oracle/js/parser/ParserException.java + src/com/oracle/js/parser/RecompilableScriptFunctionData.java + src/com/oracle/js/parser/Scanner.java + src/com/oracle/js/parser/ScriptEnvironment.java + src/com/oracle/js/parser/Source.java + src/com/oracle/js/parser/Token.java + src/com/oracle/js/parser/TokenKind.java + src/com/oracle/js/parser/TokenLookup.java + src/com/oracle/js/parser/TokenStream.java + src/com/oracle/js/parser/TokenType.java + src/com/oracle/js/parser/ir/AccessNode.java + src/com/oracle/js/parser/ir/Assignment.java + src/com/oracle/js/parser/ir/BaseNode.java + src/com/oracle/js/parser/ir/BinaryNode.java + src/com/oracle/js/parser/ir/Block.java + src/com/oracle/js/parser/ir/BlockStatement.java + src/com/oracle/js/parser/ir/BreakNode.java + src/com/oracle/js/parser/ir/BreakableNode.java + src/com/oracle/js/parser/ir/BreakableStatement.java + src/com/oracle/js/parser/ir/CallNode.java + src/com/oracle/js/parser/ir/CaseNode.java + src/com/oracle/js/parser/ir/CatchNode.java + src/com/oracle/js/parser/ir/ClassNode.java + src/com/oracle/js/parser/ir/ContinueNode.java + src/com/oracle/js/parser/ir/DebuggerNode.java + src/com/oracle/js/parser/ir/EmptyNode.java + src/com/oracle/js/parser/ir/ErrorNode.java + src/com/oracle/js/parser/ir/ExportClauseNode.java + src/com/oracle/js/parser/ir/ExportNode.java + src/com/oracle/js/parser/ir/ExportSpecifierNode.java + src/com/oracle/js/parser/ir/Expression.java + src/com/oracle/js/parser/ir/ExpressionList.java + src/com/oracle/js/parser/ir/ExpressionStatement.java + src/com/oracle/js/parser/ir/Flags.java + src/com/oracle/js/parser/ir/ForNode.java + src/com/oracle/js/parser/ir/FromNode.java + src/com/oracle/js/parser/ir/FunctionCall.java + src/com/oracle/js/parser/ir/FunctionNode.java + src/com/oracle/js/parser/ir/IdentNode.java + src/com/oracle/js/parser/ir/IfNode.java + src/com/oracle/js/parser/ir/ImportClauseNode.java + src/com/oracle/js/parser/ir/ImportNode.java + src/com/oracle/js/parser/ir/ImportSpecifierNode.java + src/com/oracle/js/parser/ir/IndexNode.java + src/com/oracle/js/parser/ir/JoinPredecessorExpression.java + src/com/oracle/js/parser/ir/JsxAttributeNode.java + src/com/oracle/js/parser/ir/JsxElementNode.java + src/com/oracle/js/parser/ir/JumpStatement.java + src/com/oracle/js/parser/ir/Label.java + src/com/oracle/js/parser/ir/LabelNode.java + src/com/oracle/js/parser/ir/Labels.java + src/com/oracle/js/parser/ir/LexicalContext.java + src/com/oracle/js/parser/ir/LexicalContextExpression.java + src/com/oracle/js/parser/ir/LexicalContextNode.java + src/com/oracle/js/parser/ir/LexicalContextStatement.java + src/com/oracle/js/parser/ir/LiteralNode.java + src/com/oracle/js/parser/ir/LoopNode.java + src/com/oracle/js/parser/ir/Module.java + src/com/oracle/js/parser/ir/NameSpaceImportNode.java + src/com/oracle/js/parser/ir/NamedImportsNode.java + src/com/oracle/js/parser/ir/Node.java + src/com/oracle/js/parser/ir/ObjectNode.java + src/com/oracle/js/parser/ir/PropertyKey.java + src/com/oracle/js/parser/ir/PropertyNode.java + src/com/oracle/js/parser/ir/ReturnNode.java + src/com/oracle/js/parser/ir/RuntimeNode.java + src/com/oracle/js/parser/ir/Statement.java + src/com/oracle/js/parser/ir/SwitchNode.java + src/com/oracle/js/parser/ir/Symbol.java + src/com/oracle/js/parser/ir/Terminal.java + src/com/oracle/js/parser/ir/TernaryNode.java + src/com/oracle/js/parser/ir/ThrowNode.java + src/com/oracle/js/parser/ir/TryNode.java + src/com/oracle/js/parser/ir/UnaryNode.java + src/com/oracle/js/parser/ir/VarNode.java + src/com/oracle/js/parser/ir/WhileNode.java + src/com/oracle/js/parser/ir/WithNode.java + src/com/oracle/js/parser/ir/visitor/NodeVisitor.java + src/com/oracle/js/parser/ir/visitor/TranslatorNodeVisitor.java + src/com/oracle/js/parser/package-info.java + src/com/oracle/js/parser/resources/Messages.properties + test/unit/src/com/oracle/js/parser/DumpingVisitor.java + test/unit/src/com/oracle/js/parser/ManualTest.java + test/unit/src/com/oracle/js/parser/ParserTest.java + + This JS parser is a fork from graaljs. + + diff --git a/webcommon/libs.nashorn/nbproject/project.properties b/webcommon/libs.nashorn/nbproject/project.properties index 3f38055691ff..1f187157c6a8 100644 --- a/webcommon/libs.nashorn/nbproject/project.properties +++ b/webcommon/libs.nashorn/nbproject/project.properties @@ -17,6 +17,4 @@ javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial -is.autoload=true - -release.external/graaljs-parser-1.2.jar=modules/ext/graaljs-parser-1.2.jar \ No newline at end of file +is.autoload=true \ No newline at end of file diff --git a/webcommon/libs.nashorn/nbproject/project.xml b/webcommon/libs.nashorn/nbproject/project.xml index 23b760ee774e..ee1135052ee5 100644 --- a/webcommon/libs.nashorn/nbproject/project.xml +++ b/webcommon/libs.nashorn/nbproject/project.xml @@ -25,13 +25,18 @@ org.netbeans.libs.nashorn + + + unit + + org.netbeans.libs.junit4 + + + + com.oracle.js.parser - - ext/graaljs-parser-1.2.jar - external/graaljs-parser-1.2.jar - diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/AbstractParser.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/AbstractParser.java new file mode 100644 index 000000000000..8476bb9d6fc4 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/AbstractParser.java @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenType.COMMENT; +import static com.oracle.js.parser.TokenType.DIRECTIVE_COMMENT; +import static com.oracle.js.parser.TokenType.EOF; +import static com.oracle.js.parser.TokenType.EOL; +import static com.oracle.js.parser.TokenType.IDENT; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import com.oracle.js.parser.Lexer.LexerToken; +import com.oracle.js.parser.ir.IdentNode; +import com.oracle.js.parser.ir.LiteralNode; + +/** + * Base class for parsers. + */ +public abstract class AbstractParser { + /** Source to parse. */ + protected final Source source; + + /** Error manager to report errors. */ + protected final ErrorManager errors; + + /** Stream of lex tokens to parse. */ + protected TokenStream stream; + + /** Index of current token. */ + protected int k; + + /** Previous token - accessible to sub classes */ + protected long previousToken; + + /** Descriptor of current token. */ + protected long token; + + /** Type of current token. */ + protected TokenType type; + + /** Type of last token. */ + protected TokenType last; + + /** Start position of current token. */ + protected int start; + + /** Finish position of previous token. */ + protected int finish; + + /** Current line number. */ + protected int line; + + /** Position of last EOL + 1. */ + protected int linePosition; + + /** Lexer used to scan source content. */ + protected Lexer lexer; + + /** Is this parser running under strict mode? */ + protected boolean isStrictMode; + + /** Is this parser running under strong mode? */ + protected boolean isStrongMode; + + /** Is this parser parsing module? */ + protected boolean isModule; + + /** What should line numbers be counted from? */ + protected final int lineOffset; + + private final Map canonicalNames = new HashMap<>(); + + /** + * Construct a parser. + * + * @param source Source to parse. + * @param errors Error reporting manager. + * @param strict True if we are in strict mode + * @param lineOffset Offset from which lines should be counted + */ + protected AbstractParser(final Source source, final ErrorManager errors, final boolean strict, final int lineOffset) { + if (source.getLength() > Token.LENGTH_MASK) { + throw new RuntimeException("Source exceeds size limit of " + Token.LENGTH_MASK + " bytes"); + } + this.source = source; + this.errors = errors; + this.k = -1; + this.token = Token.toDesc(EOL, 0, 1); + this.type = EOL; + this.last = EOL; + this.isStrictMode = strict; + this.lineOffset = lineOffset; + } + + /** + * Get the ith token. + * + * @param i Index of token. + * + * @return the token + */ + protected final long getToken(final int i) { + // Make sure there are enough tokens available. + while (i > stream.last()) { + // If we need to buffer more for lookahead. + if (stream.isFull()) { + stream.grow(); + } + + // Get more tokens. + lexer.lexify(); + } + + return stream.get(i); + } + + // Checkstyle: stop + /** + * Return the tokenType of the ith token. + * + * @param i Index of token + * + * @return the token type + */ + protected final TokenType T(final int i) { + // Get token descriptor and extract tokenType. + return Token.descType(getToken(i)); + } + + // Checkstyle: resume + /** + * Seek next token that is not an EOL or comment. + * + * @return tokenType of next token. + */ + protected final TokenType next() { + do { + nextOrEOL(); + } while (type == EOL || type == COMMENT); + + return type; + } + + /** + * Seek next token or EOL (skipping comments.) + * + * @return tokenType of next token. + */ + protected final TokenType nextOrEOL() { + do { + nextToken(); + if (type == DIRECTIVE_COMMENT) { + checkDirectiveComment(); + } + } while (type == COMMENT || type == DIRECTIVE_COMMENT); + + return type; + } + + // sourceURL= after directive comment + private static final String SOURCE_URL_PREFIX = "sourceURL="; + + // currently only @sourceURL=foo supported + private void checkDirectiveComment() { + // if already set, ignore this one + if (source.getExplicitURL() != null) { + return; + } + + final String comment = (String) lexer.getValueOf(token, isStrictMode); + final int len = comment.length(); + // 4 characters for directive comment marker //@\s or //#\s + if (len > 4 && comment.substring(4).startsWith(SOURCE_URL_PREFIX)) { + source.setExplicitURL(comment.substring(4 + SOURCE_URL_PREFIX.length())); + } + } + + /** + * Seek next token. + * + * @return tokenType of next token. + */ + private TokenType nextToken() { + // Capture last token type, but ignore comments (which are irrelevant for the purpose of + // newline detection). + if (type != COMMENT) { + last = type; + } + if (type != EOF) { + + // Set up next token. + k++; + final long lastToken = token; + previousToken = token; + token = getToken(k); + type = Token.descType(token); + + // do this before the start is changed below + if (last != EOL) { + finish = start + Token.descLength(lastToken); + } + + if (type == EOL) { + line = Token.descLength(token); + linePosition = Token.descPosition(token); + } else { + start = Token.descPosition(token); + } + + } + + return type; + } + + /** + * Get the message string for a message ID and arguments + * + * @param msgId The Message ID + * @param args The arguments + * + * @return The message string + */ + protected static String message(final String msgId, final String... args) { + return ECMAErrors.getMessage("parser.error." + msgId, args); + } + + /** + * Report an error. + * + * @param message Error message. + * @param errorToken Offending token. + * @return ParserException upon failure. Caller should throw and not ignore + */ + protected final ParserException error(final String message, final long errorToken) { + return error(JSErrorType.SyntaxError, message, errorToken); + } + + /** + * Report an error. + * + * @param errorType The error type + * @param message Error message. + * @param errorToken Offending token. + * @return ParserException upon failure. Caller should throw and not ignore + */ + protected final ParserException error(final JSErrorType errorType, final String message, final long errorToken) { + final int position = Token.descPosition(errorToken); + final int lineNum = source.getLine(position); + final int columnNum = source.getColumn(position); + final String formatted = ErrorManager.format(message, source, lineNum, columnNum, errorToken); + return new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken); + } + + /** + * Report an error. + * + * @param message Error message. + * @return ParserException upon failure. Caller should throw and not ignore + */ + protected final ParserException error(final String message) { + return error(JSErrorType.SyntaxError, message); + } + + /** + * Report an error. + * + * @param errorType The error type + * @param message Error message. + * @return ParserException upon failure. Caller should throw and not ignore + */ + protected final ParserException error(final JSErrorType errorType, final String message) { + // TODO - column needs to account for tabs. + final int position = Token.descPosition(token); + final int column = position - linePosition; + final String formatted = ErrorManager.format(message, source, line, column, token); + return new ParserException(errorType, formatted, source, line, column, token); + } + + /** + * Report a warning to the error manager. + * + * @param errorType The error type of the warning + * @param message Warning message. + * @param errorToken error token + */ + protected final void warning(final JSErrorType errorType, final String message, final long errorToken) { + errors.warning(error(errorType, message, errorToken)); + } + + /** + * Generate 'expected' message. + * + * @param expected Expected tokenType. + * + * @return the message string + */ + protected final String expectMessage(final TokenType expected) { + final String tokenString = Token.toString(source, token); + String msg; + + if (expected == null) { + msg = AbstractParser.message("expected.stmt", tokenString); + } else { + final String expectedName = expected.getNameOrType(); + msg = AbstractParser.message("expected", expectedName, tokenString); + } + + return msg; + } + + /** + * Check current token and advance to the next token. + * + * @param expected Expected tokenType. + * + * @throws ParserException on unexpected token type + */ + protected final void expect(final TokenType expected) throws ParserException { + expectDontAdvance(expected); + next(); + } + + /** + * Check current token, but don't advance to the next token. + * + * @param expected Expected tokenType. + * + * @throws ParserException on unexpected token type + */ + protected final void expectDontAdvance(final TokenType expected) throws ParserException { + if (type != expected) { + throw error(expectMessage(expected)); + } + } + + /** + * Check next token, get its value and advance. + * + * @param expected Expected tokenType. + * @return The JavaScript value of the token + * @throws ParserException on unexpected token type + */ + protected final Object expectValue(final TokenType expected) throws ParserException { + if (type != expected) { + throw error(expectMessage(expected)); + } + + final Object value = getValue(); + + next(); + + return value; + } + + /** + * Get the value of the current token. + * + * @return JavaScript value of the token. + */ + protected final Object getValue() { + return getValue(token); + } + + /** + * Get the value of a specific token + * + * @param valueToken the token + * + * @return JavaScript value of the token + */ + protected final Object getValue(final long valueToken) { + try { + return lexer.getValueOf(valueToken, isStrictMode); + } catch (final ParserException e) { + errors.error(e); + } + + return null; + } + + /** + * Certain future reserved words can be used as identifiers in non-strict mode. Check if the + * current token is one such. + * + * @return true if non strict mode identifier + */ + protected final boolean isNonStrictModeIdent() { + return !isStrictMode && type.getKind() == TokenKind.FUTURESTRICT; + } + + /** + * Get ident. + * + * @return Ident node. + */ + protected final IdentNode getIdent() { + // Capture IDENT token. + long identToken = token; + + if (isNonStrictModeIdent()) { + // Fake out identifier. + identToken = Token.recast(token, IDENT); + // Get IDENT. + final String ident = (String) getValue(identToken); + + next(); + + // Create IDENT node. + return createIdentNode(identToken, finish, ident).setIsFutureStrictName(); + } + + // Get IDENT. + final String ident = (String) expectValue(IDENT); + if (ident == null) { + return null; + } + // Create IDENT node. + return createIdentNode(identToken, finish, ident); + } + + /** + * Creates a new {@link IdentNode} as if invoked with a + * {@link IdentNode#IdentNode(long, int, String) constructor} but making sure that the + * {@code name} is deduplicated within this parse job. + * + * @param identToken the token for the new {@code IdentNode} + * @param identFinish the finish for the new {@code IdentNode} + * @param name the name for the new {@code IdentNode}. It will be de-duplicated. + * @return a newly constructed {@code IdentNode} with the specified token, finish, and name; the + * name will be deduplicated. + */ + protected IdentNode createIdentNode(final long identToken, final int identFinish, final String name) { + final String existingName = canonicalNames.putIfAbsent(name, name); + final String canonicalName = existingName != null ? existingName : name; + return new IdentNode(identToken, identFinish, canonicalName); + } + + /** + * Check if current token is in identifier name + * + * @return true if current token is an identifier name + */ + protected final boolean isIdentifierName(long token) { + final TokenType currentType = Token.descType(token); + final TokenKind kind = currentType.getKind(); + if (kind == TokenKind.KEYWORD || kind == TokenKind.FUTURE || kind == TokenKind.FUTURESTRICT) { + return true; + } + + // only literals allowed are null, false and true + if (kind == TokenKind.LITERAL) { + switch (currentType) { + case FALSE: + case NULL: + case TRUE: + return true; + default: + return false; + } + } + + // Fake out identifier. + final long identToken = Token.recast(token, IDENT); + // Get IDENT. + final String ident = (String) getValue(identToken); + return !ident.isEmpty() && Character.isJavaIdentifierStart(ident.charAt(0)); + } + + /** + * Create an IdentNode from the current token + * + * @return an IdentNode representing the current token + */ + protected final IdentNode getIdentifierName() { + if (type == IDENT) { + return getIdent(); + } else if (isIdentifierName(token)) { + // Fake out identifier. + final long identToken = Token.recast(token, IDENT); + // Get IDENT. + final String ident = (String) getValue(identToken); + next(); + // Create IDENT node. + return createIdentNode(identToken, finish, ident); + } else { + expect(IDENT); + return null; + } + } + + /** + * Create a LiteralNode from the current token + * + * @return LiteralNode representing the current token + * @throws ParserException if any literals fails to parse + */ + protected final LiteralNode getLiteral() throws ParserException { + // Capture LITERAL token. + final long literalToken = token; + + // Create literal node. + final Object value = getValue(); + // Advance to have a correct finish + next(); + + LiteralNode node = null; + + if (value == null) { + node = LiteralNode.newInstance(literalToken, finish); + } else if (value instanceof Number) { + node = LiteralNode.newInstance(literalToken, finish, (Number) value, getNumberToStringConverter()); + } else if (value instanceof String) { + node = LiteralNode.newInstance(literalToken, finish, (String) value); + } else if (value instanceof LexerToken) { + validateLexerToken((LexerToken) value); + node = LiteralNode.newInstance(literalToken, finish, (LexerToken) value); + } else { + assert false : "unknown type for LiteralNode: " + value.getClass(); + } + + return node; + } + + /** + * Lexer token validation hook for subclasses. + * + * @param lexerToken the lexer token to validate + */ + protected void validateLexerToken(final LexerToken lexerToken) { + } + + /** + * Custom number-to-string converter used to convert numeric property names to strings. + * + * @return custom number-to-string converter or {@code null} to use the default converter + */ + protected Function getNumberToStringConverter() { + return null; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ECMAErrors.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ECMAErrors.java new file mode 100644 index 000000000000..8aa0a8b5aa90 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ECMAErrors.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +// @formatter:off +/** + * Helper class to throw various standard "ECMA error" exceptions such as Error, ReferenceError, TypeError etc. + */ +public final class ECMAErrors { + private static final String MESSAGES_RESOURCE = "com.oracle.js.parser.resources.Messages"; + + private static final ResourceBundle MESSAGES_BUNDLE; + static { + MESSAGES_BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE, Locale.getDefault()); + } + + private ECMAErrors() { + } + + /** + * Get the exception message by placing the args in the resource defined + * by the resource tag. This is visible to, e.g. the {@link com.oracle.js.parser.Parser} + * can use it to generate compile time messages with the correct locale + * + * @param msgId the resource tag (message id) + * @param args arguments to error string + * + * @return the filled out error string + */ + public static String getMessage(final String msgId, final String... args) { + try { + return new MessageFormat(MESSAGES_BUNDLE.getString(msgId)).format(args); + } catch (final MissingResourceException e) { + throw new RuntimeException("no message resource found for message id: " + msgId); + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ErrorManager.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ErrorManager.java new file mode 100644 index 000000000000..90056d405c00 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ErrorManager.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import java.io.PrintWriter; + +/** + * Handles JavaScript error reporting. + */ +public abstract class ErrorManager { + // TODO - collect and sort/collapse error messages. + // TODO - property based error messages. + /** Error count. */ + private int errors; + + /** Warning count */ + private int warnings; + + /** Limit of the number of messages. */ + private int limit; + + /** Treat warnings as errors. */ + private boolean warningsAsErrors; + + /** First thrown ParserException. */ + private ParserException parserException; + + /** + * Constructor + */ + protected ErrorManager() { + this.limit = 100; + this.warningsAsErrors = false; + } + + /** + * Check to see if number of errors exceed limit. + */ + private void checkLimit() { + int count = errors; + + if (warningsAsErrors) { + count += warnings; + } + + if (limit != 0 && count > limit) { + // throw rangeError("too.many.errors", Integer.toString(limit)); + throw new RuntimeException("too many errors"); + } + } + + /** + * Format an error message to include source and line information. + * + * @param message Error message string. + * @param source Source file information. + * @param line Source line number. + * @param column Source column number. + * @param token Offending token descriptor. + * @return formatted string + */ + public static String format(final String message, final Source source, final int line, final int column, final long token) { + final String eoln = System.lineSeparator(); + final int position = Token.descPosition(token); + final StringBuilder sb = new StringBuilder(); + + // Source description and message. + sb.append(source.getName()).append(':').append(line).append(':').append(column).append(' ').append(message).append(eoln); + + // Source content. + final String sourceLine = source.getSourceLine(position); + sb.append(sourceLine).append(eoln); + + // Pointer to column. + for (int i = 0; i < column; i++) { + if (sourceLine.charAt(i) == '\t') { + sb.append('\t'); + } else { + sb.append(' '); + } + } + + sb.append('^'); + // Use will append eoln. + // buffer.append(eoln); + + return sb.toString(); + } + + /** + * Report an error or warning message. + * + * @param message Error message string. + */ + protected void message(final String message) { + } + + /** + * Report an error using information provided by the ParserException + * + * @param e ParserException object + */ + + public void error(final ParserException e) { + if (this.parserException == null) { + this.parserException = e; + } + error(e.getMessage()); + } + + /** + * Report an error message provided + * + * @param message Error message string. + */ + public void error(final String message) { + message(message); + errors++; + checkLimit(); + } + + /** + * Report a warning using information provided by the ParserException + * + * @param e ParserException object + */ + public void warning(final ParserException e) { + warning(e.getMessage()); + } + + /** + * Report a warning message provided + * + * @param message Error message string. + */ + public void warning(final String message) { + message(message); + warnings++; + checkLimit(); + } + + /** + * Test to see if errors have occurred. + * + * @return True if errors. + */ + public boolean hasErrors() { + return errors != 0; + } + + /** + * Get the message limit + * + * @return max number of messages + */ + public int getLimit() { + return limit; + } + + /** + * Set the message limit + * + * @param limit max number of messages + */ + public void setLimit(final int limit) { + this.limit = limit; + } + + /** + * Check whether warnings should be treated like errors + * + * @return true if warnings should be treated like errors + */ + public boolean isWarningsAsErrors() { + return warningsAsErrors; + } + + /** + * Set warnings to be treated as errors + * + * @param warningsAsErrors true if warnings should be treated as errors, false otherwise + */ + public void setWarningsAsErrors(final boolean warningsAsErrors) { + this.warningsAsErrors = warningsAsErrors; + } + + /** + * Get the number of errors + * + * @return number of errors + */ + public int getNumberOfErrors() { + return errors; + } + + /** + * Get number of warnings + * + * @return number of warnings + */ + public int getNumberOfWarnings() { + return warnings; + } + + public ParserException getParserException() { + return parserException; + } + + /** + * {@link ErrorManager} that reports to a {@link PrintWriter}. + */ + public static class PrintWriterErrorManager extends ErrorManager { + /** Reporting writer. */ + private final PrintWriter writer; + + /** + * Constructor. Reports to {@link System#err}. + */ + public PrintWriterErrorManager() { + this(new PrintWriter(System.err, true)); + } + + /** + * Constructor. + * + * @param writer I/O writer to report on. + */ + public PrintWriterErrorManager(final PrintWriter writer) { + this.writer = writer; + } + + @Override + protected void message(String message) { + writer.println(message); + } + } + + /** + * {@link ErrorManager} that reports to a {@link StringBuilder}. + */ + public static class StringBuilderErrorManager extends ErrorManager { + private final StringBuilder buffer = new StringBuilder(0); + + public StringBuilderErrorManager() { + } + + @Override + protected void message(String message) { + buffer.append(message).append('\n'); + } + + public String getOutput() { + return buffer.toString(); + } + } + + /** + * ThrowErrorManager that throws ParserException upon error conditions. + */ + public static class ThrowErrorManager extends ErrorManager { + @Override + public void error(final String message) { + throw new ParserException(message); + } + + @Override + public void error(final ParserException e) { + throw e; + } + + @Override + protected void message(String message) { + System.err.println(message); + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/JSErrorType.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/JSErrorType.java new file mode 100644 index 000000000000..2039681f5c51 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/JSErrorType.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +/** + * Native ECMAScript Error types. + */ +public enum JSErrorType { + Error, + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + URIError +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/JSType.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/JSType.java new file mode 100644 index 000000000000..04fd99323c86 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/JSType.java @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + + +// @formatter:off +/** + * Representation for ECMAScript types - this maps directly to the ECMA script standard + */ +public final class JSType { + private JSType() { + } + + /** Max value for an uint32 in JavaScript */ + private static final long MAX_UINT = 0xFFFF_FFFFL; + + private static final double INT32_LIMIT = 4294967296.0; + + /** + * Returns true if double number can be represented as an int + * + * @param number a long to inspect + * + * @return true for int representable longs + */ + public static boolean isRepresentableAsInt(final long number) { + return (int) number == number; + } + + /** + * Returns true if double number can be represented as an int. Note that it returns true for negative + * zero. If you need to exclude negative zero, use {@link #isStrictlyRepresentableAsInt(double)}. + * + * @param number a double to inspect + * + * @return true for int representable doubles + */ + public static boolean isRepresentableAsInt(final double number) { + return (int) number == number; + } + + /** + * Returns true if double number can be represented as an int. Note that it returns false for negative + * zero. If you don't need to distinguish negative zero, use {@link #isRepresentableAsInt(double)}. + * + * @param number a double to inspect + * + * @return true for int representable doubles + */ + public static boolean isStrictlyRepresentableAsInt(final double number) { + return isRepresentableAsInt(number) && isNotNegativeZero(number); + } + + /** + * Returns true if Object can be represented as an int + * + * @param obj an object to inspect + * + * @return true for int representable objects + */ + public static boolean isRepresentableAsInt(final Object obj) { + if (obj instanceof Number) { + return isRepresentableAsInt(((Number) obj).doubleValue()); + } + return false; + } + + /** + * Returns true if double number can be represented as a long. Note that it returns true for negative + * zero. If you need to exclude negative zero, use {@link #isStrictlyRepresentableAsLong(double)}. + * + * @param number a double to inspect + * @return true for long representable doubles + */ + public static boolean isRepresentableAsLong(final double number) { + return (long) number == number; + } + + /** + * Returns true if double number can be represented as a long. Note that it returns false for negative + * zero. If you don't need to distinguish negative zero, use {@link #isRepresentableAsLong(double)}. + * + * @param number a double to inspect + * + * @return true for long representable doubles + */ + public static boolean isStrictlyRepresentableAsLong(final double number) { + return isRepresentableAsLong(number) && isNotNegativeZero(number); + } + + /** + * Returns true if Object can be represented as a long + * + * @param obj an object to inspect + * + * @return true for long representable objects + */ + public static boolean isRepresentableAsLong(final Object obj) { + if (obj instanceof Number) { + return isRepresentableAsLong(((Number) obj).doubleValue()); + } + return false; + } + + /** + * Returns true if the number is the negative zero ({@code -0.0d}). + * + * @param number the number to test + * @return true if it is the negative zero, false otherwise. + */ + public static boolean isNegativeZero(final double number) { + return number == 0.0d && Double.doubleToRawLongBits(number) == 0x8000000000000000L; + } + + /** + * Returns true if the number is not the negative zero ({@code -0.0d}). + * @param number the number to test + * @return true if it is not the negative zero, false otherwise. + */ + private static boolean isNotNegativeZero(final double number) { + return Double.doubleToRawLongBits(number) != 0x8000000000000000L; + } + + /** + * JavaScript compliant conversion of number to boolean + * + * @param num a number + * + * @return a boolean + */ + public static boolean toBoolean(final double num) { + return num != 0 && !Double.isNaN(num); + } + + /** + * JavaScript compliant conversion of Object to boolean See ECMA 9.2 ToBoolean + * + * @param obj an object + * + * @return a boolean + */ + public static boolean toBoolean(final Object obj) { + if (obj instanceof Boolean) { + return (Boolean) obj; + } + + if (obj == null) { + return false; + } + + if (obj instanceof Number) { + final double num = ((Number) obj).doubleValue(); + return num != 0 && !Double.isNaN(num); + } + + if (obj instanceof String) { + return ((String) obj).length() > 0; + } + + return true; + } + + /** + * JavaScript compliant conversion of number to String + * + * @param num a number + * @param radix a radix for the conversion + * + * @return a string + */ + public static String toString(final double num, final int radix) { + assert radix >= 2 && radix <= 36 : "invalid radix"; + + if (isRepresentableAsInt(num)) { + return Integer.toString((int) num, radix); + } + + if (num == Double.POSITIVE_INFINITY) { + return "Infinity"; + } + + if (num == Double.NEGATIVE_INFINITY) { + return "-Infinity"; + } + + if (Double.isNaN(num)) { + return "NaN"; + } + + if (num == 0.0) { + return "0"; + } + + final String chars = "0123456789abcdefghijklmnopqrstuvwxyz"; + final StringBuilder sb = new StringBuilder(); + + final boolean negative = num < 0.0; + final double signedNum = negative ? -num : num; + + double intPart = Math.floor(signedNum); + double decPart = signedNum - intPart; + + // encode integer part from least significant digit, then reverse + do { + final double remainder = intPart % radix; + sb.append(chars.charAt((int) remainder)); + intPart -= remainder; + intPart /= radix; + } while (intPart >= 1.0); + + if (negative) { + sb.append('-'); + } + sb.reverse(); + + // encode decimal part + if (decPart > 0.0) { + final int dot = sb.length(); + sb.append('.'); + do { + decPart *= radix; + final double d = Math.floor(decPart); + sb.append(chars.charAt((int) d)); + decPart -= d; + } while (decPart > 0.0 && sb.length() - dot < 1100); + // somewhat arbitrarily use same limit as V8 + } + + return sb.toString(); + } + + /** + * JavaScript compliant conversion of Object to number See ECMA 9.3 ToNumber + * + * @param obj an object + * + * @return a number + */ + public static double toNumber(final Object obj) { + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } + return toNumberGeneric(obj); + } + + /** + * Digit representation for a character + * + * @param ch a character + * @param radix radix + * + * @return the digit for this character + */ + public static int digit(final char ch, final int radix) { + return digit(ch, radix, false); + } + + /** + * Digit representation for a character + * + * @param ch a character + * @param radix radix + * @param onlyIsoLatin1 iso latin conversion only + * + * @return the digit for this character + */ + public static int digit(final char ch, final int radix, final boolean onlyIsoLatin1) { + final char maxInRadix = (char) ('a' + (radix - 1) - 10); + final char c = Character.toLowerCase(ch); + + if (c >= 'a' && c <= maxInRadix) { + return Character.digit(ch, radix); + } + + if (Character.isDigit(ch)) { + if (!onlyIsoLatin1 || ch >= '0' && ch <= '9') { + return Character.digit(ch, radix); + } + } + + return -1; + } + + /** + * JavaScript compliant String to number conversion + * + * @param str a string + * + * @return a number + */ + public static double toNumber(final String str) { + int end = str.length(); + if (end == 0) { + return 0.0; // Empty string + } + + int start = 0; + char f = str.charAt(0); + + while (Lexer.isJSWhitespace(f)) { + if (++start == end) { + return 0.0d; // All whitespace string + } + f = str.charAt(start); + } + + // Guaranteed to terminate even without start >= end check, as the previous loop found at + // least one non-whitespace character. + while (Lexer.isJSWhitespace(str.charAt(end - 1))) { + end--; + } + + final boolean negative; + if (f == '-') { + if (++start == end) { + return Double.NaN; // Single-char "-" string + } + f = str.charAt(start); + negative = true; + } else { + if (f == '+') { + if (++start == end) { + return Double.NaN; // Single-char "+" string + } + f = str.charAt(start); + } + negative = false; + } + + final double value; + if (start + 1 < end && f == '0' && Character.toLowerCase(str.charAt(start + 1)) == 'x') { + // decode hex string + value = parseRadix(str.toCharArray(), start + 2, end, 16); + } else if (f == 'I' && end - start == 8 && str.regionMatches(start, "Infinity", 0, 8)) { + return negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } else { + // Fast (no NumberFormatException) path to NaN for non-numeric strings. + for (int i = start; i < end; i++) { + f = str.charAt(i); + if ((f < '0' || f > '9') && f != '.' && f != 'e' && f != 'E' && f != '+' && f != '-') { + return Double.NaN; + } + } + try { + value = Double.parseDouble(str.substring(start, end)); + } catch (final NumberFormatException e) { + return Double.NaN; + } + } + + return negative ? -value : value; + } + + /** + * Converts an Object to long. + * + *

+ * Note that this returns {@link java.lang.Long#MAX_VALUE} or {@link java.lang.Long#MIN_VALUE} + * for double values that exceed the long range, including positive and negative Infinity. It is + * the caller's responsibility to handle such values correctly. + *

+ * + * @param obj an object + * @return a long + */ + public static long toLong(final Object obj) { + return obj instanceof Long ? ((Long)obj) : toLong(toNumber(obj)); + } + + /** + * Converts a double to long. + * + * @param num the double to convert + * @return the converted long value + */ + public static long toLong(final double num) { + return (long) num; + } + + /** + * JavaScript compliant Object to int32 conversion See ECMA 9.5 ToInt32 + * + * @param obj an object + * @return an int32 + */ + public static int toInt32(final Object obj) { + return toInt32(toNumber(obj)); + } + + // Minimum and maximum range between which every long value can be precisely represented as a double. + private static final long MAX_PRECISE_DOUBLE = 1L << 53; + private static final long MIN_PRECISE_DOUBLE = -MAX_PRECISE_DOUBLE; + + /** + * JavaScript compliant long to int32 conversion + * + * @param num a long + * @return an int32 + */ + public static int toInt32(final long num) { + return (int) (num >= MIN_PRECISE_DOUBLE && num <= MAX_PRECISE_DOUBLE ? num : (long) (num % INT32_LIMIT)); + } + + /** + * JavaScript compliant number to int32 conversion + * + * @param num a number + * @return an int32 + */ + public static int toInt32(final double num) { + return (int) doubleToInt32(num); + } + + /** + * JavaScript compliant Object to uint32 conversion + * + * @param obj an object + * @return a uint32 + */ + public static long toUint32(final Object obj) { + return toUint32(toNumber(obj)); + } + + /** + * JavaScript compliant number to uint32 conversion + * + * @param num a number + * @return a uint32 + */ + public static long toUint32(final double num) { + return doubleToInt32(num) & MAX_UINT; + } + + /** + * JavaScript compliant Object to uint16 conversion ECMA 9.7 ToUint16: (Unsigned 16 Bit Integer) + * + * @param obj an object + * @return a uint16 + */ + public static int toUint16(final Object obj) { + return toUint16(toNumber(obj)); + } + + /** + * JavaScript compliant number to uint16 conversion + * + * @param num a number + * @return a uint16 + */ + public static int toUint16(final int num) { + return num & 0xffff; + } + + /** + * JavaScript compliant number to uint16 conversion + * + * @param num a number + * @return a uint16 + */ + public static int toUint16(final long num) { + return (int) num & 0xffff; + } + + /** + * JavaScript compliant number to uint16 conversion + * + * @param num a number + * @return a uint16 + */ + public static int toUint16(final double num) { + return (int) doubleToInt32(num) & 0xffff; + } + + private static long doubleToInt32(final double num) { + final int exponent = Math.getExponent(num); + if (exponent < 31) { + return (long) num; // Fits into 32 bits + } + if (exponent >= 84) { + // Either infinite or NaN or so large that shift / modulo will produce 0 + // (52 bit mantissa + 32 bit target width). + return 0; + } + // This is rather slow and could probably be sped up using bit-fiddling. + final double d = num >= 0 ? Math.floor(num) : Math.ceil(num); + return (long) (d % INT32_LIMIT); + } + + /** + * Check whether a number is finite + * + * @param num a number + * @return true if finite + */ + public static boolean isFinite(final double num) { + return !Double.isInfinite(num) && !Double.isNaN(num); + } + + private static double parseRadix(final char[] chars, final int start, final int length, final int radix) { + int pos = 0; + + for (int i = start; i < length; i++) { + if (digit(chars[i], radix) == -1) { + return Double.NaN; + } + pos++; + } + + if (pos == 0) { + return Double.NaN; + } + + double value = 0.0; + for (int i = start; i < start + pos; i++) { + value *= radix; + value += digit(chars[i], radix); + } + + return value; + } + + private static double toNumberGeneric(final Object obj) { + if (obj == null) { + return +0.0; + } + + if (obj instanceof String) { + return toNumber((String) obj); + } + + if (obj instanceof Boolean) { + return (Boolean) obj ? 1 : +0.0; + } + + return Double.NaN; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Lexer.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Lexer.java new file mode 100644 index 000000000000..defb07a362be --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Lexer.java @@ -0,0 +1,2333 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenType.ADD; +import static com.oracle.js.parser.TokenType.BIGINT; +import static com.oracle.js.parser.TokenType.BINARY_NUMBER; +import static com.oracle.js.parser.TokenType.COMMENT; +import static com.oracle.js.parser.TokenType.DECIMAL; +import static com.oracle.js.parser.TokenType.DIRECTIVE_COMMENT; +import static com.oracle.js.parser.TokenType.EOF; +import static com.oracle.js.parser.TokenType.EOL; +import static com.oracle.js.parser.TokenType.ERROR; +import static com.oracle.js.parser.TokenType.ESCSTRING; +import static com.oracle.js.parser.TokenType.EXECSTRING; +import static com.oracle.js.parser.TokenType.FLOATING; +import static com.oracle.js.parser.TokenType.FUNCTION; +import static com.oracle.js.parser.TokenType.HEXADECIMAL; +import static com.oracle.js.parser.TokenType.LBRACE; +import static com.oracle.js.parser.TokenType.LPAREN; +import static com.oracle.js.parser.TokenType.OCTAL; +import static com.oracle.js.parser.TokenType.OCTAL_LEGACY; +import static com.oracle.js.parser.TokenType.RBRACE; +import static com.oracle.js.parser.TokenType.REGEX; +import static com.oracle.js.parser.TokenType.RPAREN; +import static com.oracle.js.parser.TokenType.STRING; +import static com.oracle.js.parser.TokenType.TEMPLATE; +import static com.oracle.js.parser.TokenType.TEMPLATE_HEAD; +import static com.oracle.js.parser.TokenType.TEMPLATE_MIDDLE; +import static com.oracle.js.parser.TokenType.TEMPLATE_TAIL; +import static com.oracle.js.parser.TokenType.XML; + +import java.math.BigInteger; +import java.util.ArrayDeque; +import java.util.Deque; + +// @formatter:off +/** + * Responsible for converting source content into a stream of tokens. + * + */ +@SuppressWarnings("fallthrough") +public class Lexer extends Scanner { + private static final long MIN_INT_L = Integer.MIN_VALUE; + private static final long MAX_INT_L = Integer.MAX_VALUE; + + private static final boolean XML_LITERALS = false; + + /** Content source. */ + private final Source source; + + /** Buffered stream for tokens. */ + private final TokenStream stream; + + /** True if here and edit strings are supported. */ + private final boolean scripting; + + /** True if shebang is supported. */ + private final boolean shebang; + + /** ecmascript edition to support*/ + private final int ecmascriptEdition; + + /** True if parsing JSX. */ + private final boolean jsx; + + /** True if a nested scan. (scan to completion, no EOF.) */ + private final boolean nested; + + /** Pending new line number and position. */ + int pendingLine; + + /** Position of last EOL + 1. */ + private int linePosition; + + /** Type of last token added. */ + private TokenType last; + + private final boolean pauseOnFunctionBody; + private boolean pauseOnNextLeftBrace; + + private int jsxTagCount; + + private boolean jsxTag; + + private boolean jsxClosing; + + private boolean template; + + private boolean templateExpression; + + private int nextStateChange; + + private int openExpressionBraces; + + private final Deque innerStates = new ArrayDeque<>(); + + private static final String SPACETAB = " \t"; // ASCII space and tab + private static final String LFCR = "\n\r"; // line feed and carriage return (ctrl-m) + + private static final String JSON_WHITESPACE_EOL = LFCR; + private static final String JSON_WHITESPACE = SPACETAB + LFCR; + + private static final String JAVASCRIPT_WHITESPACE_EOL = + LFCR + + "\u2028" + // line separator + "\u2029" // paragraph separator + ; + private static final String JAVASCRIPT_WHITESPACE = + SPACETAB + + JAVASCRIPT_WHITESPACE_EOL + + "\u000b" + // tabulation line + "\u000c" + // ff (ctrl-l) + "\u00a0" + // Latin-1 space + "\u1680" + // Ogham space mark + "\u180e" + // separator, Mongolian vowel + "\u2000" + // en quad + "\u2001" + // em quad + "\u2002" + // en space + "\u2003" + // em space + "\u2004" + // three-per-em space + "\u2005" + // four-per-em space + "\u2006" + // six-per-em space + "\u2007" + // figure space + "\u2008" + // punctuation space + "\u2009" + // thin space + "\u200a" + // hair space + "\u202f" + // narrow no-break space + "\u205f" + // medium mathematical space + "\u3000" + // ideographic space + "\ufeff" // byte order mark + ; + + private static final String JAVASCRIPT_WHITESPACE_IN_REGEXP = + "\\u000a" + // line feed + "\\u000d" + // carriage return (ctrl-m) + "\\u2028" + // line separator + "\\u2029" + // paragraph separator + "\\u0009" + // tab + "\\u0020" + // ASCII space + "\\u000b" + // tabulation line + "\\u000c" + // ff (ctrl-l) + "\\u00a0" + // Latin-1 space + "\\u1680" + // Ogham space mark + "\\u180e" + // separator, Mongolian vowel + "\\u2000" + // en quad + "\\u2001" + // em quad + "\\u2002" + // en space + "\\u2003" + // em space + "\\u2004" + // three-per-em space + "\\u2005" + // four-per-em space + "\\u2006" + // six-per-em space + "\\u2007" + // figure space + "\\u2008" + // punctuation space + "\\u2009" + // thin space + "\\u200a" + // hair space + "\\u202f" + // narrow no-break space + "\\u205f" + // medium mathematical space + "\\u3000" + // ideographic space + "\\ufeff" // byte order mark + ; + + public static String unicodeEscape(final char ch) { + final StringBuilder sb = new StringBuilder(); + + sb.append("\\u"); + + final String hex = Integer.toHexString(ch); + for (int i = hex.length(); i < 4; i++) { + sb.append('0'); + } + sb.append(hex); + + return sb.toString(); + } + + /** + * Constructor + * + * @param source the source + * @param stream the token stream to lex + */ + public Lexer(final Source source, final TokenStream stream) { + this(source, stream, false, 5, false, false); + } + + /** + * Constructor + * + * @param source the source + * @param stream the token stream to lex + * @param scripting are we in scripting mode + * @param ecmascriptEdition are we in ECMAScript 6 mode + * @param shebang do we support shebang + */ + public Lexer(final Source source, final TokenStream stream, final boolean scripting, final int ecmascriptEdition, final boolean shebang, final boolean jsx) { + this(source, 0, source.getLength(), stream, scripting, ecmascriptEdition, shebang, false, jsx); + } + + /** + * Constructor + * + * @param source the source + * @param start start position in source from which to start lexing + * @param len length of source segment to lex + * @param stream token stream to lex + * @param scripting are we in scripting mode + * @param ecmascriptEdition are we in ECMAScript 6 mode + * @param shebang do we support shebang + * @param pauseOnFunctionBody if true, lexer will return from {@link #lexify()} when it encounters a + * function body. This is used with the feature where the parser is skipping nested function bodies to + * avoid reading ahead unnecessarily when we skip the function bodies. + */ + public Lexer(final Source source, final int start, final int len, final TokenStream stream, final boolean scripting, final int ecmascriptEdition, final boolean shebang, final boolean pauseOnFunctionBody, final boolean jsx) { + super(source.getContent(), 1, start, len); + this.source = source; + this.stream = stream; + this.scripting = scripting; + this.ecmascriptEdition = ecmascriptEdition; + this.jsx = jsx; + this.shebang = shebang; + this.nested = false; + this.pendingLine = 1; + this.last = EOL; + + this.pauseOnFunctionBody = pauseOnFunctionBody; + } + + private Lexer(final Lexer lexer, final State state) { + super(lexer, state); + + source = lexer.source; + stream = lexer.stream; + scripting = lexer.scripting; + ecmascriptEdition = lexer.ecmascriptEdition; + jsx = lexer.jsx; + shebang = lexer.shebang; + nested = true; + + pendingLine = state.pendingLine; + linePosition = state.linePosition; + last = EOL; + pauseOnFunctionBody = false; + } + + static class State extends Scanner.State { + /** Pending new line number and position. */ + public final int pendingLine; + + /** Position of last EOL + 1. */ + public final int linePosition; + + /** Type of last token added. */ + public final TokenType last; + + /* + * Constructor. + */ + + State(final int position, final int limit, final int line, + final int pendingLine, final int linePosition, final TokenType last) { + super(position, limit, line); + + this.pendingLine = pendingLine; + this.linePosition = linePosition; + this.last = last; + } + } + + /** + * Save the state of the scan. + * + * @return Captured state. + */ + @Override + State saveState() { + return new State(position, limit, line, pendingLine, linePosition, last); + } + + /** + * Restore the state of the scan. + * + * @param state + * Captured state. + */ + void restoreState(final State state) { + super.restoreState(state); + + pendingLine = state.pendingLine; + linePosition = state.linePosition; + last = state.last; + } + + /** + * Add a new token to the stream. + * + * @param type + * Token type. + * @param start + * Start position. + * @param end + * End position. + */ + protected void add(final TokenType type, final int start, final int end) { + // Record last token. + last = type; + + // Only emit the last EOL in a cluster. + if (type == EOL) { + pendingLine = end; + linePosition = start; + } else { + // Write any pending EOL to stream. + if (pendingLine != -1) { + stream.put(Token.toDesc(EOL, linePosition, pendingLine)); + pendingLine = -1; + } + + // Write token to stream. + stream.put(Token.toDesc(type, start, end - start)); + } + } + + /** + * Add a new token to the stream. + * + * @param type + * Token type. + * @param start + * Start position. + */ + protected void add(final TokenType type, final int start) { + add(type, start, position); + } + + /** + * Return the String of valid whitespace characters for regular + * expressions in JavaScript + * @return regexp whitespace string + */ + public static String getWhitespaceRegExp() { + return JAVASCRIPT_WHITESPACE_IN_REGEXP; + } + + /** + * Skip end of line. + * + * @param addEOL true if EOL token should be recorded. + */ + private void skipEOL(final boolean addEOL) { + + if (ch0 == '\r') { // detect \r\n pattern + skip(1); + if (ch0 == '\n') { + skip(1); + } + } else { // all other space, ch0 is guaranteed to be EOL or \0 + skip(1); + } + + // bump up line count + line++; + + if (addEOL) { + // Add an EOL token. + add(EOL, position, line); + } + } + + /** + * Skip over rest of line including end of line. + * + * @param addEOL true if EOL token should be recorded. + */ + private void skipLine(final boolean addEOL) { + // Ignore characters. + while (!isEOL(ch0) && !atEOF()) { + skip(1); + } + // Skip over end of line. + skipEOL(addEOL); + } + + /** + * Test whether a char is valid JavaScript whitespace + * @param ch a char + * @return true if valid JavaScript whitespace + */ + public static boolean isJSWhitespace(final char ch) { + return JAVASCRIPT_WHITESPACE.indexOf(ch) != -1; + } + + /** + * Test whether a char is valid JavaScript end of line + * @param ch a char + * @return true if valid JavaScript end of line + */ + public static boolean isJSEOL(final char ch) { + return JAVASCRIPT_WHITESPACE_EOL.indexOf(ch) != -1; + } + + /** + * Test whether a char is valid JSON whitespace + * @param ch a char + * @return true if valid JSON whitespace + */ + public static boolean isJsonWhitespace(final char ch) { + return JSON_WHITESPACE.indexOf(ch) != -1; + } + + /** + * Test whether a char is valid JSON end of line + * @param ch a char + * @return true if valid JSON end of line + */ + public static boolean isJsonEOL(final char ch) { + return JSON_WHITESPACE_EOL.indexOf(ch) != -1; + } + + + /** + * Test if char is a string delimiter, e.g. '\' or '"'. + * @param ch a char + * @return true if string delimiter + */ + protected boolean isStringDelimiter(final char ch) { + return ch == '\'' || ch == '"'; + } + + /** + * Test if char is a template literal delimiter ('`'). + */ + private static boolean isTemplateDelimiter(char ch) { + return ch == '`'; + } + + /** + * Test whether a char is valid JavaScript whitespace + * @param ch a char + * @return true if valid JavaScript whitespace + */ + protected boolean isWhitespace(final char ch) { + return Lexer.isJSWhitespace(ch); + } + + /** + * Test whether a char is valid JavaScript end of line + * @param ch a char + * @return true if valid JavaScript end of line + */ + protected boolean isEOL(final char ch) { + return Lexer.isJSEOL(ch); + } + + /** + * Skip over whitespace and detect end of line, adding EOL tokens if + * encountered. + * + * @param addEOL true if EOL tokens should be recorded. + */ + private void skipWhitespace(final boolean addEOL) { + while (isWhitespace(ch0)) { + if (isEOL(ch0)) { + skipEOL(addEOL); + } else { + skip(1); + } + } + } + + /** + * Skip over comments. + * + * @return True if a comment. + */ + protected boolean skipComments() { + // Save the current position. + final int start = position; + + if (ch0 == '/') { + // Is it a // comment. + if (ch1 == '/') { + // Skip over //. + skip(2); + + boolean directiveComment = false; + if ((ch0 == '#' || ch0 == '@') && (ch1 == ' ')) { + directiveComment = true; + } + + // Scan for EOL. + while (!atEOF() && !isEOL(ch0)) { + skip(1); + } + // Did detect a comment. + add(directiveComment ? DIRECTIVE_COMMENT : COMMENT, start); + return true; + } else if (ch1 == '*') { + // Skip over /*. + skip(2); + // Scan for */. + while (!atEOF() && !(ch0 == '*' && ch1 == '/')) { + // If end of line handle else skip character. + if (isEOL(ch0)) { + skipEOL(true); + } else { + skip(1); + } + } + + if (atEOF()) { + // TODO - Report closing */ missing in parser. + add(ERROR, start); + } else { + // Skip */. + skip(2); + } + + // Did detect a comment. + add(COMMENT, start); + return true; + } + } else if (ch0 == '#') { + assert scripting; + // shell style comment + // Skip over #. + skip(1); + // Scan for EOL. + while (!atEOF() && !isEOL(ch0)) { + skip(1); + } + // Did detect a comment. + add(COMMENT, start); + return true; + } + + // Not a comment. + return false; + } + + /** + * Convert a regex token to a token object. + * + * @param start Position in source content. + * @param length Length of regex token. + * @return Regex token object. + */ + public RegexToken valueOfPattern(final int start, final int length) { + // Save the current position. + final int savePosition = position; + // Reset to beginning of content. + reset(start); + // Buffer for recording characters. + final StringBuilder sb = new StringBuilder(length); + + // Skip /. + skip(1); + boolean inBrackets = false; + // Scan for closing /, stopping at end of line. + while (!atEOF() && ch0 != '/' && !isEOL(ch0) || inBrackets) { + // Skip over escaped character. + if (ch0 == '\\') { + sb.append(ch0); + sb.append(ch1); + skip(2); + } else { + if (ch0 == '[') { + inBrackets = true; + } else if (ch0 == ']') { + inBrackets = false; + } + + // Skip literal character. + sb.append(ch0); + skip(1); + } + } + + // Get pattern as string. + final String regex = sb.toString(); + + // Skip /. + skip(1); + + // Options as string. + final String options = source.getString(position, scanIdentifier()); + + reset(savePosition); + + // Compile the pattern. + return new RegexToken(regex, options); + } + + /** + * Return true if the given token can be the beginning of a literal. + * + * @param token a token + * @return true if token can start a literal. + */ + public boolean canStartLiteral(final TokenType token) { + return token.startsWith('/') + || ((scripting || XML_LITERALS) && token.startsWith('<')) + || (jsx && token.startsWith('<')); + } + + /** + * interface to receive line information for multi-line literals. + */ + protected interface LineInfoReceiver { + /** + * Receives line information + * @param line last line number + * @param linePosition position of last line + */ + void lineInfo(int line, int linePosition); + } + + /** + * Check whether the given token represents the beginning of a literal. If so scan + * the literal and return {@code true}, otherwise return false. + * + * @param token the token. + * @param startTokenType the token type. + * @param lir LineInfoReceiver that receives line info for multi-line string literals. + * @return True if a literal beginning with startToken was found and scanned. + */ + protected boolean scanLiteral(final long token, final TokenType startTokenType, final LineInfoReceiver lir) { + // Check if it can be a literal. + if (!canStartLiteral(startTokenType)) { + return false; + } + // We break on ambiguous tokens so if we already moved on it can't be a literal. + if (stream.get(stream.last()) != token) { + return false; + } + // Rewind to token start position + reset(Token.descPosition(token)); + + if (ch0 == '/') { + return scanRegEx(); + } else if (ch0 == '<') { + if (ch1 == '<') { + return scanHereString(lir); + } else if (Character.isJavaIdentifierStart(ch1)) { + return scanXMLLiteral(); + } + } + + return false; + } + + protected boolean scanJsx(final long token, final TokenType startTokenType) { + // Check if it can be a literal. + if (!startTokenType.startsWith('<')) { + return false; + } + // We break on ambiguous tokens so if we already moved on it can't be a literal. + if (stream.get(stream.last()) != token) { + return false; + } + // Rewind to token start position + reset(Token.descPosition(token)); + + if (ch0 == '<' && ch1 != '<') { + jsxTagCount = 1; + jsxTag = true; + skip(1); + return true; + } + return false; + } + + /** + * Scan over regex literal. + * + * @return True if a regex literal. + */ + private boolean scanRegEx() { + assert ch0 == '/'; + // Make sure it's not a comment. + if (ch1 != '/' && ch1 != '*') { + // Record beginning of literal. + final int start = position; + // Skip /. + skip(1); + boolean inBrackets = false; + + // Scan for closing /, stopping at end of line. + while (!atEOF() && (ch0 != '/' || inBrackets) && !isEOL(ch0)) { + // Skip over escaped character. + if (ch0 == '\\') { + skip(1); + if (isEOL(ch0)) { + reset(start); + return false; + } + skip(1); + } else { + if (ch0 == '[') { + inBrackets = true; + } else if (ch0 == ']') { + inBrackets = false; + } + + // Skip literal character. + skip(1); + } + } + + // If regex literal. + if (ch0 == '/') { + // Skip /. + skip(1); + + // Skip over options. + while (!atEOF() && Character.isJavaIdentifierPart(ch0) || ch0 == '\\' && ch1 == 'u') { + skip(1); + } + + // Add regex token. + add(REGEX, start); + // Regex literal detected. + return true; + } + + // False start try again. + reset(start); + } + + // Regex literal not detected. + return false; + } + + /** + * Convert a digit to a integer. Can't use Character.digit since we are + * restricted to ASCII by the spec. + * + * @param ch Character to convert. + * @param base Numeric base. + * + * @return The converted digit or -1 if invalid. + */ + protected static int convertDigit(final char ch, final int base) { + int digit; + + if ('0' <= ch && ch <= '9') { + digit = ch - '0'; + } else if ('A' <= ch && ch <= 'Z') { + digit = ch - 'A' + 10; + } else if ('a' <= ch && ch <= 'z') { + digit = ch - 'a' + 10; + } else { + return -1; + } + + return digit < base ? digit : -1; + } + + + /** + * Get the value of a hexadecimal numeric sequence. + * + * @param length Number of digits. + * @param type Type of token to report against. + * @return Value of sequence or < 0 if no digits. + */ + private int hexSequence(final int length, final TokenType type) { + int value = 0; + + for (int i = 0; i < length; i++) { + final int digit = convertDigit(ch0, 16); + + if (digit == -1) { + error(Lexer.message("invalid.hex"), type, position, limit); + return i == 0 ? -1 : value; + } + + value = digit | value << 4; + skip(1); + } + + return value; + } + + /** + * Get the value of a variable-length hexadecimal numeric sequence delimited by curly braces. + * + * @param type Type of token to report against. + * @return Value of sequence or < 0 if no digits. + */ + private int varlenHexSequence(final TokenType type) { + assert ch0 == '{'; + skip(1); + + int value = 0; + + for (int i = 0; !atEOF(); i++) { + if (ch0 == '}') { + if (i != 0) { + skip(1); + return value; + } else { + error(Lexer.message("invalid.hex"), type, position, limit); + skip(1); + return -1; + } + } + + final int digit = convertDigit(ch0, 16); + + if (digit == -1) { + error(Lexer.message("invalid.hex"), type, position, limit); + return i == 0 ? -1 : value; + } + + value = digit | value << 4; + + if (value > 1114111) { + error(Lexer.message("invalid.hex"), type, position, limit); + return -1; + } + + skip(1); + } + + return value; + } + + /** + * Get the value of a UnicodeEscapeSequence ('u' already scanned). + * + * @param type Type of token to report against. + * @return Value of sequence or < 0 if no digits. + */ + private int unicodeEscapeSequence(final TokenType type) { + if (ch0 == '{' && ecmascriptEdition >= 6) { + return varlenHexSequence(type); + } else { + return hexSequence(4, type); + } + } + + /** + * Get the value of an octal numeric sequence. This parses up to 3 digits with a maximum value of 255. + * + * @return Value of sequence. + */ + private int octalSequence() { + int value = 0; + + for (int i = 0; i < 3; i++) { + final int digit = convertDigit(ch0, 8); + + if (digit == -1) { + break; + } + value = digit | value << 3; + skip(1); + + if (i == 1 && value >= 32) { + break; + } + } + return value; + } + + /** + * Convert a string to a JavaScript identifier. + * + * @param start Position in source content. + * @param length Length of token. + * @return Ident string or null if an error. + */ + private String valueOfIdent(final int start, final int length) throws RuntimeException { + // Save the current position. + final int savePosition = position; + // End of scan. + final int end = start + length; + // Reset to beginning of content. + reset(start); + // Buffer for recording characters. + final StringBuilder sb = new StringBuilder(length); + + // Scan until end of line or end of file. + while (!atEOF() && position < end && !isEOL(ch0)) { + // If escape character. + if (ch0 == '\\' && ch1 == 'u') { + skip(2); + final int ch = unicodeEscapeSequence(TokenType.IDENT); + if (Character.isBmpCodePoint(ch) && isWhitespace((char)ch)) { + return null; + } + if (ch < 0) { + sb.append('\\'); + sb.append('u'); + } else { + sb.appendCodePoint(ch); + } + } else { + // Add regular character. + sb.append(ch0); + skip(1); + } + } + + // Restore position. + reset(savePosition); + + return sb.toString(); + } + + /** + * Scan over and identifier or keyword. Handles identifiers containing + * encoded Unicode chars. + * + * Example: + * + * var \u0042 = 44; + */ + private void scanIdentifierOrKeyword() { + // Record beginning of identifier. + final int start = position; + // Scan identifier. + final int length = scanIdentifier(); + // Check to see if it is a keyword. + final TokenType type = TokenLookup.lookupKeyword(content, start, length); + if (type == FUNCTION && pauseOnFunctionBody) { + pauseOnNextLeftBrace = true; + } + // Add keyword or identifier token. + add(type, start); + } + + /** + * Convert a string to a JavaScript string object. + * + * @param start Position in source content. + * @param length Length of token. + * @return JavaScript string object. + */ + private String valueOfString(final int start, final int length, final boolean strict) { + // Save the current position. + final int savePosition = position; + // Calculate the end position. + final int end = start + length; + // Reset to beginning of string. + reset(start); + + // Buffer for recording characters. + final StringBuilder sb = new StringBuilder(length); + + // Scan until end of string. + while (position < end) { + // If escape character. + if (ch0 == '\\') { + skip(1); + + final char next = ch0; + final int afterSlash = position; + + skip(1); + + // Special characters. + switch (next) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + if (strict) { + // "\0" itself is allowed in strict mode. Only other 'real' + // octal escape sequences are not allowed (eg. "\02", "\31"). + // See section 7.8.4 String literals production EscapeSequence + if (next != '0' || (ch0 >= '0' && ch0 <= '9')) { + error(Lexer.message("strict.no.octal"), STRING, position, limit); + } + } + reset(afterSlash); + // Octal sequence. + final int ch = octalSequence(); + + if (ch < 0) { + sb.append('\\'); + sb.append('x'); + } else { + sb.append((char)ch); + } + break; + } + case 'n': + sb.append('\n'); + break; + case 't': + sb.append('\t'); + break; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case '\'': + sb.append('\''); + break; + case '\"': + sb.append('\"'); + break; + case '\\': + sb.append('\\'); + break; + case '\r': // CR | CRLF + if (ch0 == '\n') { + skip(1); + } + // fall through + case '\n': // LF + case '\u2028': // LS + case '\u2029': // PS + // continue on the next line, slash-return continues string + // literal + break; + case 'x': { + // Hex sequence. + final int ch = hexSequence(2, STRING); + + if (ch < 0) { + sb.append('\\'); + sb.append('x'); + } else { + sb.append((char)ch); + } + break; + } + case 'u': { + // Unicode sequence. + final int ch = unicodeEscapeSequence(STRING); + + if (ch < 0) { + sb.append('\\'); + sb.append('u'); + } else { + sb.appendCodePoint(ch); + } + break; + } + case 'v': + sb.append('\u000B'); + break; + // All other characters. + default: + sb.append(next); + break; + } + } else if (ch0 == '\r') { + // Convert CR-LF or CR to LF line terminator. + sb.append('\n'); + skip(ch1 == '\n' ? 2 : 1); + } else { + // Add regular character. + sb.append(ch0); + skip(1); + } + } + + // Restore position. + reset(savePosition); + + return sb.toString(); + } + + /** + * Scan over a string literal. + * @param add true if we are not just scanning but should actually modify the token stream + */ + protected void scanString(final boolean add) { + // Type of string. + TokenType type = STRING; + // Record starting quote. + final char quote = ch0; + // Skip over quote. + skip(1); + + // Record beginning of string content. + final State stringState = saveState(); + + // Scan until close quote or end of line. + while (!atEOF() && ch0 != quote && !isEOL(ch0)) { + // Skip over escaped character. + if (ch0 == '\\') { + type = ESCSTRING; + skip(1); + if (!isEscapeCharacter(ch0)) { + error(Lexer.message("invalid.escape.char"), STRING, position, limit); + } + if (isEOL(ch0)) { + // Multiline string literal + skipEOL(false); + continue; + } + } + // Skip literal character. + skip(1); + } + + // If close quote. + if (ch0 == quote) { + // Skip close quote. + skip(1); + } else { + error(Lexer.message("missing.close.quote"), STRING, position, limit); + } + + // If not just scanning. + if (add) { + // Record end of string. + stringState.setLimit(position - 1); + + if (scripting && !stringState.isEmpty()) { + switch (quote) { + case '`': + // Mark the beginning of an exec string. + add(EXECSTRING, stringState.position, stringState.limit); + // Frame edit string with left brace. + add(LBRACE, stringState.position, stringState.position); + // Process edit string. + editString(type, stringState); + // Frame edit string with right brace. + add(RBRACE, stringState.limit, stringState.limit); + break; + case '"': + // Only edit double quoted strings. + editString(type, stringState); + break; + case '\'': + // Add string token without editing. + add(type, stringState.position, stringState.limit); + break; + default: + break; + } + } else { + /// Add string token without editing. + add(type, stringState.position, stringState.limit); + } + } + } + + /** + * Is the given character a valid escape char after "\" ? + * + * @param ch character to be checked + * @return if the given character is valid after "\" + */ + protected boolean isEscapeCharacter(final char ch) { + return true; + } + + /** + * Convert string to number. + * + * @param valueString String to convert. + * @param radix Numeric base. + * @return Converted number. + */ + private static Number valueOf(final String valueString, final int radix) throws NumberFormatException { + try { + final long value = Long.parseLong(valueString, radix); + if (value >= MIN_INT_L && value <= MAX_INT_L) { + return (int)value; + } + return value; + } catch (final NumberFormatException e) { + if (radix == 10) { + return Double.valueOf(valueString); + } + // (CWirth) added by Oracle Labs Graal.js + if (radix == 16 && valueString.length() >= 15) { + //special case to parse large hex values; see testv8/hex-parsing.js + return (new BigInteger(valueString, 16)).doubleValue(); + } + + double value = 0.0; + + for (int i = 0; i < valueString.length(); i++) { + final char ch = valueString.charAt(i); + // Preverified, should always be a valid digit. + final int digit = convertDigit(ch, radix); + value *= radix; + value += digit; + } + + return value; + } + } + + /** + * Scan a number. + */ + protected void scanNumber() { + // Record beginning of number. + final int start = position; + // Assume value is a decimal. + TokenType type = DECIMAL; + + // First digit of number. + int digit = convertDigit(ch0, 10); + + // If number begins with 0x. + if (digit == 0 && (ch1 == 'x' || ch1 == 'X') && convertDigit(ch2, 16) != -1) { + // Skip over 0xN. + skip(3); + // Skip over remaining digits. + while (convertDigit(ch0, 16) != -1 || (ecmascriptEdition >= 12 && ch0 == '_')) { + skip(1); + } + + type = HEXADECIMAL; + } else if (digit == 0 && ecmascriptEdition >= 6 && (ch1 == 'o' || ch1 == 'O') && convertDigit(ch2, 8) != -1) { + // Skip over 0oN. + skip(3); + // Skip over remaining digits. + while (convertDigit(ch0, 8) != -1) { + skip(1); + } + + type = OCTAL; + } else if (digit == 0 && ecmascriptEdition >= 6 && (ch1 == 'b' || ch1 == 'B') && convertDigit(ch2, 2) != -1) { + // Skip over 0bN. + skip(3); + // Skip over remaining digits. + while (convertDigit(ch0, 2) != -1 || (ecmascriptEdition >= 12 && ch0 == '_')) { + skip(1); + } + + type = BINARY_NUMBER; + } else { + // Check for possible octal constant. + boolean octal = digit == 0; + // Skip first digit if not leading '.'. + if (digit != -1) { + skip(1); + } + + // Skip remaining digits. + while ((digit = convertDigit(ch0, 10)) != -1 || (ecmascriptEdition >= 12 && ch0 == '_')) { + // Check octal only digits. + octal = octal && digit < 8; + // Skip digit. + skip(1); + } + + if (octal && position - start > 1) { + type = OCTAL_LEGACY; + } else if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') { + // Must be a double. + if (ch0 == '.') { + // Skip period. + skip(1); + // Skip mantissa. + while (convertDigit(ch0, 10) != -1 || (ecmascriptEdition >= 12 && ch0 == '_')) { + skip(1); + } + } + + // Detect exponent. + if (ch0 == 'E' || ch0 == 'e') { + // Skip E. + skip(1); + // Detect and skip exponent sign. + if (ch0 == '+' || ch0 == '-') { + skip(1); + } + // Skip exponent. + while (convertDigit(ch0, 10) != -1 || (ecmascriptEdition >= 12 && ch0 == '_')) { + skip(1); + } + } + + type = FLOATING; + } + } + + if(type == DECIMAL && ch0 == 'n' && ecmascriptEdition >= 11) { + type = BIGINT; + skip(1); + } else if (Character.isJavaIdentifierStart(ch0)) { + error(Lexer.message("missing.space.after.number"), type, position, 1); + } + + // Add number token. + add(type, start); + } + + /** + * Convert a regex token to a token object. + * + * @param start Position in source content. + * @param length Length of regex token. + * @return Regex token object. + */ + XMLToken valueOfXML(final int start, final int length) { + return new XMLToken(source.getString(start, length)); + } + + /** + * Scan over a XML token. + * + * @return TRUE if is an XML literal. + */ + private boolean scanXMLLiteral() { + assert ch0 == '<' && Character.isJavaIdentifierStart(ch1); + if (XML_LITERALS) { + // Record beginning of xml expression. + final int start = position; + + int openCount = 0; + + do { + if (ch0 == '<') { + if (ch1 == '/' && Character.isJavaIdentifierStart(ch2)) { + skip(3); + openCount--; + } else if (Character.isJavaIdentifierStart(ch1)) { + skip(2); + openCount++; + } else if (ch1 == '?') { + skip(2); + } else if (ch1 == '!' && ch2 == '-' && ch3 == '-') { + skip(4); + } else { + reset(start); + return false; + } + + while (!atEOF() && ch0 != '>') { + if (ch0 == '/' && ch1 == '>') { + openCount--; + skip(1); + break; + } else if (ch0 == '\"' || ch0 == '\'') { + scanString(false); + } else { + skip(1); + } + } + + if (ch0 != '>') { + reset(start); + return false; + } + + skip(1); + } else if (atEOF()) { + reset(start); + return false; + } else { + skip(1); + } + } while (openCount > 0); + + add(XML, start); + return true; + } + + return false; + } + + private void scanJsxIdentifier() { + final int start = position; + int length = scanIdentifier(); + if (length > 0) { + if (ch0 == '-') { + length++; + skip(1); + } + } + add(TokenType.JSX_IDENTIFIER, start); + } + + private void scanJsxText() { + final int start = position; + + // Make sure remaining characters are valid source characters. + while (!atEOF()) { + if (ch0 == '{' || ch0 == '}' || ch0 == '<' || ch0 == '>') { + break; + } else { + skip(1); + } + } + + add(TokenType.JSX_TEXT, start); + } + + private void scanJsxString() { + assert ch0 == '"' || ch0 == '\''; + + // Record starting quote. + final char quote = ch0; + // Skip over quote. + skip(1); + + final int start = position; + + // Make sure remaining characters are valid source characters. + while (!atEOF()) { + if (ch0 == quote) { + skip(1); + break; + } else { + skip(1); + } + } + + add(TokenType.JSX_STRING, start, position - 1); + } + + /** + * Scan over identifier characters. + * + * @return Length of identifier or zero if none found. + */ + private int scanIdentifier() { + final int start = position; + + // Make sure first character is valid start character. + if (ch0 == '\\' && ch1 == 'u') { + skip(2); + final int ch = unicodeEscapeSequence(TokenType.IDENT); + + if (!Character.isJavaIdentifierStart(ch)) { + error(Lexer.message("illegal.identifier.character"), TokenType.IDENT, start, position); + } + } else if ((!Character.isJavaIdentifierStart(ch0)) && (ecmascriptEdition < 13 || ch0 != '#')) { + // Not an identifier. + return 0; + } else { + skip(1); + } + + // Make sure remaining characters are valid part characters. + while (!atEOF()) { + if (ch0 == '\\' && ch1 == 'u') { + skip(2); + final int ch = unicodeEscapeSequence(TokenType.IDENT); + + if (!Character.isJavaIdentifierPart(ch)) { + error(Lexer.message("illegal.identifier.character"), TokenType.IDENT, start, position); + } + } else if (Character.isJavaIdentifierPart(ch0)) { + skip(1); + } else { + break; + } + } + + // Length of identifier sequence. + return position - start; + } + + /** + * Compare two identifiers (in content) for equality. + * + * @param aStart Start of first identifier. + * @param aLength Length of first identifier. + * @param bStart Start of second identifier. + * @param bLength Length of second identifier. + * @return True if equal. + */ + private boolean identifierEqual(final int aStart, final int aLength, final int bStart, final int bLength) { + if (aLength == bLength) { + for (int i = 0; i < aLength; i++) { + if (content.charAt(aStart + i) != content.charAt(bStart + i)) { + return false; + } + } + + return true; + } + + return false; + } + + /** + * Detect if a line starts with a marker identifier. + * + * @param identStart Start of identifier. + * @param identLength Length of identifier. + * @return True if detected. + */ + private boolean hasHereMarker(final int identStart, final int identLength) { + // Skip any whitespace. + skipWhitespace(false); + + return identifierEqual(identStart, identLength, position, scanIdentifier()); + } + + /** + * Lexer to service edit strings. + */ + private static class EditStringLexer extends Lexer { + /** Type of string literals to emit. */ + final TokenType stringType; + + /* + * Constructor. + */ + + EditStringLexer(final Lexer lexer, final TokenType stringType, final State stringState) { + super(lexer, stringState); + + this.stringType = stringType; + } + + /** + * Lexify the contents of the string. + */ + @Override + public void lexify() { + // Record start of string position. + int stringStart = position; + // Indicate that the priming first string has not been emitted. + boolean primed = false; + + while (true) { + // Detect end of content. + if (atEOF()) { + break; + } + + // Honour escapes (should be well formed.) + if (ch0 == '\\' && stringType == ESCSTRING) { + skip(2); + + continue; + } + + // If start of expression. + if (ch0 == '$' && ch1 == '{') { + if (!primed || stringStart != position) { + if (primed) { + add(ADD, stringStart, stringStart + 1); + } + + add(stringType, stringStart, position); + primed = true; + } + + // Skip ${ + skip(2); + + // Save expression state. + final State expressionState = saveState(); + + // Start with one open brace. + int braceCount = 1; + + // Scan for the rest of the string. + while (!atEOF()) { + // If closing brace. + if (ch0 == '}') { + // Break only only if matching brace. + if (--braceCount == 0) { + break; + } + } else if (ch0 == '{') { + // Bump up the brace count. + braceCount++; + } + + // Skip to next character. + skip(1); + } + + // If braces don't match then report an error. + if (braceCount != 0) { + error(Lexer.message("edit.string.missing.brace"), LBRACE, expressionState.position - 1, 1); + } + + // Mark end of expression. + expressionState.setLimit(position); + // Skip closing brace. + skip(1); + + // Start next string. + stringStart = position; + + // Concatenate expression. + add(ADD, expressionState.position, expressionState.position + 1); + add(LPAREN, expressionState.position, expressionState.position + 1); + + // Scan expression. + final Lexer lexer = new Lexer(this, expressionState); + lexer.lexify(); + + // Close out expression parenthesis. + add(RPAREN, position - 1, position); + + continue; + } + + // Next character in string. + skip(1); + } + + // If there is any unemitted string portion. + if (stringStart != limit) { + // Concatenate remaining string. + if (primed) { + add(ADD, stringStart, 1); + } + + add(stringType, stringStart, limit); + } + } + + } + + /** + * Edit string for nested expressions. + * + * @param stringType Type of string literals to emit. + * @param stringState State of lexer at start of string. + */ + private void editString(final TokenType stringType, final State stringState) { + // Use special lexer to scan string. + final EditStringLexer lexer = new EditStringLexer(this, stringType, stringState); + lexer.lexify(); + + // Need to keep lexer informed. + last = stringType; + } + + /** + * Scan over a here string. + * + * @return TRUE if is a here string. + */ + private boolean scanHereString(final LineInfoReceiver lir) { + assert ch0 == '<' && ch1 == '<'; + if (scripting) { + // Record beginning of here string. + final State saved = saveState(); + + // << or <<< + final boolean excludeLastEOL = ch2 != '<'; + + if (excludeLastEOL) { + skip(2); + } else { + skip(3); + } + + // Scan identifier. It might be quoted, indicating that no string editing should take place. + final char quoteChar = ch0; + final boolean noStringEditing = quoteChar == '"' || quoteChar == '\''; + if (noStringEditing) { + skip(1); + } + final int identStart = position; + final int identLength = scanIdentifier(); + if (noStringEditing) { + if (ch0 != quoteChar) { + error(Lexer.message("here.non.matching.delimiter"), last, position, position); + restoreState(saved); + return false; + } + skip(1); + } + + // Check for identifier. + if (identLength == 0) { + // Treat as shift. + restoreState(saved); + + return false; + } + + // Record rest of line. + final State restState = saveState(); + // keep line number updated + int lastLine = line; + + skipLine(false); + lastLine++; + int lastLinePosition = position; + restState.setLimit(position); + + // Record beginning of string. + final State stringState = saveState(); + int stringEnd = position; + + // Hunt down marker. + while (!atEOF()) { + // Skip any whitespace. + skipWhitespace(false); + + if (hasHereMarker(identStart, identLength)) { + break; + } + + skipLine(false); + lastLine++; + lastLinePosition = position; + stringEnd = position; + } + + // notify last line information + lir.lineInfo(lastLine, lastLinePosition); + + // Record end of string. + stringState.setLimit(stringEnd); + + // If marker is missing. + if (stringState.isEmpty() || atEOF()) { + error(Lexer.message("here.missing.end.marker", source.getString(identStart, identLength)), last, position, position); + restoreState(saved); + + return false; + } + + // Remove last end of line if specified. + if (excludeLastEOL) { + // Handles \n. + if (content.charAt(stringEnd - 1) == '\n') { + stringEnd--; + } + + // Handles \r and \r\n. + if (content.charAt(stringEnd - 1) == '\r') { + stringEnd--; + } + + // Update end of string. + stringState.setLimit(stringEnd); + } + + // Edit string if appropriate. + if (!noStringEditing && !stringState.isEmpty()) { + editString(STRING, stringState); + } else { + // Add here string. + add(STRING, stringState.position, stringState.limit); + } + + // Scan rest of original line. + final Lexer restLexer = new Lexer(this, restState); + + restLexer.lexify(); + + return true; + } + + return false; + } + + private void handleTemplate() { + int start = position; + while (!atEOF()) { + // Skip over escaped character. + if (ch0 == '`') { + skip(1); + + // TEMPLATE or TEMPLATE_TAIL + add(templateExpression ? TEMPLATE_TAIL : TEMPLATE, start, position - 1); + template = false; + templateExpression = false; + break; + } else if (ch0 == '$' && ch1 == '{') { + skip(2); + + // TEMPLATE_HEAD or TEMPLATE_MIDDLE + add(templateExpression ? TEMPLATE_MIDDLE : TEMPLATE_HEAD, start, position - 2); + templateExpression = true; + + innerStates.push(new TemplateState(template, templateExpression, nextStateChange)); + template = false; + templateExpression = false; + nextStateChange = openExpressionBraces; + openExpressionBraces++; + break; + } else if (ch0 == '\\') { + skip(1); + // EscapeSequence + if (!isEscapeCharacter(ch0)) { + error(Lexer.message("invalid.escape.char"), TEMPLATE, position, limit); + } + if (isEOL(ch0)) { + // LineContinuation + skipEOL(false); + continue; + } + } else if (isEOL(ch0)) { + // LineTerminatorSequence + skipEOL(false); + continue; + } + + // Skip literal character. + skip(1); + } + } + + private void handleJsx() { + if (jsxTag) { + if (Character.isJavaIdentifierStart(ch0) || ch0 == '\\' && ch1 == 'u') { + // Scan and add identifier or keyword. + scanJsxIdentifier(); + } else if (isStringDelimiter(ch0)) { + scanJsxString(); + } else { + switch (ch0) { + case '=': + case '.': + case ':': + add(TokenLookup.lookupOperator(ch0, ch1, ch2, ch3), position, position + 1); + skip(1); + break; + case '{': + skip(1); + add(LBRACE, position - 1); + innerStates.push(new JsxState(jsxTagCount, jsxTag, jsxClosing, nextStateChange)); + jsxTagCount = 0; + jsxTag = false; + jsxClosing = false; + nextStateChange = openExpressionBraces; + openExpressionBraces++; + break; + case '<': + skip(1); + add(TokenType.JSX_ELEM_START, position - 1); + jsxTagCount++; + break; + case '/': + skip(1); + add(TokenType.JSX_ELEM_CLOSE, position - 1); + jsxClosing = true; + break; + case '>': + skip(1); + add(TokenType.JSX_ELEM_END, position - 1); + jsxTag = false; + if (jsxClosing) { + jsxClosing = false; + jsxTagCount--; + } + break; + default: + skip(1); + add(ERROR, position - 1); + break; + } + } + } else { + switch (ch0) { + case '<': + skip(1); + add(TokenType.JSX_ELEM_START, position - 1); + if (ch0 != '/') { + jsxTagCount++; + } + jsxTag = true; + break; + case '{': + skip(1); + add(LBRACE, position - 1); + innerStates.push(new JsxState(jsxTagCount, jsxTag, jsxClosing, nextStateChange)); + jsxTagCount = 0; + jsxTag = false; + jsxClosing = false; + nextStateChange = openExpressionBraces; + openExpressionBraces++; + break; + case '}': + case '>': + // we are not in tag and not in expression + // so this is either lex error or we may emit + // proper tokens and parser will fail + skip(1); + add(ERROR, position - 1); + break; + default: + scanJsxText(); + break; + } + } + } + + /** + * Breaks source content down into lex units, adding tokens to the token + * stream. The routine scans until the stream buffer is full. Can be called + * repeatedly until EOF is detected. + */ + public void lexify() { + while (!stream.isFull() || nested) { + // Detect end of file. + if (atEOF()) { + if (!nested) { + if (template) { + error(Lexer.message("missing.close.quote"), TEMPLATE, position, limit); + } + // Add an EOF token at the end. + add(EOF, position); + } + + break; + } + + if (template) { + handleTemplate(); + continue; + } + + // Skip over whitespace. + skipWhitespace(true); + + // Detect end of file. + if (atEOF()) { + if (!nested) { + // Add an EOF token at the end. + add(EOF, position); + } + + break; + } + + // Check for comments. Note that we don't scan for regexp and other literals here as + // we may not have enough context to distinguish them from similar looking operators. + // Instead we break on ambiguous operators below and let the parser decide. + if (ch0 == '/' && skipComments()) { + continue; + } + + if ((scripting || shebang) && ch0 == '#' && skipComments()) { + continue; + } + + if (jsxTagCount > 0) { + handleJsx(); + continue; + } + + // TokenType for lookup of delimiter or operator. + TokenType type; + + if (ch0 == '.' && convertDigit(ch1, 10) != -1) { + // '.' followed by digit. + // Scan and add a number. + scanNumber(); + } else if ((type = TokenLookup.lookupOperator(ch0, ch1, ch2, ch3)) != null && type.isSupported(ecmascriptEdition)) { + if (!innerStates.isEmpty()) { + if (type == LBRACE) { + openExpressionBraces++; + } else if (type == RBRACE) { + if (--openExpressionBraces == nextStateChange) { + InnerState state = innerStates.pop(); + state.restore(this); + nextStateChange = state.nextStateChange(); + skip(1); + if (state.emitRightCurly()) { + add(RBRACE, position - 1); + } + break; + } + } + } + + // Get the number of characters in the token. + final int typeLength = type.getLength(); + // Skip that many characters. + skip(typeLength); + // Add operator token. + add(type, position - typeLength); + // Some operator tokens also mark the beginning of regexp, XML, or here string literals. + // We break to let the parser decide what it is. + if (canStartLiteral(type)) { + break; + } else if (type == LBRACE && pauseOnNextLeftBrace) { + pauseOnNextLeftBrace = false; + break; + } + } else if (Character.isJavaIdentifierStart(ch0) || ch0 == '\\' && ch1 == 'u' || ch0 == '#') { + // Scan and add identifier or keyword. + scanIdentifierOrKeyword(); + } else if (isStringDelimiter(ch0)) { + // Scan and add a string. + scanString(true); + } else if (Character.isDigit(ch0)) { + // Scan and add a number. + scanNumber(); + } else if (isTemplateDelimiter(ch0) && ecmascriptEdition >= 6) { + // Scan and add template in ES6 mode. + //scanTemplate(); + template = true; + skip(1); + } else if (isTemplateDelimiter(ch0) && scripting) { + // Scan and add an exec string ('`') in scripting mode. + scanString(true); + } else { + // Don't recognize this character. + skip(1); + add(ERROR, position - 1); + } + } + } + + /** + * Return value of token given its token descriptor. + * + * @param token Token descriptor. + * @return JavaScript value. + */ + Object getValueOf(final long token, final boolean strict) { + final int start = Token.descPosition(token); + final int len = Token.descLength(token); + + switch (Token.descType(token)) { + case DECIMAL: + return Lexer.valueOf(source.getString(start, len).replace("_", ""), 10); // number + case HEXADECIMAL: + return Lexer.valueOf(source.getString(start + 2, len - 2).replace("_", ""), 16); // number + case OCTAL_LEGACY: + return Lexer.valueOf(source.getString(start, len).replace("_", ""), 8); // number + case OCTAL: + return Lexer.valueOf(source.getString(start + 2, len - 2).replace("_", ""), 8); // number + case BINARY_NUMBER: + return Lexer.valueOf(source.getString(start + 2, len - 2).replace("_", ""), 2); // number + case FLOATING: + final String str = source.getString(start, len).replace("_", ""); + final double value = Double.valueOf(str); + if (str.indexOf('.') != -1) { + return value; //number + } + //anything without an explicit decimal point is still subject to a + //"representable as int or long" check. Then the programmer does not + //explicitly code something as a double. For example new Color(int, int, int) + //and new Color(float, float, float) will get ambiguous for cases like + //new Color(1.0, 1.5, 1.5) if we don't respect the decimal point. + //yet we don't want e.g. 1e6 to be a double unnecessarily + if (JSType.isStrictlyRepresentableAsInt(value)) { + return (int)value; + } else if (JSType.isStrictlyRepresentableAsLong(value)) { + return (long)value; + } + return value; + case BIGINT: + return new BigInteger(source.getString(start, len - 1).replace("_", "")); // number + case JSX_TEXT: + case JSX_STRING: + case STRING: + return source.getString(start, len); // String + case ESCSTRING: + return valueOfString(start, len, strict); // String + case IDENT: + return valueOfIdent(start, len); // String + case REGEX: + return valueOfPattern(start, len); // RegexToken::LexerToken + case TEMPLATE: + case TEMPLATE_HEAD: + case TEMPLATE_MIDDLE: + case TEMPLATE_TAIL: + return valueOfString(start, len, true); // String + case XML: + return valueOfXML(start, len); // XMLToken::LexerToken + case DIRECTIVE_COMMENT: + return source.getString(start, len); + case JSX_IDENTIFIER: + return valueOfIdent(start, len); // String + default: + break; + } + + return null; + } + + /** + * Get the raw string value of a template literal string part. + * + * @param token template string token + * @return raw string + */ + public String valueOfRawString(final long token) { + final int start = Token.descPosition(token); + final int length = Token.descLength(token); + + // Save the current position. + final int savePosition = position; + // Calculate the end position. + final int end = start + length; + // Reset to beginning of string. + reset(start); + + // Buffer for recording characters. + final StringBuilder sb = new StringBuilder(length); + + // Scan until end of string. + while (position < end) { + if (ch0 == '\r') { + // Convert CR-LF or CR to LF line terminator. + sb.append('\n'); + skip(ch1 == '\n' ? 2 : 1); + } else { + // Add regular character. + sb.append(ch0); + skip(1); + } + } + + // Restore position. + reset(savePosition); + + return sb.toString(); + } + + /** + * Get the correctly localized error message for a given message id format arguments + * @param msgId message id + * @param args format arguments + * @return message + */ + protected static String message(final String msgId, final String... args) { + return ECMAErrors.getMessage("lexer.error." + msgId, args); + } + + /** + * Generate a runtime exception + * + * @param message error message + * @param type token type + * @param start start position of lexed error + * @param length length of lexed error + * @throws ParserException unconditionally + */ + protected void error(final String message, final TokenType type, final int start, final int length) throws ParserException { + final long token = Token.toDesc(type, start, length); + final int pos = Token.descPosition(token); + final int lineNum = source.getLine(pos); + final int columnNum = source.getColumn(pos); + final String formatted = ErrorManager.format(message, source, lineNum, columnNum, token); + throw new ParserException(JSErrorType.SyntaxError, formatted, source, lineNum, columnNum, token); + } + + /** + * Helper class for Lexer tokens, e.g XML or RegExp tokens. + * This is the abstract superclass + */ + public abstract static class LexerToken { + private final String expression; + + /** + * Constructor + * @param expression token expression + */ + protected LexerToken(final String expression) { + this.expression = expression; + } + + /** + * Get the expression + * @return expression + */ + public String getExpression() { + return expression; + } + } + + /** + * Temporary container for regular expressions. + */ + public static class RegexToken extends LexerToken { + /** Options. */ + private final String options; + + /** + * Constructor. + * + * @param expression regexp expression + * @param options regexp options + */ + public RegexToken(final String expression, final String options) { + super(expression); + this.options = options; + } + + /** + * Get regexp options + * @return options + */ + public String getOptions() { + return options; + } + + @Override + public String toString() { + return '/' + getExpression() + '/' + options; + } + } + + /** + * Temporary container for XML expression. + */ + public static class XMLToken extends LexerToken { + + /** + * Constructor. + * + * @param expression XML expression + */ + public XMLToken(final String expression) { + super(expression); + } + } + + public interface InnerState { + + void restore(Lexer lexer); + + boolean emitRightCurly(); + + int nextStateChange(); + } + + public static class JsxState implements InnerState { + + private final int jsxTagCount; + + private final boolean jsxTag; + + private final boolean jsxClosing; + + private final int expressionBraces; + + public JsxState(int jsxTagCount, boolean jsxTag, boolean jsxClosing, int expressionBraces) { + this.jsxTagCount = jsxTagCount; + this.jsxTag = jsxTag; + this.jsxClosing = jsxClosing; + this.expressionBraces = expressionBraces; + } + + @Override + public void restore(Lexer lexer) { + lexer.jsxTagCount = jsxTagCount; + lexer.jsxTag = jsxTag; + lexer.jsxClosing = jsxClosing; + } + + @Override + public boolean emitRightCurly() { + return true; + } + + @Override + public int nextStateChange() { + return expressionBraces; + } + } + + public static class TemplateState implements InnerState { + + private final boolean template; + + private final boolean templateExpression; + + private final int expressionBraces; + + public TemplateState(boolean template, boolean templateExpression, int expressionBraces) { + this.template = template; + this.templateExpression = templateExpression; + this.expressionBraces = expressionBraces; + } + + @Override + public void restore(Lexer lexer) { + lexer.template = template; + lexer.templateExpression = templateExpression; + } + + @Override + public boolean emitRightCurly() { + return false; + } + + @Override + public int nextStateChange() { + return expressionBraces; + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Namespace.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Namespace.java new file mode 100644 index 000000000000..76c58eb765e5 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Namespace.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import java.util.HashMap; + +/** + * A name space hierarchy, where each level holds a name directory with names that may be unique for + * each level. + */ + +public class Namespace { + /** Parent namespace. */ + private final Namespace parent; + + /** Name directory - version count for each name */ + private final HashMap directory; + + /** + * Constructor + */ + public Namespace() { + this(null); + } + + /** + * Constructor + * + * @param parent parent name space + */ + public Namespace(final Namespace parent) { + this.parent = parent; + this.directory = new HashMap<>(); + } + + /** + * Return the parent Namespace of this space. + * + * @return parent name space + */ + public Namespace getParent() { + return parent; + } + + /** + * Create a uniqueName name in the namespace in the form base$n where n varies . + * + * @param base Base of name. Base will be returned if uniqueName. + * + * @return Generated uniqueName name. + */ + public String uniqueName(final String base) { + for (Namespace namespace = this; namespace != null; namespace = namespace.getParent()) { + final HashMap namespaceDirectory = namespace.directory; + final Integer counter = namespaceDirectory.get(base); + + if (counter != null) { + final int count = counter + 1; + namespaceDirectory.put(base, count); + return base + '-' + count; + } + } + + directory.put(base, 0); + + return base; + } + + @Override + public String toString() { + return directory.toString(); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Parser.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Parser.java new file mode 100644 index 000000000000..60311b923387 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Parser.java @@ -0,0 +1,6077 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenType.ARROW; +import static com.oracle.js.parser.TokenType.ASSIGN; +import static com.oracle.js.parser.TokenType.AT; +import static com.oracle.js.parser.TokenType.AWAIT; +import static com.oracle.js.parser.TokenType.CASE; +import static com.oracle.js.parser.TokenType.CATCH; +import static com.oracle.js.parser.TokenType.CLASS; +import static com.oracle.js.parser.TokenType.COLON; +import static com.oracle.js.parser.TokenType.COMMARIGHT; +import static com.oracle.js.parser.TokenType.COMMENT; +import static com.oracle.js.parser.TokenType.CONST; +import static com.oracle.js.parser.TokenType.DECPOSTFIX; +import static com.oracle.js.parser.TokenType.DECPREFIX; +import static com.oracle.js.parser.TokenType.DEFAULT; +import static com.oracle.js.parser.TokenType.ELLIPSIS; +import static com.oracle.js.parser.TokenType.ELSE; +import static com.oracle.js.parser.TokenType.EOF; +import static com.oracle.js.parser.TokenType.EOL; +import static com.oracle.js.parser.TokenType.EQ_STRICT; +import static com.oracle.js.parser.TokenType.ESCSTRING; +import static com.oracle.js.parser.TokenType.EXPORT; +import static com.oracle.js.parser.TokenType.EXTENDS; +import static com.oracle.js.parser.TokenType.FINALLY; +import static com.oracle.js.parser.TokenType.FUNCTION; +import static com.oracle.js.parser.TokenType.IDENT; +import static com.oracle.js.parser.TokenType.IF; +import static com.oracle.js.parser.TokenType.IMPORT; +import static com.oracle.js.parser.TokenType.INCPOSTFIX; +import static com.oracle.js.parser.TokenType.LBRACE; +import static com.oracle.js.parser.TokenType.LBRACKET; +import static com.oracle.js.parser.TokenType.LET; +import static com.oracle.js.parser.TokenType.LPAREN; +import static com.oracle.js.parser.TokenType.MUL; +import static com.oracle.js.parser.TokenType.OPTIONAL_ACCESS; +import static com.oracle.js.parser.TokenType.PERIOD; +import static com.oracle.js.parser.TokenType.RBRACE; +import static com.oracle.js.parser.TokenType.RBRACKET; +import static com.oracle.js.parser.TokenType.RPAREN; +import static com.oracle.js.parser.TokenType.SEMICOLON; +import static com.oracle.js.parser.TokenType.SPREAD_ARRAY; +import static com.oracle.js.parser.TokenType.SPREAD_OBJECT; +import static com.oracle.js.parser.TokenType.STATIC; +import static com.oracle.js.parser.TokenType.STRING; +import static com.oracle.js.parser.TokenType.SUPER; +import static com.oracle.js.parser.TokenType.TEMPLATE; +import static com.oracle.js.parser.TokenType.TEMPLATE_HEAD; +import static com.oracle.js.parser.TokenType.TEMPLATE_MIDDLE; +import static com.oracle.js.parser.TokenType.TEMPLATE_TAIL; +import static com.oracle.js.parser.TokenType.TERNARY; +import static com.oracle.js.parser.TokenType.VAR; +import static com.oracle.js.parser.TokenType.VOID; +import static com.oracle.js.parser.TokenType.WHILE; +import static com.oracle.js.parser.TokenType.YIELD; +import static com.oracle.js.parser.TokenType.YIELD_STAR; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import com.oracle.js.parser.ir.AccessNode; +import com.oracle.js.parser.ir.BaseNode; +import com.oracle.js.parser.ir.BinaryNode; +import com.oracle.js.parser.ir.Block; +import com.oracle.js.parser.ir.BlockStatement; +import com.oracle.js.parser.ir.BreakNode; +import com.oracle.js.parser.ir.CallNode; +import com.oracle.js.parser.ir.CaseNode; +import com.oracle.js.parser.ir.CatchNode; +import com.oracle.js.parser.ir.ClassNode; +import com.oracle.js.parser.ir.ContinueNode; +import com.oracle.js.parser.ir.DebuggerNode; +import com.oracle.js.parser.ir.EmptyNode; +import com.oracle.js.parser.ir.ErrorNode; +import com.oracle.js.parser.ir.ExportClauseNode; +import com.oracle.js.parser.ir.ExportNode; +import com.oracle.js.parser.ir.ExportSpecifierNode; +import com.oracle.js.parser.ir.Expression; +import com.oracle.js.parser.ir.ExpressionList; +import com.oracle.js.parser.ir.ExpressionStatement; +import com.oracle.js.parser.ir.ForNode; +import com.oracle.js.parser.ir.FromNode; +import com.oracle.js.parser.ir.FunctionNode; +import com.oracle.js.parser.ir.IdentNode; +import com.oracle.js.parser.ir.IfNode; +import com.oracle.js.parser.ir.ImportClauseNode; +import com.oracle.js.parser.ir.ImportNode; +import com.oracle.js.parser.ir.ImportSpecifierNode; +import com.oracle.js.parser.ir.IndexNode; +import com.oracle.js.parser.ir.JoinPredecessorExpression; +import com.oracle.js.parser.ir.JsxAttributeNode; +import com.oracle.js.parser.ir.JsxElementNode; +import com.oracle.js.parser.ir.LabelNode; +import com.oracle.js.parser.ir.LexicalContext; +import com.oracle.js.parser.ir.LiteralNode; +import com.oracle.js.parser.ir.LiteralNode.ArrayLiteralNode; +import com.oracle.js.parser.ir.Module; +import com.oracle.js.parser.ir.Module.ExportEntry; +import com.oracle.js.parser.ir.Module.ImportEntry; +import com.oracle.js.parser.ir.NameSpaceImportNode; +import com.oracle.js.parser.ir.NamedImportsNode; +import com.oracle.js.parser.ir.Node; +import com.oracle.js.parser.ir.ObjectNode; +import com.oracle.js.parser.ir.PropertyKey; +import com.oracle.js.parser.ir.PropertyNode; +import com.oracle.js.parser.ir.ReturnNode; +import com.oracle.js.parser.ir.RuntimeNode; +import com.oracle.js.parser.ir.Statement; +import com.oracle.js.parser.ir.SwitchNode; +import com.oracle.js.parser.ir.TernaryNode; +import com.oracle.js.parser.ir.ThrowNode; +import com.oracle.js.parser.ir.TryNode; +import com.oracle.js.parser.ir.UnaryNode; +import com.oracle.js.parser.ir.VarNode; +import com.oracle.js.parser.ir.WhileNode; +import com.oracle.js.parser.ir.WithNode; +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import java.util.Set; + +// @formatter:off +/** + * Builds the IR. + */ +@SuppressWarnings("fallthrough") +public class Parser extends AbstractParser { + /** The arguments variable name. */ + private static final String ARGUMENTS_NAME = "arguments"; + /** The eval function variable name. */ + private static final String EVAL_NAME = "eval"; + /** EXEC name - special property used by $EXEC API. */ + private static final String EXEC_NAME = "$EXEC"; + /** Function name prefix for anonymous functions. */ + private static final String ANON_FUNCTION_PREFIX = "L:"; + /** Function name for the program entry point. */ + private static final String PROGRAM_NAME = ":program"; + /** Function name prefix for arrow functions. */ + private static final String ARROW_FUNCTION_PREFIX = "=>:"; + private static final char NESTED_FUNCTION_SEPARATOR = '#'; + + private static final String ASYNC_IDENT = "async"; + + /** Current env. */ + private final ScriptEnvironment env; + + /** Is scripting mode. */ + private final boolean scripting; + + /** Is shebang supported */ + private final boolean shebang; + + private List functionDeclarations; + + private final ParserContext lc; + private final Deque defaultNames; + + /** Namespace for function names where not explicitly given */ + private final Namespace namespace; + + /** to receive line information from Lexer when scanning multine literals. */ + protected final Lexer.LineInfoReceiver lineInfoReceiver; + + private RecompilableScriptFunctionData reparsedFunction; + + /** + * Constructor + * + * @param env script environment + * @param source source to parse + * @param errors error manager + */ + public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors) { + this(env, source, errors, env.strict); + } + + /** + * Constructor + * + * @param env script environment + * @param source source to parse + * @param errors error manager + * @param strict strict + */ + public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict) { + this(env, source, errors, strict, 0); + } + + /** + * Construct a parser. + * + * @param env script environment + * @param source source to parse + * @param errors error manager + * @param strict parser created with strict mode enabled. + * @param lineOffset line offset to start counting lines from + */ + public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset) { + super(source, errors, strict, lineOffset); + this.lc = new ParserContext(); + this.defaultNames = new ArrayDeque<>(); + this.env = env; + this.namespace = new Namespace(env.getNamespace()); + this.scripting = env.scripting; + this.shebang = env.shebang; + if (this.scripting) { + this.lineInfoReceiver = new Lexer.LineInfoReceiver() { + @Override + public void lineInfo(final int receiverLine, final int receiverLinePosition) { + // update the parser maintained line information + Parser.this.line = receiverLine; + Parser.this.linePosition = receiverLinePosition; + } + }; + } else { + // non-scripting mode script can't have multi-line literals + this.lineInfoReceiver = null; + } + } + + /** + * Sets the name for the first function. This is only used when reparsing anonymous functions to ensure they can + * preserve their already assigned name, as that name doesn't appear in their source text. + * @param name the name for the first parsed function. + */ + public void setFunctionName(final String name) { + defaultNames.push(createIdentNode(0, 0, name)); + } + + /** + * Execute parse and return the resulting function node. + * Errors will be thrown and the error manager will contain information + * if parsing should fail + * + * This is the default parse call, which will name the function node {@link #PROGRAM_NAME}. + * + * @return function node resulting from successful parse + */ + public FunctionNode parse() { + return parse(PROGRAM_NAME, 0, source.getLength(), false); + } + + /** + * Sets the @link RecompilableScriptFunctionData representing the function being reparsed (when this + * parser instance is used to reparse a previously parsed function, as part of its on-demand compilation). + * This will trigger various special behaviors, such as skipping nested function bodies. + * @param reparsedFunction the function being reparsed. + */ + public void setReparsedFunction(final RecompilableScriptFunctionData reparsedFunction) { + this.reparsedFunction = reparsedFunction; + } + + /** + * Set up first token. Skips opening EOL. + */ + private void scanFirstToken() { + k = -1; + next(); + } + + /** + * Execute parse and return the resulting function node. + * Errors will be thrown and the error manager will contain information + * if parsing should fail + * + * This should be used to create one and only one function node + * + * @param scriptName name for the script, given to the parsed FunctionNode + * @param startPos start position in source + * @param len length of parse + * @param allowPropertyFunction if true, "get" and "set" are allowed as first tokens of the program, followed by + * a property getter or setter function. This is used when reparsing a function that can potentially be defined as a + * property getter or setter in an object literal. + * + * @return function node resulting from successful parse + */ + public FunctionNode parse(final String scriptName, final int startPos, final int len, final boolean allowPropertyFunction) { + try { + stream = new TokenStream(); + lexer = new Lexer(source, startPos, len, stream, scripting && env.syntaxExtensions, env.ecmascriptEdition, shebang && env.syntaxExtensions, reparsedFunction != null, env.jsx); + lexer.line = lexer.pendingLine = lineOffset + 1; + line = lineOffset; + + scanFirstToken(); + // Begin parse. + return program(scriptName, allowPropertyFunction); + } catch (final Exception e) { + handleParseException(e); + + return null; + } + } + + /** + * Parse and return the resulting module. + * Errors will be thrown and the error manager will contain information + * if parsing should fail + * + * @param moduleName name for the module, given to the parsed FunctionNode + * @param startPos start position in source + * @param len length of parse + * + * @return function node resulting from successful parse + */ + public FunctionNode parseModule(final String moduleName, final int startPos, final int len) { + try { + stream = new TokenStream(); + lexer = new Lexer(source, startPos, len, stream, scripting && env.syntaxExtensions, env.ecmascriptEdition, shebang && env.syntaxExtensions, reparsedFunction != null, env.jsx); + lexer.line = lexer.pendingLine = lineOffset + 1; + line = lineOffset; + + scanFirstToken(); + // Begin parse. + return module(moduleName); + } catch (final Exception e) { + handleParseException(e); + + return null; + } + } + + public FunctionNode parseModule(final String moduleName) { + return parseModule(moduleName, 0, source.getLength()); + } + + /** + * Parse and return the list of function parameter list. A comma + * separated list of function parameter identifiers is expected to be parsed. + * Errors will be thrown and the error manager will contain information + * if parsing should fail. This method is used to check if parameter Strings + * passed to "Function" constructor is a valid or not. + * + * @return the list of IdentNodes representing the formal parameter list + */ + public List parseFormalParameterList() { + try { + stream = new TokenStream(); + lexer = new Lexer(source, stream, scripting && env.syntaxExtensions, env.ecmascriptEdition, shebang && env.syntaxExtensions, env.jsx); + + scanFirstToken(); + + return formalParameterList(TokenType.EOF, false, false); + } catch (final Exception e) { + handleParseException(e); + return null; + } + } + + /** + * Execute parse and return the resulting function node. + * Errors will be thrown and the error manager will contain information + * if parsing should fail. This method is used to check if code String + * passed to "Function" constructor is a valid function body or not. + * + * @return function node resulting from successful parse + */ + public FunctionNode parseFunctionBody() { + try { + stream = new TokenStream(); + lexer = new Lexer(source, stream, scripting && env.syntaxExtensions, env.ecmascriptEdition, shebang && env.syntaxExtensions, env.jsx); + final int functionLine = line; + + scanFirstToken(); + + // Make a fake token for the function. + final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength()); + // Set up the function to append elements. + + final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), PROGRAM_NAME); + final ParserContextFunctionNode function = createParserContextFunctionNode(ident, functionToken, FunctionNode.Kind.NORMAL, functionLine, Collections.emptyList()); + lc.push(function); + + final ParserContextBlockNode body = newBlock(); + + functionDeclarations = new ArrayList<>(); + sourceElements(false); + addFunctionDeclarations(function); + functionDeclarations = null; + + restoreBlock(body); + body.setFlag(Block.NEEDS_SCOPE); + + verifyBlockScopedBindings(body.getStatements()); + + final Block functionBody = new Block(functionToken, finish, body.getFlags() | Block.IS_SYNTHETIC | Block.IS_BODY, body.getStatements()); + lc.pop(function); + + expect(EOF); + + final FunctionNode functionNode = createFunctionNode( + function, + functionToken, + ident, + Collections.emptyList(), + FunctionNode.Kind.NORMAL, + functionLine, + functionBody); + return functionNode; + } catch (final Exception e) { + handleParseException(e); + return null; + } + } + + private void handleParseException(final Exception e) { + // Extract message from exception. The message will be in error + // message format. + String message = e.getMessage(); + + // If empty message. + if (message == null) { + message = e.toString(); + } + + // Issue message. + if (e instanceof ParserException) { + errors.error((ParserException)e); + } else { + errors.error(message); + } + + if (env.dumpOnError) { + e.printStackTrace(env.getErr()); + } + } + + /** + * Skip to a good parsing recovery point. + */ + private void recover(final Exception e) { + if (e != null) { + // Extract message from exception. The message will be in error + // message format. + String message = e.getMessage(); + + // If empty message. + if (message == null) { + message = e.toString(); + } + + // Issue message. + if (e instanceof ParserException) { + errors.error((ParserException)e); + } else { + errors.error(message); + } + + if (env.dumpOnError) { + e.printStackTrace(env.getErr()); + } + } + + // Skip to a recovery point. +loop: + while (true) { + switch (type) { + case EOF: + // Can not go any further. + break loop; + case EOL: + case SEMICOLON: + case RBRACE: + // Good recovery points. + next(); + break loop; + default: + // So we can recover after EOL. + nextOrEOL(); + break; + } + } + } + + /** + * Set up a new block. + * + * @return New block. + */ + private ParserContextBlockNode newBlock() { + return lc.push(new ParserContextBlockNode(token)); + } + + private ParserContextFunctionNode createParserContextFunctionNode(final IdentNode ident, final long functionToken, final FunctionNode.Kind kind, final int functionLine, final List parameters) { + // Build function name. + final StringBuilder sb = new StringBuilder(); + + final ParserContextFunctionNode parentFunction = lc.getCurrentFunction(); + if (parentFunction != null && !parentFunction.isProgram()) { + sb.append(parentFunction.getName()).append(NESTED_FUNCTION_SEPARATOR); + } + + assert ident.getName() != null; + sb.append(ident.getName()); + + final String name = namespace.uniqueName(sb.toString()); + //assert parentFunction != null || name.equals(PROGRAM.symbolName()) : "name = " + name; + + int flags = 0; + if (isStrictMode) { + flags |= FunctionNode.IS_STRICT; + } + if (parentFunction == null) { + flags |= FunctionNode.IS_PROGRAM; + } + + final ParserContextFunctionNode functionNode = new ParserContextFunctionNode(functionToken, ident, name, namespace, functionLine, kind, parameters); + functionNode.setFlag(flags); + return functionNode; + } + + private FunctionNode createFunctionNode(final ParserContextFunctionNode function, final long startToken, final IdentNode ident, final List parameters, final FunctionNode.Kind kind, final int functionLine, final Block body) { + assert body.isFunctionBody() || body.getFlag(Block.IS_PARAMETER_BLOCK) && ((BlockStatement) body.getLastStatement()).getBlock().isFunctionBody(); + // Start new block. + final FunctionNode functionNode = + new FunctionNode( + source, + functionLine, + body.getToken(), + Token.descPosition(body.getToken()), + startToken, + function.getLastToken(), + namespace, + ident, + function.getName(), + parameters, + kind, + function.getFlags(), + body, + function.getEndParserState(), + function.getModule()); + + return functionNode; + } + + /** + * Restore the current block. + */ + private ParserContextBlockNode restoreBlock(final ParserContextBlockNode block) { + return lc.pop(block); + } + + /** + * Get the statements in a block. + * @return Block statements. + */ + private Block getBlock(final boolean needsBraces) { + final long blockToken = token; + final ParserContextBlockNode newBlock = newBlock(); + try { + // Block opening brace. + if (needsBraces) { + expect(LBRACE); + } + // Accumulate block statements. + statementList(); + + } finally { + restoreBlock(newBlock); + } + + // Block closing brace. + int realFinish; + if (needsBraces) { + expectDontAdvance(RBRACE); + // otherwise in case block containing single braced block the inner + // block could end somewhere later after comments and spaces + realFinish = Token.descPosition(token) + Token.descLength(token); + expect(RBRACE); + } else { + realFinish = finish; + } + + verifyBlockScopedBindings(newBlock.getStatements()); + + final int flags = newBlock.getFlags() | (needsBraces ? 0 : Block.IS_SYNTHETIC); + return new Block(blockToken, Math.max(realFinish, Token.descPosition(blockToken)), flags, newBlock.getStatements()); + } + + /** + * Get the statements in a case clause. + */ + private List caseStatementList() { + final ParserContextBlockNode newBlock = newBlock(); + try { + statementList(); + } finally { + restoreBlock(newBlock); + } + return newBlock.getStatements(); + } + + /** + * Get all the statements generated by a single statement. + * @return Statements. + */ + private Block getStatement() { + return getStatement(false); + } + + private Block getStatement(boolean labelledStatement) { + if (type == LBRACE) { + return getBlock(true); + } + // Set up new block. Captures first token. + final ParserContextBlockNode newBlock = newBlock(); + try { + statement(false, false, true, labelledStatement, Collections.emptyList()); + } finally { + restoreBlock(newBlock); + } + + verifyBlockScopedBindings(newBlock.getStatements()); + + return new Block(newBlock.getToken(), finish, newBlock.getFlags() | Block.IS_SYNTHETIC, newBlock.getStatements()); + } + + /** + * Detect calls to special functions. + * @param ident Called function. + */ + private void detectSpecialFunction(final IdentNode ident) { + final String name = ident.getName(); + + if (EVAL_NAME.equals(name)) { + markEval(lc); + } else if (SUPER.getName().equals(name)) { + assert ident.isDirectSuper(); + markSuperCall(lc); + } + } + + /** + * Detect use of special properties. + * @param ident Referenced property. + */ + private void detectSpecialProperty(final IdentNode ident) { + if (isArguments(ident)) { + // skip over arrow functions, e.g. function f() { return (() => arguments.length)(); } + getCurrentNonArrowFunction().setFlag(FunctionNode.USES_ARGUMENTS); + } + } + + private boolean useBlockScope() { + return env.ecmascriptEdition >= 6; + } + + private boolean isAtLeastES6() { + return env.ecmascriptEdition >= 6; + } + + private boolean isAtLeastES7() { + return env.ecmascriptEdition >= 7; + } + + private boolean isAtLeastES11() { + return env.ecmascriptEdition >= 11; + } + + private static boolean isArguments(final String name) { + return ARGUMENTS_NAME.equals(name); + } + + static boolean isArguments(final IdentNode ident) { + return isArguments(ident.getName()); + } + + /** + * Tells whether a IdentNode can be used as L-value of an assignment + * + * @param ident IdentNode to be checked + * @return whether the ident can be used as L-value + */ + private static boolean checkIdentLValue(final IdentNode ident) { + return ident.tokenType().getKind() != TokenKind.KEYWORD; + } + + /** + * Verify an assignment expression. + * @param op Operation token. + * @param lhs Left hand side expression. + * @param rhs Right hand side expression. + * @return Verified expression. + */ + private Expression verifyAssignment(final long op, final Expression lhs, final Expression rhs) { + final TokenType opType = Token.descType(op); + + switch (opType) { + case ASSIGN: + case ASSIGN_ADD: + case ASSIGN_BIT_AND: + case ASSIGN_BIT_OR: + case ASSIGN_BIT_XOR: + case ASSIGN_DIV: + case ASSIGN_MOD: + case ASSIGN_MUL: + case ASSIGN_EXP: + case ASSIGN_SAR: + case ASSIGN_SHL: + case ASSIGN_SHR: + case ASSIGN_SUB: + case ASSIGN_LOG_AND: + case ASSIGN_LOG_OR: + case ASSIGN_NULLISH: + if (lhs instanceof IdentNode) { + if (!checkIdentLValue((IdentNode)lhs)) { + return referenceError(lhs, rhs, false); + } + verifyIdent((IdentNode)lhs, "assignment"); + break; + } else if (lhs instanceof AccessNode || lhs instanceof IndexNode) { + break; + } else if (opType == ASSIGN && isDestructuringLhs(lhs)) { + verifyDestructuringAssignmentPattern(lhs, "assignment"); + break; + } else { + return referenceError(lhs, rhs, env.earlyLvalueError); + } + default: + break; + } + + assert !BinaryNode.isLogical(opType); + return new BinaryNode(op, lhs, rhs); + } + + private boolean isDestructuringLhs(Expression lhs) { + if (lhs instanceof ObjectNode || lhs instanceof ArrayLiteralNode) { + return isAtLeastES6(); + } + return false; + } + + private void verifyDestructuringAssignmentPattern(Expression pattern, String contextString) { + assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode; + pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) { + @Override + protected void verifySpreadElement(Expression lvalue) { + if (!checkValidLValue(lvalue, contextString)) { + throw error(AbstractParser.message("invalid.lvalue"), lvalue.getToken()); + } + } + + @Override + public boolean enterIdentNode(IdentNode identNode) { + verifyIdent(identNode, contextString); + if (!checkIdentLValue(identNode)) { + referenceError(identNode, null, true); + return false; + } + return false; + } + + @Override + public boolean enterAccessNode(AccessNode accessNode) { + return false; + } + + @Override + public boolean enterIndexNode(IndexNode indexNode) { + return false; + } + + @Override + protected boolean enterDefault(Node node) { + throw error(String.format("unexpected node in AssignmentPattern: %s", node)); + } + }); + } + + private static Expression newBinaryExpression(final long op, final Expression lhs, final Expression rhs) { + final TokenType opType = Token.descType(op); + + // Build up node. + if (BinaryNode.isLogical(opType)) { + return new BinaryNode(op, new JoinPredecessorExpression(lhs), new JoinPredecessorExpression(rhs)); + } + return new BinaryNode(op, lhs, rhs); + } + + + /** + * Reduce increment/decrement to simpler operations. + * @param firstToken First token. + * @param tokenType Operation token (INCPREFIX/DEC.) + * @param expression Left hand side expression. + * @param isPostfix Prefix or postfix. + * @return Reduced expression. + */ + private static UnaryNode incDecExpression(final long firstToken, final TokenType tokenType, final Expression expression, final boolean isPostfix) { + if (isPostfix) { + return new UnaryNode(Token.recast(firstToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX), expression.getStart(), Token.descPosition(firstToken) + Token.descLength(firstToken), expression); + } + + return new UnaryNode(firstToken, expression); + } + + /** + * ----------------------------------------------------------------------- + * + * Grammar based on + * + * ECMAScript Language Specification + * ECMA-262 5th Edition / December 2009 + * + * ----------------------------------------------------------------------- + */ + + /** + * Program : + * SourceElements? + * + * See 14 + * + * Parse the top level script. + */ + private FunctionNode program(final String scriptName, final boolean allowPropertyFunction) { + // Make a pseudo-token for the script holding its start and length. + int functionStart = Math.min(Token.descPosition(Token.withDelimiter(token)), finish); + final long functionToken = Token.toDesc(FUNCTION, functionStart, source.getLength() - functionStart); + final int functionLine = line; + + final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), scriptName); + final ParserContextFunctionNode script = createParserContextFunctionNode( + ident, + functionToken, + FunctionNode.Kind.SCRIPT, + functionLine, + Collections.emptyList()); + lc.push(script); + final ParserContextBlockNode body = newBlock(); + + functionDeclarations = new ArrayList<>(); + sourceElements(allowPropertyFunction); + addFunctionDeclarations(script); + functionDeclarations = null; + + restoreBlock(body); + body.setFlag(Block.NEEDS_SCOPE); + + verifyBlockScopedBindings(body.getStatements()); + + final Block programBody = new Block(functionToken, finish, body.getFlags() | Block.IS_SYNTHETIC | Block.IS_BODY, body.getStatements()); + lc.pop(script); + script.setLastToken(token); + + expect(EOF); + + return createFunctionNode(script, functionToken, ident, Collections.emptyList(), FunctionNode.Kind.SCRIPT, functionLine, programBody); + } + + /** + * Directive value or null if statement is not a directive. + * + * @param stmt Statement to be checked + * @return Directive value if the given statement is a directive + */ + private String getDirective(final Node stmt) { + if (stmt instanceof ExpressionStatement) { + final Node expr = ((ExpressionStatement)stmt).getExpression(); + if (expr instanceof LiteralNode) { + final LiteralNode lit = (LiteralNode)expr; + final long litToken = lit.getToken(); + final TokenType tt = Token.descType(litToken); + // A directive is either a string or an escape string + if (tt == TokenType.STRING || tt == TokenType.ESCSTRING) { + // Make sure that we don't unescape anything. Return as seen in source! + return source.getString(lit.getStart(), Token.descLength(litToken)); + } + } + } + + return null; + } + + /** + * SourceElements : + * SourceElement + * SourceElements SourceElement + * + * See 14 + * + * Parse the elements of the script or function. + */ + private void sourceElements(final boolean shouldAllowPropertyFunction) { + List directiveStmts = null; + boolean checkDirective = true; + boolean allowPropertyFunction = shouldAllowPropertyFunction; + final boolean oldStrictMode = isStrictMode; + + + try { + // If is a script, then process until the end of the script. + while (type != EOF) { + // Break if the end of a code block. + if (type == RBRACE) { + break; + } + + try { + // Get the next element. + statement(true, allowPropertyFunction, false, false, Collections.emptyList()); + allowPropertyFunction = false; + + // check for directive prologues + if (checkDirective) { + // skip any debug statement like line number to get actual first line + final Statement lastStatement = lc.getLastStatement(); + + // get directive prologue, if any + final String directive = getDirective(lastStatement); + + // If we have seen first non-directive statement, + // no more directive statements!! + checkDirective = directive != null; + + if (checkDirective) { + if (!oldStrictMode) { + if (directiveStmts == null) { + directiveStmts = new ArrayList<>(); + } + directiveStmts.add(lastStatement); + } + + // handle use strict directive + if ("use strict".equals(directive)) { + isStrictMode = true; + final ParserContextFunctionNode function = lc.getCurrentFunction(); + function.setFlag(FunctionNode.IS_STRICT); + + // We don't need to check these, if lexical environment is already strict + if (!oldStrictMode && directiveStmts != null) { + // check that directives preceding this one do not violate strictness + for (final Node statement : directiveStmts) { + // the get value will force unescape of preceding + // escaped string directives + getValue(statement.getToken()); + } + + // verify that function name as well as parameter names + // satisfy strict mode restrictions. + verifyIdent(function.getIdent(), "function name"); + for (final IdentNode param : function.getParameters()) { + verifyIdent(param, "function parameter"); + } + } + } + } + } + } catch (final Exception e) { + final int errorLine = line; + final long errorToken = token; + //recover parsing + recover(e); + final ErrorNode errorExpr = new ErrorNode(errorToken, finish); + final ExpressionStatement expressionStatement = new ExpressionStatement(errorLine, errorToken, finish, errorExpr); + appendStatement(expressionStatement); + } + + // No backtracking from here on. + stream.commit(k); + } + } finally { + isStrictMode = oldStrictMode; + } + } + + /** + * Parse any of the basic statement types. + * + * Statement : + * BlockStatement + * VariableStatement + * EmptyStatement + * ExpressionStatement + * IfStatement + * BreakableStatement + * ContinueStatement + * BreakStatement + * ReturnStatement + * WithStatement + * LabelledStatement + * ThrowStatement + * TryStatement + * DebuggerStatement + * + * BreakableStatement : + * IterationStatement + * SwitchStatement + * + * BlockStatement : + * Block + * + * Block : + * { StatementList opt } + * + * StatementList : + * StatementListItem + * StatementList StatementListItem + * + * StatementItem : + * Statement + * Declaration + * + * Declaration : + * HoistableDeclaration + * ClassDeclaration + * LexicalDeclaration + * + * HoistableDeclaration : + * FunctionDeclaration + * GeneratorDeclaration + */ + private void statement() { + statement(false, false, false, false, Collections.emptyList()); + } + + /** + * @param topLevel does this statement occur at the "top level" of a script or a function? + * @param allowPropertyFunction allow property "get" and "set" functions? + * @param singleStatement are we in a single statement context? + */ + private void statement(final boolean topLevel, final boolean allowPropertyFunction, + final boolean singleStatement, final boolean labelledStatement, List decorators) { + if (!decorators.isEmpty() && type != CLASS) { + throw error(expectMessage(CLASS)); + } + switch (type) { + case LBRACE: + block(); + break; + case VAR: + variableStatement(type); + break; + case SEMICOLON: + emptyStatement(); + break; + case IF: + ifStatement(); + break; + case FOR: + forStatement(); + break; + case WHILE: + whileStatement(); + break; + case DO: + doStatement(); + break; + case CONTINUE: + continueStatement(); + break; + case BREAK: + breakStatement(); + break; + case RETURN: + returnStatement(); + break; + case WITH: + withStatement(); + break; + case SWITCH: + switchStatement(); + break; + case THROW: + throwStatement(); + break; + case TRY: + tryStatement(); + break; + case DEBUGGER: + debuggerStatement(); + break; + case RPAREN: + case RBRACKET: + case EOF: + expect(SEMICOLON); + break; + case FUNCTION: + // As per spec (ECMA section 12), function declarations as arbitrary statement + // is not "portable". Implementation can issue a warning or disallow the same. + if (singleStatement) { + // ES6 B.3.2 Labelled Function Declarations + // It is a Syntax Error if any strict mode source code matches this rule: + // LabelledItem : FunctionDeclaration. + if (!labelledStatement || isStrictMode) { + throw error(AbstractParser.message("expected.stmt", "function declaration"), token); + } + } + functionExpression(true, topLevel || labelledStatement, false); + return; + default: + if (useBlockScope() && (type == LET && lookaheadIsLetDeclaration(false) || type == CONST)) { + if (singleStatement) { + throw error(AbstractParser.message("expected.stmt", type.getName() + " declaration"), token); + } + variableStatement(type); + break; + // either it is ES6 class or it starts with @something as is ES7 decorated class + } else if (isAtLeastES6() && (type == CLASS || (isAtLeastES7() && type == AT))) { + if (singleStatement) { + throw error(AbstractParser.message("expected.stmt", "class declaration"), token); + } + classDeclaration(false, decorators); + break; + // start of async function + } else if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(false)) { + nextOrEOL(); + functionExpression(true, topLevel || labelledStatement, true); + break; + } + if (env.constAsVar && type == CONST) { + variableStatement(TokenType.VAR); + break; + } + + if (isBindingIdentifier()) { + if (T(k + 1) == COLON && (type != YIELD || !inGeneratorFunction()) && (!isAwait(token) || !inAsyncFunction())) { + labelStatement(); + return; + } + if (allowPropertyFunction) { + final String ident = (String)getValue(); + final long propertyToken = token; + final int propertyLine = line; + if ("get".equals(ident)) { + next(); + addPropertyFunctionStatement(propertyGetterFunction(propertyToken, propertyLine)); + return; + } else if ("set".equals(ident)) { + next(); + addPropertyFunctionStatement(propertySetterFunction(propertyToken, propertyLine)); + return; + } + } + } + + expressionStatement(); + break; + } + } + + private void addPropertyFunctionStatement(final PropertyFunction propertyFunction) { + final FunctionNode fn = propertyFunction.functionNode; + functionDeclarations.add(new ExpressionStatement(fn.getLineNumber(), fn.getToken(), finish, fn)); + } + + /** + * ClassDeclaration[Yield, Default] : + * class BindingIdentifier[?Yield] ClassTail[?Yield] + * [+Default] class ClassTail[?Yield] + */ + private ClassNode classDeclaration(boolean isDefault, List exportDecorators) { + int classLineNumber = line; + + ClassNode classExpression = classExpression(!isDefault, exportDecorators); + + if (!isDefault) { + VarNode classVar = new VarNode(classLineNumber, classExpression.getToken(), classExpression.getIdent().getFinish(), classExpression.getIdent(), classExpression, VarNode.IS_CONST); + appendStatement(classVar); + } + return classExpression; + } + + /** + * ClassExpression[Yield] : + * class BindingIdentifier[?Yield]opt ClassTail[?Yield] + */ + private ClassNode classExpression(boolean isStatement, List exportDecorators) { + assert type == CLASS || type == AT; + int classLineNumber = line; + long classToken = token; + List decorators = new ArrayList<>(exportDecorators); + decorators.addAll(decoratorList()); + expect(CLASS); + + IdentNode className = null; + if (isStatement || type == IDENT) { + className = getIdent(); + } + + return classTail(classLineNumber, classToken, className, decorators); + } + + private static final class ClassElementKey { + private final boolean isStatic; + private final String propertyName; + + private ClassElementKey(boolean isStatic, String propertyName) { + this.isStatic = isStatic; + this.propertyName = propertyName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (isStatic ? 1231 : 1237); + result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ClassElementKey) { + ClassElementKey other = (ClassElementKey) obj; + return this.isStatic == other.isStatic && Objects.equals(this.propertyName, other.propertyName); + } + return false; + } + } + + /** + * Parse ClassTail and ClassBody. + * + * ClassTail[Yield] : + * ClassHeritage[?Yield]opt { ClassBody[?Yield]opt } + * ClassHeritage[Yield] : + * extends LeftHandSideExpression[?Yield] + * + * ClassBody[Yield] : + * ClassElementList[?Yield] + * ClassElementList[Yield] : + * ClassElement[?Yield] + * ClassElementList[?Yield] ClassElement[?Yield] + * ClassElement[Yield] : + * MethodDefinition[?Yield] + * static MethodDefinition[?Yield] + * ; + */ + private ClassNode classTail(int classLineNumber, long classToken, IdentNode className, List decorators) { + boolean oldStrictMode = isStrictMode; + isStrictMode = true; + try { + Expression classHeritage = null; + if (type == EXTENDS) { + next(); + classHeritage = leftHandSideExpression(); + } + + expect(LBRACE); + + PropertyNode constructor = null; + ArrayList classElements = new ArrayList<>(); + Map keyToIndexMap = new HashMap<>(); + for (;;) { + if (type == SEMICOLON) { + next(); + continue; + } + if (type == RBRACE) { + break; + } + + List methodDecorators; + if (type == AT) { + methodDecorators = decoratorList(); + } else { + methodDecorators = Collections.emptyList(); + } + long classElementToken = token; + boolean isStatic = false; + if (type == STATIC) { + isStatic = true; + next(); + } + boolean async = false; + if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(true)) { + async = true; + next(); + } + boolean generator = false; + if (!async && type == MUL && isAtLeastES6()) { + generator = true; + next(); + } + PropertyNode classElement = classElement(isStatic, classHeritage != null, generator, async, methodDecorators); + if (classElement.isComputed()) { + classElements.add(classElement); + } else if (!classElement.isStatic() && classElement.getKeyName().equals("constructor")) { + if (constructor == null) { + constructor = classElement; + } else { + throw error(AbstractParser.message("multiple.constructors"), classElementToken); + } + } else { + // Check for duplicate method definitions and combine accessor methods. + // In ES6, a duplicate is never an error regardless of strict mode (in consequence of computed property names). + + final ClassElementKey key = new ClassElementKey(classElement.isStatic(), classElement.getKeyName()); + final Integer existing = keyToIndexMap.get(key); + + if (existing == null) { + keyToIndexMap.put(key, classElements.size()); + classElements.add(classElement); + } else { + final PropertyNode existingProperty = classElements.get(existing); + + final Expression value = classElement.getValue(); + final FunctionNode getter = classElement.getGetter(); + final FunctionNode setter = classElement.getSetter(); + + if (value != null || existingProperty.getValue() != null) { + keyToIndexMap.put(key, classElements.size()); + classElements.add(classElement); + } else if (getter != null) { + assert existingProperty.getGetter() != null || existingProperty.getSetter() != null; + classElements.set(existing, existingProperty.setGetter(getter)); + } else if (setter != null) { + assert existingProperty.getGetter() != null || existingProperty.getSetter() != null; + classElements.set(existing, existingProperty.setSetter(setter)); + } + } + } + } + + long lastToken = token; + expect(RBRACE); + + if (constructor == null) { + constructor = createDefaultClassConstructor(classLineNumber, classToken, lastToken, className, classHeritage != null); + } + + classElements.trimToSize(); + return new ClassNode(classLineNumber, classToken, finish, className, classHeritage, constructor, classElements, decorators); + } finally { + isStrictMode = oldStrictMode; + } + } + + private PropertyNode createDefaultClassConstructor(int classLineNumber, long classToken, long lastToken, IdentNode className, boolean subclass) { + final int ctorFinish = finish; + final List statements; + final List parameters; + final long identToken = Token.recast(classToken, TokenType.IDENT); + if (subclass) { + IdentNode superIdent = createIdentNode(identToken, ctorFinish, SUPER.getName()).setIsDirectSuper(); + IdentNode argsIdent = createIdentNode(identToken, ctorFinish, "args").setIsRestParameter(); + Expression spreadArgs = new UnaryNode(Token.recast(classToken, TokenType.SPREAD_ARGUMENT), argsIdent); + CallNode superCall = new CallNode(classLineNumber, classToken, ctorFinish, superIdent, Collections.singletonList(spreadArgs), false); + statements = Collections.singletonList(new ExpressionStatement(classLineNumber, classToken, ctorFinish, superCall)); + parameters = Collections.singletonList(argsIdent); + } else { + statements = Collections.emptyList(); + parameters = Collections.emptyList(); + } + + verifyBlockScopedBindings(statements); + + Block body = new Block(classToken, ctorFinish, Block.IS_BODY, statements); + IdentNode ctorName = className != null ? className : createIdentNode(identToken, ctorFinish, "constructor"); + ParserContextFunctionNode function = createParserContextFunctionNode(ctorName, classToken, FunctionNode.Kind.NORMAL, classLineNumber, parameters); + function.setLastToken(lastToken); + + function.setFlag(FunctionNode.IS_METHOD); + function.setFlag(FunctionNode.IS_CLASS_CONSTRUCTOR); + if (subclass) { + function.setFlag(FunctionNode.IS_SUBCLASS_CONSTRUCTOR); + function.setFlag(FunctionNode.HAS_DIRECT_SUPER); + } + if (className == null) { + function.setFlag(FunctionNode.IS_ANONYMOUS); + } + + PropertyNode constructor = new PropertyNode(classToken, ctorFinish, ctorName, createFunctionNode( + function, + classToken, + ctorName, + parameters, + FunctionNode.Kind.NORMAL, + classLineNumber, + body + ), null, null, false, false, Collections.emptyList()); + return constructor; + } + + private PropertyNode classElement(boolean isStatic, boolean subclass, boolean generator, boolean async, List decorators) { + final long methodToken = token; + final int methodLine = line; + final boolean computed = type == LBRACKET; + final boolean isIdent = type == IDENT; + Expression propertyName = propertyName(); + int flags = FunctionNode.IS_METHOD; + if (!computed) { + final String name = ((PropertyKey)propertyName).getPropertyName(); + if (!generator && isIdent && type != LPAREN && name.equals("get") + && (!isAtLeastES7() || isPropertyName(token))) { + PropertyFunction methodDefinition = propertyGetterFunction(methodToken, methodLine, flags); + verifyAllowedMethodName(methodDefinition.key, isStatic, methodDefinition.computed, generator, async, true); + return new PropertyNode(methodToken, finish, methodDefinition.key, null, methodDefinition.functionNode, null, isStatic, methodDefinition.computed, decorators); + } else if (!generator && isIdent && type != LPAREN && name.equals("set") + && (!isAtLeastES7() || isPropertyName(token))) { + PropertyFunction methodDefinition = propertySetterFunction(methodToken, methodLine, flags); + verifyAllowedMethodName(methodDefinition.key, isStatic, methodDefinition.computed, generator, async, true); + return new PropertyNode(methodToken, finish, methodDefinition.key, null, null, methodDefinition.functionNode, isStatic, methodDefinition.computed, decorators); + } else { + if (!isStatic && !generator && name.equals("constructor")) { + flags |= FunctionNode.IS_CLASS_CONSTRUCTOR; + if (subclass) { + flags |= FunctionNode.IS_SUBCLASS_CONSTRUCTOR; + } + } + if (async) { + flags |= FunctionNode.IS_ASYNC; + } + verifyAllowedMethodName(propertyName, isStatic, computed, generator, async, false); + } + } + // ClassFieldInitializer + if (isAtLeastES7() && type != LPAREN && !async) { + // XXX decorators on properties + Expression assignment = null; + if (type == ASSIGN) { + next(); + assignment = assignmentExpression(false); + } + endOfLine(); + return new PropertyNode(methodToken, finish, propertyName, assignment, null, null, isStatic, computed, decorators); + } + PropertyFunction methodDefinition = propertyMethodFunction(propertyName, methodToken, methodLine, generator, false, flags, computed); + return new PropertyNode(methodToken, finish, methodDefinition.key, methodDefinition.functionNode, null, null, isStatic, computed, decorators); + } + + /** + * https://github.com/wycats/javascript-decorators + */ + private List decoratorList() { + if (!isAtLeastES7() || type != AT) { + return Collections.emptyList(); + } + List decorators = new ArrayList<>(); + for (;;) { + next(); + Expression decorator = leftHandSideExpression(); + if (decorator == null) { + throw error(AbstractParser.message("expected.lvalue", type.getNameOrType())); + } + // FIXME https://github.com/wycats/javascript-decorators/issues/10 #2 + decorators.add(decorator); + if (type != AT) { + break; + } + } + return decorators; + } + + /** + * ES6 14.5.1 Static Semantics: Early Errors. + */ + private void verifyAllowedMethodName(Expression key, boolean isStatic, boolean computed, boolean generator, boolean async, boolean accessor) { + if (!computed) { + if (!isStatic && generator && ((PropertyKey) key).getPropertyName().equals("constructor")) { + throw error(AbstractParser.message("generator.constructor"), key.getToken()); + } + if (!isStatic && accessor && ((PropertyKey) key).getPropertyName().equals("constructor")) { + throw error(AbstractParser.message("accessor.constructor"), key.getToken()); + } + if (isStatic && ((PropertyKey) key).getPropertyName().equals("prototype")) { + throw error(AbstractParser.message("static.prototype.method"), key.getToken()); + } + } + } + + private boolean isPropertyName(long token) { + TokenType currentType = Token.descType(token); + if (currentType == LBRACKET && isAtLeastES6()) { + // computed property + return true; + } + + switch (currentType) { + case IDENT: + return true; + case OCTAL_LEGACY: + if (isStrictMode) { + return false; + } + case STRING: + case ESCSTRING: + case DECIMAL: + case HEXADECIMAL: + case OCTAL: + case BINARY_NUMBER: + case FLOATING: + case BIGINT: + return true; + default: + return isIdentifierName(token); + } + } + + /** + * block : + * { StatementList? } + * + * see 12.1 + * + * Parse a statement block. + */ + private void block() { + appendStatement(new BlockStatement(line, getBlock(true))); + } + + /** + * StatementList : + * Statement + * StatementList Statement + * + * See 12.1 + * + * Parse a list of statements. + */ + private void statementList() { + // Accumulate statements until end of list. */ +loop: + while (type != EOF) { + switch (type) { + case EOF: + case CASE: + case DEFAULT: + case RBRACE: + break loop; + default: + break; + } + + // Get next statement. + statement(); + } + } + + /** + * Make sure that the identifier name used is allowed. + * + * @param ident Identifier that is verified + * @param contextString String used in error message to give context to the user + */ + private void verifyIdent(final IdentNode ident, final String contextString) { + verifyStrictIdent(ident, contextString); + if (isAtLeastES6()) { + TokenType tokenType = TokenLookup.lookupKeyword(ident.getName(), 0, ident.getName().length()); + if (tokenType != IDENT && tokenType.getKind() != TokenKind.FUTURESTRICT) { + throw error(expectMessage(IDENT)); + } + if (isModule && "await".equals(ident.getName())) { + throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); + } + } + } + + /** + * Make sure that in strict mode, the identifier name used is allowed. + * + * @param ident Identifier that is verified + * @param contextString String used in error message to give context to the user + */ + private void verifyStrictIdent(final IdentNode ident, final String contextString) { + if (isStrictMode) { + switch (ident.getName()) { + case "eval": + case "arguments": + throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); + default: + break; + } + + if (ident.isFutureStrictName()) { + throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken()); + } + } + } + + /** + * Verify, that lexical bindings are declared only once and don't collide + * with var declared bindings. + */ + private void verifyBlockScopedBindings(final List statements) { + Set boundNames = new HashSet<>(); + for(Statement s: statements) { + if(s instanceof VarNode) { + String name = ((VarNode) s).getName().getName(); + if(((VarNode) s).isBlockScoped() && boundNames.contains(name)) { + throw error(AbstractParser.message("redeclaration.of.binding", name), s.getToken()); + } + boundNames.add(name); + } + } + } + + /** + * VariableStatement : + * var VariableDeclarationList ; + * + * VariableDeclarationList : + * VariableDeclaration + * VariableDeclarationList , VariableDeclaration + * + * VariableDeclaration : + * Identifier Initializer? + * + * Initializer : + * = AssignmentExpression + * + * See 12.2 + * + * Parse a VAR statement. + */ + private void variableStatement(final TokenType varType) { + variableDeclarationList(varType, true, -1); + } + + private static final class ForVariableDeclarationListResult { + /** First missing const or binding pattern initializer. */ + Expression missingAssignment; + /** First declaration with an initializer. */ + long declarationWithInitializerToken; + /** Destructuring assignments. */ + Expression init; + Expression firstBinding; + Expression secondBinding; + + void recordMissingAssignment(Expression binding) { + if (missingAssignment == null) { + missingAssignment = binding; + } + } + + void recordDeclarationWithInitializer(long token) { + if (declarationWithInitializerToken == 0L) { + declarationWithInitializerToken = token; + } + } + + void addBinding(Expression binding) { + if (firstBinding == null) { + firstBinding = binding; + } else if (secondBinding == null) { + secondBinding = binding; + } + // ignore the rest + } + + void addAssignment(Expression assignment) { + if (init == null) { + init = assignment; + } else { + init = new BinaryNode(Token.recast(init.getToken(), COMMARIGHT), init, assignment); + } + } + } + + /** + * @param isStatement {@code true} if a VariableStatement, {@code false} if a {@code for} loop VariableDeclarationList + */ + private ForVariableDeclarationListResult variableDeclarationList(final TokenType varType, final boolean isStatement, final int sourceOrder) { + // VAR tested in caller. + assert varType == VAR || varType == LET || varType == CONST; + next(); + + int varFlags = 0; + if (varType == LET) { + varFlags |= VarNode.IS_LET; + } else if (varType == CONST) { + varFlags |= VarNode.IS_CONST; + } + + ForVariableDeclarationListResult forResult = isStatement ? null : new ForVariableDeclarationListResult(); + while (true) { + // Get starting token. + final int varLine = line; + final long varToken = Token.recast(token, varType); + // Get name of var. + if (type == YIELD && inGeneratorFunction()) { + expect(IDENT); + } + + final String contextString = "variable name"; + final Expression binding = bindingIdentifierOrPattern(contextString); + final boolean isDestructuring = !(binding instanceof IdentNode); + if (isDestructuring) { + final int finalVarFlags = varFlags | VarNode.IS_DESTRUCTURING; + verifyDestructuringBindingPattern(binding, new Consumer() { + public void accept(IdentNode identNode) { + verifyIdent(identNode, contextString); + final VarNode var = new VarNode(varLine, varToken, sourceOrder, identNode.getFinish(), identNode.setIsDeclaredHere(), null, finalVarFlags); + appendStatement(var); + } + }); + } + + // Assume no init. + Expression init = null; + + // Look for initializer assignment. + if (type == ASSIGN) { + if (!isStatement) { + forResult.recordDeclarationWithInitializer(varToken); + } + next(); + + // Get initializer expression. Suppress IN if not statement. + if (!isDestructuring) { + defaultNames.push(binding); + } + try { + init = assignmentExpression(!isStatement); + } finally { + if (!isDestructuring) { + defaultNames.pop(); + } + } + } else if (isStatement) { + if (isDestructuring) { + throw error(AbstractParser.message("missing.destructuring.assignment"), token); + } else if (varType == CONST) { + throw error(AbstractParser.message("missing.const.assignment", ((IdentNode)binding).getName())); + } + // else, if we are in a for loop, delay checking until we know the kind of loop + } + + if (!isDestructuring) { + assert init != null || varType != CONST || !isStatement; + final IdentNode ident = (IdentNode)binding; + if (!isStatement) { + if (ident.getName().equals("let")) { + throw error("let is not a valid binding name in a for loop"); // ES6 13.7.5.1 + } + if (init == null && varType == CONST) { + forResult.recordMissingAssignment(binding); + } + forResult.addBinding(binding); + } + final VarNode var = new VarNode(varLine, varToken, sourceOrder, finish, ident.setIsDeclaredHere(), init, varFlags); + appendStatement(var); + } else { + assert init != null || !isStatement; + if (init != null) { + final Expression assignment = verifyAssignment(Token.recast(varToken, ASSIGN), binding, init); + if (isStatement) { + appendStatement(new ExpressionStatement(varLine, assignment.getToken(), finish, assignment)); + } else { + forResult.addAssignment(assignment); + forResult.addBinding(assignment); + } + } else if (!isStatement) { + forResult.recordMissingAssignment(binding); + forResult.addBinding(binding); + } + } + + if (type != COMMARIGHT) { + break; + } + next(); + } + + // If is a statement then handle end of line. + if (isStatement) { + endOfLine(); + } + return forResult; + } + + private boolean isBindingIdentifier() { + return type == IDENT || isNonStrictModeIdent(); + } + + private IdentNode bindingIdentifier(String contextString) { + final IdentNode name = getIdent(); + verifyIdent(name, contextString); + return name; + } + + private Expression bindingPattern() { + if (type == LBRACKET) { + return arrayLiteral(); + } else if (type == LBRACE) { + return objectLiteral(); + } else { + throw error(AbstractParser.message("expected.binding")); + } + } + + private Expression bindingIdentifierOrPattern(String contextString) { + if (isBindingIdentifier() || !(isAtLeastES6())) { + return bindingIdentifier(contextString); + } else { + return bindingPattern(); + } + } + + private abstract class VerifyDestructuringPatternNodeVisitor extends NodeVisitor { + VerifyDestructuringPatternNodeVisitor(LexicalContext lc) { + super(lc); + } + + @Override + public boolean enterLiteralNode(LiteralNode literalNode) { + if (literalNode.isArray()) { + if (((ArrayLiteralNode)literalNode).hasSpread() && ((ArrayLiteralNode)literalNode).hasTrailingComma()) { + throw error("Rest element must be last", literalNode.getElementExpressions().get(literalNode.getElementExpressions().size() - 1).getToken()); + } + boolean restElement = false; + for (Expression element : literalNode.getElementExpressions()) { + if (element != null) { + if (restElement) { + throw error("Unexpected element after rest element", element.getToken()); + } + if (element.isTokenType(SPREAD_ARRAY)) { + restElement = true; + Expression lvalue = ((UnaryNode) element).getExpression(); + verifySpreadElement(lvalue); + } + element.accept(this); + } + } + return false; + } else { + return enterDefault(literalNode); + } + } + + protected abstract void verifySpreadElement(Expression lvalue); + + @Override + public boolean enterObjectNode(ObjectNode objectNode) { + boolean restElement = false; + for (PropertyNode property : objectNode.getElements()) { + if (property != null) { + if (restElement) { + throw error("Unexpected element after rest element", property.getToken()); + } + Expression value = property.getValue(); + if (value.isTokenType(SPREAD_OBJECT)) { + restElement = true; + Expression lvalue = ((UnaryNode) value).getExpression(); + verifySpreadElement(lvalue); + } + property.accept(this); + } + } + return false; + } + + @Override + public boolean enterPropertyNode(PropertyNode propertyNode) { + if (propertyNode.getValue() != null) { + propertyNode.getValue().accept(this); + return false; + } else { + return enterDefault(propertyNode); + } + } + + @Override + public boolean enterBinaryNode(BinaryNode binaryNode) { + if (binaryNode.isTokenType(ASSIGN)) { + binaryNode.lhs().accept(this); + // Initializer(rhs) can be any AssignmentExpression + return false; + } else { + return enterDefault(binaryNode); + } + } + + @Override + public boolean enterUnaryNode(UnaryNode unaryNode) { + if (unaryNode.isTokenType(SPREAD_ARRAY) || unaryNode.isTokenType(SPREAD_OBJECT)) { + // rest element + return true; + } else { + return enterDefault(unaryNode); + } + } + } + + /** + * Verify destructuring variable declaration binding pattern and extract bound variable declarations. + */ + private void verifyDestructuringBindingPattern(Expression pattern, Consumer identifierCallback) { + assert pattern instanceof ObjectNode || pattern instanceof ArrayLiteralNode; + pattern.accept(new VerifyDestructuringPatternNodeVisitor(new LexicalContext()) { + @Override + protected void verifySpreadElement(Expression lvalue) { + if (lvalue instanceof IdentNode) { + // checked in identifierCallback + } else if (isDestructuringLhs(lvalue)) { + verifyDestructuringBindingPattern(lvalue, identifierCallback); + } else { + throw error("Expected a valid binding identifier", lvalue.getToken()); + } + } + + @Override + public boolean enterIdentNode(IdentNode identNode) { + identifierCallback.accept(identNode); + return false; + } + + @Override + protected boolean enterDefault(Node node) { + throw error(String.format("unexpected node in BindingPattern: %s", node)); + } + }); + } + + /** + * EmptyStatement : + * ; + * + * See 12.3 + * + * Parse an empty statement. + */ + private void emptyStatement() { + if (env.emptyStatements) { + appendStatement(new EmptyNode(line, token, Token.descPosition(token) + Token.descLength(token))); + } + + // SEMICOLON checked in caller. + next(); + } + + /** + * ExpressionStatement : + * Expression ; // [lookahead ~( or function )] + * + * See 12.4 + * + * Parse an expression used in a statement block. + */ + private void expressionStatement() { + // Lookahead checked in caller. + final int expressionLine = line; + final long expressionToken = token; + + // Get expression and add as statement. + final Expression expression = expression(); + + ExpressionStatement expressionStatement = null; + if (expression != null) { + endOfLine(); + expressionStatement = new ExpressionStatement(expressionLine, expressionToken, finish, expression); + appendStatement(expressionStatement); + } else { + expect(null); + endOfLine(); + } + } + + /** + * IfStatement : + * if ( Expression ) Statement else Statement + * if ( Expression ) Statement + * + * See 12.5 + * + * Parse an IF statement. + */ + private void ifStatement() { + // Capture IF token. + final int ifLine = line; + final long ifToken = token; + // IF tested in caller. + next(); + + expect(LPAREN); + final Expression test = expression(); + expect(RPAREN); + final Block pass = getStatement(); + + Block fail = null; + if (type == ELSE) { + next(); + fail = getStatement(); + } + + appendStatement(new IfNode(ifLine, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail)); + } + + /** + * ... IterationStatement: + * ... + * for ( Expression[NoIn]?; Expression? ; Expression? ) Statement + * for ( var VariableDeclarationList[NoIn]; Expression? ; Expression? ) Statement + * for ( LeftHandSideExpression in Expression ) Statement + * for ( var VariableDeclaration[NoIn] in Expression ) Statement + * + * See 12.6 + * + * Parse a FOR statement. + */ + private void forStatement() { + final long forToken = token; + final int forLine = line; + // start position of this for statement. This is used + // for sort order for variables declared in the initializer + // part of this 'for' statement (if any). + final int forStart = Token.descPosition(forToken); + // When ES6 for-let is enabled we create a container block to capture the LET. + final ParserContextBlockNode outer = useBlockScope() ? newBlock() : null; + + // Create FOR node, capturing FOR token. + final ParserContextLoopNode forNode = new ParserContextLoopNode(); + lc.push(forNode); + Block body = null; + Expression init = null; + JoinPredecessorExpression test = null; + JoinPredecessorExpression modify = null; + ForVariableDeclarationListResult varDeclList = null; + + int flags = 0; + boolean isForOf = false; + + try { + // FOR tested in caller. + next(); + + // Nashorn extension: for each expression. + // iterate property values rather than property names. + if (env.syntaxExtensions && type == IDENT && "each".equals(getValue())) { + flags |= ForNode.IS_FOR_EACH; + next(); + } + + expect(LPAREN); + + TokenType varType = null; + switch (type) { + case VAR: + // Var declaration captured in for outer block. + varDeclList = variableDeclarationList(varType = type, false, forStart); + break; + case SEMICOLON: + break; + default: + if (useBlockScope() && (type == LET && lookaheadIsLetDeclaration(true) || type == CONST)) { + if (type == LET) { + flags |= ForNode.PER_ITERATION_SCOPE; + } + // LET/CONST declaration captured in container block created above. + varDeclList = variableDeclarationList(varType = type, false, forStart); + break; + } + if (env.constAsVar && type == CONST) { + // Var declaration captured in for outer block. + varDeclList = variableDeclarationList(varType = TokenType.VAR, false, forStart); + break; + } + + init = expression(true, false); + break; + } + + switch (type) { + case SEMICOLON: + // for (init; test; modify) + if (varDeclList != null) { + assert init == null; + init = varDeclList.init; + // late check for missing assignment, now we know it's a for (init; test; modify) loop + if (varDeclList.missingAssignment != null) { + if (varDeclList.missingAssignment instanceof IdentNode) { + throw error(AbstractParser.message("missing.const.assignment", ((IdentNode)varDeclList.missingAssignment).getName())); + } else { + throw error(AbstractParser.message("missing.destructuring.assignment"), varDeclList.missingAssignment.getToken()); + } + } + } + + // for each (init; test; modify) is invalid + if ((flags & ForNode.IS_FOR_EACH) != 0) { + throw error(AbstractParser.message("for.each.without.in"), token); + } + + expect(SEMICOLON); + if (type != SEMICOLON) { + test = joinPredecessorExpression(); + } + expect(SEMICOLON); + if (type != RPAREN) { + modify = joinPredecessorExpression(); + } + break; + + case IDENT: + if (isAtLeastES6() && "of".equals(getValue())) { + isForOf = true; + // fall through + } else { + expect(SEMICOLON); // fail with expected message + break; + } + case IN: + flags |= isForOf ? ForNode.IS_FOR_OF : ForNode.IS_FOR_IN; + test = new JoinPredecessorExpression(); + if (varDeclList != null) { + // for (var|let|const ForBinding in|of expression) + if (varDeclList.secondBinding != null) { + // for (var i, j in obj) is invalid + throw error(AbstractParser.message("many.vars.in.for.in.loop", isForOf ? "of" : "in"), varDeclList.secondBinding.getToken()); + } + if (varDeclList.declarationWithInitializerToken != 0 && (isStrictMode || type != TokenType.IN || varType != VAR || varDeclList.init != null)) { + // ES5 legacy: for (var i = AssignmentExpressionNoIn in Expression) + // Invalid in ES6, but allow it in non-strict mode if no ES6 features used, + // i.e., error if strict, for-of, let/const, or destructuring + throw error(AbstractParser.message("for.in.loop.initializer", isForOf ? "of" : "in"), varDeclList.declarationWithInitializerToken); + } + init = varDeclList.firstBinding; + assert init instanceof IdentNode || isDestructuringLhs(init); + if (varType == CONST) { + flags |= ForNode.PER_ITERATION_SCOPE; + } + } else { + // for (LeftHandSideExpression in|of expression) + assert init != null : "for..in/of init expression can not be null here"; + + // check if initial expression is a valid L-value + if (!checkValidLValue(init, isForOf ? "for-of iterator" : "for-in iterator")) { + throw error(AbstractParser.message("not.lvalue.for.in.loop", isForOf ? "of" : "in"), init.getToken()); + } + } + + next(); + + // For-of only allows AssignmentExpression. + modify = isForOf ? new JoinPredecessorExpression(assignmentExpression(false)) : joinPredecessorExpression(); + break; + + default: + expect(SEMICOLON); + break; + } + + expect(RPAREN); + + // Set the for body. + body = getStatement(); + } finally { + lc.pop(forNode); + + for (final Statement var : forNode.getStatements()) { + assert var instanceof VarNode; + appendStatement(var); + } + if (body != null) { + appendStatement(new ForNode(forLine, forToken, body.getFinish(), body, (forNode.getFlags() | flags), init, test, modify)); + } + if (outer != null) { + restoreBlock(outer); + verifyBlockScopedBindings(outer.getStatements()); + if (body != null) { + appendStatement(new BlockStatement(forLine, new Block( + outer.getToken(), + body.getFinish(), + outer.getStatements()))); + } + } + } + } + + private boolean checkValidLValue(Expression init, String contextString) { + if (init instanceof IdentNode) { + if (!checkIdentLValue((IdentNode)init)) { + return false; + } + verifyIdent((IdentNode)init, contextString); + return true; + } else if (init instanceof AccessNode || init instanceof IndexNode) { + return true; + } else if (isDestructuringLhs(init)) { + verifyDestructuringAssignmentPattern(init, contextString); + return true; + } else { + return false; + } + } + + private boolean lookaheadIsLetDeclaration(boolean ofContextualKeyword) { + assert type == LET; + for (int i = 1;; i++) { + TokenType t = T(k + i); + switch (t) { + case EOL: + case COMMENT: + continue; + case IDENT: + if (ofContextualKeyword && isAtLeastES6() && "of".equals(getValue(getToken(k + i)))) { + return false; + } + // fall through + case LBRACKET: + case LBRACE: + return true; + default: + // accept future strict tokens in non-strict mode (including LET) + if (!isStrictMode && t.getKind() == TokenKind.FUTURESTRICT) { + return true; + } + return false; + } + } + } + + /** + * ...IterationStatement : + * ... + * while ( Expression ) Statement + * ... + * + * See 12.6 + * + * Parse while statement. + */ + private void whileStatement() { + // Capture WHILE token. + final long whileToken = token; + final int whileLine = line; + // WHILE tested in caller. + next(); + + final ParserContextLoopNode whileNode = new ParserContextLoopNode(); + lc.push(whileNode); + + JoinPredecessorExpression test = null; + Block body = null; + + try { + expect(LPAREN); + test = joinPredecessorExpression(); + expect(RPAREN); + body = getStatement(); + } finally { + lc.pop(whileNode); + } + + if (body != null) { + appendStatement(new WhileNode(whileLine, whileToken, body.getFinish(), false, test, body)); + } + } + + /** + * ...IterationStatement : + * ... + * do Statement while( Expression ) ; + * ... + * + * See 12.6 + * + * Parse DO WHILE statement. + */ + private void doStatement() { + // Capture DO token. + final long doToken = token; + int doLine = 0; + // DO tested in the caller. + next(); + + final ParserContextLoopNode doWhileNode = new ParserContextLoopNode(); + lc.push(doWhileNode); + + Block body = null; + JoinPredecessorExpression test = null; + + try { + // Get DO body. + body = getStatement(); + + expect(WHILE); + expect(LPAREN); + doLine = line; + test = joinPredecessorExpression(); + expect(RPAREN); + + if (type == SEMICOLON) { + endOfLine(); + } + } finally { + lc.pop(doWhileNode); + } + + appendStatement(new WhileNode(doLine, doToken, finish, true, test, body)); + } + + /** + * ContinueStatement : + * continue Identifier? ; // [no LineTerminator here] + * + * See 12.7 + * + * Parse CONTINUE statement. + */ + private void continueStatement() { + // Capture CONTINUE token. + final int continueLine = line; + final long continueToken = token; + // CONTINUE tested in caller. + nextOrEOL(); + + ParserContextLabelNode labelNode = null; + + // SEMICOLON or label. + switch (type) { + case RBRACE: + case SEMICOLON: + case EOL: + case EOF: + break; + + default: + final IdentNode ident = getIdent(); + labelNode = lc.findLabel(ident.getName()); + + if (labelNode == null) { + throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); + } + + break; + } + + final String labelName = labelNode == null ? null : labelNode.getLabelName(); + final ParserContextLoopNode targetNode = lc.getContinueTo(labelName); + + if (targetNode == null) { + throw error(AbstractParser.message("illegal.continue.stmt"), continueToken); + } + + endOfLine(); + + // Construct and add CONTINUE node. + appendStatement(new ContinueNode(continueLine, continueToken, finish, labelName)); + } + + /** + * BreakStatement : + * break Identifier? ; // [no LineTerminator here] + * + * See 12.8 + * + */ + private void breakStatement() { + // Capture BREAK token. + final int breakLine = line; + final long breakToken = token; + // BREAK tested in caller. + nextOrEOL(); + + ParserContextLabelNode labelNode = null; + + // SEMICOLON or label. + switch (type) { + case RBRACE: + case SEMICOLON: + case EOL: + case EOF: + break; + + default: + final IdentNode ident = getIdent(); + labelNode = lc.findLabel(ident.getName()); + + if (labelNode == null) { + throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken()); + } + + break; + } + + //either an explicit label - then get its node or just a "break" - get first breakable + //targetNode is what we are breaking out from. + final String labelName = labelNode == null ? null : labelNode.getLabelName(); + final ParserContextBreakableNode targetNode = lc.getBreakable(labelName); + if (targetNode == null) { + throw error(AbstractParser.message("illegal.break.stmt"), breakToken); + } + + endOfLine(); + + // Construct and add BREAK node. + appendStatement(new BreakNode(breakLine, breakToken, finish, labelName)); + } + + /** + * ReturnStatement : + * return Expression? ; // [no LineTerminator here] + * + * See 12.9 + * + * Parse RETURN statement. + */ + private void returnStatement() { + // check for return outside function + if (lc.getCurrentFunction().getKind() == FunctionNode.Kind.SCRIPT || lc.getCurrentFunction().getKind() == FunctionNode.Kind.MODULE) { + throw error(AbstractParser.message("invalid.return")); + } + + // Capture RETURN token. + final int returnLine = line; + final long returnToken = token; + // RETURN tested in caller. + nextOrEOL(); + + Expression expression = null; + + // SEMICOLON or expression. + switch (type) { + case RBRACE: + case SEMICOLON: + case EOL: + case EOF: + break; + + default: + expression = expression(); + break; + } + + endOfLine(); + + // Construct and add RETURN node. + appendStatement(new ReturnNode(returnLine, returnToken, finish, expression)); + } + + /** + * Parse YieldExpression. + * + * YieldExpression[In] : + * yield + * yield [no LineTerminator here] AssignmentExpression[?In, Yield] + * yield [no LineTerminator here] * AssignmentExpression[?In, Yield] + */ + private Expression yieldExpression(boolean noIn) { + assert inGeneratorFunction(); + // Capture YIELD token. + long yieldToken = token; + // YIELD tested in caller. + assert type == YIELD; + nextOrEOL(); + + Expression expression = null; + + boolean yieldAsterisk = false; + if (type == MUL) { + yieldAsterisk = true; + yieldToken = Token.recast(yieldToken, YIELD_STAR); + next(); + } + + switch (type) { + case RBRACE: + case SEMICOLON: + case EOL: + case EOF: + case COMMARIGHT: + case RPAREN: + case RBRACKET: + case COLON: + if (!yieldAsterisk) { + // treat (yield) as (yield void 0) + expression = newUndefinedLiteral(yieldToken, finish); + if (type == EOL) { + next(); + } + break; + } else { + // AssignmentExpression required, fall through + } + + default: + expression = assignmentExpression(noIn); + break; + } + + // Construct and add YIELD node. + return new UnaryNode(yieldToken, expression); + } + + private Expression awaitExpression() { + assert inAsyncFunction(); + // Capture await token. + long awaitToken = token; + nextOrEOL(); + + Expression expression = unaryExpression(); + + // Construct and add AWAIT node. + return new UnaryNode(Token.recast(awaitToken, AWAIT), expression); + } + + private static UnaryNode newUndefinedLiteral(long token, int finish) { + return new UnaryNode(Token.recast(token, VOID), LiteralNode.newInstance(token, finish, 0)); + } + + /** + * WithStatement : + * with ( Expression ) Statement + * + * See 12.10 + * + * Parse WITH statement. + */ + private void withStatement() { + // Capture WITH token. + final int withLine = line; + final long withToken = token; + // WITH tested in caller. + next(); + + // ECMA 12.10.1 strict mode restrictions + if (isStrictMode) { + throw error(AbstractParser.message("strict.no.with"), withToken); + } + + expect(LPAREN); + final Expression expression = expression(); + expect(RPAREN); + final Block body = getStatement(); + + appendStatement(new WithNode(withLine, withToken, finish, expression, body)); + } + + /** + * SwitchStatement : + * switch ( Expression ) CaseBlock + * + * CaseBlock : + * { CaseClauses? } + * { CaseClauses? DefaultClause CaseClauses } + * + * CaseClauses : + * CaseClause + * CaseClauses CaseClause + * + * CaseClause : + * case Expression : StatementList? + * + * DefaultClause : + * default : StatementList? + * + * See 12.11 + * + * Parse SWITCH statement. + */ + private void switchStatement() { + final int switchLine = line; + final long switchToken = token; + + // Block to capture variables declared inside the switch statement. + final ParserContextBlockNode switchBlock = newBlock(); + + // SWITCH tested in caller. + next(); + + // Create and add switch statement. + final ParserContextSwitchNode switchNode = new ParserContextSwitchNode(); + lc.push(switchNode); + + CaseNode defaultCase = null; + // Prepare to accumulate cases. + final List cases = new ArrayList<>(); + + Expression expression = null; + + try { + expect(LPAREN); + expression = expression(); + expect(RPAREN); + + expect(LBRACE); + + + while (type != RBRACE) { + // Prepare for next case. + Expression caseExpression = null; + final long caseToken = token; + + switch (type) { + case CASE: + next(); + caseExpression = expression(); + break; + + case DEFAULT: + if (defaultCase != null) { + throw error(AbstractParser.message("duplicate.default.in.switch")); + } + next(); + break; + + default: + // Force an error. + expect(CASE); + break; + } + + expect(COLON); + + // Get CASE body. + List statements = caseStatementList(); + final CaseNode caseNode = new CaseNode(caseToken, finish, caseExpression, statements); + + if (caseExpression == null) { + defaultCase = caseNode; + } + + cases.add(caseNode); + } + + next(); + } finally { + lc.pop(switchNode); + restoreBlock(switchBlock); + } + + SwitchNode switchStatement = new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCase); + appendStatement(new BlockStatement(switchLine, new Block(switchToken, finish, switchBlock.getFlags() | Block.IS_SYNTHETIC | Block.IS_SWITCH_BLOCK, switchStatement))); + } + + /** + * LabelledStatement : + * Identifier : Statement + * + * See 12.12 + * + * Parse label statement. + */ + private void labelStatement() { + // Capture label token. + final long labelToken = token; + // Get label ident. + final IdentNode ident = getIdent(); + + expect(COLON); + + if (lc.findLabel(ident.getName()) != null) { + throw error(AbstractParser.message("duplicate.label", ident.getName()), labelToken); + } + + final ParserContextLabelNode labelNode = new ParserContextLabelNode(ident.getName()); + Block body = null; + try { + lc.push(labelNode); + body = getStatement(true); + } finally { + lc.pop(labelNode); + } + + appendStatement(new LabelNode(line, labelToken, finish, ident.getName(), body)); + } + + /** + * ThrowStatement : + * throw Expression ; // [no LineTerminator here] + * + * See 12.13 + * + * Parse throw statement. + */ + private void throwStatement() { + // Capture THROW token. + final int throwLine = line; + final long throwToken = token; + // THROW tested in caller. + nextOrEOL(); + + Expression expression = null; + + // SEMICOLON or expression. + switch (type) { + case RBRACE: + case SEMICOLON: + case EOL: + break; + + default: + expression = expression(); + break; + } + + if (expression == null) { + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); + } + + endOfLine(); + + appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, false)); + } + + /** + * TryStatement : + * try Block Catch + * try Block Finally + * try Block Catch Finally + * + * Catch : + * catch( Identifier if Expression ) Block + * catch( Identifier ) Block + * + * Finally : + * finally Block + * + * See 12.14 + * + * Parse TRY statement. + */ + private void tryStatement() { + // Capture TRY token. + final int tryLine = line; + final long tryToken = token; + // TRY tested in caller. + next(); + + // Container block needed to act as target for labeled break statements + final int startLine = line; + final ParserContextBlockNode outer = newBlock(); + // Create try. + + try { + final Block tryBody = getBlock(true); + final List catchBlocks = new ArrayList<>(); + + while (type == CATCH) { + final int catchLine = line; + final long catchToken = token; + next(); + + if(env.ecmascriptEdition < 10) { + expectDontAdvance(LPAREN); + } + + final IdentNode exception; + final Expression ifExpression; + + if (type == LPAREN) { + next(); + + exception = getIdent(); + + // ECMA 12.4.1 strict mode restrictions + verifyIdent(exception, "catch argument"); + + // Nashorn extension: catch clause can have optional + // condition. So, a single try can have more than one + // catch clause each with it's own condition. + if (env.syntaxExtensions && type == IF) { + next(); + // Get the exception condition. + ifExpression = expression(); + } else { + ifExpression = null; + } + + expect(RPAREN); + } else { + exception = null; + ifExpression = null; + } + + final ParserContextBlockNode catchBlock = newBlock(); + try { + // Get CATCH body. + final Block catchBody = getBlock(true); + final CatchNode catchNode = new CatchNode(catchLine, catchToken, finish, exception, ifExpression, catchBody, false); + appendStatement(catchNode); + } finally { + restoreBlock(catchBlock); + + verifyBlockScopedBindings(catchBlock.getStatements()); + + catchBlocks.add(new Block(catchBlock.getToken(), Math.max(finish, Token.descPosition(catchBlock.getToken())), catchBlock.getFlags() | Block.IS_SYNTHETIC, catchBlock.getStatements())); + } + + // If unconditional catch then should to be the end. + if (ifExpression == null) { + break; + } + } + + // Prepare to capture finally statement. + Block finallyStatements = null; + + if (type == FINALLY) { + next(); + finallyStatements = getBlock(true); + } + + // Need at least one catch or a finally. + if (catchBlocks.isEmpty() && finallyStatements == null) { + throw error(AbstractParser.message("missing.catch.or.finally"), tryToken); + } + + final TryNode tryNode = new TryNode(tryLine, tryToken, finish, tryBody, catchBlocks, finallyStatements); + // Add try. + assert lc.peek() == outer; + appendStatement(tryNode); + } finally { + restoreBlock(outer); + } + + verifyBlockScopedBindings(outer.getStatements()); + + appendStatement(new BlockStatement(startLine, new Block(tryToken, finish, outer.getFlags() | Block.IS_SYNTHETIC, outer.getStatements()))); + } + + /** + * DebuggerStatement : + * debugger ; + * + * See 12.15 + * + * Parse debugger statement. + */ + private void debuggerStatement() { + // Capture DEBUGGER token. + final int debuggerLine = line; + final long debuggerToken = token; + // DEBUGGER tested in caller. + next(); + endOfLine(); + appendStatement(new DebuggerNode(debuggerLine, debuggerToken, finish)); + } + + /** + * PrimaryExpression : + * this + * IdentifierReference + * Literal + * ArrayLiteral + * ObjectLiteral + * RegularExpressionLiteral + * TemplateLiteral + * CoverParenthesizedExpressionAndArrowParameterList + * + * CoverParenthesizedExpressionAndArrowParameterList : + * ( Expression ) + * ( ) + * ( ... BindingIdentifier ) + * ( Expression , ... BindingIdentifier ) + * + * Parse primary expression. + * @return Expression node. + */ + @SuppressWarnings("fallthrough") + private Expression primaryExpression() { + // Capture first token. + final int primaryLine = line; + final long primaryToken = token; + + switch (type) { + case THIS: + final String name = type.getName(); + next(); + markThis(lc); + return new IdentNode(primaryToken, finish, name); + case IDENT: + final IdentNode ident = getIdent(); + if (ident == null) { + break; + } + detectSpecialProperty(ident); + return ident; + case OCTAL_LEGACY: + if (isStrictMode) { + throw error(AbstractParser.message("strict.no.octal"), token); + } + case STRING: + case ESCSTRING: + case DECIMAL: + case HEXADECIMAL: + case OCTAL: + case BINARY_NUMBER: + case FLOATING: + case REGEX: + case XML: + case BIGINT: + return getLiteral(); + case EXECSTRING: + return execString(primaryLine, primaryToken); + case FALSE: + next(); + return LiteralNode.newInstance(primaryToken, finish, false); + case TRUE: + next(); + return LiteralNode.newInstance(primaryToken, finish, true); + case NULL: + next(); + return LiteralNode.newInstance(primaryToken, finish); + case LBRACKET: + return arrayLiteral(); + case LBRACE: + return objectLiteral(); + case LPAREN: + next(); + + if (isAtLeastES6()) { + if (type == RPAREN) { + // () + nextOrEOL(); + expectDontAdvance(ARROW); + return new ExpressionList(primaryToken, finish, Collections.emptyList()); + } else if (type == ELLIPSIS) { + // (...rest) + IdentNode restParam = formalParameterList(false, false).get(0); + expectDontAdvance(RPAREN); + nextOrEOL(); + expectDontAdvance(ARROW); + return new ExpressionList(primaryToken, finish, Collections.singletonList(restParam)); + } + } + + final Expression expression = expression(false, true); + + expect(RPAREN); + + return expression; + case TEMPLATE: + case TEMPLATE_HEAD: + return templateLiteral(); + + default: + if (env.jsx && lexer.scanJsx(primaryToken, type)) { + return jsxElement(primaryToken); + } + // In this context some operator tokens mark the start of a literal. + if (lexer.scanLiteral(primaryToken, type, lineInfoReceiver)) { + next(); + return getLiteral(); + } + if (isNonStrictModeIdent()) { + return getIdent(); + } + break; + } + + return null; + } + + /** + * Convert execString to a call to $EXEC. + * + * @param primaryToken Original string token. + * @return callNode to $EXEC. + */ + CallNode execString(final int primaryLine, final long primaryToken) { + // Synthesize an ident to call $EXEC. + final IdentNode execIdent = new IdentNode(primaryToken, finish, EXEC_NAME); + // Skip over EXECSTRING. + next(); + // Set up argument list for call. + // Skip beginning of edit string expression. + expect(LBRACE); + // Add the following expression to arguments. + final List arguments = Collections.singletonList(expression()); + // Skip ending of edit string expression. + expect(RBRACE); + + return new CallNode(primaryLine, primaryToken, finish, execIdent, arguments, false); + } + + /** + * ArrayLiteral : + * [ Elision? ] + * [ ElementList ] + * [ ElementList , Elision? ] + * [ expression for (LeftHandExpression in expression) ( (if ( Expression ) )? ] + * + * ElementList : Elision? AssignmentExpression + * ElementList , Elision? AssignmentExpression + * + * Elision : + * , + * Elision , + * + * See 12.1.4 + * JavaScript 1.8 + * + * Parse array literal. + * @return Expression node. + */ + private LiteralNode arrayLiteral() { + // Capture LBRACKET token. + final long arrayToken = token; + // LBRACKET tested in caller. + next(); + + // Prepare to accumulate elements. + final List elements = new ArrayList<>(); + // Track elisions. + boolean elision = true; + boolean hasSpread = false; +loop: + while (true) { + long spreadToken = 0; + switch (type) { + case RBRACKET: + next(); + + break loop; + + case COMMARIGHT: + next(); + + // If no prior expression + if (elision) { + elements.add(null); + } + + elision = true; + + break; + + case ELLIPSIS: + if (isAtLeastES6()) { + hasSpread = true; + spreadToken = token; + next(); + } + // fall through + + default: + if (!elision) { + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); + } + + // Add expression element. + Expression expression = assignmentExpression(false); + if (expression != null) { + if (spreadToken != 0) { + expression = new UnaryNode(Token.recast(spreadToken, SPREAD_ARRAY), expression); + } + elements.add(expression); + } else { + expect(RBRACKET); + } + + elision = false; + break; + } + } + + return LiteralNode.newInstance(arrayToken, finish, elements, hasSpread, elision); + } + + /** + * ObjectLiteral : + * { } + * { PropertyNameAndValueList } { PropertyNameAndValueList , } + * + * PropertyNameAndValueList : + * PropertyAssignment + * PropertyNameAndValueList , PropertyAssignment + * + * See 11.1.5 + * + * Parse an object literal. + * @return Expression node. + */ + private ObjectNode objectLiteral() { + // Capture LBRACE token. + final long objectToken = token; + // LBRACE tested in caller. + next(); + + // Object context. + // Prepare to accumulate elements. + final List elements = new ArrayList<>(); + final Map map = new HashMap<>(); + + // Create a block for the object literal. + boolean commaSeen = true; +loop: + while (true) { + switch (type) { + case RBRACE: + next(); + break loop; + + case COMMARIGHT: + if (commaSeen) { + throw error(AbstractParser.message("expected.property.id", type.getNameOrType())); + } + next(); + commaSeen = true; + break; + + default: + if (!commaSeen) { + throw error(AbstractParser.message("expected.comma", type.getNameOrType())); + } + + commaSeen = false; + // Get and add the next property. + final PropertyNode property = propertyAssignment(); + + if (property.isComputed() || property.getKey().isTokenType(SPREAD_OBJECT)) { + elements.add(property); + break; + } + + final String key = property.getKeyName(); + final Integer existing = map.get(key); + + if (existing == null) { + map.put(key, elements.size()); + elements.add(property); + break; + } + + final PropertyNode existingProperty = elements.get(existing); + + // ECMA section 11.1.5 Object Initialiser + // point # 4 on property assignment production + final Expression value = property.getValue(); + final FunctionNode getter = property.getGetter(); + final FunctionNode setter = property.getSetter(); + + final Expression prevValue = existingProperty.getValue(); + final FunctionNode prevGetter = existingProperty.getGetter(); + final FunctionNode prevSetter = existingProperty.getSetter(); + + if (!isAtLeastES6()) { + checkPropertyRedefinition(property, value, getter, setter, prevValue, prevGetter, prevSetter); + } else { + if (property.getKey() instanceof IdentNode && ((IdentNode)property.getKey()).isProtoPropertyName() && + existingProperty.getKey() instanceof IdentNode && ((IdentNode)existingProperty.getKey()).isProtoPropertyName()) { + throw error(AbstractParser.message("multiple.proto.key"), property.getToken()); + } + } + + if (value != null || prevValue != null) { + map.put(key, elements.size()); + elements.add(property); + } else if (getter != null) { + assert prevGetter != null || prevSetter != null; + elements.set(existing, existingProperty.setGetter(getter)); + } else if (setter != null) { + assert prevGetter != null || prevSetter != null; + elements.set(existing, existingProperty.setSetter(setter)); + } + break; + } + } + + return new ObjectNode(objectToken, finish, elements); + } + + private void checkPropertyRedefinition(final PropertyNode property, final Expression value, final FunctionNode getter, final FunctionNode setter, final Expression prevValue, final FunctionNode prevGetter, final FunctionNode prevSetter) { + // ECMA 11.1.5 strict mode restrictions + if (isStrictMode && value != null && prevValue != null) { + throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken()); + } + + final boolean isPrevAccessor = prevGetter != null || prevSetter != null; + final boolean isAccessor = getter != null || setter != null; + + // data property redefined as accessor property + if (prevValue != null && isAccessor) { + throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken()); + } + + // accessor property redefined as data + if (isPrevAccessor && value != null) { + throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken()); + } + + if (isAccessor && isPrevAccessor) { + if (getter != null && prevGetter != null || + setter != null && prevSetter != null) { + throw error(AbstractParser.message("property.redefinition", property.getKeyName()), property.getToken()); + } + } + } + + /** + * LiteralPropertyName : + * IdentifierName + * StringLiteral + * NumericLiteral + * + * @return PropertyName node + */ + @SuppressWarnings("fallthrough") + private PropertyKey literalPropertyName() { + switch (type) { + case IDENT: + return getIdent().setIsPropertyName(); + case OCTAL_LEGACY: + if (isStrictMode) { + throw error(AbstractParser.message("strict.no.octal"), token); + } + case STRING: + case ESCSTRING: + case DECIMAL: + case HEXADECIMAL: + case OCTAL: + case BINARY_NUMBER: + case FLOATING: + case BIGINT: + return getLiteral(); + default: + return getIdentifierName().setIsPropertyName(); + } + } + + /** + * ComputedPropertyName : + * AssignmentExpression + * + * @return PropertyName node + */ + private Expression computedPropertyName() { + expect(LBRACKET); + Expression expression = assignmentExpression(false); + expect(RBRACKET); + return expression; + } + + /** + * PropertyName : + * LiteralPropertyName + * ComputedPropertyName + * + * @return PropertyName node + */ + private Expression propertyName() { + if (type == LBRACKET && isAtLeastES6()) { + return computedPropertyName(); + } else { + return (Expression)literalPropertyName(); + } + } + + /** + * PropertyAssignment : + * PropertyName : AssignmentExpression + * get PropertyName ( ) { FunctionBody } + * set PropertyName ( PropertySetParameterList ) { FunctionBody } + * + * PropertySetParameterList : + * Identifier + * + * PropertyName : + * IdentifierName + * StringLiteral + * NumericLiteral + * + * See 11.1.5 + * + * Parse an object literal property. + * @return Property or reference node. + */ + private PropertyNode propertyAssignment() { + final Expression propertyName; + final boolean isIdentifier; + + List decorators = decoratorList(); + boolean method = isAtLeastES7() && !decorators.isEmpty(); + + boolean async = false; + if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(true)) { + async = true; + next(); + } + boolean generator = false; + if (!async && type == MUL && isAtLeastES6()) { + generator = true; + next(); + } + + // We capture first tokens here to be consistent with classElement. + // Capture firstToken. + final long propertyToken = token; + final int functionLine = line; + + final boolean computed = type == LBRACKET; + if (type == IDENT) { + // Get IDENT. + final String ident = (String)expectValue(IDENT); + + if (type != COLON && (type != LPAREN || !isAtLeastES6())) { + final long getSetToken = propertyToken; + + switch (ident) { + case "get": + final PropertyFunction getter = propertyGetterFunction(getSetToken, functionLine); + return new PropertyNode(propertyToken, finish, getter.key, null, getter.functionNode, null, false, getter.computed, decorators); + + case "set": + final PropertyFunction setter = propertySetterFunction(getSetToken, functionLine); + return new PropertyNode(propertyToken, finish, setter.key, null, null, setter.functionNode, false, setter.computed, decorators); + default: + break; + } + } + + isIdentifier = true; + IdentNode identNode = createIdentNode(propertyToken, finish, ident).setIsPropertyName(); + if (type == COLON && ident.equals("__proto__")) { + identNode = identNode.setIsProtoPropertyName(); + } + propertyName = identNode; + } else if (type == ELLIPSIS && isAtLeastES7()) { + if (method) { + // we do not allow decorators on spread property + throw error(AbstractParser.message("decorator.method.only")); + } + long spreadToken = Token.recast(propertyToken, TokenType.SPREAD_OBJECT); + next(); + Expression assignment = new UnaryNode(spreadToken, assignmentExpression(false)); + // FIXME start at ... or after it ? + return new PropertyNode(propertyToken, finish, assignment, assignment, null, null, false, false, decorators); + } else { + isIdentifier = isNonStrictModeIdent(); + propertyName = propertyName(); + } + + Expression propertyValue; + + if (generator || method || async) { + expectDontAdvance(LPAREN); + } + + if (type == LPAREN && isAtLeastES6()) { + propertyValue = propertyMethodFunction(propertyName, propertyToken, functionLine, generator, async, FunctionNode.IS_METHOD, computed).functionNode; + } else if (isIdentifier && (type == COMMARIGHT || type == RBRACE || type == ASSIGN) && isAtLeastES6()) { + propertyValue = createIdentNode(propertyToken, finish, ((IdentNode) propertyName).getPropertyName()); + if (type == ASSIGN && isAtLeastES6()) { + // TODO if not destructuring, this is a SyntaxError + long assignToken = token; + next(); + Expression rhs = assignmentExpression(false); + propertyValue = verifyAssignment(assignToken, propertyValue, rhs); + } + } else { + expect(COLON); + + defaultNames.push(propertyName); + try { + propertyValue = assignmentExpression(false); + } finally { + defaultNames.pop(); + } + } + + return new PropertyNode(propertyToken, finish, propertyName, propertyValue, null, null, false, computed, decorators); + } + + private PropertyFunction propertyGetterFunction(final long getSetToken, final int functionLine) { + return propertyGetterFunction(getSetToken, functionLine, FunctionNode.IS_METHOD); + } + + private PropertyFunction propertyGetterFunction(final long getSetToken, final int functionLine, final int flags) { + final boolean computed = type == LBRACKET; + final Expression propertyName = propertyName(); + final String getterName = propertyName instanceof PropertyKey ? ((PropertyKey) propertyName).getPropertyName() : getDefaultValidFunctionName(functionLine, false); + final IdentNode getNameNode = createIdentNode(propertyName.getToken(), finish, ("get " + getterName)); + expect(LPAREN); + expect(RPAREN); + + final ParserContextFunctionNode functionNode = createParserContextFunctionNode(getNameNode, getSetToken, FunctionNode.Kind.GETTER, functionLine, Collections.emptyList()); + functionNode.setFlag(flags); + if (computed) { + functionNode.setFlag(FunctionNode.IS_ANONYMOUS); + } + lc.push(functionNode); + + Block functionBody; + + + try { + functionBody = functionBody(functionNode); + } finally { + lc.pop(functionNode); + } + + final FunctionNode function = createFunctionNode( + functionNode, + getSetToken, + getNameNode, + Collections.emptyList(), + FunctionNode.Kind.GETTER, + functionLine, + functionBody); + + return new PropertyFunction(propertyName, function, computed); + } + + private PropertyFunction propertySetterFunction(final long getSetToken, final int functionLine) { + return propertySetterFunction(getSetToken, functionLine, FunctionNode.IS_METHOD); + } + + private PropertyFunction propertySetterFunction(final long getSetToken, final int functionLine, final int flags) { + final boolean computed = type == LBRACKET; + final Expression propertyName = propertyName(); + final String setterName = propertyName instanceof PropertyKey ? ((PropertyKey) propertyName).getPropertyName() : getDefaultValidFunctionName(functionLine, false); + final IdentNode setNameNode = createIdentNode(propertyName.getToken(), finish, ("set " + setterName)); + expect(LPAREN); + // be sloppy and allow missing setter parameter even though + // spec does not permit it! + final IdentNode argIdent; + if (isBindingIdentifier()) { + argIdent = getIdent(); + verifyIdent(argIdent, "setter argument"); + } else { + argIdent = null; + } + expect(RPAREN); + final List parameters = new ArrayList<>(); + if (argIdent != null) { + parameters.add(argIdent); + } + + + final ParserContextFunctionNode functionNode = createParserContextFunctionNode(setNameNode, getSetToken, FunctionNode.Kind.SETTER, functionLine, parameters); + functionNode.setFlag(flags); + if (computed) { + functionNode.setFlag(FunctionNode.IS_ANONYMOUS); + } + lc.push(functionNode); + + Block functionBody; + try { + functionBody = functionBody(functionNode); + } finally { + lc.pop(functionNode); + } + + + final FunctionNode function = createFunctionNode( + functionNode, + getSetToken, + setNameNode, + parameters, + FunctionNode.Kind.SETTER, + functionLine, + functionBody); + + return new PropertyFunction(propertyName, function, computed); + } + + private PropertyFunction propertyMethodFunction(Expression key, final long methodToken, final int methodLine, final boolean generator, final boolean async, final int flags, boolean computed) { + final String methodName = key instanceof PropertyKey ? ((PropertyKey) key).getPropertyName() : getDefaultValidFunctionName(methodLine, false); + final IdentNode methodNameNode = createIdentNode(((Node)key).getToken(), finish, methodName); + + FunctionNode.Kind functionKind = generator ? FunctionNode.Kind.GENERATOR : FunctionNode.Kind.NORMAL; + final ParserContextFunctionNode functionNode = createParserContextFunctionNode(methodNameNode, methodToken, functionKind, methodLine, null); + functionNode.setFlag(flags); + if (computed) { + functionNode.setFlag(FunctionNode.IS_ANONYMOUS); + } + if (async) { + functionNode.setFlag(FunctionNode.IS_ASYNC); + } + lc.push(functionNode); + + try { + ParserContextBlockNode parameterBlock = newBlock(); + final List parameters; + try { + expect(LPAREN); + parameters = formalParameterList(generator, async); + functionNode.setParameters(parameters); + expect(RPAREN); + } finally { + restoreBlock(parameterBlock); + } + + Block functionBody = functionBody(functionNode); + + functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock); + + final FunctionNode function = createFunctionNode( + functionNode, + methodToken, + methodNameNode, + parameters, + functionKind, + methodLine, + functionBody); + return new PropertyFunction(key, function, computed); + } finally { + lc.pop(functionNode); + } + } + + private static class PropertyFunction { + final Expression key; + final FunctionNode functionNode; + final boolean computed; + + PropertyFunction(final Expression key, final FunctionNode function, final boolean computed) { + this.key = key; + this.functionNode = function; + this.computed = computed; + } + } + + /** + * LeftHandSideExpression : + * NewExpression + * CallExpression + * + * CallExpression : + * MemberExpression Arguments + * SuperCall + * CallExpression Arguments + * CallExpression [ Expression ] + * CallExpression . IdentifierName + * + * SuperCall : + * super Arguments + * + * ImportCall : + * import ( AssignmentExpression ) + * + * See 11.2 + * + * Parse left hand side expression. + * @return Expression node. + */ + private Expression leftHandSideExpression() { + int callLine = line; + long callToken = token; + + Expression lhs = memberExpression(); + + if (type == LPAREN) { + final List arguments = optimizeList(argumentList()); + + // Catch special functions. + if (lhs instanceof IdentNode) { + detectSpecialFunction((IdentNode)lhs); + } + + lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false); + } else if (type == IMPORT && this.isAtLeastES11()) { + final String name2 = type.getName(); + next(); + IdentNode identNode = new IdentNode(token, finish, name2); + + final List arguments = optimizeList(argumentList()); + + lhs = new CallNode(callLine, callToken, finish, identNode, arguments, false); + } + +loop: + while (true) { + // Capture token. + callLine = line; + callToken = token; + + switch (type) { + case LPAREN: { + // Get NEW or FUNCTION arguments. + final List arguments = optimizeList(argumentList()); + + // Create call node. + lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false); + + break; + } + case LBRACKET: { + next(); + + // Get array index. + final Expression rhs = expression(); + + expect(RBRACKET); + + // Create indexing node. + lhs = new IndexNode(callToken, finish, lhs, rhs); + + break; + } + case PERIOD: + next(); + + final IdentNode property = getIdentifierName(); + + // Create property access node. + lhs = new AccessNode(callToken, finish, lhs, property.getName(), false); + break; + case OPTIONAL_ACCESS: + next(); + + if(type == LPAREN) { + // Get NEW or FUNCTION arguments. + final List arguments = optimizeList(argumentList()); + + // Create call node. + lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false, true); + } else if (type == LBRACKET) { + next(); + + // Get array index. + final Expression index = expression(); + + expect(RBRACKET); + + // Create indexing node. + lhs = new IndexNode(callToken, finish, lhs, index, true); + } else { + final IdentNode property2 = getIdentifierName(); + + // Create property access node. + lhs = new AccessNode(callToken, finish, lhs, property2.getName(), true); + } + + break; + case TEMPLATE: + case TEMPLATE_HEAD: { + // tagged template literal + final List arguments = templateLiteralArgumentList(); + + // Create call node. + lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false); + + break; + } + default: + break loop; + } + } + + return lhs; + } + + /** + * NewExpression : + * MemberExpression + * new NewExpression + * + * See 11.2 + * + * Parse new expression. + * @return Expression node. + */ + private Expression newExpression() { + final long newToken = token; + // NEW is tested in caller. + next(); + + if (type == PERIOD && isAtLeastES6()) { + next(); + if (type == IDENT && "target".equals(getValue())) { + if (lc.getCurrentFunction().isProgram()) { + throw error(AbstractParser.message("new.target.in.function"), token); + } + next(); + markNewTarget(lc); + return new IdentNode(newToken, finish, "new.target"); + } else { + throw error(AbstractParser.message("expected.target"), token); + } + } + + // Get function base. + final int callLine = line; + final Expression constructor = memberExpression(); + if (constructor == null) { + return null; + } + // Get arguments. + ArrayList arguments; + + // Allow for missing arguments. + if (type == LPAREN) { + arguments = argumentList(); + } else { + arguments = new ArrayList<>(); + } + + // Nashorn extension: This is to support the following interface implementation + // syntax: + // + // var r = new java.lang.Runnable() { + // run: function() { println("run"); } + // }; + // + // The object literal following the "new Constructor()" expression + // is passed as an additional (last) argument to the constructor. + if (env.syntaxExtensions && type == LBRACE) { + arguments.add(objectLiteral()); + } + + final CallNode callNode = new CallNode(callLine, constructor.getToken(), finish, constructor, optimizeList(arguments), true); + + return new UnaryNode(newToken, callNode); + } + + /** + * MemberExpression : + * PrimaryExpression + * FunctionExpression + * ClassExpression + * GeneratorExpression + * MemberExpression [ Expression ] + * MemberExpression . IdentifierName + * MemberExpression TemplateLiteral + * SuperProperty + * MetaProperty + * new MemberExpression Arguments + * + * SuperProperty : + * super [ Expression ] + * super . IdentifierName + * + * MetaProperty : + * NewTarget + * + * Parse member expression. + * @return Expression node. + */ + private Expression memberExpression() { + // Prepare to build operation. + Expression lhs; + boolean isSuper = false; + + switch (type) { + case NEW: + // Get new expression. + lhs = newExpression(); + break; + + case FUNCTION: + // Get function expression. + lhs = functionExpression(false, false, false); + break; + + case CLASS: + case AT: + if (isAtLeastES6() && (type == CLASS || (isAtLeastES7() && type == AT))) { + lhs = classExpression(false, Collections.emptyList()); + break; + } else { + // fall through + } + + case SUPER: + if (isAtLeastES6()) { + ParserContextFunctionNode currentFunction = getCurrentNonArrowFunction(); + if (currentFunction.isMethod()) { + long identToken = Token.recast(token, IDENT); + next(); + lhs = createIdentNode(identToken, finish, SUPER.getName()); + + switch (type) { + case LBRACKET: + case PERIOD: + getCurrentNonArrowFunction().setFlag(FunctionNode.USES_SUPER); + isSuper = true; + break; + case LPAREN: + if (currentFunction.isSubclassConstructor()) { + lhs = ((IdentNode)lhs).setIsDirectSuper(); + break; + } else { + // fall through to throw error + } + default: + throw error(AbstractParser.message("invalid.super"), identToken); + } + break; + } else { + // fall through + } + } else { + // fall through + } + + default: + if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(false)) { + nextOrEOL(); + lhs = functionExpression(false, false, true); + break; + } + // Get primary expression. + lhs = primaryExpression(); + break; + } + +loop: + while (true) { + // Capture token. + final long callToken = token; + + switch (type) { + case LBRACKET: { + next(); + + // Get array index. + final Expression index = expression(); + + expect(RBRACKET); + + // Create indexing node. + lhs = new IndexNode(callToken, finish, lhs, index); + + if (isSuper) { + isSuper = false; + lhs = ((BaseNode) lhs).setIsSuper(); + } + + break; + } + case PERIOD: { + if (lhs == null) { + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); + } + + next(); + + final IdentNode property = getIdentifierName(); + + // Create property access node. + lhs = new AccessNode(callToken, finish, lhs, property.getName(), false); + + if (isSuper) { + isSuper = false; + lhs = ((BaseNode) lhs).setIsSuper(); + } + + break; + } + case TEMPLATE: + case TEMPLATE_HEAD: { + // tagged template literal + final int callLine = line; + final List arguments = templateLiteralArgumentList(); + + lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false); + + break; + } + default: + break loop; + } + } + + return lhs; + } + + /** + * Arguments : + * ( ) + * ( ArgumentList ) + * + * ArgumentList : + * AssignmentExpression + * ... AssignmentExpression + * ArgumentList , AssignmentExpression + * ArgumentList , ... AssignmentExpression + * + * See 11.2 + * + * Parse function call arguments. + * @return Argument list. + */ + private ArrayList argumentList() { + // Prepare to accumulate list of arguments. + final ArrayList nodeList = new ArrayList<>(); + // LPAREN tested in caller. + next(); + + // Track commas. + boolean first = true; + + while (type != RPAREN) { + // Comma prior to every argument except the first. + if (!first) { + expect(COMMARIGHT); + // if it was a trailing comma + if (isAtLeastES7() && type == RPAREN) { + break; + } + } else { + first = false; + } + + long spreadToken = 0; + if (type == ELLIPSIS && isAtLeastES6()) { + spreadToken = token; + next(); + } + + // Get argument expression. + Expression expression = assignmentExpression(false); + if (spreadToken != 0) { + expression = new UnaryNode(Token.recast(spreadToken, TokenType.SPREAD_ARGUMENT), expression); + } + nodeList.add(expression); + } + + expect(RPAREN); + return nodeList; + } + + private static List optimizeList(final ArrayList list) { + switch(list.size()) { + case 0: { + return Collections.emptyList(); + } + case 1: { + return Collections.singletonList(list.get(0)); + } + default: { + list.trimToSize(); + return list; + } + } + } + + /** + * FunctionDeclaration : + * function Identifier ( FormalParameterList? ) { FunctionBody } + * + * FunctionExpression : + * function Identifier? ( FormalParameterList? ) { FunctionBody } + * + * See 13 + * + * Parse function declaration. + * @param isStatement True if for is a statement. + * + * @return Expression node. + */ + private Expression functionExpression(final boolean isStatement, final boolean topLevel, final boolean async) { + final long functionToken = token; + final int functionLine = line; + // FUNCTION is tested in caller. + assert type == FUNCTION; + next(); + + boolean generator = false; + if (type == MUL && isAtLeastES6()) { + generator = true; + next(); + } + + IdentNode name = null; + + if (isBindingIdentifier()) { + if (type == YIELD && ((!isStatement && generator) || (isStatement && inGeneratorFunction()))) { + // 12.1.1 Early SyntaxError if: + // GeneratorExpression with BindingIdentifier yield + // HoistableDeclaration with BindingIdentifier yield in generator function body + expect(IDENT); + } + if (isAwait(token) && ((!isStatement && async) || (isStatement && inAsyncFunction()))) { + expect(IDENT); + } + name = getIdent(); + verifyIdent(name, "function name"); + } else if (isStatement) { + // Nashorn extension: anonymous function statements. + // Do not allow anonymous function statement if extensions + // are now allowed. But if we are reparsing then anon function + // statement is possible - because it was used as function + // expression in surrounding code. + if (!env.syntaxExtensions && reparsedFunction == null) { + expect(IDENT); + } + } + + // name is null, generate anonymous name + boolean isAnonymous = false; + if (name == null) { + final String tmpName = getDefaultValidFunctionName(functionLine, isStatement); + name = new IdentNode(functionToken, Token.descPosition(functionToken), tmpName); + isAnonymous = true; + } + + FunctionNode.Kind functionKind = generator ? FunctionNode.Kind.GENERATOR : FunctionNode.Kind.NORMAL; + List parameters = Collections.emptyList(); + final ParserContextFunctionNode functionNode = createParserContextFunctionNode(name, functionToken, functionKind, functionLine, parameters); + if (async) { + functionNode.setFlag(FunctionNode.IS_ASYNC); + } + lc.push(functionNode); + + Block functionBody = null; + // Hide the current default name across function boundaries. E.g. "x3 = function x1() { function() {}}" + // If we didn't hide the current default name, then the innermost anonymous function would receive "x3". + hideDefaultName(); + try { + ParserContextBlockNode parameterBlock = newBlock(); + try { + expect(LPAREN); + parameters = formalParameterList(generator, async); + functionNode.setParameters(parameters); + expect(RPAREN); + } finally { + restoreBlock(parameterBlock); + } + + functionBody = functionBody(functionNode); + + functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock); + } finally { + defaultNames.pop(); + lc.pop(functionNode); + } + + if (isStatement) { + functionNode.setFlag(FunctionNode.IS_STATEMENT); + if (topLevel || useBlockScope() || (!isStrictMode && env.functionDeclarationHoisting && env.functionStatement == ScriptEnvironment.FunctionStatementBehavior.ACCEPT)) { + functionNode.setFlag(FunctionNode.IS_DECLARED); + } else if (isStrictMode) { + throw error(JSErrorType.SyntaxError, AbstractParser.message("strict.no.func.decl.here"), functionToken); + } else if (env.functionStatement == ScriptEnvironment.FunctionStatementBehavior.ERROR) { + throw error(JSErrorType.SyntaxError, AbstractParser.message("no.func.decl.here"), functionToken); + } else if (env.functionStatement == ScriptEnvironment.FunctionStatementBehavior.WARNING) { + warning(JSErrorType.SyntaxError, AbstractParser.message("no.func.decl.here.warn"), functionToken); + } + if (isArguments(name)) { + lc.getCurrentFunction().setFlag(FunctionNode.DEFINES_ARGUMENTS); + } + } + + if (isAnonymous) { + functionNode.setFlag(FunctionNode.IS_ANONYMOUS); + } + + verifyParameterList(parameters, functionNode); + + final FunctionNode function = createFunctionNode( + functionNode, + functionToken, + name, + parameters, + functionKind, + functionLine, + functionBody); + + if (isStatement) { + if (isAnonymous) { + appendStatement(new ExpressionStatement(functionLine, functionToken, finish, function)); + return function; + } + + // mark ES6 block functions as lexically scoped + final int varFlags = (topLevel || !useBlockScope()) ? 0 : VarNode.IS_LET; + final VarNode varNode = new VarNode(functionLine, functionToken, finish, name, function, varFlags); + if (topLevel) { + functionDeclarations.add(varNode); + } else if (useBlockScope()) { + prependStatement(varNode); // Hoist to beginning of current block + } else { + appendStatement(varNode); + } + } + + return function; + } + + private void verifyParameterList(final List parameters, final ParserContextFunctionNode functionNode) { + IdentNode duplicateParameter = functionNode.getDuplicateParameterBinding(); + if (duplicateParameter != null) { + if (functionNode.isStrict() || functionNode.getKind() == FunctionNode.Kind.ARROW || !functionNode.isSimpleParameterList()) { + throw error(AbstractParser.message("strict.param.redefinition", duplicateParameter.getName()), duplicateParameter.getToken()); + } + + final int arity = parameters.size(); + final HashSet parametersSet = new HashSet<>(arity); + + for (int i = arity - 1; i >= 0; i--) { + final IdentNode parameter = parameters.get(i); + String parameterName = parameter.getName(); + + if (parametersSet.contains(parameterName)) { + // redefinition of parameter name, rename in non-strict mode + parameterName = functionNode.uniqueName(parameterName); + final long parameterToken = parameter.getToken(); + parameters.set(i, new IdentNode(parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName))); + } + parametersSet.add(parameterName); + } + } + } + + private Block maybeWrapBodyInParameterBlock(Block functionBody, ParserContextBlockNode parameterBlock) { + assert functionBody.isFunctionBody(); + if (!parameterBlock.getStatements().isEmpty()) { + parameterBlock.appendStatement(new BlockStatement(functionBody)); + + verifyBlockScopedBindings(parameterBlock.getStatements()); + + return new Block(parameterBlock.getToken(), functionBody.getFinish(), (functionBody.getFlags() | Block.IS_PARAMETER_BLOCK) & ~Block.IS_BODY, parameterBlock.getStatements()); + } + return functionBody; + } + + private String getDefaultValidFunctionName(final int functionLine, final boolean isStatement) { + final String defaultFunctionName = getDefaultFunctionName(); + if (isValidIdentifier(defaultFunctionName)) { + if (isStatement) { + // The name will be used as the LHS of a symbol assignment. We add the anonymous function + // prefix to ensure that it can't clash with another variable. + return ANON_FUNCTION_PREFIX + defaultFunctionName; + } + return defaultFunctionName; + } + return ANON_FUNCTION_PREFIX + functionLine; + } + + private static boolean isValidIdentifier(final String name) { + if (name == null || name.isEmpty()) { + return false; + } + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + return false; + } + for (int i = 1; i < name.length(); ++i) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + return false; + } + } + return true; + } + + private String getDefaultFunctionName() { + if (!defaultNames.isEmpty()) { + final Object nameExpr = defaultNames.peek(); + if (nameExpr instanceof PropertyKey) { + markDefaultNameUsed(); + return ((PropertyKey)nameExpr).getPropertyName(); + } else if (nameExpr instanceof AccessNode) { + markDefaultNameUsed(); + return ((AccessNode)nameExpr).getProperty(); + } + } + return null; + } + + private void markDefaultNameUsed() { + defaultNames.pop(); + hideDefaultName(); + } + + private void hideDefaultName() { + // Can be any value as long as getDefaultFunctionName doesn't recognize it as something it can extract a value + // from. Can't be null + defaultNames.push(""); + } + + /** + * FormalParameterList : + * Identifier + * FormalParameterList , Identifier + * + * See 13 + * + * Parse function parameter list. + * @return List of parameter nodes. + */ + private List formalParameterList(final boolean yield, final boolean await) { + return formalParameterList(RPAREN, yield, await); + } + + /** + * Same as the other method of the same name - except that the end + * token type expected is passed as argument to this method. + * + * FormalParameterList : + * Identifier + * FormalParameterList , Identifier + * + * See 13 + * + * Parse function parameter list. + * @return List of parameter nodes. + */ + private List formalParameterList(final TokenType endType, final boolean yield, final boolean await) { + assert endType != COMMARIGHT; + // Prepare to gather parameters. + final ArrayList parameters = new ArrayList<>(); + // Track commas. + boolean first = true; + + while (type != endType) { + // Comma prior to every argument except the first. + if (!first) { + expect(COMMARIGHT); + // if it was a trailing comma + if (isAtLeastES7() && type == endType) { + break; + } + } else { + first = false; + } + + boolean restParameter = false; + if (type == ELLIPSIS && isAtLeastES6()) { + next(); + restParameter = true; + } + + if (type == YIELD && yield || isAwait(token) && await) { + expect(IDENT); + } + + final long paramToken = token; + final int paramLine = line; + final String contextString = "function parameter"; + IdentNode ident; + if (isBindingIdentifier() || restParameter || !(isAtLeastES6())) { + ident = bindingIdentifier(contextString); + + if (restParameter) { + ident = ident.setIsRestParameter(); + // rest parameter must be last + expectDontAdvance(endType); + parameters.add(ident); + break; + } else if (type == ASSIGN && isAtLeastES6()) { + next(); + ident = ident.setIsDefaultParameter(); + + if (type == YIELD && yield || isAwait(token) && await) { + // error: yield in default expression + expect(IDENT); + } + + // default parameter + Expression initializer = assignmentExpression(false); + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + // desugar to: param = (param === undefined) ? initializer : param; + // possible alternative: if (param === undefined) param = initializer; + BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish)); + TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident)); + BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), ident, value); + lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment)); + } + } + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + currentFunction.addParameterBinding(ident); + if (ident.isRestParameter() || ident.isDefaultParameter()) { + currentFunction.setSimpleParameterList(false); + } + } + } else { + final Expression pattern = bindingPattern(); + // Introduce synthetic temporary parameter to capture the object to be destructured. + ident = createIdentNode(paramToken, pattern.getFinish(), String.format("arguments[%d]", parameters.size())).setIsDestructuredParameter(); + verifyDestructuringParameterBindingPattern(pattern, paramToken, paramLine, contextString); + + Expression value = ident; + if (type == ASSIGN) { + next(); + ident = ident.setIsDefaultParameter(); + + // binding pattern with initializer. desugar to: (param === undefined) ? initializer : param + Expression initializer = assignmentExpression(false); + // TODO initializer must not contain yield expression if yield=true (i.e. this is generator function's parameter list) + BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish)); + value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident)); + } + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + // destructuring assignment + BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), pattern, value); + lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment)); + } + } + parameters.add(ident); + } + + parameters.trimToSize(); + return parameters; + } + + private void verifyDestructuringParameterBindingPattern(final Expression pattern, final long paramToken, final int paramLine, final String contextString) { + verifyDestructuringBindingPattern(pattern, new Consumer() { + public void accept(IdentNode identNode) { + verifyIdent(identNode, contextString); + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + // declare function-scope variables for destructuring bindings + lc.getFunctionBody(currentFunction).appendStatement( + new VarNode(paramLine, Token.recast(paramToken, VAR), pattern.getFinish(), identNode, null).setFlag(VarNode.IS_DESTRUCTURING)); + // detect duplicate bounds names in parameter list + currentFunction.addParameterBinding(identNode); + currentFunction.setSimpleParameterList(false); + } + } + }); + } + + /** + * FunctionBody : + * SourceElements? + * + * See 13 + * + * Parse function body. + * @return function node (body.) + */ + private Block functionBody(final ParserContextFunctionNode functionNode) { + long lastToken = 0L; + ParserContextBlockNode body = null; + final long bodyToken = token; + Block functionBody; + int bodyFinish = 0; + + final boolean parseBody; + Object endParserState = null; + try { + // Create a new function block. + body = newBlock(); + assert functionNode != null; + final int functionId = functionNode.getId(); + parseBody = reparsedFunction == null || functionId <= reparsedFunction.getFunctionNodeId(); + // Nashorn extension: expression closures + if ((env.syntaxExtensions || functionNode.getKind() == FunctionNode.Kind.ARROW) && type != LBRACE) { + /* + * Example: + * + * function square(x) x * x; + * print(square(3)); + */ + + // just expression as function body + final Expression expr = assignmentExpression(false); + lastToken = previousToken; + functionNode.setLastToken(previousToken); + assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode); + // EOL uses length field to store the line number + final int lastFinish = Token.descPosition(lastToken) + (Token.descType(lastToken) == EOL ? 0 : Token.descLength(lastToken)); + // Only create the return node if we aren't skipping nested functions. Note that we aren't + // skipping parsing of these extended functions; they're considered to be small anyway. Also, + // they don't end with a single well known token, so it'd be very hard to get correctly (see + // the note below for reasoning on skipping happening before instead of after RBRACE for + // details). + if (parseBody) { + final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr); + appendStatement(returnNode); + } + bodyFinish = finish; + } else { + expectDontAdvance(LBRACE); + if (parseBody || !skipFunctionBody(functionNode)) { + next(); + // Gather the function elements. + final List prevFunctionDecls = functionDeclarations; + functionDeclarations = new ArrayList<>(); + try { + sourceElements(false); + addFunctionDeclarations(functionNode); + } finally { + functionDeclarations = prevFunctionDecls; + } + + lastToken = token; + if (parseBody) { + // Since the lexer can read ahead and lexify some number of tokens in advance and have + // them buffered in the TokenStream, we need to produce a lexer state as it was just + // before it lexified RBRACE, and not whatever is its current (quite possibly well read + // ahead) state. + endParserState = new ParserState(Token.descPosition(token), line, linePosition); + + // NOTE: you might wonder why do we capture/restore parser state before RBRACE instead of + // after RBRACE; after all, we could skip the below "expect(RBRACE);" if we captured the + // state after it. The reason is that RBRACE is a well-known token that we can expect and + // will never involve us getting into a weird lexer state, and as such is a great reparse + // point. Typical example of a weird lexer state after RBRACE would be: + // function this_is_skipped() { ... } "use strict"; + // because lexer is doing weird off-by-one maneuvers around string literal quotes. Instead + // of compensating for the possibility of a string literal (or similar) after RBRACE, + // we'll rather just restart parsing from this well-known, friendly token instead. + } + } + bodyFinish = finish; + functionNode.setLastToken(token); + expect(RBRACE); + } + } finally { + restoreBlock(body); + } + + // NOTE: we can only do alterations to the function node after restoreFunctionNode. + + if (parseBody) { + functionNode.setEndParserState(endParserState); + } else if (!body.getStatements().isEmpty()) { + // This is to ensure the body is empty when !parseBody but we couldn't skip parsing it (see + // skipFunctionBody() for possible reasons). While it is not strictly necessary for correctness to + // enforce empty bodies in nested functions that were supposed to be skipped, we do assert it as + // an invariant in few places in the compiler pipeline, so for consistency's sake we'll throw away + // nested bodies early if we were supposed to skip 'em. + body.setStatements(Collections.emptyList()); + } + + if (reparsedFunction != null) { + // We restore the flags stored in the function's ScriptFunctionData that we got when we first + // eagerly parsed the code. We're doing it because some flags would be set based on the + // content of the function, or even content of its nested functions, most of which are normally + // skipped during an on-demand compilation. + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); + if (data != null) { + // Data can be null if when we originally parsed the file, we removed the function declaration + // as it was dead code. + functionNode.setFlag(data.getFunctionFlags()); + // This compensates for missing markEval() in case the function contains an inner function + // that contains eval(), that now we didn't discover since we skipped the inner function. + if (functionNode.hasNestedEval()) { + assert functionNode.hasScopeBlock(); + body.setFlag(Block.NEEDS_SCOPE); + } + } + } + + verifyBlockScopedBindings(body.getStatements()); + + functionBody = new Block(bodyToken, bodyFinish, body.getFlags() | Block.IS_BODY, body.getStatements()); + return functionBody; + } + + private boolean skipFunctionBody(final ParserContextFunctionNode functionNode) { + if (reparsedFunction == null) { + // Not reparsing, so don't skip any function body. + return false; + } + // Skip to the RBRACE of this function, and continue parsing from there. + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); + if (data == null) { + // Nested function is not known to the reparsed function. This can happen if the FunctionNode was + // in dead code that was removed. Both FoldConstants and Lower prune dead code. In that case, the + // FunctionNode was dropped before a RecompilableScriptFunctionData could've been created for it. + return false; + } + final ParserState parserState = (ParserState)data.getEndParserState(); + assert parserState != null; + + if (k < stream.last() && start < parserState.position && parserState.position <= Token.descPosition(stream.get(stream.last()))) { + // RBRACE is already in the token stream, so fast forward to it + for (; k < stream.last(); k++) { + long nextToken = stream.get(k + 1); + if (Token.descPosition(nextToken) == parserState.position && Token.descType(nextToken) == RBRACE) { + token = stream.get(k); + type = Token.descType(token); + next(); + assert type == RBRACE && start == parserState.position; + return true; + } + } + } + + stream.reset(); + lexer = parserState.createLexer(source, lexer, stream, scripting && env.syntaxExtensions, env.ecmascriptEdition, shebang, env.jsx); + line = parserState.line; + linePosition = parserState.linePosition; + // Doesn't really matter, but it's safe to treat it as if there were a semicolon before + // the RBRACE. + type = SEMICOLON; + scanFirstToken(); + + return true; + } + + /** + * Encapsulates part of the state of the parser, enough to reconstruct the state of both parser and lexer + * for resuming parsing after skipping a function body. + */ + private static class ParserState { + private final int position; + private final int line; + private final int linePosition; + + ParserState(final int position, final int line, final int linePosition) { + this.position = position; + this.line = line; + this.linePosition = linePosition; + } + + Lexer createLexer(final Source source, final Lexer lexer, final TokenStream stream, final boolean scripting, final int ecmascriptEdition, final boolean shebang, final boolean jsx) { + final Lexer newLexer = new Lexer(source, position, lexer.limit - position, stream, scripting, ecmascriptEdition, shebang, true, jsx); + newLexer.restoreState(new Lexer.State(position, Integer.MAX_VALUE, line, -1, linePosition, SEMICOLON)); + return newLexer; + } + } + + private void addFunctionDeclarations(final ParserContextFunctionNode functionNode) { + VarNode lastDecl = null; + for (int i = functionDeclarations.size() - 1; i >= 0; i--) { + Statement decl = functionDeclarations.get(i); + if (lastDecl == null && decl instanceof VarNode) { + decl = lastDecl = ((VarNode)decl).setFlag(VarNode.IS_LAST_FUNCTION_DECLARATION); + functionNode.setFlag(FunctionNode.HAS_FUNCTION_DECLARATIONS); + } + prependStatement(decl); + } + } + + private RuntimeNode referenceError(final Expression lhs, final Expression rhs, final boolean earlyError) { + if (earlyError) { + throw error(JSErrorType.ReferenceError, AbstractParser.message("invalid.lvalue"), lhs.getToken()); + } + final ArrayList args = new ArrayList<>(); + args.add(lhs); + if (rhs == null) { + args.add(LiteralNode.newInstance(lhs.getToken(), lhs.getFinish())); + } else { + args.add(rhs); + } + args.add(LiteralNode.newInstance(lhs.getToken(), lhs.getFinish(), lhs.toString())); + return new RuntimeNode(lhs.getToken(), lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args); + } + + /** + * PostfixExpression : + * LeftHandSideExpression + * LeftHandSideExpression ++ // [no LineTerminator here] + * LeftHandSideExpression -- // [no LineTerminator here] + * + * See 11.3 + * + * UnaryExpression : + * PostfixExpression + * delete UnaryExpression + * void UnaryExpression + * typeof UnaryExpression + * ++ UnaryExpression + * -- UnaryExpression + * + UnaryExpression + * - UnaryExpression + * ~ UnaryExpression + * ! UnaryExpression + * + * See 11.4 + * + * Parse unary expression. + * @return Expression node. + */ + private Expression unaryExpression() { + final int unaryLine = line; + final long unaryToken = token; + + switch (type) { + case DELETE: { + next(); + final Expression expr = unaryExpression(); + + if (type == TokenType.EXP) { + throw error(AbstractParser.message("unexpected.token", type.getNameOrType())); + } + + if (expr instanceof BaseNode || expr instanceof IdentNode) { + return new UnaryNode(unaryToken, expr); + } + appendStatement(new ExpressionStatement(unaryLine, unaryToken, finish, expr)); + return LiteralNode.newInstance(unaryToken, finish, true); + } + case VOID: + case TYPEOF: + case ADD: + case SUB: + case BIT_NOT: + case NOT: + next(); + final Expression expr = unaryExpression(); + + if (type == TokenType.EXP) { + throw error(AbstractParser.message("unexpected.token", type.getNameOrType())); + } + + return new UnaryNode(unaryToken, expr); + + case INCPREFIX: + case DECPREFIX: + final TokenType opType = type; + next(); + + final Expression lhs = unaryExpression(); + // ++, -- without operand.. + if (lhs == null) { + throw error(AbstractParser.message("expected.lvalue", type.getNameOrType())); + } + + return verifyIncDecExpression(unaryToken, opType, lhs, false); + + default: + if (isAwait(token) && inAsyncFunction() && isAtLeastES7()) { + return awaitExpression(); + } + break; + } + + Expression expression = leftHandSideExpression(); + + if (last != EOL) { + switch (type) { + case INCPREFIX: + case DECPREFIX: + final long opToken = token; + final TokenType opType = type; + final Expression lhs = expression; + // ++, -- without operand.. + if (lhs == null) { + throw error(AbstractParser.message("expected.lvalue", type.getNameOrType())); + } + next(); + + return verifyIncDecExpression(opToken, opType, lhs, true); + default: + break; + } + } + + if (expression == null) { + throw error(AbstractParser.message("expected.operand", type.getNameOrType())); + } + + return expression; + } + + private Expression verifyIncDecExpression(final long unaryToken, final TokenType opType, final Expression lhs, final boolean isPostfix) { + assert lhs != null; + + if (!(lhs instanceof AccessNode || + lhs instanceof IndexNode || + lhs instanceof IdentNode)) { + return referenceError(lhs, null, env.earlyLvalueError); + } + + if (lhs instanceof IdentNode) { + if (!checkIdentLValue((IdentNode)lhs)) { + return referenceError(lhs, null, false); + } + assert opType == TokenType.INCPREFIX || opType == TokenType.DECPREFIX; + String contextString = opType == TokenType.INCPREFIX ? "operand for ++ operator" : "operand for -- operator"; + verifyIdent((IdentNode)lhs, contextString); + } + + return incDecExpression(unaryToken, opType, lhs, isPostfix); + } + + /** + * {@code + * MultiplicativeExpression : + * UnaryExpression + * MultiplicativeExpression * UnaryExpression + * MultiplicativeExpression / UnaryExpression + * MultiplicativeExpression % UnaryExpression + * + * See 11.5 + * + * AdditiveExpression : + * MultiplicativeExpression + * AdditiveExpression + MultiplicativeExpression + * AdditiveExpression - MultiplicativeExpression + * + * See 11.6 + * + * ShiftExpression : + * AdditiveExpression + * ShiftExpression << AdditiveExpression + * ShiftExpression >> AdditiveExpression + * ShiftExpression >>> AdditiveExpression + * + * See 11.7 + * + * RelationalExpression : + * ShiftExpression + * RelationalExpression < ShiftExpression + * RelationalExpression > ShiftExpression + * RelationalExpression <= ShiftExpression + * RelationalExpression >= ShiftExpression + * RelationalExpression instanceof ShiftExpression + * RelationalExpression in ShiftExpression // if !noIf + * + * See 11.8 + * + * RelationalExpression + * EqualityExpression == RelationalExpression + * EqualityExpression != RelationalExpression + * EqualityExpression === RelationalExpression + * EqualityExpression !== RelationalExpression + * + * See 11.9 + * + * BitwiseANDExpression : + * EqualityExpression + * BitwiseANDExpression & EqualityExpression + * + * BitwiseXORExpression : + * BitwiseANDExpression + * BitwiseXORExpression ^ BitwiseANDExpression + * + * BitwiseORExpression : + * BitwiseXORExpression + * BitwiseORExpression | BitwiseXORExpression + * + * See 11.10 + * + * LogicalANDExpression : + * BitwiseORExpression + * LogicalANDExpression && BitwiseORExpression + * + * LogicalORExpression : + * LogicalANDExpression + * LogicalORExpression || LogicalANDExpression + * + * See 11.11 + * + * ConditionalExpression : + * LogicalORExpression + * LogicalORExpression ? AssignmentExpression : AssignmentExpression + * + * See 11.12 + * + * AssignmentExpression : + * ConditionalExpression + * LeftHandSideExpression AssignmentOperator AssignmentExpression + * + * AssignmentOperator : + * = *= /= %= += -= <<= >>= >>>= &= ^= |= + * + * See 11.13 + * + * Expression : + * AssignmentExpression + * Expression , AssignmentExpression + * + * See 11.14 + * } + * + * Parse expression. + * @return Expression node. + */ + protected Expression expression() { + // This method is protected so that subclass can get details + // at expression start point! + + // Include commas in expression parsing. + return expression(false, false); + } + + private Expression expression(final boolean noIn, boolean parenthesized) { + Expression assignmentExpression = assignmentExpression(noIn); + while (type == COMMARIGHT) { + long commaToken = token; + next(); + if (isAtLeastES7() && parenthesized && type == RPAREN) { + // allow trailing comma + break; + } + + boolean rhsRestParameter = false; + if (type == ELLIPSIS && isAtLeastES6()) { + // (a, b, ...rest) is not a valid expression, unless we're parsing the parameter list of an arrow function (we need to throw the right error). + // But since the rest parameter is always last, at least we know that the expression has to end here and be followed by RPAREN and ARROW, so peek ahead. + if (isRestParameterEndOfArrowFunctionParameterList()) { + next(); + rhsRestParameter = true; + } + } + + Expression rhs = assignmentExpression(noIn); + + if (rhsRestParameter) { + rhs = ((IdentNode)rhs).setIsRestParameter(); + // Our only valid move is to end Expression here and continue with ArrowFunction. + // We've already checked that this is the parameter list of an arrow function (see above). + // RPAREN is next, so we'll finish the binary expression and drop out of the loop. + assert type == RPAREN; + } + + assignmentExpression = new BinaryNode(commaToken, assignmentExpression, rhs); + } + return assignmentExpression; + } + + private Expression expression(final int minPrecedence, final boolean noIn) { + return expression(unaryExpression(), minPrecedence, noIn); + } + + private JoinPredecessorExpression joinPredecessorExpression() { + return new JoinPredecessorExpression(expression()); + } + + private Expression expression(final Expression exprLhs, final int minPrecedence, final boolean noIn) { + // Get the precedence of the next operator. + int precedence = type.getPrecedence(); + Expression lhs = exprLhs; + + // While greater precedence. + while (checkOperator(noIn) && precedence >= minPrecedence) { + // Capture the operator token. + final long op = token; + + if (type == TERNARY) { + // Skip operator. + next(); + + // Pass expression. Middle expression of a conditional expression can be a "in" + // expression - even in the contexts where "in" is not permitted. + final Expression trueExpr = assignmentExpression(false); + + expect(COLON); + + // Fail expression. + final Expression falseExpr = assignmentExpression(noIn); + + // Build up node. + lhs = new TernaryNode(op, lhs, new JoinPredecessorExpression(trueExpr), new JoinPredecessorExpression(falseExpr)); + } else { + // Skip operator. + next(); + + assert !Token.descType(op).isAssignment(); + // Get the next primary expression. + Expression rhs = unaryExpression(); + + // Get precedence of next operator. + int nextPrecedence = type.getPrecedence(); + + // Subtask greater precedence. + while (checkOperator(noIn) && + (nextPrecedence > precedence || + nextPrecedence == precedence && !type.isLeftAssociative())) { + rhs = expression(rhs, nextPrecedence, noIn); + nextPrecedence = type.getPrecedence(); + } + lhs = newBinaryExpression(op, lhs, rhs); + } + + precedence = type.getPrecedence(); + } + + return lhs; + } + + private boolean checkOperator(final boolean noIn) { + return type.isOperator(noIn) && (type != TokenType.EXP || isAtLeastES6()); + } + + /** + * AssignmentExpression. + * + * AssignmentExpression[In, Yield] : + * ConditionalExpression[?In, ?Yield] + * [+Yield] YieldExpression[?In] + * ArrowFunction[?In, ?Yield] + * LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield] + * LeftHandSideExpression[?Yield] AssignmentOperator AssignmentExpression[?In, ?Yield] + */ + protected Expression assignmentExpression(final boolean noIn) { + // This method is protected so that subclass can get details + // at assignment expression start point! + + if (type == YIELD && inGeneratorFunction() && isAtLeastES6()) { + return yieldExpression(noIn); + } + + final long startToken = token; + final int startLine = line; + final int pos = k; + Expression exprLhs = conditionalExpression(noIn); + + boolean asyncArrow = false; + if (isAtLeastES7()) { + // FIXME do we have a better way + if ((exprLhs instanceof IdentNode) && ASYNC_IDENT.equals(((IdentNode) exprLhs).getName())) { + if (isNonStrictModeIdent() || type == IDENT) { + boolean containsEol = false; + for (int i = pos + 1; i < k; i++) { + TokenType t = T(i); + if (t != COMMENT) { + containsEol = true; + break; + } + } + if (!containsEol) { + // async arrow function with one parameter such as "async x => {x}" + asyncArrow = true; + exprLhs = conditionalExpression(noIn); + } + } + } + + if (!asyncArrow && (exprLhs instanceof CallNode) && type == ARROW) { + Expression function = ((CallNode) exprLhs).getFunction(); + if ((function instanceof IdentNode) && ASYNC_IDENT.equals(((IdentNode) function).getName())) { + asyncArrow = true; + } + } + } + + if (type == ARROW && isAtLeastES6()) { + if (checkNoLineTerminator()) { + final Expression paramListExpr; + if (exprLhs instanceof ExpressionList) { + paramListExpr = (((ExpressionList)exprLhs).getExpressions().isEmpty() ? null : ((ExpressionList)exprLhs).getExpressions().get(0)); + } else { + paramListExpr = exprLhs; + } + return arrowFunction(startToken, startLine, paramListExpr, asyncArrow); + } + } + assert !(exprLhs instanceof ExpressionList); + + if (type.isAssignment()) { + final boolean isAssign = type == ASSIGN; + if (isAssign) { + defaultNames.push(exprLhs); + } + try { + long assignToken = token; + next(); + Expression exprRhs = assignmentExpression(noIn); + return verifyAssignment(assignToken, exprLhs, exprRhs); + } finally { + if (isAssign) { + defaultNames.pop(); + } + } + } else { + return exprLhs; + } + } + + /** + * ConditionalExpression. + */ + private Expression conditionalExpression(boolean noIn) { + return expression(TERNARY.getPrecedence(), noIn); + } + + /** + * ArrowFunction. + * + * @param startToken start token of the ArrowParameters expression + * @param functionLine start line of the arrow function + * @param paramListExpr ArrowParameters expression or {@code null} for {@code ()} (empty list) + */ + private Expression arrowFunction(final long startToken, final int functionLine, final Expression paramListExpr, boolean async) { + // caller needs to check that there's no LineTerminator between parameter list and arrow + assert type != ARROW || checkNoLineTerminator(); + expect(ARROW); + + final long functionToken = Token.recast(startToken, ARROW); + final IdentNode name = new IdentNode(functionToken, Token.descPosition(functionToken), ARROW_FUNCTION_PREFIX + functionLine); + final ParserContextFunctionNode functionNode = createParserContextFunctionNode(name, functionToken, FunctionNode.Kind.ARROW, functionLine, null); + functionNode.setFlag(FunctionNode.IS_ANONYMOUS); + if (async) { + functionNode.setFlag(FunctionNode.IS_ASYNC); + } + + lc.push(functionNode); + try { + ParserContextBlockNode parameterBlock = newBlock(); + final List parameters; + try { + parameters = convertArrowFunctionParameterList(paramListExpr, functionLine, async); + functionNode.setParameters(parameters); + + if (!functionNode.isSimpleParameterList()) { + markEvalInArrowParameterList(parameterBlock); + } + } finally { + restoreBlock(parameterBlock); + } + Block functionBody = functionBody(functionNode); + + functionBody = maybeWrapBodyInParameterBlock(functionBody, parameterBlock); + + verifyParameterList(parameters, functionNode); + + final FunctionNode function = createFunctionNode( + functionNode, + functionToken, + name, + parameters, + FunctionNode.Kind.ARROW, + functionLine, + functionBody); + return function; + } finally { + lc.pop(functionNode); + } + } + + private void markEvalInArrowParameterList(ParserContextBlockNode parameterBlock) { + Iterator iter = lc.getFunctions(); + ParserContextFunctionNode current = iter.next(); + ParserContextFunctionNode parent = iter.next(); + if (parent.getFlag(FunctionNode.HAS_EVAL) != 0) { + // we might have flagged has-eval in the parent function during parsing the parameter list, + // if the parameter list contains eval; must tag arrow function as has-eval. + for (Statement st : parameterBlock.getStatements()) { + st.accept(new NodeVisitor(new LexicalContext()) { + @Override + public boolean enterCallNode(CallNode callNode) { + if (callNode.getFunction() instanceof IdentNode && ((IdentNode) callNode.getFunction()).getName().equals("eval")) { + current.setFlag(FunctionNode.HAS_EVAL); + } + return true; + } + }); + } + // TODO: function containing the arrow function should not be flagged has-eval + } + } + + private List convertArrowFunctionParameterList(Expression paramListExpr, int functionLine, boolean async) { + List parameters; + if (paramListExpr == null) { + // empty parameter list, i.e. () => + parameters = Collections.emptyList(); + } else if (paramListExpr instanceof IdentNode || paramListExpr.isTokenType(ASSIGN) || isDestructuringLhs(paramListExpr)) { + parameters = Collections.singletonList(verifyArrowParameter(paramListExpr, 0, functionLine)); + } else if (paramListExpr instanceof BinaryNode && Token.descType(paramListExpr.getToken()) == COMMARIGHT) { + parameters = new ArrayList<>(); + Expression car = paramListExpr; + do { + Expression cdr = ((BinaryNode) car).rhs(); + parameters.add(0, verifyArrowParameter(cdr, parameters.size(), functionLine)); + car = ((BinaryNode) car).lhs(); + } while (car instanceof BinaryNode && Token.descType(car.getToken()) == COMMARIGHT); + parameters.add(0, verifyArrowParameter(car, parameters.size(), functionLine)); + } else if (paramListExpr instanceof CallNode && async) { + parameters = new ArrayList<>(); + for (Expression param : ((CallNode) paramListExpr).getArgs()) { + parameters.add(verifyArrowParameter(param, parameters.size(), functionLine)); + } + } else { + throw error(AbstractParser.message("expected.arrow.parameter"), paramListExpr.getToken()); + } + return parameters; + } + + private IdentNode verifyArrowParameter(Expression param, int index, int paramLine) { + final String contextString = "function parameter"; + if (param instanceof IdentNode) { + IdentNode ident = (IdentNode)param; + verifyStrictIdent(ident, contextString); + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + currentFunction.addParameterBinding(ident); + } + return ident; + } + + if (param.isTokenType(ASSIGN)) { + Expression lhs = ((BinaryNode) param).lhs(); + long paramToken = lhs.getToken(); + Expression initializer = ((BinaryNode) param).rhs(); + if (lhs instanceof IdentNode) { + // default parameter + IdentNode ident = (IdentNode) lhs; + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish)); + TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident)); + BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), ident, value); + lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment)); + + currentFunction.addParameterBinding(ident); + currentFunction.setSimpleParameterList(false); + } + return ident; + } else if (isDestructuringLhs(lhs)) { + // binding pattern with initializer + // Introduce synthetic temporary parameter to capture the object to be destructured. + IdentNode ident = createIdentNode(paramToken, param.getFinish(), String.format("arguments[%d]", index)).setIsDestructuredParameter().setIsDefaultParameter(); + verifyDestructuringParameterBindingPattern(param, paramToken, paramLine, contextString); + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + BinaryNode test = new BinaryNode(Token.recast(paramToken, EQ_STRICT), ident, newUndefinedLiteral(paramToken, finish)); + TernaryNode value = new TernaryNode(Token.recast(paramToken, TERNARY), test, new JoinPredecessorExpression(initializer), new JoinPredecessorExpression(ident)); + BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), param, value); + lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment)); + } + return ident; + } + } else if (isDestructuringLhs(param)) { + // binding pattern + long paramToken = param.getToken(); + + // Introduce synthetic temporary parameter to capture the object to be destructured. + IdentNode ident = createIdentNode(paramToken, param.getFinish(), String.format("arguments[%d]", index)).setIsDestructuredParameter(); + verifyDestructuringParameterBindingPattern(param, paramToken, paramLine, contextString); + + ParserContextFunctionNode currentFunction = lc.getCurrentFunction(); + if (currentFunction != null) { + BinaryNode assignment = new BinaryNode(Token.recast(paramToken, ASSIGN), param, ident); + lc.getFunctionBody(currentFunction).appendStatement(new ExpressionStatement(paramLine, assignment.getToken(), assignment.getFinish(), assignment)); + } + return ident; + } + throw error(AbstractParser.message("invalid.arrow.parameter"), param.getToken()); + } + + private boolean checkNoLineTerminator() { + assert type == ARROW; + if (last == RPAREN) { + return true; + } else if (last == IDENT) { + return true; + } + for (int i = k - 1; i >= 0; i--) { + TokenType t = T(i); + switch (t) { + case RPAREN: + case IDENT: + return true; + case EOL: + return false; + case COMMENT: + continue; + default: + if (t.getKind() == TokenKind.FUTURESTRICT) { + return true; + } + return false; + } + } + return false; + } + + /** + * Peek ahead to see if what follows after the ellipsis is a rest parameter + * at the end of an arrow function parameter list. + */ + private boolean isRestParameterEndOfArrowFunctionParameterList() { + assert type == ELLIPSIS; + // find IDENT, RPAREN, ARROW, in that order, skipping over EOL (where allowed) and COMMENT + int i = 1; + for (;;) { + TokenType t = T(k + i++); + if (t == IDENT) { + break; + } else if (t == EOL || t == COMMENT) { + continue; + } else { + return false; + } + } + for (;;) { + TokenType t = T(k + i++); + if (t == RPAREN) { + break; + } else if (t == EOL || t == COMMENT) { + continue; + } else { + return false; + } + } + for (;;) { + TokenType t = T(k + i++); + if (t == ARROW) { + break; + } else if (t == COMMENT) { + continue; + } else { + return false; + } + } + return true; + } + + /** + * Parse an end of line. + */ + private void endOfLine() { + switch (type) { + case SEMICOLON: + case EOL: + next(); + break; + case RPAREN: + case RBRACKET: + case RBRACE: + case EOF: + break; + default: + if (last != EOL) { + expect(SEMICOLON); + } + break; + } + } + + /** + * Parse untagged template literal as string concatenation. + */ + private Expression templateLiteral() { + assert type == TEMPLATE || type == TEMPLATE_HEAD; + final boolean noSubstitutionTemplate = type == TEMPLATE; + long lastLiteralToken = token; + LiteralNode literal = getLiteral(); + if (noSubstitutionTemplate) { + return literal; + } + + Expression concat = literal; + TokenType lastLiteralType; + do { + Expression expression = expression(); + if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) { + throw error(AbstractParser.message("unterminated.template.expression"), token); + } + expression = new RuntimeNode(Token.recast(expression.getToken(), VOID), expression.getFinish(), RuntimeNode.Request.TO_STRING, expression); + concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, expression); + lastLiteralType = type; + lastLiteralToken = token; + literal = getLiteral(); + concat = new BinaryNode(Token.recast(lastLiteralToken, TokenType.ADD), concat, literal); + } while (lastLiteralType == TEMPLATE_MIDDLE); + return concat; + } + + /** + * Parse tagged template literal as argument list. + * @return argument list for a tag function call (template object, ...substitutions) + */ + private List templateLiteralArgumentList() { + assert type == TEMPLATE || type == TEMPLATE_HEAD; + final ArrayList argumentList = new ArrayList<>(); + final ArrayList rawStrings = new ArrayList<>(); + final ArrayList cookedStrings = new ArrayList<>(); + argumentList.add(null); // filled at the end + + final long templateToken = token; + final boolean hasSubstitutions = type == TEMPLATE_HEAD; + addTemplateLiteralString(rawStrings, cookedStrings); + + if (hasSubstitutions) { + TokenType lastLiteralType; + do { + Expression expression = expression(); + if (type != TEMPLATE_MIDDLE && type != TEMPLATE_TAIL) { + throw error(AbstractParser.message("unterminated.template.expression"), token); + } + argumentList.add(expression); + + lastLiteralType = type; + addTemplateLiteralString(rawStrings, cookedStrings); + } while (lastLiteralType == TEMPLATE_MIDDLE); + } + + final LiteralNode rawStringArray = LiteralNode.newInstance(templateToken, finish, rawStrings); + final LiteralNode cookedStringArray = LiteralNode.newInstance(templateToken, finish, cookedStrings); + final RuntimeNode templateObject = new RuntimeNode(templateToken, finish, RuntimeNode.Request.GET_TEMPLATE_OBJECT, rawStringArray, cookedStringArray); + argumentList.set(0, templateObject); + return optimizeList(argumentList); + } + + private void addTemplateLiteralString(final ArrayList rawStrings, final ArrayList cookedStrings) { + final long stringToken = token; + final String rawString = lexer.valueOfRawString(stringToken); + final String cookedString = (String) getValue(); + next(); + rawStrings.add(LiteralNode.newInstance(stringToken, finish, rawString)); + cookedStrings.add(LiteralNode.newInstance(stringToken, finish, cookedString)); + } + + + /** + * Parse a module. + * + * Module : + * ModuleBody? + * + * ModuleBody : + * ModuleItemList + */ + private FunctionNode module(final String moduleName) { + boolean oldStrictMode = isStrictMode; + boolean oldModule = isModule; + try { + isStrictMode = true; // Module code is always strict mode code. (ES6 10.2.1) + isModule = true; + + // Make a pseudo-token for the script holding its start and length. + int functionStart = Math.min(Token.descPosition(Token.withDelimiter(token)), finish); + final long functionToken = Token.toDesc(FUNCTION, functionStart, source.getLength() - functionStart); + final int functionLine = line; + + final IdentNode ident = new IdentNode(functionToken, Token.descPosition(functionToken), moduleName); + final ParserContextFunctionNode script = createParserContextFunctionNode( + ident, + functionToken, + FunctionNode.Kind.MODULE, + functionLine, + Collections.emptyList()); + lc.push(script); + + final ParserContextModuleNode module = new ParserContextModuleNode(moduleName); + lc.push(module); + + final ParserContextBlockNode body = newBlock(); + + functionDeclarations = new ArrayList<>(); + moduleBody(); + addFunctionDeclarations(script); + functionDeclarations = null; + + restoreBlock(body); + body.setFlag(Block.NEEDS_SCOPE); + + verifyBlockScopedBindings(body.getStatements()); + + final Block programBody = new Block(functionToken, finish, body.getFlags() | Block.IS_SYNTHETIC | Block.IS_BODY, body.getStatements()); + lc.pop(module); + lc.pop(script); + script.setLastToken(token); + + expect(EOF); + + script.setModule(module.createModule()); + return createFunctionNode(script, functionToken, ident, Collections.emptyList(), FunctionNode.Kind.MODULE, functionLine, programBody); + } finally { + isModule = oldModule; + isStrictMode = oldStrictMode; + } + } + + /** + * Parse module body. + * + * ModuleBody : + * ModuleItemList + * + * ModuleItemList : + * ModuleItem + * ModuleItemList ModuleItem + * + * ModuleItem : + * ImportDeclaration + * ExportDeclaration + * StatementListItem + */ + private void moduleBody() { + // FIXME this decorator handling is not described in spec + // yet certain frameworks uses it this way + List decorators = new ArrayList<>(); + loop: while (type != EOF) { + switch (type) { + case EOF: + break loop; + case IMPORT: + importDeclaration(); + decorators.clear(); + break; + case EXPORT: + exportDeclaration(decorators); + decorators.clear(); + break; + case AT: + if (isAtLeastES7()) { + decorators.addAll(decoratorList()); + break; + } + default: + // StatementListItem + statement(true, false, false, false, decorators); + decorators.clear(); + break; + } + } + } + + + /** + * Parse import declaration. + * + * ImportDeclaration : + * import ImportClause FromClause ; + * import ModuleSpecifier ; + * ImportClause : + * ImportedDefaultBinding + * NameSpaceImport + * NamedImports + * ImportedDefaultBinding , NameSpaceImport + * ImportedDefaultBinding , NamedImports + * ImportedDefaultBinding : + * ImportedBinding + * ModuleSpecifier : + * StringLiteral + * ImportedBinding : + * BindingIdentifier + */ + private void importDeclaration() { + final long importToken = token; + expect(IMPORT); + ParserContextModuleNode module = lc.getCurrentModule(); + if (type == STRING || type == ESCSTRING) { + // import ModuleSpecifier ; + String moduleSpecifier = (String) getValue(); + LiteralNode specifier = LiteralNode.newInstance(token, finish, moduleSpecifier); + next(); + module.addModuleRequest(moduleSpecifier); + module.addImport(new ImportNode(importToken, Token.descPosition(importToken), finish, specifier)); + } else { + // import ImportClause FromClause ; + List importEntries; + ImportClauseNode importClause; + final long startToken = token; + if (type == MUL) { + NameSpaceImportNode namespaceNode = nameSpaceImport(); + importClause = new ImportClauseNode(startToken, Token.descPosition(startToken), finish, namespaceNode); + importEntries = Collections.singletonList( + ImportEntry.importStarAsNameSpaceFrom(namespaceNode.getBindingIdentifier().getName())); + } else if (type == LBRACE) { + NamedImportsNode namedImportsNode = namedImports(); + importClause = new ImportClauseNode(startToken, Token.descPosition(startToken), finish, namedImportsNode); + importEntries = convert(namedImportsNode); + } else if (isBindingIdentifier()) { + // ImportedDefaultBinding + IdentNode importedDefaultBinding = bindingIdentifier("ImportedBinding"); + ImportEntry defaultImport = ImportEntry.importDefault(importedDefaultBinding.getName()); + + if (type == COMMARIGHT) { + next(); + if (type == MUL) { + NameSpaceImportNode namespaceNode = nameSpaceImport(); + importClause = new ImportClauseNode(startToken, Token.descPosition(startToken), finish, importedDefaultBinding, namespaceNode); + importEntries = new ArrayList<>(2); + importEntries.add(defaultImport); + importEntries.add(ImportEntry.importStarAsNameSpaceFrom(namespaceNode.getBindingIdentifier().getName())); + } else if (type == LBRACE) { + NamedImportsNode namedImportsNode = namedImports(); + importClause = new ImportClauseNode(startToken, Token.descPosition(startToken), finish, importedDefaultBinding, namedImportsNode); + importEntries = convert(namedImportsNode); + importEntries.add(0, defaultImport); + } else { + // expected NameSpaceImport or NamedImports + throw error(AbstractParser.message("expected.named.import")); + } + } else { + importClause = new ImportClauseNode(startToken, Token.descPosition(startToken), finish, importedDefaultBinding); + importEntries = Collections.singletonList(defaultImport); + } + } else { + // expected ImportClause or ModuleSpecifier + throw error(AbstractParser.message("expected.import")); + } + + FromNode fromNode = fromClause(); + module.addImport(new ImportNode(importToken, Token.descPosition(importToken), finish, importClause, fromNode)); + String moduleSpecifier = fromNode.getModuleSpecifier().getValue(); + module.addModuleRequest(moduleSpecifier); + for (int i = 0; i < importEntries.size(); i++) { + module.addImportEntry(importEntries.get(i).withFrom(moduleSpecifier)); + } + } + endOfLine(); + } + + /** + * NameSpaceImport : + * * as ImportedBinding + * + * @return imported binding identifier + */ + private NameSpaceImportNode nameSpaceImport() { + final long startToken = token; + assert type == MUL; + next(); + long asToken = token; + String as = (String) expectValue(IDENT); + if (!"as".equals(as)) { + throw error(AbstractParser.message("expected.as"), asToken); + } + IdentNode localNameSpace = bindingIdentifier("ImportedBinding"); + return new NameSpaceImportNode(startToken, Token.descPosition(startToken), finish, localNameSpace); + } + + /** + * NamedImports : + * { } + * { ImportsList } + * { ImportsList , } + * ImportsList : + * ImportSpecifier + * ImportsList , ImportSpecifier + * ImportSpecifier : + * ImportedBinding + * IdentifierName as ImportedBinding + * ImportedBinding : + * BindingIdentifier + */ + private NamedImportsNode namedImports() { + final long startToken = token; + assert type == LBRACE; + next(); + List importSpecifiers = new ArrayList<>(); + while (type != RBRACE) { + boolean bindingIdentifier = isBindingIdentifier(); + long nameToken = token; + IdentNode importName = getIdentifierName(); + if (type == IDENT && "as".equals(getValue())) { + next(); + IdentNode localName = bindingIdentifier("ImportedBinding"); + importSpecifiers.add(new ImportSpecifierNode(nameToken, Token.descPosition(nameToken), finish, localName, importName)); + //importEntries.add(ImportEntry.importSpecifier(importName.getName(), localName.getName())); + } else if (!bindingIdentifier) { + // expected BindingIdentifier + throw error(AbstractParser.message("expected.binding.identifier"), nameToken); + } else { + importSpecifiers.add(new ImportSpecifierNode(nameToken, Token.descPosition(nameToken), finish, importName, null)); + //importEntries.add(ImportEntry.importSpecifier(importName.getName())); + } + if (type == COMMARIGHT) { + next(); + } else { + break; + } + } + expect(RBRACE); + return new NamedImportsNode(startToken, Token.descPosition(startToken), finish, importSpecifiers); + } + + /** + * FromClause : + * from ModuleSpecifier + */ + private FromNode fromClause() { + int fromStart = start; + long fromToken = token; + String name = (String) expectValue(IDENT); + if (!"from".equals(name)) { + throw error(AbstractParser.message("expected.from"), fromToken); + } + if (type == STRING || type == ESCSTRING) { + String moduleSpecifier = (String) getValue(); + LiteralNode specifier = LiteralNode.newInstance(token, finish, moduleSpecifier); + next(); + return new FromNode(fromToken, fromStart, finish, specifier); + } else { + throw error(expectMessage(STRING)); + } + } + + /** + * Parse export declaration. + * + * ExportDeclaration : + * export * FromClause ; + * export ExportClause FromClause ; + * export ExportClause ; + * export VariableStatement + * export Declaration + * export default HoistableDeclaration[Default] + * export default ClassDeclaration[Default] + * export default [lookahead !in {function, class}] AssignmentExpression[In] ; + */ + private void exportDeclaration(List decorators) { + final long exportToken = token; + expect(EXPORT); + ParserContextModuleNode module = lc.getCurrentModule(); + if (!decorators.isEmpty() && type != DEFAULT && type != CLASS && type != AT) { + throw error(expectMessage(CLASS)); + } + + switch (type) { + case MUL: { + next(); + IdentNode exportName = null; + if (type == IDENT && "as".equals(getValue()) && isAtLeastES11()) { + next(); + exportName = getIdentifierName(); + } + FromNode from = fromClause(); + String moduleRequest = from.getModuleSpecifier().getValue(); + module.addModuleRequest(moduleRequest); + if(exportName != null) { + module.addStarExportEntry(ExportEntry.exportStarFromAs(moduleRequest, exportName.getName())); + } else { + module.addStarExportEntry(ExportEntry.exportStarFrom(moduleRequest)); + } + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, from, exportName)); + endOfLine(); + break; + } + case LBRACE: { + ExportClauseNode exportClause = exportClause(); + if (type == IDENT && "from".equals(getValue())) { + FromNode from = fromClause(); + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, exportClause, from)); + String moduleRequest = from.getModuleSpecifier().getValue(); + module.addModuleRequest(moduleRequest); + List exportEntries = convert(exportClause); + for (int i = 0; i < exportEntries.size(); i++) { + module.addIndirectExportEntry(exportEntries.get(i).withFrom(moduleRequest)); + } + } else { + for (ExportSpecifierNode specifier : exportClause.getExportSpecifiers()) { + verifyIdent(specifier.getIdentifier(), "ExportedBinding"); + } + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, exportClause)); + List exportEntries = convert(exportClause); + for (int i = 0; i < exportEntries.size(); i++) { + module.addLocalExportEntry(exportEntries.get(i)); + } + } + endOfLine(); + break; + } + case DEFAULT: + next(); + if (!decorators.isEmpty() && type != AT && type != CLASS) { + throw error(expectMessage(CLASS)); + } + Expression assignmentExpression; + IdentNode ident; + int lineNumber = line; + long rhsToken = token; + boolean declaration; + switch (type) { + case FUNCTION: + assignmentExpression = functionExpression(false, true, false); + ident = ((FunctionNode) assignmentExpression).getIdent(); + declaration = true; + break; + // this is according to current decorator spec + case CLASS: + case AT: + assignmentExpression = classDeclaration(true, decorators); + ident = ((ClassNode) assignmentExpression).getIdent(); + declaration = true; + break; + default: + if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(false)) { + nextOrEOL(); + assignmentExpression = functionExpression(false, true, true); + ident = ((FunctionNode) assignmentExpression).getIdent(); + declaration = true; + break; + } + assignmentExpression = assignmentExpression(false); + ident = null; + declaration = false; + break; + } + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, assignmentExpression, true)); + if (ident != null) { + module.addLocalExportEntry(ExportEntry.exportDefault(ident.getName())); + } else { + ident = createIdentNode(Token.recast(rhsToken, IDENT), finish, Module.DEFAULT_EXPORT_BINDING_NAME); + lc.appendStatementToCurrentNode(new VarNode(lineNumber, Token.recast(rhsToken, LET), finish, ident, assignmentExpression) + .setFlag(VarNode.IS_EXPORT)); + if (!declaration) { + endOfLine(); + } + module.addLocalExportEntry(ExportEntry.exportDefault()); + } + break; + case VAR: + case LET: + case CONST: + List statements = lc.getCurrentBlock().getStatements(); + int previousEnd = statements.size(); + variableStatement(type); + for (Statement statement : statements.subList(previousEnd, statements.size())) { + if (statement instanceof VarNode) { + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, (VarNode) statement)); + module.addLocalExportEntry(ExportEntry.exportSpecifier(((VarNode) statement).getName().getName())); + } + } + break; + // this is according to current decorator spec + case CLASS: + case AT: { + ClassNode classDeclaration = classDeclaration(false, decorators); + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, classDeclaration, false)); + module.addLocalExportEntry(ExportEntry.exportSpecifier(classDeclaration.getIdent().getName())); + break; + } + case FUNCTION: { + FunctionNode functionDeclaration = (FunctionNode) functionExpression(true, true, false); + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, functionDeclaration, false)); + module.addLocalExportEntry(ExportEntry.exportSpecifier(functionDeclaration.getIdent().getName())); + break; + } + default: + if (isAtLeastES7() && type == IDENT && ASYNC_IDENT.equals((String) getValue(token)) + && lookaheadIsAsyncFunction(false)) { + nextOrEOL(); + FunctionNode functionDeclaration = (FunctionNode) functionExpression(true, true, true); + module.addExport(new ExportNode(exportToken, Token.descPosition(exportToken), finish, functionDeclaration, false)); + module.addLocalExportEntry(ExportEntry.exportSpecifier(functionDeclaration.getIdent().getName())); + break; + } + throw error(AbstractParser.message("invalid.export"), token); + } + } + + /** + * ExportClause : + * { } + * { ExportsList } + * { ExportsList , } + * ExportsList : + * ExportSpecifier + * ExportsList , ExportSpecifier + * ExportSpecifier : + * IdentifierName + * IdentifierName as IdentifierName + * + * @return a list of ExportSpecifiers + */ + private ExportClauseNode exportClause() { + final long startToken = token; + assert type == LBRACE; + next(); + List exports = new ArrayList<>(); + while (type != RBRACE) { + long nameToken = token; + IdentNode localName = getIdentifierName(); + if (type == IDENT && "as".equals(getValue())) { + next(); + IdentNode exportName = getIdentifierName(); + exports.add(new ExportSpecifierNode(nameToken, Token.descPosition(nameToken), finish, localName, exportName)); + //exports.add(ExportEntry.exportSpecifier(exportName.getName(), localName.getName())); + } else { + exports.add(new ExportSpecifierNode(nameToken, Token.descPosition(nameToken), finish, localName, null)); + //exports.add(ExportEntry.exportSpecifier(localName.getName())); + } + if (type == COMMARIGHT) { + next(); + } else { + break; + } + } + expect(RBRACE); + return new ExportClauseNode(startToken, Token.descPosition(startToken), finish, exports); + } + + private Expression jsxElement(long startToken) { + long realStart = startToken; + if (Token.descType(realStart) == TokenType.LT) { + realStart = Token.recast(realStart, TokenType.JSX_ELEM_START); + } + assert Token.descType(realStart) == TokenType.JSX_ELEM_START; + next(); + + String name = jsxElementName(); + + if (type == TokenType.JSX_ELEM_CLOSE) { + next(); + expect(TokenType.JSX_ELEM_END); + return new JsxElementNode(name, Collections.emptyList(), Collections.emptyList(), realStart, finish); + } + + // parse attributes + List attributes = new ArrayList<>(); + for (;;) { + Expression attribute = jsxAttribute(); + if (attribute == null) { + break; + } else { + attributes.add(attribute); + } + } + + boolean closed = false; + if (type == TokenType.JSX_ELEM_CLOSE) { + next(); + closed = true; + } + expect(TokenType.JSX_ELEM_END); + if (closed) { + return new JsxElementNode(name, attributes, Collections.emptyList(), realStart, finish); + } + + // parse children + List children = new ArrayList<>(); + children: + for (;;) { + switch (type) { + case JSX_TEXT: + long textStart = token; + String text = (String) getValue(token); + children.add(LiteralNode.newInstance(textStart, finish, text)); + next(); + break; + case JSX_ELEM_START: + if (T(k + 1) == TokenType.JSX_ELEM_CLOSE) { + next(); + next(); + String endName = jsxElementName(); + if (!endName.equals(name)) { + throw error(AbstractParser.message("expected.jsx.name.mismatch", name, endName)); + } + expect(TokenType.JSX_ELEM_END); + break children; + } else { + children.add(jsxElement(token)); + } + break; + case LBRACE: + if (T(k + 1) == TokenType.RBRACE) { + next(); + next(); + } else { + next(); + children.add(assignmentExpression(false)); + expect(RBRACE); + } + break; + default: + throw error(AbstractParser.message("expected.jsx.child", type.getNameOrType())); + } + } + return new JsxElementNode(name, attributes, children, realStart, finish); + } + + private Expression jsxAttribute() { + Expression attribute = null; + if (type == LBRACE) { + next(); + expect(ELLIPSIS); + // FIXME noin? + attribute = new UnaryNode(Token.recast(token, SPREAD_OBJECT), assignmentExpression(false)); + expect(RBRACE); + } else if (type == TokenType.JSX_IDENTIFIER) { + long attrToken = token; + // attribute + StringBuilder attrName = new StringBuilder((String) getValue(token)); + next(); + if (type == TokenType.COLON) { + expectDontAdvance(TokenType.JSX_IDENTIFIER); + attrName.append(":").append((String) getValue(token)); + next(); + } + Expression value = null; + if (type == TokenType.ASSIGN) { + next(); + switch (type) { + case JSX_STRING: + long textStart = token; + String text = (String) getValue(token); + value = LiteralNode.newInstance(textStart, finish, text); + next(); + break; + case LBRACE: + next(); + value = assignmentExpression(false); + expect(RBRACE); + break; + case JSX_ELEM_START: + value = jsxElement(token); + break; + default: + throw error(AbstractParser.message("expected.jsx.attribute", type.getNameOrType())); + } + } + attribute = new JsxAttributeNode(attrName.toString(), value, attrToken, finish); + } + return attribute; + } + + private String jsxElementName() { + expectDontAdvance(TokenType.JSX_IDENTIFIER); + StringBuilder name = new StringBuilder((String) getValue(token)); + next(); + if (type == TokenType.COLON) { + next(); + expectDontAdvance(TokenType.JSX_IDENTIFIER); + name.append(":").append((String) getValue(token)); + next(); + } else if (type == TokenType.PERIOD) { + for (;;) { + next(); + expectDontAdvance(TokenType.JSX_IDENTIFIER); + name.append(".").append((String) getValue(token)); + next(); + if (type != TokenType.PERIOD) { + break; + } + } + } + return name.toString(); + } + + @Override + public String toString() { + return "'JavaScript Parsing'"; + } + + private static void markEval(final ParserContext lc) { + final Iterator iter = lc.getFunctions(); + boolean flaggedCurrentFn = false; + while (iter.hasNext()) { + final ParserContextFunctionNode fn = iter.next(); + if (!flaggedCurrentFn) { + fn.setFlag(FunctionNode.HAS_EVAL); + flaggedCurrentFn = true; + if (fn.getKind() == FunctionNode.Kind.ARROW) { + // possible use of this in an eval that's nested in an arrow function, e.g.: + // function fun(){ return (() => eval("this"))(); }; + markThis(lc); + markNewTarget(lc); + } + } else { + fn.setFlag(FunctionNode.HAS_NESTED_EVAL); + } + final ParserContextBaseNode body = lc.getFunctionBody(fn); + // NOTE: it is crucial to mark the body of the outer function as needing scope even when we skip + // parsing a nested function. functionBody() contains code to compensate for the lack of invoking + // this method when the parser skips a nested function. + body.setFlag(Block.NEEDS_SCOPE); + fn.setFlag(FunctionNode.HAS_SCOPE_BLOCK); + } + } + + private void prependStatement(final Statement statement) { + lc.prependStatementToCurrentNode(statement); + } + + private void appendStatement(final Statement statement) { + lc.appendStatementToCurrentNode(statement); + } + + private static void markSuperCall(final ParserContext lc) { + final Iterator iter = lc.getFunctions(); + while (iter.hasNext()) { + final ParserContextFunctionNode fn = iter.next(); + if (fn.getKind() != FunctionNode.Kind.ARROW) { + assert fn.isSubclassConstructor(); + fn.setFlag(FunctionNode.HAS_DIRECT_SUPER); + break; + } + } + } + + private ParserContextFunctionNode getCurrentNonArrowFunction() { + final Iterator iter = lc.getFunctions(); + while (iter.hasNext()) { + final ParserContextFunctionNode fn = iter.next(); + if (fn.getKind() != FunctionNode.Kind.ARROW) { + return fn; + } + } + return null; + } + + private static void markThis(final ParserContext lc) { + final Iterator iter = lc.getFunctions(); + while (iter.hasNext()) { + final ParserContextFunctionNode fn = iter.next(); + fn.setFlag(FunctionNode.USES_THIS); + if (fn.getKind() != FunctionNode.Kind.ARROW) { + break; + } + } + } + + private static void markNewTarget(final ParserContext lc) { + final Iterator iter = lc.getFunctions(); + while (iter.hasNext()) { + final ParserContextFunctionNode fn = iter.next(); + if (fn.getKind() != FunctionNode.Kind.ARROW) { + if (!fn.isProgram()) { + fn.setFlag(FunctionNode.USES_NEW_TARGET); + } + break; + } + } + } + + private boolean inGeneratorFunction() { + return lc.getCurrentFunction().getKind() == FunctionNode.Kind.GENERATOR; + } + + private boolean inAsyncFunction() { + return lc.getCurrentFunction().isAsync(); + } + + private boolean isAwait(long token) { + return Token.descType(token) == IDENT && "await".equals((String) getValue(token)); + } + + private boolean lookaheadIsAsyncFunction(boolean method) { + assert type == IDENT && ASYNC_IDENT.equals((String) getValue(token)); + for (int i = 1;; i++) { + long token = getToken(k + i); + TokenType t = Token.descType(token); + switch (t) { + case COMMENT: + continue; + case FUNCTION: + return !method; + case EOL: + return false; + default: + return method && isPropertyName(token); + } + } + } + + private static List convert(NamedImportsNode namedImportsNode) { + List importEntries = new ArrayList<>(namedImportsNode.getImportSpecifiers().size()); + for (ImportSpecifierNode s : namedImportsNode.getImportSpecifiers()) { + if (s.getIdentifier() != null) { + importEntries.add(ImportEntry.importSpecifier(s.getIdentifier().getName(), s.getBindingIdentifier().getName())); + } else { + importEntries.add(ImportEntry.importSpecifier(s.getBindingIdentifier().getName())); + } + } + return importEntries; + } + + private static List convert(ExportClauseNode exportClauseNode) { + List exports = new ArrayList<>(exportClauseNode.getExportSpecifiers().size()); + for (ExportSpecifierNode s : exportClauseNode.getExportSpecifiers()) { + if (s.getExportIdentifier() != null) { + exports.add(ExportEntry.exportSpecifier(s.getExportIdentifier().getName(), s.getIdentifier().getName())); + } else { + exports.add(ExportEntry.exportSpecifier(s.getIdentifier().getName())); + } + } + return exports; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContext.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContext.java new file mode 100644 index 000000000000..8dccc071de8d --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContext.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.oracle.js.parser.ir.Statement; + +/** + * A class that tracks the current lexical context of node visitation as a stack of + * {@code ParserContextNode} nodes. Has special methods to retrieve useful subsets of the context. + * + * This is implemented with a primitive array and a stack pointer, because it really makes a + * difference performance wise. None of the collection classes were optimal + */ + +class ParserContext { + + private ParserContextNode[] stack; + private int sp; + + private static final int INITIAL_DEPTH = 16; + + /** + * Constructs a ParserContext, initializes the stack + */ + ParserContext() { + this.sp = 0; + this.stack = new ParserContextNode[INITIAL_DEPTH]; + } + + /** + * Pushes a new block on top of the context, making it the innermost open block. + * + * @param node the new node + * @return The node that was pushed + */ + public T push(final T node) { + assert !contains(node); + if (sp == stack.length) { + final ParserContextNode[] newStack = new ParserContextNode[sp * 2]; + System.arraycopy(stack, 0, newStack, 0, sp); + stack = newStack; + } + stack[sp] = node; + sp++; + + return node; + } + + /** + * The topmost node on the stack + * + * @return The topmost node on the stack + */ + public ParserContextNode peek() { + return stack[sp - 1]; + } + + /** + * Removes and returns the topmost Node from the stack. + * + * @param node The node expected to be popped, used for sanity check + * @return The removed node + */ + public T pop(final T node) { + --sp; + @SuppressWarnings("unchecked") + final T popped = (T) stack[sp]; + stack[sp] = null; + assert node == popped; + + return popped; + } + + /** + * Tests if a node is on the stack. + * + * @param node The node to test + * @return true if stack contains node, false otherwise + */ + public boolean contains(final ParserContextNode node) { + for (int i = 0; i < sp; i++) { + if (stack[i] == node) { + return true; + } + } + return false; + } + + /** + * Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on stack + * + * @return Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on + * stack + */ + private ParserContextBreakableNode getBreakable() { + for (final NodeIterator iter = new NodeIterator<>(ParserContextBreakableNode.class, getCurrentFunction()); iter.hasNext();) { + final ParserContextBreakableNode next = iter.next(); + if (next.isBreakableWithoutLabel()) { + return next; + } + } + return null; + } + + /** + * Find the breakable node corresponding to this label. + * + * @param labelName name of the label to search for. If null, the closest breakable node will be + * returned unconditionally, e.g. a while loop with no label + * @return closest breakable node + */ + public ParserContextBreakableNode getBreakable(final String labelName) { + if (labelName != null) { + final ParserContextLabelNode foundLabel = findLabel(labelName); + if (foundLabel != null) { + // iterate to the nearest breakable to the foundLabel + ParserContextBreakableNode breakable = null; + for (final NodeIterator iter = new NodeIterator<>(ParserContextBreakableNode.class, foundLabel); iter.hasNext();) { + breakable = iter.next(); + } + return breakable; + } + return null; + } else { + return getBreakable(); + } + } + + /** + * Returns the loop node of the current loop, or null if not inside a loop + * + * @return loop noder + */ + public ParserContextLoopNode getCurrentLoop() { + final Iterator iter = new NodeIterator<>(ParserContextLoopNode.class, getCurrentFunction()); + return iter.hasNext() ? iter.next() : null; + } + + private ParserContextLoopNode getContinueTo() { + return getCurrentLoop(); + } + + /** + * Find the continue target node corresponding to this label. + * + * @param labelName label name to search for. If null the closest loop node will be returned + * unconditionally, e.g. a while loop with no label + * @return closest continue target node + */ + public ParserContextLoopNode getContinueTo(final String labelName) { + if (labelName != null) { + final ParserContextLabelNode foundLabel = findLabel(labelName); + if (foundLabel != null) { + // iterate to the nearest loop to the foundLabel + ParserContextLoopNode loop = null; + for (final NodeIterator iter = new NodeIterator<>(ParserContextLoopNode.class, foundLabel); iter.hasNext();) { + loop = iter.next(); + } + return loop; + } + return null; + } + return getContinueTo(); + } + + /** + * Get the function body of a function node on the stack. This will trigger an assertion if node + * isn't present + * + * @param functionNode function node + * @return body of function node + */ + public ParserContextBaseNode getFunctionBody(final ParserContextFunctionNode functionNode) { + for (int i = sp - 1; i >= 0; i--) { + if (stack[i] == functionNode) { + return (ParserContextBaseNode) stack[i + 1]; + } + } + throw new AssertionError(functionNode.getName() + " not on context stack"); + } + + /** + * Check the stack for a given label node by name + * + * @param name name of the label + * @return LabelNode if found, null otherwise + */ + public ParserContextLabelNode findLabel(final String name) { + for (final Iterator iter = new NodeIterator<>(ParserContextLabelNode.class, getCurrentFunction()); iter.hasNext();) { + final ParserContextLabelNode next = iter.next(); + if (next.getLabelName().equals(name)) { + return next; + } + } + return null; + } + + /** + * Prepends a statement to the current node. + * + * @param statement The statement to prepend + */ + public void prependStatementToCurrentNode(final Statement statement) { + assert statement != null; + stack[sp - 1].prependStatement(statement); + } + + /** + * Appends a statement to the current Node. + * + * @param statement The statement to append + */ + public void appendStatementToCurrentNode(final Statement statement) { + assert statement != null; + stack[sp - 1].appendStatement(statement); + } + + /** + * Returns the innermost function in the context. + * + * @return the innermost function in the context. + */ + public ParserContextFunctionNode getCurrentFunction() { + for (int i = sp - 1; i >= 0; i--) { + if (stack[i] instanceof ParserContextFunctionNode) { + return (ParserContextFunctionNode) stack[i]; + } + } + return null; + } + + /** + * Returns an iterator over all blocks in the context, with the top block (innermost lexical + * context) first. + * + * @return an iterator over all blocks in the context. + */ + public Iterator getBlocks() { + return new NodeIterator<>(ParserContextBlockNode.class); + } + + /** + * Returns the innermost block in the context. + * + * @return the innermost block in the context. + */ + public ParserContextBlockNode getCurrentBlock() { + return getBlocks().next(); + } + + /** + * The last statement added to the context + * + * @return The last statement added to the context + */ + public Statement getLastStatement() { + if (sp == 0) { + return null; + } + final ParserContextNode top = stack[sp - 1]; + final int s = top.getStatements().size(); + return s == 0 ? null : top.getStatements().get(s - 1); + } + + /** + * Returns an iterator over all functions in the context, with the top (innermost open) function + * first. + * + * @return an iterator over all functions in the context. + */ + public Iterator getFunctions() { + return new NodeIterator<>(ParserContextFunctionNode.class); + } + + public ParserContextModuleNode getCurrentModule() { + final Iterator iter = new NodeIterator<>(ParserContextModuleNode.class, getCurrentFunction()); + return iter.hasNext() ? iter.next() : null; + } + + private class NodeIterator implements Iterator { + private int index; + private T next; + private final Class clazz; + private ParserContextNode until; + + NodeIterator(final Class clazz) { + this(clazz, null); + } + + NodeIterator(final Class clazz, final ParserContextNode until) { + this.index = sp - 1; + this.clazz = clazz; + this.until = until; + this.next = findNext(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public T next() { + if (next == null) { + throw new NoSuchElementException(); + } + final T lnext = next; + next = findNext(); + return lnext; + } + + @SuppressWarnings("unchecked") + private T findNext() { + for (int i = index; i >= 0; i--) { + final Object node = stack[i]; + if (node == until) { + return null; + } + if (clazz.isAssignableFrom(node.getClass())) { + index = i - 1; + return (T) node; + } + } + return null; + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBaseNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBaseNode.java new file mode 100644 index 000000000000..d46a376b50b3 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBaseNode.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.js.parser.ir.Statement; + +/** + * Base class for parser context nodes + */ +abstract class ParserContextBaseNode implements ParserContextNode { + /** + * Flags for this node + */ + protected int flags; + + private List statements; + + /** + * Constructor + */ + ParserContextBaseNode() { + this.statements = new ArrayList<>(); + } + + /** + * @return The flags for this node + */ + @Override + public int getFlags() { + return flags; + } + + /** + * Returns a single flag + * + * @param flag + * @return A single flag + */ + protected int getFlag(final int flag) { + return (flags & flag); + } + + /** + * @param flag + * @return the new flags + */ + @Override + public int setFlag(final int flag) { + flags |= flag; + return flags; + } + + /** + * @return The list of statements that belongs to this node + */ + @Override + public List getStatements() { + return statements; + } + + /** + * @param statements + */ + @Override + public void setStatements(final List statements) { + this.statements = statements; + } + + /** + * Adds a Statement at the end of the Statementlist + * + * @param statement The statement to add + */ + @Override + public void appendStatement(final Statement statement) { + this.statements.add(statement); + } + + /** + * Adds a statement at the begining of the statementlist + * + * @param statement The statement to add + */ + @Override + public void prependStatement(final Statement statement) { + this.statements.add(0, statement); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBlockNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBlockNode.java new file mode 100644 index 000000000000..3d9be413ad71 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBlockNode.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +/** + * A ParserContextNode that represents a block that is currently being parsed + */ +class ParserContextBlockNode extends ParserContextBaseNode implements ParserContextBreakableNode { + + private final long token; + + /** + * Constructs a ParserContextBlockNode + * + * @param token The first token of the block + */ + ParserContextBlockNode(final long token) { + this.token = token; + } + + @Override + public boolean isBreakableWithoutLabel() { + return false; + } + + /** + * Get token + * + * @return The first token of the block + */ + public long getToken() { + return token; + } + +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBreakableNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBreakableNode.java new file mode 100644 index 000000000000..b83c035c9693 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextBreakableNode.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import com.oracle.js.parser.ir.BreakNode; + +/** + * An interface that is implemented by ParserContextNodes that can contain a {@link BreakNode} + */ +interface ParserContextBreakableNode extends ParserContextNode { + + /** + * Returns true if not i breakable without label, false otherwise + * + * @return Returns true if not i breakable without label, false otherwise + */ + boolean isBreakableWithoutLabel(); +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextFunctionNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextFunctionNode.java new file mode 100644 index 000000000000..0fa00e45c6cb --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextFunctionNode.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +import java.util.HashSet; +import java.util.List; + +import com.oracle.js.parser.ir.FunctionNode; +import com.oracle.js.parser.ir.IdentNode; +import com.oracle.js.parser.ir.Module; + +/** + * ParserContextNode that represents a function that is currently being parsed + */ +class ParserContextFunctionNode extends ParserContextBaseNode { + + /** Function name */ + private final String name; + + /** Function identifier node */ + private final IdentNode ident; + + /** Name space for function */ + private final Namespace namespace; + + /** Line number for function declaration */ + private final int line; + + /** + * Function node kind, see FunctionNode.Kind + */ + private final FunctionNode.Kind kind; + + /** List of parameter identifiers for function */ + private List parameters; + + /** Token for function start */ + private final long token; + + /** Last function token */ + private long lastToken; + + /** Opaque node for parser end state, see {@link Parser} */ + private Object endParserState; + + private HashSet parameterBoundNames; + private IdentNode duplicateParameterBinding; + private boolean simpleParameterList = true; + + private Module module; + + /** + * @param token The token for the function + * @param ident External function name + * @param name Internal name of the function + * @param namespace Function's namespace + * @param line The source line of the function + * @param kind Function kind + * @param parameters The parameters of the function + */ + ParserContextFunctionNode(final long token, final IdentNode ident, final String name, final Namespace namespace, final int line, final FunctionNode.Kind kind, + final List parameters) { + this.ident = ident; + this.namespace = namespace; + this.line = line; + this.kind = kind; + this.name = name; + this.parameters = parameters; + this.token = token; + } + + /** + * @return Internal name of the function + */ + public String getName() { + return name; + } + + /** + * @return The external identifier for the function + */ + public IdentNode getIdent() { + return ident; + } + + /** + * + * @return true if function is the program function + */ + public boolean isProgram() { + return getFlag(FunctionNode.IS_PROGRAM) != 0; + } + + /** + * @return if function in strict mode + */ + public boolean isStrict() { + return getFlag(FunctionNode.IS_STRICT) != 0; + } + + /** + * @return true if the function has nested evals + */ + public boolean hasNestedEval() { + return getFlag(FunctionNode.HAS_NESTED_EVAL) != 0; + } + + /** + * Returns true if any of the blocks in this function create their own scope. + * + * @return true if any of the blocks in this function create their own scope. + */ + public boolean hasScopeBlock() { + return getFlag(FunctionNode.HAS_SCOPE_BLOCK) != 0; + } + + /** + * Create a unique name in the namespace of this FunctionNode + * + * @param base prefix for name + * @return base if no collision exists, otherwise a name prefix with base + */ + public String uniqueName(final String base) { + return namespace.uniqueName(base); + } + + /** + * @return line number of the function + */ + public int getLineNumber() { + return line; + } + + /** + * @return The kind if function + */ + public FunctionNode.Kind getKind() { + return kind; + } + + /** + * Get parameters + * + * @return The parameters of the function + */ + public List getParameters() { + return parameters; + } + + void setParameters(List parameters) { + this.parameters = parameters; + } + + /** + * Set last token + * + * @param token New last token + */ + public void setLastToken(final long token) { + this.lastToken = token; + + } + + /** + * @return lastToken Function's last token + */ + public long getLastToken() { + return lastToken; + } + + /** + * Returns the ParserState of when the parsing of this function was ended + * + * @return endParserState The end parser state + */ + public Object getEndParserState() { + return endParserState; + } + + /** + * Sets the ParserState of when the parsing of this function was ended + * + * @param endParserState The end parser state + */ + public void setEndParserState(final Object endParserState) { + this.endParserState = endParserState; + } + + /** + * Returns the if of this function + * + * @return The function id + */ + public int getId() { + return isProgram() ? -1 : Token.descPosition(token); + } + + public boolean isMethod() { + return getFlag(FunctionNode.IS_METHOD) != 0; + } + + public boolean isClassConstructor() { + return getFlag(FunctionNode.IS_CLASS_CONSTRUCTOR) != 0; + } + + public boolean isSubclassConstructor() { + return getFlag(FunctionNode.IS_SUBCLASS_CONSTRUCTOR) != 0; + } + + boolean addParameterBinding(IdentNode bindingIdentifier) { + if (Parser.isArguments(bindingIdentifier)) { + setFlag(FunctionNode.DEFINES_ARGUMENTS); + } + + if (parameterBoundNames == null) { + parameterBoundNames = new HashSet<>(); + } + if (parameterBoundNames.add(bindingIdentifier.getName())) { + return true; + } else { + duplicateParameterBinding = bindingIdentifier; + return false; + } + } + + public IdentNode getDuplicateParameterBinding() { + return duplicateParameterBinding; + } + + public boolean isSimpleParameterList() { + return simpleParameterList; + } + + public void setSimpleParameterList(boolean simpleParameterList) { + this.simpleParameterList = simpleParameterList; + } + + public Module getModule() { + return module; + } + + public void setModule(Module module) { + this.module = module; + } + + public boolean isAsync() { + return getFlag(FunctionNode.IS_ASYNC) != 0; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLabelNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLabelNode.java new file mode 100644 index 000000000000..3bba2fa3cefe --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLabelNode.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +/** + * ParserContextNode that represents a LabelNode + */ +class ParserContextLabelNode extends ParserContextBaseNode { + + /** Name for label */ + private final String name; + + /** + * Constructor + * + * @param name The name of the label + */ + ParserContextLabelNode(final String name) { + this.name = name; + } + + /** + * Returns the name of the label + * + * @return name of label + */ + public String getLabelName() { + return name; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLoopNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLoopNode.java new file mode 100644 index 000000000000..131065688b4d --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextLoopNode.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +/** + * A ParserContextNode that represents a loop that is being parsed. + */ +class ParserContextLoopNode extends ParserContextBaseNode implements ParserContextBreakableNode { + + @Override + public boolean isBreakableWithoutLabel() { + return true; + } + +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextModuleNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextModuleNode.java new file mode 100644 index 000000000000..060398f452a6 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextModuleNode.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +import com.oracle.js.parser.ir.ExportNode; +import com.oracle.js.parser.ir.ImportNode; +import java.util.ArrayList; +import java.util.List; + +import com.oracle.js.parser.ir.Module; +import com.oracle.js.parser.ir.Module.ExportEntry; +import com.oracle.js.parser.ir.Module.ImportEntry; + +/** + * ParserContextNode that represents a module. + */ +class ParserContextModuleNode extends ParserContextBaseNode { + + /** Module name. */ + private final String name; + + private List requestedModules = new ArrayList<>(); + private List importEntries = new ArrayList<>(); + private List localExportEntries = new ArrayList<>(); + private List indirectExportEntries = new ArrayList<>(); + private List starExportEntries = new ArrayList<>(); + + private List imports = new ArrayList<>(); + private List exports = new ArrayList<>(); + + /** + * Constructor. + * + * @param name name of the module + */ + ParserContextModuleNode(final String name) { + this.name = name; + } + + /** + * Returns the name of the module. + * + * @return name of the module + */ + public String getModuleName() { + return name; + } + + public void addImport(ImportNode importNode) { + imports.add(importNode); + } + + public void addExport(ExportNode exportNode) { + exports.add(exportNode); + } + + public void addModuleRequest(String moduleRequest) { + requestedModules.add(moduleRequest); + } + + public void addImportEntry(ImportEntry importEntry) { + importEntries.add(importEntry); + } + + public void addLocalExportEntry(ExportEntry exportEntry) { + localExportEntries.add(exportEntry); + } + + public void addIndirectExportEntry(ExportEntry exportEntry) { + indirectExportEntries.add(exportEntry); + } + + public void addStarExportEntry(ExportEntry exportEntry) { + starExportEntries.add(exportEntry); + } + + public Module createModule() { + return new Module(requestedModules, importEntries, localExportEntries, indirectExportEntries, starExportEntries, imports, exports); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextNode.java new file mode 100644 index 000000000000..bc43835e8dd8 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextNode.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +import java.util.List; + +import com.oracle.js.parser.ir.Statement; + +/** + * Used for keeping state when needed in the parser. + */ +interface ParserContextNode { + /** + * @return The flags for this node + */ + int getFlags(); + + /** + * @param flag The flag to set + * @return All current flags after update + */ + int setFlag(final int flag); + + /** + * @return The list of statements that belongs to this node + */ + List getStatements(); + + /** + * @param statements The statement list + */ + void setStatements(final List statements); + + /** + * Adds a Statement at the end of the Statementlist + * + * @param statement The statement to add + */ + void appendStatement(final Statement statement); + + /** + * Adds a statement at the begining of the statementlist + * + * @param statement The statement to add + */ + void prependStatement(final Statement statement); + +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextSwitchNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextSwitchNode.java new file mode 100644 index 000000000000..0ba2a7f9ba07 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserContextSwitchNode.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.js.parser; + +/** + * A ParserContextNode that represents a SwitchNode that is currently being parsed. + */ +class ParserContextSwitchNode extends ParserContextBaseNode implements ParserContextBreakableNode { + + @Override + public boolean isBreakableWithoutLabel() { + return true; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserException.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserException.java new file mode 100644 index 000000000000..0b2436c213a8 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ParserException.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + + +/** + * ECMAScript parser exceptions. + */ +@SuppressWarnings("serial") +public final class ParserException extends RuntimeException { + // script file name + private String fileName; + // script line number + private int line; + // script column number + private int column; + + // Source from which this ParserException originated + private final Source source; + // token responsible for this exception + private final long token; + // if this is translated as ECMA error, which type should be used? + private final JSErrorType errorType; + + /** + * Constructor. + * + * @param msg exception message for this parser error. + */ + public ParserException(final String msg) { + this(JSErrorType.SyntaxError, msg, null, -1, -1, -1); + } + + /** + * Constructor. + * + * @param errorType error type + * @param msg exception message + * @param source source from which this exception originates + * @param line line number of exception + * @param column column number of exception + * @param token token from which this exception originates + * + */ + public ParserException(final JSErrorType errorType, final String msg, final Source source, final int line, final int column, final long token) { + super(msg); + this.fileName = source != null ? source.getName() : null; + this.line = line; + this.column = column; + this.source = source; + this.token = token; + this.errorType = errorType; + } + + /** + * Get the source file name for this {@code ParserException}. + * + * @return the file name + */ + public String getFileName() { + return fileName; + } + + /** + * Set the source file name for this {@code ParserException}. + * + * @param fileName the file name + */ + public void setFileName(final String fileName) { + this.fileName = fileName; + } + + /** + * Get the line number for this {@code ParserException}. + * + * @return the line number + */ + public int getLineNumber() { + return line; + } + + /** + * Set the line number for this {@code ParserException}. + * + * @param line the line number + */ + public void setLineNumber(final int line) { + this.line = line; + } + + /** + * Get the column for this {@code ParserException}. + * + * @return the column number + */ + public int getColumnNumber() { + return column; + } + + /** + * Set the column for this {@code ParserException}. + * + * @param column the column number + */ + public void setColumnNumber(final int column) { + this.column = column; + } + + /** + * Get the {@code Source} of this {@code ParserException}. + * + * @return source + */ + public Source getSource() { + return source; + } + + /** + * Get the token responsible for this {@code ParserException}. + * + * @return token + */ + public long getToken() { + return token; + } + + /** + * Get token position within source where the error originated. + * + * @return token position if available, else -1 + */ + public int getPosition() { + return Token.descPosition(token); + } + + /** + * Get the {@code JSErrorType} of this {@code ParserException}. + * + * @return error type + */ + public JSErrorType getErrorType() { + return errorType; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/RecompilableScriptFunctionData.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/RecompilableScriptFunctionData.java new file mode 100644 index 000000000000..ec1ef5f9fe2a --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/RecompilableScriptFunctionData.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +public interface RecompilableScriptFunctionData { + RecompilableScriptFunctionData getScriptFunctionData(int functionId); + + int getFunctionNodeId(); + + int getFunctionFlags(); + + Object getEndParserState(); +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Scanner.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Scanner.java new file mode 100644 index 000000000000..4d78966fbed2 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Scanner.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +// @formatter:off +/** + * Utility for scanning thru a char array. + * + */ +public class Scanner { + /** Characters to scan. */ + protected final String content; + + /** Position in content. */ + protected int position; + + /** Scan limit. */ + protected final int limit; + + /** Current line number. */ + protected int line; + + /** Current character in stream */ + protected char ch0; + /** 1 character lookahead */ + protected char ch1; + /** 2 character lookahead */ + protected char ch2; + /** 3 character lookahead */ + protected char ch3; + + /** + * Constructor + * + * @param content content to scan + * @param line start line number + * @param start position index in content where to start + * @param length length of input + */ + protected Scanner(final String content, final int line, final int start, final int length) { + this.content = content; + this.position = start; + this.limit = start + length; + this.line = line; + + reset(position); + } + + /** + * Copy constructor + * + * @param scanner scanner + * @param state state, the state is a tuple {position, limit, line} only visible internally + */ + Scanner(final Scanner scanner, final State state) { + content = scanner.content; + position = state.position; + limit = state.limit; + line = state.line; + + reset(position); + } + + /** + * Information needed to restore previous state. + */ + static class State { + /** Position in content. */ + public final int position; + + /** Scan limit. */ + public int limit; + + /** Current line number. */ + public final int line; + + State(final int position, final int limit, final int line) { + this.position = position; + this.limit = limit; + this.line = line; + } + + /** + * Change the limit for a new scanner. + * @param limit New limit. + */ + void setLimit(final int limit) { + this.limit = limit; + } + + boolean isEmpty() { + return position == limit; + } + } + + /** + * Save the state of the scan. + * @return Captured state. + */ + State saveState() { + return new State(position, limit, line); + } + + /** + * Restore the state of the scan. + * @param state Captured state. + */ + void restoreState(final State state) { + position = state.position; + line = state.line; + + reset(position); + } + + /** + * Returns true of scanner is at end of input + * @return true if no more input + */ + protected final boolean atEOF() { + return position == limit; + } + + /** + * Get the ith character from the content. + * @param i Index of character. + * @return ith character or '\0' if beyond limit. + */ + protected final char charAt(final int i) { + // Get a character from the content, '\0' if beyond the end of file. + return i < limit ? content.charAt(i) : '\0'; + } + + /** + * Reset to a character position. + * @param i Position in content. + */ + protected final void reset(final int i) { + ch0 = charAt(i); + ch1 = charAt(i + 1); + ch2 = charAt(i + 2); + ch3 = charAt(i + 3); + position = i < limit ? i : limit; + } + + /** + * Skip ahead a number of characters. + * @param n Number of characters to skip. + */ + protected final void skip(final int n) { + if (n == 1 && !atEOF()) { + ch0 = ch1; + ch1 = ch2; + ch2 = ch3; + ch3 = charAt(position + 4); + position++; + } else if (n != 0) { + reset(position + n); + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ScriptEnvironment.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ScriptEnvironment.java new file mode 100644 index 000000000000..35179b936230 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ScriptEnvironment.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import java.io.PrintWriter; + +/** + * Parser environment consists of command line options, and output and error writers, etc. + */ +public final class ScriptEnvironment { + /** Error writer for this environment */ + private final PrintWriter err; + + /** Top level namespace. */ + private final Namespace namespace; + + /** Accept "const" keyword and treat it as variable. Interim feature */ + final boolean constAsVar; + + /** Display stack trace upon error, default is false */ + final boolean dumpOnError; + + /** Invalid lvalue expressions should be reported as early errors */ + final boolean earlyLvalueError; + + /** Empty statements should be preserved in the AST */ + final boolean emptyStatements; + + /** ecmascriptEdition to support */ + final int ecmascriptEdition; + + /** Enable JSX extension. */ + final boolean jsx; + + /** + * Behavior when encountering a function declaration in a lexical context where only statements + * are acceptable (function declarations are source elements, but not statements). + */ + public enum FunctionStatementBehavior { + /** + * Accept the function declaration silently and treat it as if it were a function expression + * assigned to a local variable. + */ + ACCEPT, + /** + * Log a parser warning, but accept the function declaration and treat it as if it were a + * function expression assigned to a local variable. + */ + WARNING, + /** + * Raise a {@code SyntaxError}. + */ + ERROR + } + + /** + * Behavior when encountering a function declaration in a lexical context where only statements + * are acceptable (function declarations are source elements, but not statements). + */ + final FunctionStatementBehavior functionStatement; + + /** Do not support non-standard syntax extensions. */ + final boolean syntaxExtensions; + + /** is this environment in scripting mode? */ + final boolean scripting; + + /** does the environment support shebang? */ + final boolean shebang; + + /** is this environment in strict mode? */ + final boolean strict; + + final boolean functionDeclarationHoisting; + + private ScriptEnvironment( + boolean strict, int ecmascriptEdition, boolean jsx, + boolean earlyLvalueError, boolean emptyStatements, + boolean syntaxExtensions, boolean scripting, boolean shebang, + boolean constAsVar, boolean functionDeclarationHoisting, + FunctionStatementBehavior functionStatementBehavior, + PrintWriter dumpOnError) { + this.namespace = new Namespace(); + this.err = dumpOnError; + + this.constAsVar = constAsVar; + this.dumpOnError = dumpOnError != null; + this.earlyLvalueError = earlyLvalueError; + this.emptyStatements = emptyStatements; + this.functionStatement = functionStatementBehavior; + this.syntaxExtensions = syntaxExtensions; + this.strict = strict; + this.scripting = scripting; + this.shebang = shebang; + this.ecmascriptEdition = ecmascriptEdition; + this.jsx = jsx; + this.functionDeclarationHoisting = functionDeclarationHoisting; + } + + /** + * Get the error stream for this environment + * + * @return error print writer + */ + PrintWriter getErr() { + return err; + } + + /** + * Get the namespace for this environment + * + * @return namespace + */ + Namespace getNamespace() { + return namespace; + } + + public boolean isStrict() { + return strict; + } + + public static Builder builder() { + return new Builder(); + } + + @SuppressWarnings("hiding") + public static final class Builder { + private boolean constAsVar; + private boolean earlyLvalueError = true; + private boolean emptyStatements; + private int ecmacriptEdition = 6; + private boolean jsx = false; + private boolean syntaxExtensions = true; + private boolean scripting; + private boolean shebang; + private boolean strict; + private boolean functionDeclarationHoisting; + private FunctionStatementBehavior functionStatementBehavior = FunctionStatementBehavior.ERROR; + private PrintWriter dumpOnError; + + private Builder() { + } + + public Builder constAsVar(boolean constAsVar) { + this.constAsVar = constAsVar; + return this; + } + + public Builder earlyLvalueError(boolean earlyLvalueError) { + this.earlyLvalueError = earlyLvalueError; + return this; + } + + public Builder emptyStatements(boolean emptyStatements) { + this.emptyStatements = emptyStatements; + return this; + } + + public Builder ecmacriptEdition(int ecmacriptEdition) { + this.ecmacriptEdition = ecmacriptEdition; + return this; + } + + public Builder jsx(boolean jsx) { + this.jsx = jsx; + return this; + } + + public Builder syntaxExtensions(boolean syntaxExtensions) { + this.syntaxExtensions = syntaxExtensions; + return this; + } + + public Builder scripting(boolean scripting) { + this.scripting = scripting; + return this; + } + + public Builder shebang(boolean shebang) { + this.shebang = shebang; + return this; + } + + public Builder strict(boolean strict) { + this.strict = strict; + return this; + } + + public Builder functionStatementBehavior(FunctionStatementBehavior functionStatementBehavior) { + this.functionStatementBehavior = functionStatementBehavior; + return this; + } + + public Builder dumpOnError(PrintWriter dumpOnError) { + this.dumpOnError = dumpOnError; + return this; + } + + public Builder functionDeclarationHoisting(boolean functionDeclarationHoisting) { + this.functionDeclarationHoisting = functionDeclarationHoisting; + return this; + } + + public ScriptEnvironment build() { + return new ScriptEnvironment(strict, ecmacriptEdition, jsx, + earlyLvalueError, emptyStatements, syntaxExtensions, + scripting, shebang, constAsVar, functionDeclarationHoisting, + functionStatementBehavior, dumpOnError); + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Source.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Source.java new file mode 100644 index 000000000000..45ab52efa71d --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Source.java @@ -0,0 +1,891 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Objects; + +// @formatter:off +/** + * Source objects track the origin of JavaScript entities. + */ +public final class Source { + private static final int BUF_SIZE = 8 * 1024; + + /** + * Descriptive name of the source as supplied by the user. Used for error + * reporting to the user. For example, SyntaxError will use this to print message. + * Used to implement __FILE__. Also used for SourceFile in .class for debugger usage. + */ + private final String name; + + /** + * Base directory the File or base part of the URL. Used to implement __DIR__. + * Used to load scripts relative to the 'directory' or 'base' URL of current script. + * This will be null when it can't be computed. + */ + private final String base; + + /** Source content */ + private final Data data; + + /** Cached hash code */ + private int hash; + + /** Base64-encoded SHA1 digest of this source object */ + private volatile byte[] digest; + + /** source URL set via //@ sourceURL or //# sourceURL directive */ + private String explicitURL; + + // Do *not* make this public, ever! Trusts the URL and content. + private Source(final String name, final String base, final Data data) { + this.name = name; + this.base = base; + this.data = data; + } + + private static Source sourceFor(final String name, final String base, final URLData data) throws IOException { + try { + final Source newSource = new Source(name, base, data); + + // All sources in cache must be fully loaded + data.load(); + + return newSource; + } catch (final RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } + throw e; + } + } + + // Wrapper to manage lazy loading + private interface Data { + + URL url(); + + int length(); + + long lastModified(); + + String data(); + + boolean isEvalCode(); + } + + private static final class RawData implements Data { + private final String source; + private final boolean evalCode; + private int hash; + + private RawData(final String source, final boolean evalCode) { + this.source = Objects.requireNonNull(source); + this.evalCode = evalCode; + } + + private RawData(final Reader reader) throws IOException { + this(readFully(reader), false); + } + + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + h = hash = source.hashCode() ^ (evalCode ? 1 : 0); + } + return h; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof RawData) { + final RawData other = (RawData)obj; + return source.equals(other.source) && evalCode == other.evalCode; + } + return false; + } + + @Override + public String toString() { + return data(); + } + + @Override + public URL url() { + return null; + } + + @Override + public int length() { + return source.length(); + } + + @Override + public long lastModified() { + return 0; + } + + public String data() { + return source; + } + + @Override + public boolean isEvalCode() { + return evalCode; + } + } + + private static class URLData implements Data { + private final URL url; + protected final Charset cs; + private int hash; + protected String source; + protected int length; + protected long lastModified; + + private URLData(final URL url, final Charset cs) { + this.url = Objects.requireNonNull(url); + this.cs = cs; + } + + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + h = hash = url.hashCode(); + } + return h; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (!(other instanceof URLData)) { + return false; + } + + final URLData otherData = (URLData) other; + + if (url.equals(otherData.url)) { + // Make sure both have meta data loaded + try { + if (isDeferred()) { + // Data in cache is always loaded, and we only compare to cached data. + assert !otherData.isDeferred(); + loadMeta(); + } else if (otherData.isDeferred()) { + otherData.loadMeta(); + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + + // Compare meta data + return this.length == otherData.length && this.lastModified == otherData.lastModified; + } + return false; + } + + @Override + public String toString() { + return data(); + } + + @Override + public URL url() { + return url; + } + + @Override + public int length() { + return length; + } + + @Override + public long lastModified() { + return lastModified; + } + + public String data() { + assert !isDeferred(); + return source; + } + + @Override + public boolean isEvalCode() { + return false; + } + + boolean isDeferred() { + return source == null; + } + + protected void load() throws IOException { + if (source == null) { + final URLConnection c = url.openConnection(); + try (InputStream in = c.getInputStream()) { + source = cs == null ? readFully(in) : readFully(in, cs); + length = source.length(); + lastModified = c.getLastModified(); + } + } + } + + protected void loadMeta() throws IOException { + if (length == 0 && lastModified == 0) { + final URLConnection c = url.openConnection(); + length = c.getContentLength(); + lastModified = c.getLastModified(); + } + } + } + + private static class FileData extends URLData { + private final File file; + + private FileData(final File file, final Charset cs) { + super(getURLFromFile(file), cs); + this.file = file; + + } + + @Override + protected void loadMeta() { + if (length == 0 && lastModified == 0) { + length = (int) file.length(); + lastModified = file.lastModified(); + } + } + + @Override + protected void load() throws IOException { + if (source == null) { + source = cs == null ? readFully(file) : readFully(file, cs); + length = source.length(); + lastModified = file.lastModified(); + } + } + } + + private String data() { + return data.data(); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param content contents as string + * @param isEval does this represent code from 'eval' call? + * @return source instance + */ + public static Source sourceFor(final String name, final String content, final boolean isEval) { + return new Source(name, baseName(name), new RawData(content, isEval)); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param content contents as string + * @return source instance + */ + public static Source sourceFor(final String name, final String content) { + return sourceFor(name, content, false); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param url url from which source can be loaded + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final URL url) throws IOException { + return sourceFor(name, url, null); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param url url from which source can be loaded + * @param cs Charset used to convert bytes to chars + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final URL url, final Charset cs) throws IOException { + return sourceFor(name, baseURL(url), new URLData(url, cs)); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param file file from which source can be loaded + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final File file) throws IOException { + return sourceFor(name, file, null); + } + + /** + * Returns a Source instance + * + * @param name source name + * @param path path from which source can be loaded + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final Path path) throws IOException { + File file = null; + try { + file = path.toFile(); + } catch (final UnsupportedOperationException uoe) { + } + + if (file != null) { + return sourceFor(name, file); + } else { + return sourceFor(name, Files.newBufferedReader(path)); + } + } + + /** + * Returns a Source instance + * + * @param name source name + * @param file file from which source can be loaded + * @param cs Charset used to convert bytes to chars + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final File file, final Charset cs) throws IOException { + final File absFile = file.getAbsoluteFile(); + return sourceFor(name, dirName(absFile, null), new FileData(file, cs)); + } + + /** + * Returns a Source instance. + * + * @param name source name + * @param reader reader from which source can be loaded + * + * @return source instance + * + * @throws IOException if source cannot be loaded + */ + public static Source sourceFor(final String name, final Reader reader) throws IOException { + return new Source(name, baseName(name), new RawData(reader)); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Source)) { + return false; + } + final Source other = (Source) obj; + return Objects.equals(name, other.name) && data.equals(other.data); + } + + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + h = hash = data.hashCode() ^ Objects.hashCode(name); + } + return h; + } + + /** + * Fetch source content. + * @return Source content. + */ + public String getString() { + return data.toString(); + } + + /** + * Get the user supplied name of this script. + * @return User supplied source name. + */ + public String getName() { + return name; + } + + /** + * Get the last modified time of this script. + * @return Last modified time. + */ + public long getLastModified() { + return data.lastModified(); + } + + /** + * Get the "directory" part of the file or "base" of the URL. + * @return base of file or URL. + */ + public String getBase() { + return base; + } + + /** + * Fetch a portion of source content. + * @param start start index in source + * @param len length of portion + * @return Source content portion. + */ + public String getString(final int start, final int len) { + return data().substring(start, start + len); + } + + /** + * Fetch a portion of source content associated with a token. + * @param token Token descriptor. + * @return Source content portion. + */ + public String getString(final long token) { + final int start = Token.descPosition(token); + final int len = Token.descLength(token); + return getString(start, len); + } + + /** + * Returns the source URL of this script Source. Can be null if Source + * was created from a String or a char[]. + * + * @return URL source or null + */ + public URL getURL() { + return data.url(); + } + + /** + * Get explicit source URL. + * @return URL set via sourceURL directive + */ + public String getExplicitURL() { + return explicitURL; + } + + /** + * Set explicit source URL. + * @param explicitURL URL set via sourceURL directive + */ + public void setExplicitURL(String explicitURL) { + this.explicitURL = explicitURL; + } + + /** + * Returns whether this source was submitted via 'eval' call or not. + * + * @return true if this source represents code submitted via 'eval' + */ + public boolean isEvalCode() { + return data.isEvalCode(); + } + + /** + * Find the beginning of the line containing position. + * @param position Index to offending token. + * @return Index of first character of line. + */ + private int findBOLN(final int position) { + final String d = data(); + for (int i = position - 1; i > 0; i--) { + final char ch = d.charAt(i); + + if (ch == '\n' || ch == '\r') { + return i + 1; + } + } + + return 0; + } + + /** + * Find the end of the line containing position. + * @param position Index to offending token. + * @return Index of last character of line. + */ + private int findEOLN(final int position) { + final String d = data(); + final int length = d.length(); + for (int i = position; i < length; i++) { + final char ch = d.charAt(i); + + if (ch == '\n' || ch == '\r') { + return i - 1; + } + } + + return length - 1; + } + + /** + * Return line number of character position. + * + *

This method can be expensive for large sources as it iterates through + * all characters up to {@code position}.

+ * + * @param position Position of character in source content. + * @return Line number. + */ + public int getLine(final int position) { + final String d = data(); + // Line count starts at 1. + int line = 1; + + for (int i = 0; i < position; i++) { + final char ch = d.charAt(i); + // Works for both \n and \r\n. + if (ch == '\n') { + line++; + } + } + + return line; + } + + /** + * Return column number of character position. + * @param position Position of character in source content. + * @return Column number. + */ + public int getColumn(final int position) { + // TODO - column needs to account for tabs. + return position - findBOLN(position); + } + + /** + * Return line text including character position. + * @param position Position of character in source content. + * @return Line text. + */ + public String getSourceLine(final int position) { + // Find end of previous line. + final int first = findBOLN(position); + // Find end of this line. + final int last = findEOLN(position); + + return data().substring(first, last + 1); + } + + /** + * Get the content of this source as a {@link String}. + */ + public String getContent() { + return data(); + } + + /** + * Get the length in chars for this source + * @return length + */ + public int getLength() { + return data.length(); + } + + /** + * Read all of the source until end of file. + * + * @param reader reader opened to source stream + * @return source as content + * @throws IOException if source could not be read + */ + public static String readFully(final Reader reader) throws IOException { + final char[] arr = new char[BUF_SIZE]; + final StringBuilder sb = new StringBuilder(); + + try { + int numChars; + while ((numChars = reader.read(arr, 0, arr.length)) > 0) { + sb.append(arr, 0, numChars); + } + } finally { + reader.close(); + } + + return sb.toString(); + } + + /** + * Read all of the source until end of file. + * + * @param file source file + * @return source as content + * @throws IOException if source could not be read + */ + public static String readFully(final File file) throws IOException { + if (!file.isFile()) { + throw new IOException(file + " is not a file"); + } + return byteArrayToString(Files.readAllBytes(file.toPath())); + } + + /** + * Read all of the source until end of file. + * + * @param file source file + * @param cs Charset used to convert bytes to chars + * @return source as content + * @throws IOException if source could not be read + */ + public static String readFully(final File file, final Charset cs) throws IOException { + if (!file.isFile()) { + throw new IOException(file + " is not a file"); + } + + final byte[] buf = Files.readAllBytes(file.toPath()); + return (cs != null) ? new String(buf, cs) : byteArrayToString(buf); + } + + /** + * Read all of the source until end of stream from the given URL. + * + * @param url URL to read content from + * @return source as content + * @throws IOException if source could not be read + */ + public static String readFully(final URL url) throws IOException { + return readFully(url.openStream()); + } + + /** + * Read all of the source until end of file. + * + * @param url URL to read content from + * @param cs Charset used to convert bytes to chars + * @return source as content + * @throws IOException if source could not be read + */ + public static String readFully(final URL url, final Charset cs) throws IOException { + return readFully(url.openStream(), cs); + } + + /** + * Get a Base64-encoded SHA1 digest for this source. + * + * @return a Base64-encoded SHA1 digest for this source + */ + public String getDigest() { + return new String(getDigestBytes(), StandardCharsets.US_ASCII); + } + + private byte[] getDigestBytes() { + byte[] ldigest = digest; + if (ldigest == null) { + final String content = data(); + final byte[] bytes = new byte[content.length() * 2]; + + for (int i = 0; i < content.length(); i++) { + bytes[i * 2] = (byte) (content.charAt(i) & 0x00ff); + bytes[i * 2 + 1] = (byte) ((content.charAt(i) & 0xff00) >> 8); + } + + try { + final MessageDigest md = MessageDigest.getInstance("SHA-1"); + if (name != null) { + md.update(name.getBytes(StandardCharsets.UTF_8)); + } + if (base != null) { + md.update(base.getBytes(StandardCharsets.UTF_8)); + } + if (getURL() != null) { + md.update(getURL().toString().getBytes(StandardCharsets.UTF_8)); + } + // Message digest to file name encoder + Base64.Encoder base64 = Base64.getUrlEncoder().withoutPadding(); + digest = ldigest = base64.encode(md.digest(bytes)); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + return ldigest; + } + + /** + * Get the base url. This is currently used for testing only + * @param url a URL + * @return base URL for url + */ + public static String baseURL(final URL url) { + if (url.getProtocol().equals("file")) { + try { + final Path path = Paths.get(url.toURI()); + final Path parent = path.getParent(); + return (parent != null) ? (parent + File.separator) : null; + } catch (final SecurityException | URISyntaxException | IOError e) { + return null; + } + } + + // FIXME: is there a better way to find 'base' URL of a given URL? + String path = url.getPath(); + if (path.isEmpty()) { + return null; + } + path = path.substring(0, path.lastIndexOf('/') + 1); + final int port = url.getPort(); + try { + return new URL(url.getProtocol(), url.getHost(), port, path).toString(); + } catch (final MalformedURLException e) { + return null; + } + } + + private static String dirName(final File file, final String defaultBaseName) { + final String res = file.getParent(); + return (res != null) ? (res + File.separator) : defaultBaseName; + } + + // fake directory like name + private static String baseName(final String name) { + int idx = name.lastIndexOf('/'); + if (idx == -1) { + idx = name.lastIndexOf('\\'); + } + return (idx != -1) ? name.substring(0, idx + 1) : null; + } + + public static String readFully(final InputStream is, final Charset cs) throws IOException { + return (cs != null) ? new String(readBytes(is), cs) : readFully(is); + } + + public static String readFully(final InputStream is) throws IOException { + return byteArrayToString(readBytes(is)); + } + + private static String byteArrayToString(final byte[] bytes) { + Charset cs = StandardCharsets.UTF_8; + int start = 0; + // BOM detection. + if (bytes.length > 1 && bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) { + start = 2; + cs = StandardCharsets.UTF_16BE; + } else if (bytes.length > 1 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) { + if (bytes.length > 3 && bytes[2] == 0 && bytes[3] == 0) { + start = 4; + cs = Charset.forName("UTF-32LE"); + } else { + start = 2; + cs = StandardCharsets.UTF_16LE; + } + } else if (bytes.length > 2 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) { + start = 3; + cs = StandardCharsets.UTF_8; + } else if (bytes.length > 3 && bytes[0] == 0 && bytes[1] == 0 && bytes[2] == (byte) 0xFE && bytes[3] == (byte) 0xFF) { + start = 4; + cs = Charset.forName("UTF-32BE"); + } + + return new String(bytes, start, bytes.length - start, cs); + } + + static byte[] readBytes(final InputStream is) throws IOException { + final byte[] arr = new byte[BUF_SIZE]; + try { + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + int numBytes; + while ((numBytes = is.read(arr, 0, arr.length)) > 0) { + buf.write(arr, 0, numBytes); + } + return buf.toByteArray(); + } + } finally { + is.close(); + } + } + + @Override + public String toString() { + return getName(); + } + + private static URL getURLFromFile(final File file) { + try { + return file.toURI().toURL(); + } catch (final SecurityException | MalformedURLException ignored) { + return null; + } + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/Token.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/Token.java new file mode 100644 index 000000000000..999958800bab --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/Token.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenKind.LITERAL; + +// @formatter:off +/** + * A token is a 64 bit long value that represents a basic parse/lex unit. + * This class provides static methods to manipulate lexer tokens. + */ +public final class Token { + + /** + * We use 28 bits for the position and 28 bits for the length of the token. + * This limits the maximal length of code we can handle to 2 ^ 28 - 1 bytes. + */ + public static final int LENGTH_MASK = 0xfffffff; + + // The first 8 bits are used for the token type, followed by length and position + private static final int LENGTH_SHIFT = 8; + private static final int POSITION_SHIFT = 36; + + private Token() { + } + + /** + * Create a compact form of token information. + * @param type Type of token. + * @param position Start position of the token in the source. + * @param length Length of the token. + * @return Token descriptor. + */ + public static long toDesc(final TokenType type, final int position, final int length) { + assert position <= LENGTH_MASK && length <= LENGTH_MASK; + return (long)position << POSITION_SHIFT | + (long)length << LENGTH_SHIFT | + type.ordinal(); + } + + /** + * Extract token position from a token descriptor. + * @param token Token descriptor. + * @return Start position of the token in the source. + */ + public static int descPosition(final long token) { + return (int)(token >>> POSITION_SHIFT); + } + + /** + * Normally returns the token itself, except in case of string tokens + * which report their position past their opening delimiter and thus + * need to have position and length adjusted. + * + * @param token Token descriptor. + * @return same or adjusted token. + */ + public static long withDelimiter(final long token) { + final TokenType tokenType = Token.descType(token); + switch(tokenType) { + case STRING: + case ESCSTRING: + case EXECSTRING: + case TEMPLATE: + case TEMPLATE_TAIL: { + final int start = Token.descPosition(token) - 1; + final int len = Token.descLength(token) + 2; + return toDesc(tokenType, start, len); + } + case TEMPLATE_HEAD: + case TEMPLATE_MIDDLE: { + final int start = Token.descPosition(token) - 1; + final int len = Token.descLength(token) + 3; + return toDesc(tokenType, start, len); + } + default: { + return token; + } + } + } + + /** + * Extract token length from a token descriptor. + * @param token Token descriptor. + * @return Length of the token. + */ + public static int descLength(final long token) { + return (int)((token >>> LENGTH_SHIFT) & LENGTH_MASK); + } + + /** + * Extract token type from a token descriptor. + * @param token Token descriptor. + * @return Type of token. + */ + public static TokenType descType(final long token) { + return TokenType.getValues()[(int)token & 0xff]; + } + + /** + * Change the token to use a new type. + * + * @param token The original token. + * @param newType The new token type. + * @return The recast token. + */ + public static long recast(final long token, final TokenType newType) { + return token & ~0xFFL | newType.ordinal(); + } + + /** + * Return a string representation of a token. + * @param source Token source. + * @param token Token descriptor. + * @param verbose True to include details. + * @return String representation. + */ + public static String toString(final Source source, final long token, final boolean verbose) { + final TokenType type = Token.descType(token); + String result; + + if (source != null && type.getKind() == LITERAL) { + result = source.getString(token); + } else { + result = type.getNameOrType(); + } + + if (verbose) { + final int position = Token.descPosition(token); + final int length = Token.descLength(token); + result += " (" + position + ", " + length + ")"; + } + + return result; + } + + /** + * String conversion of token + * + * @param source the source + * @param token the token + * + * @return token as string + */ + public static String toString(final Source source, final long token) { + return Token.toString(source, token, false); + } + + /** + * String conversion of token - version without source given + * + * @param token the token + * + * @return token as string + */ + public static String toString(final long token) { + return Token.toString(null, token, false); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenKind.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenKind.java new file mode 100644 index 000000000000..5011c30cff2e --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenKind.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +/** + * Classification of token types. + */ +public enum TokenKind { + /** Error, EOF, EOL... */ + SPECIAL, + /** Unary operators. */ + UNARY, + /** Binary operators. */ + BINARY, + /** [] () {} */ + BRACKET, + /** String recognized as a keyword. */ + KEYWORD, + /** Literal constant. */ + LITERAL, + /** IR only token. */ + IR, + /** Token reserved for future usage. */ + FUTURE, + /** Token reserved for future in strict mode. */ + FUTURESTRICT, + /** JSX tokens. */ + JSX +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenLookup.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenLookup.java new file mode 100644 index 000000000000..06e1877bbfca --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenLookup.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenKind.SPECIAL; +import static com.oracle.js.parser.TokenType.IDENT; + +// @formatter:off +/** + * Fast lookup of operators and keywords. + * + */ +public final class TokenLookup { + /** + * Lookup table for tokens. + */ + private static final TokenType[] table; + + /** + * Table base character. + */ + private static final int tableBase = ' '; + + /** + * Table base character. + */ + private static final int tableLimit = '~'; + + /** + * Table size. + */ + private static final int tableLength = tableLimit - tableBase + 1; + + static { + // Construct the table. + table = new TokenType[tableLength]; + + // For each token type. + for (final TokenType tokenType : TokenType.getValues()) { + // Get the name. + final String name = tokenType.getName(); + + // Filter tokens. + if (name == null || tokenType.getKind() == TokenKind.JSX) { + continue; + } + + // Ignore null and special. + if (tokenType.getKind() != SPECIAL) { + // Get the first character of the name. + final char first = name.charAt(0); + // Translate that character into a table index. + final int index = first - tableBase; + assert index < tableLength : "Token name does not fit lookup table"; + + // Get the length of the token so that the longest come first. + final int length = tokenType.getLength(); + // Prepare for table insert. + TokenType prev = null; + TokenType next = table[index]; + + // Find the right spot in the table. + while (next != null && next.getLength() > length) { + prev = next; + next = next.getNext(); + } + + // Insert in table. + tokenType.setNext(next); + + if (prev == null) { + table[index] = tokenType; + } else { + prev.setNext(tokenType); + } + } + } + } + + private TokenLookup() { + } + + /** + * Lookup keyword. + * + * @param content parse content char array + * @param position index of position to start looking + * @param length max length to scan + * + * @return token type for keyword + */ + public static TokenType lookupKeyword(final String content, final int position, final int length) { + // First character of keyword. + final char first = content.charAt(position); + + // Must be lower case character. + if ('a' <= first && first <= 'z') { + // Convert to table index. + final int index = first - tableBase; + // Get first bucket entry. + TokenType tokenType = table[index]; + + // Search bucket list. + while (tokenType != null) { + final int tokenLength = tokenType.getLength(); + + // if we have a length match maybe a keyword. + if (tokenLength == length) { + // Do an exact compare of string. + final String name = tokenType.getName(); + int i; + for (i = 0; i < length; i++) { + if (content.charAt(position + i) != name.charAt(i)) { + break; + } + } + + if (i == length) { + // Found a match. + return tokenType; + } + } else if (tokenLength < length) { + // Rest of tokens are shorter. + break; + } + + // Try next token. + tokenType = tokenType.getNext(); + } + } + + // Not found. + return IDENT; + } + + + /** + * Lookup operator. + * + * @param ch0 0th char in stream + * @param ch1 1st char in stream + * @param ch2 2nd char in stream + * @param ch3 3rd char in stream + * + * @return the token type for the operator + */ + public static TokenType lookupOperator(final char ch0, final char ch1, final char ch2, final char ch3) { + // Ignore keyword entries. + if (tableBase < ch0 && ch0 <= tableLimit && !('a' <= ch0 && ch0 <= 'z')) { + // Convert to index. + final int index = ch0 - tableBase; + // Get first bucket entry. + TokenType tokenType = table[index]; + + // Search bucket list. + while (tokenType != null) { + final String name = tokenType.getName(); + + switch (name.length()) { + case 1: + // One character entry. + return tokenType; + case 2: + // Two character entry. + if (name.charAt(1) == ch1) { + return tokenType; + } + break; + case 3: + // Three character entry. + if (name.charAt(1) == ch1 && + name.charAt(2) == ch2) { + return tokenType; + } + break; + case 4: + // Four character entry. + if (name.charAt(1) == ch1 && + name.charAt(2) == ch2 && + name.charAt(3) == ch3) { + return tokenType; + } + break; + default: + break; + } + + // Try next token. + tokenType = tokenType.getNext(); + } + } + + // Not found. + return null; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenStream.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenStream.java new file mode 100644 index 000000000000..f5fec213eed9 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenStream.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +// @formatter:off +/** + * Handles streaming of tokens between lexer and parser. + * + */ +public class TokenStream { + /** Initial buffer size. */ + private static final int INITIAL_SIZE = 256; + + /** Token buffer. */ + private long[] buffer; + + /** Token count. */ + private int count; + + /** Cursor to write position in buffer */ + private int in; + + /** Cursor to read position in buffer */ + private int out; + + /** Base index in buffer */ + private int base; + + /** + * Constructor. + */ + public TokenStream() { + buffer = new long[INITIAL_SIZE]; + count = 0; + in = 0; + out = 0; + base = 0; + } + + /** + * Get the next position in the buffer. + * @param position Current position in buffer. + * @return Next position in buffer. + */ + private int next(final int position) { + // Next position. + int next = position + 1; + + // If exceeds buffer length. + if (next >= buffer.length) { + // Wrap around. + next = 0; + } + + return next; + } + + /** + * Get the index position in the buffer. + * @param k Seek position. + * @return Position in buffer. + */ + private int index(final int k) { + // Bias k. + int index = k - (base - out); + + // If wrap around. + if (index >= buffer.length) { + index -= buffer.length; + } + + return index; + } + + /** + * Test to see if stream is empty. + * @return True if stream is empty. + */ + public boolean isEmpty() { + return count == 0; + } + + /** + * Test to see if stream is full. + * @return True if stream is full. + */ + public boolean isFull() { + return count == buffer.length; + } + + /** + * Get the number of tokens in the buffer. + * @return Number of tokens. + */ + public int count() { + return count; + } + + /** + * Get the index of the first token in the stream. + * @return Index of first buffered token in the stream. + */ + public int first() { + return base; + } + + /** + * Get the index of the last token in the stream. + * @return Index of last buffered token in the stream. + */ + public int last() { + return base + count - 1; + } + + /** + * Remove the last token in the stream. + */ + public void removeLast() { + if (count != 0) { + count--; + in--; + + if (in < 0) { + in = buffer.length - 1; + } + } + } + + /** + * Put a token descriptor to the stream. + * @param token Token descriptor to add. + */ + public void put(final long token) { + if (count == buffer.length) { + grow(); + } + + buffer[in] = token; + count++; + in = next(in); + } + + /** + * Get the kth token descriptor from the stream. + * @param k index + * @return Token descriptor. + */ + public long get(final int k) { + return buffer[index(k)]; + } + + /** + * Advances the base of the stream. + * @param k Position of token to be the new base. + */ + public void commit(final int k) { + // Advance out. + out = index(k); + // Adjust count. + count -= k - base; + // Set base. + base = k; + } + + /** + * Grow the buffer to accommodate more token descriptors. + */ + public void grow() { + // Allocate new buffer. + final long[] newBuffer = new long[buffer.length * 2]; + + // If single chunk. + if (in > out) { + System.arraycopy(buffer, out, newBuffer, 0, count); + } else { + final int portion = buffer.length - out; + System.arraycopy(buffer, out, newBuffer, 0, portion); + System.arraycopy(buffer, 0, newBuffer, portion, count - portion); + } + + // Update buffer and indices. + out = 0; + in = count; + buffer = newBuffer; + } + + void reset() { + in = out = count = base = 0; + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenType.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenType.java new file mode 100644 index 000000000000..d38e72484119 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/TokenType.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser; + +import static com.oracle.js.parser.TokenKind.BINARY; +import static com.oracle.js.parser.TokenKind.BRACKET; +import static com.oracle.js.parser.TokenKind.FUTURE; +import static com.oracle.js.parser.TokenKind.FUTURESTRICT; +import static com.oracle.js.parser.TokenKind.IR; +import static com.oracle.js.parser.TokenKind.JSX; +import static com.oracle.js.parser.TokenKind.KEYWORD; +import static com.oracle.js.parser.TokenKind.LITERAL; +import static com.oracle.js.parser.TokenKind.SPECIAL; +import static com.oracle.js.parser.TokenKind.UNARY; + +import java.util.Locale; + +// @formatter:off +// Checkstyle: stop +/** + * Description of all the JavaScript tokens. + */ +public enum TokenType { + ERROR (SPECIAL, null), + EOF (SPECIAL, null), + EOL (SPECIAL, null), + COMMENT (SPECIAL, null), + // comments of the form //@ foo=bar or //# foo=bar + // These comments are treated as special instructions + // to the lexer, parser or codegenerator. + DIRECTIVE_COMMENT (SPECIAL, null), + + NOT (UNARY, "!", 15, false), + NE (BINARY, "!=", 9, true), + NE_STRICT (BINARY, "!==", 9, true), + MOD (BINARY, "%", 13, true), + ASSIGN_MOD (BINARY, "%=", 2, false), + BIT_AND (BINARY, "&", 8, true), + AND (BINARY, "&&", 5, true), + ASSIGN_BIT_AND (BINARY, "&=", 2, false), + LPAREN (BRACKET, "(", 17, true), + RPAREN (BRACKET, ")", 0, true), + MUL (BINARY, "*", 13, true), + ASSIGN_MUL (BINARY, "*=", 2, false), + EXP (BINARY, "**", 14, false), + ASSIGN_EXP (BINARY, "**=", 2, false), + ADD (BINARY, "+", 12, true), + INCPREFIX (UNARY, "++", 16, true), + ASSIGN_ADD (BINARY, "+=", 2, false), + COMMARIGHT (BINARY, ",", 1, true), + SUB (BINARY, "-", 12, true), + DECPREFIX (UNARY, "--", 16, true), + ASSIGN_SUB (BINARY, "-=", 2, false), + PERIOD (BRACKET, ".", 18, true), + OPTIONAL_ACCESS(BRACKET, "?.", 18, true, 11), + DIV (BINARY, "/", 13, true), + ASSIGN_DIV (BINARY, "/=", 2, false), + COLON (BINARY, ":"), + SEMICOLON (BINARY, ";"), + LT (BINARY, "<", 10, true), + SHL (BINARY, "<<", 11, true), + ASSIGN_SHL (BINARY, "<<=", 2, false), + LE (BINARY, "<=", 10, true), + ASSIGN (BINARY, "=", 2, false), + EQ (BINARY, "==", 9, true), + EQ_STRICT (BINARY, "===", 9, true), + ARROW (BINARY, "=>", 2, true), + GT (BINARY, ">", 10, true), + GE (BINARY, ">=", 10, true), + SAR (BINARY, ">>", 11, true), + ASSIGN_SAR (BINARY, ">>=", 2, false), + SHR (BINARY, ">>>", 11, true), + ASSIGN_SHR (BINARY, ">>>=", 2, false), + TERNARY (BINARY, "?", 3, false), + LBRACKET (BRACKET, "[", 18, true), + RBRACKET (BRACKET, "]", 0, true), + BIT_XOR (BINARY, "^", 7, true), + ASSIGN_BIT_XOR (BINARY, "^=", 2, false), + LBRACE (BRACKET, "{"), + BIT_OR (BINARY, "|", 6, true), + ASSIGN_BIT_OR (BINARY, "|=", 2, false), + OR (BINARY, "||", 4, true), + RBRACE (BRACKET, "}"), + BIT_NOT (UNARY, "~", 15, false), + ELLIPSIS (UNARY, "..."), + AT (UNARY, "@"), + ASSIGN_LOG_AND (BINARY, "&&=", 2, false, 12), + ASSIGN_LOG_OR (BINARY, "||=", 2, false, 12), + ASSIGN_NULLISH (BINARY, "??=", 2, false, 12), + NULLISH (BINARY, "??", 4, true, 11), + + // ECMA 7.6.1.1 Keywords, 7.6.1.2 Future Reserved Words. + // All other Java keywords are commented out. + +// ABSTRACT (FUTURE, "abstract"), +// BOOLEAN (FUTURE, "boolean"), + BREAK (KEYWORD, "break"), +// BYTE (FUTURE, "byte"), + CASE (KEYWORD, "case"), + CATCH (KEYWORD, "catch"), +// CHAR (FUTURE, "char"), + CLASS (FUTURE, "class"), + CONST (KEYWORD, "const"), + CONTINUE (KEYWORD, "continue"), + DEBUGGER (KEYWORD, "debugger"), + DEFAULT (KEYWORD, "default"), + DELETE (UNARY, "delete", 15, false), + DO (KEYWORD, "do"), +// DOUBLE (FUTURE, "double"), +// EACH (KEYWORD, "each"), // Contextual. + ELSE (KEYWORD, "else"), + ENUM (FUTURE, "enum"), + EXPORT (FUTURE, "export"), + EXTENDS (FUTURE, "extends"), + FALSE (LITERAL, "false"), +// FINAL (FUTURE, "final"), + FINALLY (KEYWORD, "finally"), +// FLOAT (FUTURE, "float"), + FOR (KEYWORD, "for"), + FUNCTION (KEYWORD, "function"), +// GET (KEYWORD, "get"), // Contextual. +// GOTO (FUTURE, "goto"), + IF (KEYWORD, "if"), + IMPLEMENTS (FUTURESTRICT, "implements"), + IMPORT (FUTURE, "import"), + IN (BINARY, "in", 10, true), + INSTANCEOF (BINARY, "instanceof", 10, true), +// INT (FUTURE, "int"), + INTERFACE (FUTURESTRICT, "interface"), + LET (FUTURESTRICT, "let"), +// LONG (FUTURE, "long"), +// NATIVE (FUTURE, "native"), + NEW (UNARY, "new", 18, false), + NULL (LITERAL, "null"), + PACKAGE (FUTURESTRICT, "package"), + PRIVATE (FUTURESTRICT, "private"), + PROTECTED (FUTURESTRICT, "protected"), + PUBLIC (FUTURESTRICT, "public"), + RETURN (KEYWORD, "return"), +// SET (KEYWORD, "set"), // Contextual. +// SHORT (FUTURE, "short"), + STATIC (FUTURESTRICT, "static"), + SUPER (FUTURE, "super"), + SWITCH (KEYWORD, "switch"), +// SYNCHRONIZED (FUTURE, "synchronized"), + THIS (KEYWORD, "this"), + THROW (KEYWORD, "throw"), +// THROWS (FUTURE, "throws"), +// TRANSIENT (FUTURE, "transient"), + TRUE (LITERAL, "true"), + TRY (KEYWORD, "try"), + TYPEOF (UNARY, "typeof", 15, false), + VAR (KEYWORD, "var"), + VOID (UNARY, "void", 15, false), +// VOLATILE (FUTURE, "volatile"), + WHILE (KEYWORD, "while"), + WITH (KEYWORD, "with"), + YIELD (FUTURESTRICT, "yield"), + + DECIMAL (LITERAL, null), + BIGINT (LITERAL, null), + HEXADECIMAL (LITERAL, null), + OCTAL_LEGACY (LITERAL, null), + OCTAL (LITERAL, null), + BINARY_NUMBER (LITERAL, null), + FLOATING (LITERAL, null), + STRING (LITERAL, null), + ESCSTRING (LITERAL, null), + EXECSTRING (LITERAL, null), + IDENT (LITERAL, null), + REGEX (LITERAL, null), + XML (LITERAL, null), + OBJECT (LITERAL, null), + ARRAY (LITERAL, null), + TEMPLATE (LITERAL, null), + TEMPLATE_HEAD (LITERAL, null), + TEMPLATE_MIDDLE(LITERAL, null), + TEMPLATE_TAIL (LITERAL, null), + + COMMALEFT (IR, null), + DECPOSTFIX (IR, null), + INCPOSTFIX (IR, null), + SPREAD_ARGUMENT(IR, null), + SPREAD_ARRAY (IR, null), + SPREAD_OBJECT (IR, null), + YIELD_STAR (IR, null), + AWAIT (IR, null), + + JSX_IDENTIFIER (JSX, null), + JSX_TEXT (JSX, null), + JSX_STRING (JSX, null), + JSX_ELEM_START (JSX, "<"), + JSX_ELEM_END (JSX, ">"), + JSX_ELEM_CLOSE (JSX, "/") + ; + + /** Next token kind in token lookup table. */ + private TokenType next; + + /** Classification of token. */ + private final TokenKind kind; + + /** Printable name of token. */ + private final String name; + + /** Operator precedence. */ + private final int precedence; + + /** Left associativity */ + private final boolean isLeftAssociative; + + private final int ecmascriptEdition; + + /** Cache values to avoid cloning. */ + private static final TokenType[] values; + + TokenType(final TokenKind kind, final String name) { + this.next = null; + this.kind = kind; + this.name = name; + this.precedence = 0; + this.isLeftAssociative = false; + this.ecmascriptEdition = 0; + } + + TokenType(final TokenKind kind, final String name, final int precedence, final boolean isLeftAssociative) { + this.next = null; + this.kind = kind; + this.name = name; + this.precedence = precedence; + this.isLeftAssociative = isLeftAssociative; + this.ecmascriptEdition = 0; + } + + TokenType(final TokenKind kind, final String name, final int precedence, final boolean isLeftAssociative, int ecmascriptEdition) { + this.next = null; + this.kind = kind; + this.name = name; + this.precedence = precedence; + this.isLeftAssociative = isLeftAssociative; + this.ecmascriptEdition = ecmascriptEdition; + } + + /** + * Determines if the token has greater precedence than other. + * + * @param other Compare token. + * @param isLeft Is to the left of the other. + * + * @return {@code true} if greater precedence. + */ + public boolean needsParens(final TokenType other, final boolean isLeft) { + return other.precedence != 0 && + (precedence > other.precedence || + precedence == other.precedence && isLeftAssociative && !isLeft); + } + + /** + * Determines if the type is a valid operator. + * + * @param noIn {@code true} if IN operator should be ignored. + * + * @return {@code true} if valid operator. + */ + public boolean isOperator(final boolean noIn) { + return kind == BINARY && (!noIn || this != IN) && precedence != 0; + } + + public int getLength() { + assert name != null : "Token name not set"; + return name.length(); + } + + public String getName() { + return name; + } + + public String getNameOrType() { + return name == null ? super.name().toLowerCase(Locale.ENGLISH) : name; + } + + public TokenType getNext() { + return next; + } + + public void setNext(final TokenType next) { + this.next = next; + } + + public TokenKind getKind() { + return kind; + } + + public int getPrecedence() { + return precedence; + } + + public boolean isLeftAssociative() { + return isLeftAssociative; + } + + boolean startsWith(final char c) { + return name != null && name.length() > 0 && name.charAt(0) == c; + } + + public int getEcmascriptEdition() { + return ecmascriptEdition; + } + + static TokenType[] getValues() { + return values; + } + + @Override + public String toString() { + return getNameOrType(); + } + + /** + * Is type one of {@code = *= /= %= += -= <<= >>= >>>= &= ^= |= **=}? + */ + public boolean isAssignment() { + switch (this) { + case ASSIGN: + case ASSIGN_ADD: + case ASSIGN_BIT_AND: + case ASSIGN_BIT_OR: + case ASSIGN_BIT_XOR: + case ASSIGN_DIV: + case ASSIGN_MOD: + case ASSIGN_EXP: + case ASSIGN_MUL: + case ASSIGN_SAR: + case ASSIGN_SHL: + case ASSIGN_SHR: + case ASSIGN_SUB: + case ASSIGN_LOG_AND: + case ASSIGN_LOG_OR: + case ASSIGN_NULLISH: + return true; + default: + return false; + } + } + + public boolean isSupported(int targetEcmascriptEdition) { + return ecmascriptEdition <= targetEcmascriptEdition; + } + + static { + // Avoid cloning of enumeration. + values = TokenType.values(); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/AccessNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/AccessNode.java new file mode 100644 index 000000000000..c046495238c0 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/AccessNode.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor; + +// @formatter:off +/** + * IR representation of a property access (period operator.) + */ +public final class AccessNode extends BaseNode { + /** Property name. */ + private final String property; + private final boolean optional; + + /** + * Constructor + * + * @param token token + * @param finish finish + * @param base base node + * @param property property + * @param optional + */ + public AccessNode(final long token, final int finish, final Expression base, final String property, final boolean optional) { + super(token, finish, base, false, false); + this.property = property; + this.optional = optional; + } + + private AccessNode(final AccessNode accessNode, final Expression base, final String property, final boolean isFunction, final boolean isSuper, boolean optional) { + super(accessNode, base, isFunction, isSuper); + this.property = property; + this.optional = optional; + } + + /** + * Assist in IR navigation. + * @param visitor IR navigating visitor. + */ + @Override + public Node accept(final NodeVisitor visitor) { + if (visitor.enterAccessNode(this)) { + return visitor.leaveAccessNode( + setBase((Expression)base.accept(visitor))); + } + return this; + } + + @Override + public R accept(TranslatorNodeVisitor visitor) { + return visitor.enterAccessNode(this); + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + final boolean needsParen = tokenType().needsParens(getBase().tokenType(), true); + + if (needsParen) { + sb.append('('); + } + + base.toString(sb, printType); + + if (needsParen) { + sb.append(')'); + } + + if (optional) { + sb.append("?."); + } else { + sb.append('.'); + } + + sb.append(property); + } + + /** + * Get the property name + * + * @return the property name + */ + public String getProperty() { + return property; + } + + public boolean isOptional() { + return optional; + } + + private AccessNode setBase(final Expression base) { + if (this.base == base) { + return this; + } + return new AccessNode(this, base, property, isFunction(), isSuper(), optional); + } + + @Override + public AccessNode setIsFunction() { + if (isFunction()) { + return this; + } + return new AccessNode(this, base, property, true, isSuper(), optional); + } + + @Override + public AccessNode setIsSuper() { + if (isSuper()) { + return this; + } + return new AccessNode(this, base, property, isFunction(), true, optional); + } + + public AccessNode setOptional() { + if (optional) { + return this; + } + return new AccessNode(this, base, property, isFunction(), isSuper(), true); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Assignment.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Assignment.java new file mode 100644 index 000000000000..9fd6a0f448a1 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Assignment.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +/** + * Can a node be an assignment under certain circumstances? Then it should implement this interface. + * + * @param the destination type + */ +public interface Assignment { + + /** + * Get assignment destination. + * + * @return the assignment destination node + */ + D getAssignmentDest(); + + /** + * Get the assignment source. + * + * @return the assignment source node + */ + Expression getAssignmentSource(); +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BaseNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BaseNode.java new file mode 100644 index 000000000000..c4e57de9ecdf --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BaseNode.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import com.oracle.js.parser.TokenType; + +// @formatter:off +/** + * IR base for accessing/indexing nodes. + * + * @see AccessNode + * @see IndexNode + */ +public abstract class BaseNode extends Expression implements FunctionCall { + + /** Base Node. */ + protected final Expression base; + + private final boolean isFunction; + + /** Super property access. */ + private final boolean isSuper; + + /** + * Constructor + * + * @param token token + * @param finish finish + * @param base base node + * @param isFunction is this a function + * @param isSuper is this a super property access + */ + public BaseNode(final long token, final int finish, final Expression base, final boolean isFunction, final boolean isSuper) { + super(token, base.getStart(), finish); + this.base = base; + this.isFunction = isFunction; + this.isSuper = isSuper; + } + + /** + * Copy constructor for immutable nodes + * @param baseNode node to inherit from + * @param base base + * @param isFunction is this a function + * @param isSuper is this a super property access + */ + protected BaseNode(final BaseNode baseNode, final Expression base, final boolean isFunction, final boolean isSuper) { + super(baseNode); + this.base = base; + this.isFunction = isFunction; + this.isSuper = isSuper; + } + + /** + * Get the base node for this access + * @return the base node + */ + public Expression getBase() { + return base; + } + + @Override + public boolean isFunction() { + return isFunction; + } + + /** + * @return {@code true} if a SuperProperty access. + */ + public boolean isSuper() { + return isSuper; + } + + /** + * Return true if this node represents an index operation normally represented as {@link IndexNode}. + * @return true if an index access. + */ + public boolean isIndex() { + return isTokenType(TokenType.LBRACKET); + } + + /** + * Mark this node as being the callee operand of a {@link CallNode}. + * @return a base node identical to this one in all aspects except with its function flag set. + */ + public abstract BaseNode setIsFunction(); + + /** + * Mark this node as being a SuperProperty access. + */ + public abstract BaseNode setIsSuper(); +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BinaryNode.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BinaryNode.java new file mode 100644 index 000000000000..90d2470ca9e5 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/BinaryNode.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import com.oracle.js.parser.TokenType; +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor; + +// @formatter:off +/** + * BinaryNode nodes represent two operand operations. + */ +public final class BinaryNode extends Expression implements Assignment { + /** Left hand side argument. */ + private final Expression lhs; + + /** Right hand side argument. */ + private final Expression rhs; + + /** + * Constructor + * + * @param token token + * @param lhs left hand side + * @param rhs right hand side + */ + public BinaryNode(final long token, final Expression lhs, final Expression rhs) { + super(token, Math.min(lhs.getStart(), rhs.getStart()), Math.max(rhs.getFinish(), lhs.getFinish())); + assert !(isTokenType(TokenType.AND) || isTokenType(TokenType.OR)) || lhs instanceof JoinPredecessorExpression; + this.lhs = lhs; + this.rhs = rhs; + } + + private BinaryNode(final BinaryNode binaryNode, final Expression lhs, final Expression rhs) { + super(binaryNode); + this.lhs = lhs; + this.rhs = rhs; + } + + /** + * Returns true if the node is a comparison operation (either equality, inequality, or relational). + * @return true if the node is a comparison operation. + */ + public boolean isComparison() { + switch (tokenType()) { + case EQ: + case EQ_STRICT: + case NE: + case NE_STRICT: + case LE: + case LT: + case GE: + case GT: + return true; + default: + return false; + } + } + + /** + * Returns true if the node is a relational operation (less than (or equals), greater than (or equals)). + * @return true if the node is a relational operation. + */ + public boolean isRelational() { + switch (tokenType()) { + case LT: + case GT: + case LE: + case GE: + return true; + default: + return false; + } + } + + /** + * Returns true if the node is a logical operation. + * @return true if the node is a logical operation. + */ + public boolean isLogical() { + return isLogical(tokenType()); + } + + /** + * Returns true if the token type represents a logical operation. + * @param tokenType the token type + * @return true if the token type represents a logical operation. + */ + public static boolean isLogical(final TokenType tokenType) { + switch (tokenType) { + case AND: + case OR: + return true; + default: + return false; + } + } + + /** + * Check if this node is an assignment + * + * @return true if this node assigns a value + */ + @Override + public boolean isAssignment() { + return tokenType().isAssignment(); + } + + @Override + public boolean isSelfModifying() { + return isAssignment() && !isTokenType(TokenType.ASSIGN); + } + + @Override + public Expression getAssignmentDest() { + return isAssignment() ? lhs() : null; + } + + @Override + public Expression getAssignmentSource() { + return rhs(); + } + + /** + * Assist in IR navigation. + * @param visitor IR navigating visitor. + */ + @Override + public Node accept(final NodeVisitor visitor) { + if (visitor.enterBinaryNode(this)) { + return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor))); + } + + return this; + } + + @Override + public R accept(TranslatorNodeVisitor visitor) { + return visitor.enterBinaryNode(this); + } + + @Override + public boolean isAlwaysFalse() { + switch (tokenType()) { + case COMMALEFT: + return lhs.isAlwaysFalse(); + case COMMARIGHT: + return rhs.isAlwaysFalse(); + default: + return false; + } + } + + @Override + public boolean isAlwaysTrue() { + switch (tokenType()) { + case COMMALEFT: + return lhs.isAlwaysTrue(); + case COMMARIGHT: + return rhs.isAlwaysTrue(); + default: + return false; + } + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + final TokenType tokenType = tokenType(); + + final boolean lhsParen = tokenType.needsParens(lhs().tokenType(), true); + final boolean rhsParen = tokenType.needsParens(rhs().tokenType(), false); + + if (lhsParen) { + sb.append('('); + } + + lhs().toString(sb, printType); + + if (lhsParen) { + sb.append(')'); + } + + sb.append(' '); + + switch (tokenType) { + case COMMALEFT: + sb.append(",<"); + break; + case COMMARIGHT: + sb.append(",>"); + break; + case INCPREFIX: + case DECPREFIX: + sb.append("++"); + break; + default: + sb.append(tokenType.getName()); + break; + } + + sb.append(' '); + + if (rhsParen) { + sb.append('('); + } + rhs().toString(sb, printType); + if (rhsParen) { + sb.append(')'); + } + } + + /** + * Get the left hand side expression for this node + * @return the left hand side expression + */ + public Expression lhs() { + return lhs; + } + + /** + * Get the right hand side expression for this node + * @return the left hand side expression + */ + public Expression rhs() { + return rhs; + } + + /** + * Set the left hand side expression for this node + * @param lhs new left hand side expression + * @return a node equivalent to this one except for the requested change. + */ + public BinaryNode setLHS(final Expression lhs) { + if (this.lhs == lhs) { + return this; + } + return new BinaryNode(this, lhs, rhs); + } + + /** + * Set the right hand side expression for this node + * @param rhs new left hand side expression + * @return a node equivalent to this one except for the requested change. + */ + public BinaryNode setRHS(final Expression rhs) { + if (this.rhs == rhs) { + return this; + } + return new BinaryNode(this, lhs, rhs); + } +} diff --git a/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Block.java b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Block.java new file mode 100644 index 000000000000..0abb39755486 --- /dev/null +++ b/webcommon/libs.nashorn/src/com/oracle/js/parser/ir/Block.java @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.js.parser.ir; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.oracle.js.parser.ir.visitor.NodeVisitor; +import com.oracle.js.parser.ir.visitor.TranslatorNodeVisitor; + +// @formatter:off +/** + * IR representation for a list of statements. + */ +public class Block extends Node implements BreakableNode, Terminal, Flags { + /** List of statements */ + protected final List statements; + + /** Symbol table - keys must be returned in the order they were put in. */ + protected final Map symbols; + + private int blockScopedOrRedeclaredSymbols; + private int declaredNames; + + /** Entry label. */ + private final Label entryLabel; + + /** Break label. */ + private final Label breakLabel; + + /** Does the block/function need a new scope? Is this synthetic? */ + protected final int flags; + + /** Flag indicating that this block needs scope */ + public static final int NEEDS_SCOPE = 1 << 0; + + /** + * Is this block tagged as terminal based on its contents + * (usually the last statement) + */ + public static final int IS_TERMINAL = 1 << 2; + + /** + * Is this block the eager global scope - i.e. the original program. This isn't true for the + * outermost level of recompiles + */ + public static final int IS_GLOBAL_SCOPE = 1 << 3; + + /** + * Is this block a synthetic one introduced by Parser? + */ + public static final int IS_SYNTHETIC = 1 << 4; + + /** + * Is this the function body block? May not be the first, if parameter list contains expressions. + */ + public static final int IS_BODY = 1 << 5; + + /** + * Is this the parameter initialization block? If present, must be the first block, immediately wrapping the function body block. + */ + public static final int IS_PARAMETER_BLOCK = 1 << 6; + + /** + * Marks the variable declaration block for case clauses of a switch statement. + */ + public static final int IS_SWITCH_BLOCK = 1 << 7; + + /** + * Constructor + * + * @param token The first token of the block + * @param finish The index of the last character + * @param flags The flags of the block + * @param statements All statements in the block + */ + public Block(final long token, final int finish, final int flags, final Statement... statements) { + super(token, finish); + assert start <= finish; + + this.statements = Arrays.asList(statements); + this.symbols = new LinkedHashMap<>(); + this.entryLabel = new Label("block_entry"); + this.breakLabel = new Label("block_break"); + final int len = statements.length; + final int terminalFlags = len > 0 && statements[len - 1].hasTerminalFlags() ? IS_TERMINAL : 0; + this.flags = terminalFlags | flags; + } + + /** + * Constructs a new block + * + * @param token The first token of the block + * @param finish The index of the last character + * @param statements All statements in the block + */ + public Block(final long token, final int finish, final List statements) { + this(token, finish, IS_SYNTHETIC, statements); + } + + /** + * Constructor + * + * @param token The first token of the block + * @param finish The index of the last character + * @param flags The flags of the block + * @param statements All statements in the block + */ + public Block(final long token, final int finish, final int flags, final List statements) { + this(token, finish, flags, statements.toArray(new Statement[statements.size()])); + } + + private Block(final Block block, final int finish, final List statements, final int flags, final Map symbols) { + super(block, finish); + this.statements = statements; + this.flags = flags; + this.symbols = new LinkedHashMap<>(symbols); + this.entryLabel = new Label(block.entryLabel); + this.breakLabel = new Label(block.breakLabel); + + this.declaredNames = block.declaredNames; + this.blockScopedOrRedeclaredSymbols = block.blockScopedOrRedeclaredSymbols; + } + + /** + * Is this block the outermost eager global scope - i.e. the primordial program? + * Used for global anchor point for scope depth computation for recompilation code + * @return true if outermost eager global scope + */ + public boolean isGlobalScope() { + return getFlag(IS_GLOBAL_SCOPE); + } + + /** + * Assist in IR navigation. + * + * @param visitor IR navigating visitor. + * @return new or same node + */ + @Override + public Node accept(final LexicalContext lc, final NodeVisitor visitor) { + if (visitor.enterBlock(this)) { + return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, statements))); + } + + return this; + } + + @Override + public R accept(LexicalContext lc, TranslatorNodeVisitor visitor) { + return visitor.enterBlock(this); + } + + /** + * Get a copy of the list for all the symbols defined in this block + * @return symbol iterator + */ + public List getSymbols() { + return Collections.unmodifiableList(new ArrayList<>(symbols.values())); + } + + /** + * Retrieves an existing symbol defined in the current block. + * @param name the name of the symbol + * @return an existing symbol with the specified name defined in the current block, or null if this block doesn't + * define a symbol with this name.T + */ + public Symbol getExistingSymbol(final String name) { + return symbols.get(name); + } + + /** + * Test if this block represents a {@code true} block in a {@code try} statement. + * This is used by the Splitter as catch blocks are not be subject to splitting. + * + * @return true if this block represents a catch block in a try statement. + */ + public boolean isCatchBlock() { + return statements.size() == 1 && statements.get(0) instanceof CatchNode; + } + + @Override + public void toString(final StringBuilder sb, final boolean printType) { + for (final Node statement : statements) { + statement.toString(sb, printType); + sb.append(';'); + } + } + + @Override + public int getFlags() { + return flags; + } + + /** + * Is this a terminal block, i.e. does it end control flow like ending with a throw or return? + * + * @return true if this node statement is terminal + */ + @Override + public boolean isTerminal() { + return getFlag(IS_TERMINAL); + } + + /** + * Get the entry label for this block + * @return the entry label + */ + public Label getEntryLabel() { + return entryLabel; + } + + @Override + public Label getBreakLabel() { + return breakLabel; + } + + /** + * Get the list of statements in this block + * + * @return a list of statements + */ + public List getStatements() { + return Collections.unmodifiableList(statements); + } + + /** + * Returns the number of statements in the block. + * @return the number of statements in the block. + */ + public int getStatementCount() { + return statements.size(); + } + + /** + * Returns the line number of the first statement in the block. + * @return the line number of the first statement in the block, or -1 if the block has no statements. + */ + public int getFirstStatementLineNumber() { + if (statements == null || statements.isEmpty()) { + return -1; + } + return statements.get(0).getLineNumber(); + } + + + /** + * Returns the first statement in the block. + * @return the first statement in the block, or null if the block has no statements. + */ + public Statement getFirstStatement() { + return statements.isEmpty() ? null : statements.get(0); + } + + /** + * Returns the last statement in the block. + * @return the last statement in the block, or null if the block has no statements. + */ + public Statement getLastStatement() { + return statements.isEmpty() ? null : statements.get(statements.size() - 1); + } + + /** + * Reset the statement list for this block + * + * @param lc lexical context + * @param statements new statement list + * @return new block if statements changed, identity of statements == block.statements + */ + public Block setStatements(final LexicalContext lc, final List statements) { + if (this.statements == statements) { + return this; + } + int lastFinish = 0; + if (!statements.isEmpty()) { + lastFinish = statements.get(statements.size() - 1).getFinish(); + } + return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols)); + } + + /** + * Add or overwrite an existing symbol in the block + * + * @param lc get lexical context + * @param symbol symbol + */ + public void putSymbol(final LexicalContext lc, final Symbol symbol) { + symbols.put(symbol.getName(), symbol); + if (symbol.isBlockScoped() || symbol.isVarRedeclaredHere()) { + blockScopedOrRedeclaredSymbols++; + } + if (symbol.isBlockScoped() || symbol.isVarDeclaredHere()) { + declaredNames++; + } + } + + /** + * Check whether scope is necessary for this Block + * + * @return true if this function needs a scope + */ + public boolean needsScope() { + return (flags & NEEDS_SCOPE) == NEEDS_SCOPE; + } + + /** + * Check whether this block is synthetic or not. + * + * @return true if this is a synthetic block + */ + public boolean isSynthetic() { + return (flags & IS_SYNTHETIC) == IS_SYNTHETIC; + } + + @Override + public Block setFlags(final LexicalContext lc, final int flags) { + if (this.flags == flags) { + return this; + } + return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols)); + } + + @Override + public Block setFlag(final LexicalContext lc, final int flag) { + return setFlags(lc, flags | flag); + } + + @Override + public boolean getFlag(final int flag) { + return (flags & flag) == flag; + } + + @Override + public boolean isBreakableWithoutLabel() { + return false; + } + + @Override + public List