From 7ec0917e9545cd1e27aee4438145a0e3799b419f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:29:57 +0300 Subject: [PATCH] fix: revert to old master (i forgot to do the new ES6 stuff in a new branch) --- .../common/{parsing => }/Filename.java | 2 +- .../jscript/common/FunctionBody.java | 6 +- .../jscript/common/Instruction.java | 352 ++- .../topchetoeu/jscript/common/Location.java | 101 + .../topchetoeu/jscript/common/RefTracker.java | 23 + .../jscript/common/ResultRunnable.java | 5 + .../common/environment/Environment.java | 199 -- .../jscript/common/environment/Key.java | 7 - .../jscript/common/environment/MultiKey.java | 7 - .../jscript/common/events/DataNotifier.java | 31 + .../jscript/common/events/Notifier.java | 19 + .../topchetoeu/jscript/common/json/JSON.java | 201 +- .../jscript/common/json/JSONElement.java | 3 +- .../jscript/common/json/JSONMap.java | 36 +- .../jscript/common/mapping/FunctionMap.java | 45 +- .../jscript/common/parsing/Location.java | 108 - .../jscript/common/parsing/ParseRes.java | 75 - .../jscript/common/parsing/Parser.java | 5 - .../jscript/common/parsing/Parsing.java | 420 ---- .../jscript/common/parsing/Source.java | 75 - .../common/parsing/SourceLocation.java | 66 - .../jscript/compilation/AssignableNode.java | 7 - .../compilation/AssignableStatement.java | 12 + .../jscript/compilation/CompileResult.java | 77 +- .../jscript/compilation/CompoundNode.java | 118 - .../compilation/CompoundStatement.java | 64 + .../compilation/DeferredIntSupplier.java | 19 - .../jscript/compilation/FunctionNode.java | 145 -- .../compilation/FunctionStatementNode.java | 25 - .../compilation/FunctionValueNode.java | 19 - .../jscript/compilation/JavaScript.java | 328 --- .../jscript/compilation/LabelContext.java | 85 - .../jscript/compilation/NodeChildren.java | 93 - .../jscript/compilation/Parameter.java | 15 - .../jscript/compilation/Parameters.java | 28 - .../compilation/{Node.java => Statement.java} | 17 +- .../compilation/ThrowSyntaxStatement.java | 18 + .../compilation/VariableDeclareNode.java | 115 - .../compilation/VariableDeclareStatement.java | 52 + .../compilation/control/BreakNode.java | 58 - .../compilation/control/BreakStatement.java | 21 + .../compilation/control/ContinueNode.java | 58 - .../control/ContinueStatement.java | 21 + .../compilation/control/DebugNode.java | 37 - .../compilation/control/DebugStatement.java | 18 + .../compilation/control/DeleteNode.java | 53 - .../compilation/control/DeleteStatement.java | 26 + .../compilation/control/DoWhileNode.java | 85 - .../compilation/control/DoWhileStatement.java | 36 + .../compilation/control/ForInNode.java | 116 - .../compilation/control/ForInStatement.java | 69 + .../jscript/compilation/control/ForNode.java | 124 -- .../compilation/control/ForOfNode.java | 125 -- .../compilation/control/ForOfStatement.java | 73 + .../compilation/control/ForStatement.java | 45 + .../jscript/compilation/control/IfNode.java | 131 -- .../compilation/control/IfStatement.java | 48 + .../compilation/control/ReturnNode.java | 50 - .../compilation/control/ReturnStatement.java | 22 + .../compilation/control/SwitchNode.java | 203 -- .../compilation/control/SwitchStatement.java | 83 + .../compilation/control/ThrowNode.java | 49 - .../compilation/control/ThrowStatement.java | 21 + .../jscript/compilation/control/TryNode.java | 134 -- .../compilation/control/TryStatement.java | 58 + .../compilation/control/WhileNode.java | 92 - .../compilation/control/WhileStatement.java | 52 + .../jscript/compilation/parsing/Operator.java | 113 + .../jscript/compilation/parsing/ParseRes.java | 100 + .../jscript/compilation/parsing/Parsing.java | 1933 +++++++++++++++++ .../jscript/compilation/parsing/RawToken.java | 15 + .../jscript/compilation/parsing/TestRes.java | 45 + .../jscript/compilation/parsing/Token.java | 58 + .../compilation/parsing/TokenType.java | 9 + .../compilation/scope/FunctionScope.java | 107 - .../compilation/scope/GlobalScope.java | 33 - .../jscript/compilation/scope/LocalScope.java | 66 - .../compilation/scope/LocalScopeRecord.java | 77 + .../jscript/compilation/scope/Scope.java | 73 - .../compilation/scope/ScopeRecord.java | 7 + .../compilation/scope/VariableDescriptor.java | 19 - .../compilation/scope/VariableList.java | 241 -- .../compilation/values/ArgumentsNode.java | 17 - .../jscript/compilation/values/ArrayNode.java | 82 - .../compilation/values/ArrayStatement.java | 40 + .../compilation/values/CallStatement.java | 43 + .../compilation/values/ChangeStatement.java | 31 + .../compilation/values/ConstantStatement.java | 47 + .../compilation/values/DiscardStatement.java | 23 + .../compilation/values/FunctionStatement.java | 127 ++ .../compilation/values/GlobalThisNode.java | 17 - .../values/GlobalThisStatement.java | 19 + ...ignNode.java => IndexAssignStatement.java} | 19 +- .../compilation/values/IndexStatement.java | 37 + .../compilation/values/LazyAndStatement.java | 28 + .../compilation/values/LazyOrStatement.java | 28 + .../compilation/values/ObjectNode.java | 180 -- .../compilation/values/ObjectStatement.java | 61 + .../values/OperationStatement.java | 36 + .../jscript/compilation/values/RegexNode.java | 76 - .../compilation/values/RegexStatement.java | 25 + .../jscript/compilation/values/ThisNode.java | 17 - .../compilation/values/TypeofStatement.java | 32 + .../values/VariableAssignStatement.java | 37 + .../values/VariableIndexStatement.java | 22 + .../compilation/values/VariableNode.java | 96 - .../compilation/values/VariableStatement.java | 31 + .../values/constants/BoolNode.java | 19 - .../values/constants/NullNode.java | 14 - .../values/constants/NumberNode.java | 40 - .../values/constants/StringNode.java | 31 - .../values/operations/CallNode.java | 188 -- .../values/operations/ChangeNode.java | 87 - .../values/operations/DiscardNode.java | 48 - .../values/operations/IndexNode.java | 75 - .../values/operations/LazyAndNode.java | 53 - .../values/operations/LazyOrNode.java | 54 - .../values/operations/OperationNode.java | 226 -- .../values/operations/TypeofNode.java | 57 - .../values/operations/VariableAssignNode.java | 35 - .../me/topchetoeu/jscript/lib/ArrayLib.java | 456 ++++ .../jscript/lib/AsyncFunctionLib.java | 87 + .../lib/AsyncGeneratorFunctionLib.java | 34 + .../jscript/lib/AsyncGeneratorLib.java | 107 + .../me/topchetoeu/jscript/lib/BooleanLib.java | 37 + .../me/topchetoeu/jscript/lib/ConsoleLib.java | 38 + .../me/topchetoeu/jscript/lib/DateLib.java | 266 +++ .../topchetoeu/jscript/lib/EncodingLib.java | 89 + .../me/topchetoeu/jscript/lib/ErrorLib.java | 54 + .../me/topchetoeu/jscript/lib/FileLib.java | 84 + .../topchetoeu/jscript/lib/FilesystemLib.java | 193 ++ .../topchetoeu/jscript/lib/FunctionLib.java | 83 + .../jscript/lib/GeneratorFunctionLib.java | 32 + .../topchetoeu/jscript/lib/GeneratorLib.java | 78 + .../me/topchetoeu/jscript/lib/Internals.java | 222 ++ .../me/topchetoeu/jscript/lib/JSONLib.java | 24 + .../me/topchetoeu/jscript/lib/MapLib.java | 87 + .../me/topchetoeu/jscript/lib/MathLib.java | 211 ++ .../me/topchetoeu/jscript/lib/NumberLib.java | 103 + .../me/topchetoeu/jscript/lib/ObjectLib.java | 273 +++ .../me/topchetoeu/jscript/lib/PromiseLib.java | 404 ++++ .../topchetoeu/jscript/lib/RangeErrorLib.java | 19 + .../me/topchetoeu/jscript/lib/RegExpLib.java | 354 +++ .../me/topchetoeu/jscript/lib/SetLib.java | 69 + .../me/topchetoeu/jscript/lib/StringLib.java | 291 +++ .../me/topchetoeu/jscript/lib/SymbolLib.java | 81 + .../jscript/lib/SyntaxErrorLib.java | 19 + .../topchetoeu/jscript/lib/ThrowableLib.java | 18 + .../topchetoeu/jscript/lib/TypeErrorLib.java | 19 + .../jscript/runtime/ArgumentsValue.java | 13 - .../topchetoeu/jscript/runtime/Childable.java | 5 + .../topchetoeu/jscript/runtime/Compiler.java | 42 +- .../topchetoeu/jscript/runtime/Context.java | 98 + .../topchetoeu/jscript/runtime/Copyable.java | 5 + .../me/topchetoeu/jscript/runtime/Engine.java | 28 +- .../jscript/runtime/Environment.java | 61 + .../topchetoeu/jscript/runtime/EventLoop.java | 35 +- .../jscript/runtime/Extensions.java | 77 + .../me/topchetoeu/jscript/runtime/Frame.java | 306 +-- .../jscript/runtime/InstructionRunner.java | 485 ++--- .../jscript/runtime/JSONConverter.java | 86 - .../me/topchetoeu/jscript/runtime/Key.java | 5 + .../jscript/runtime/SimpleRepl.java | 377 ---- .../jscript/runtime/WrapperProvider.java | 12 + .../jscript/runtime/debug/DebugContext.java | 54 +- .../jscript/runtime/debug/DebugHandler.java | 27 +- .../runtime/exceptions/ConvertException.java | 11 + .../runtime/exceptions/EngineException.java | 83 +- .../runtime/exceptions/SyntaxException.java | 4 +- .../jscript/runtime/scope/GlobalScope.java | 81 + .../jscript/runtime/scope/LocalScope.java | 29 + .../jscript/runtime/scope/ValueVariable.java | 28 + .../jscript/runtime/scope/Variable.java | 9 + .../jscript/runtime/values/ArrayValue.java | 227 ++ .../jscript/runtime/values/CodeFunction.java | 50 + .../jscript/runtime/values/ConvertHint.java | 6 + .../jscript/runtime/values/FunctionValue.java | 69 + .../jscript/runtime/values/KeyCache.java | 59 - .../jscript/runtime/values/Member.java | 130 -- .../runtime/values/NativeFunction.java | 27 + .../jscript/runtime/values/NativeWrapper.java | 81 + .../jscript/runtime/values/ObjectValue.java | 354 +++ .../jscript/runtime/values/ScopeValue.java | 54 + .../jscript/runtime/values/Symbol.java | 28 + .../jscript/runtime/values/Value.java | 690 ------ .../jscript/runtime/values/Values.java | 761 +++++++ .../runtime/values/functions/Arguments.java | 40 - .../values/functions/CodeFunction.java | 38 - .../values/functions/FunctionValue.java | 94 - .../values/functions/NativeFunction.java | 25 - .../runtime/values/objects/ArrayValue.java | 230 -- .../runtime/values/objects/ObjectValue.java | 128 -- .../runtime/values/objects/ScopeValue.java | 34 - .../runtime/values/primitives/BoolValue.java | 37 - .../values/primitives/NumberValue.java | 54 - .../values/primitives/PrimitiveValue.java | 22 - .../values/primitives/StringValue.java | 43 - .../values/primitives/SymbolValue.java | 49 - .../runtime/values/primitives/VoidValue.java | 43 - .../topchetoeu/jscript/utils/JSCompiler.java | 36 + .../topchetoeu/jscript/utils/JScriptRepl.java | 152 ++ .../jscript/utils/debug/DebugServer.java | 251 +++ .../jscript/utils/debug/Debugger.java | 37 + .../jscript/utils/debug/DebuggerProvider.java | 5 + .../jscript/utils/debug/HttpRequest.java | 102 + .../jscript/utils/debug/SimpleDebugger.java | 1091 ++++++++++ .../jscript/utils/debug/V8Error.java | 19 + .../jscript/utils/debug/V8Event.java | 22 + .../jscript/utils/debug/V8Message.java | 50 + .../jscript/utils/debug/V8Result.java | 22 + .../jscript/utils/debug/WebSocket.java | 195 ++ .../jscript/utils/debug/WebSocketMessage.java | 29 + .../jscript/utils/filesystem/ActionType.java | 28 + .../jscript/utils/filesystem/BaseFile.java | 59 + .../jscript/utils/filesystem/EntryType.java | 13 + .../jscript/utils/filesystem/ErrorReason.java | 23 + .../jscript/utils/filesystem/File.java | 169 ++ .../jscript/utils/filesystem/FileStat.java | 14 + .../jscript/utils/filesystem/Filesystem.java | 18 + .../utils/filesystem/FilesystemException.java | 86 + .../utils/filesystem/HandleManager.java | 32 + .../jscript/utils/filesystem/LineReader.java | 16 + .../jscript/utils/filesystem/LineWriter.java | 7 + .../jscript/utils/filesystem/MemoryFile.java | 36 + .../utils/filesystem/MemoryFilesystem.java | 100 + .../jscript/utils/filesystem/Mode.java | 41 + .../jscript/utils/filesystem/Paths.java | 52 + .../utils/filesystem/PhysicalFile.java | 35 + .../utils/filesystem/PhysicalFilesystem.java | 92 + .../utils/filesystem/RootFilesystem.java | 99 + .../utils/filesystem/STDFilesystem.java | 52 + .../jscript/utils/interop/Arguments.java | 139 ++ .../jscript/utils/interop/Expose.java | 14 + .../utils/interop/ExposeConstructor.java | 10 + .../jscript/utils/interop/ExposeField.java | 13 + .../jscript/utils/interop/ExposeTarget.java | 28 + .../jscript/utils/interop/ExposeType.java | 7 + .../utils/interop/NativeWrapperProvider.java | 456 ++++ .../jscript/utils/interop/OnWrapperInit.java | 12 + .../jscript/utils/interop/WrapperName.java | 12 + .../jscript/utils/mapping/SourceMap.java | 109 + .../topchetoeu/jscript/utils/mapping/VLQ.java | 95 + .../jscript/utils/modules/Module.java | 20 + .../jscript/utils/modules/ModuleRepo.java | 47 + .../jscript/utils/modules/RootModuleRepo.java | 30 + .../jscript/utils/modules/SourceModule.java | 23 + .../jscript/utils/permissions/Matcher.java | 69 + .../jscript/utils/permissions/Permission.java | 19 + .../permissions/PermissionPredicate.java | 44 + .../utils/permissions/PermissionsManager.java | 59 + .../permissions/PermissionsProvider.java | 29 + src/main/resources/lib/index.js | 321 --- 252 files changed, 14801 insertions(+), 8829 deletions(-) rename src/main/java/me/topchetoeu/jscript/common/{parsing => }/Filename.java (97%) create mode 100644 src/main/java/me/topchetoeu/jscript/common/Location.java create mode 100644 src/main/java/me/topchetoeu/jscript/common/RefTracker.java create mode 100644 src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/environment/Environment.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/environment/Key.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java create mode 100644 src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java create mode 100644 src/main/java/me/topchetoeu/jscript/common/events/Notifier.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/Location.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/Source.java delete mode 100644 src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/Parameter.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/Parameters.java rename src/main/java/me/topchetoeu/jscript/compilation/{Node.java => Statement.java} (55%) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/VariableDescriptor.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java rename src/main/java/me/topchetoeu/jscript/compilation/values/{operations/IndexAssignNode.java => IndexAssignStatement.java} (68%) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/DateLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/FileLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/Internals.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/JSONLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/MapLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/MathLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/NumberLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/SetLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/StringLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java create mode 100644 src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Childable.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Context.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Copyable.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Environment.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Extensions.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/Key.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/Member.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/Value.java create mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/Values.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java delete mode 100644 src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/modules/Module.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java create mode 100644 src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java delete mode 100644 src/main/resources/lib/index.js diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java b/src/main/java/me/topchetoeu/jscript/common/Filename.java similarity index 97% rename from src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java rename to src/main/java/me/topchetoeu/jscript/common/Filename.java index 8e57751..7ced6cb 100644 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Filename.java +++ b/src/main/java/me/topchetoeu/jscript/common/Filename.java @@ -1,4 +1,4 @@ -package me.topchetoeu.jscript.common.parsing; +package me.topchetoeu.jscript.common; import java.io.File; import java.nio.file.Path; diff --git a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java index 3b91614..d0328a0 100644 --- a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java +++ b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java @@ -3,14 +3,12 @@ package me.topchetoeu.jscript.common; public class FunctionBody { public final FunctionBody[] children; public final Instruction[] instructions; - public final int localsN, capturesN, argsN, length; + public final int localsN, argsN; - public FunctionBody(int localsN, int capturesN, int length, int argsN, Instruction[] instructions, FunctionBody[] children) { + public FunctionBody(int localsN, int argsN, Instruction[] instructions, FunctionBody[] children) { this.children = children; - this.length = length; this.argsN = argsN; this.localsN = localsN; - this.capturesN = capturesN; this.instructions = instructions; } } diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index 2e7f5b2..9460c39 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -1,62 +1,56 @@ package me.topchetoeu.jscript.common; -// import java.io.DataInputStream; -// import java.io.DataOutputStream; -// import java.io.IOException; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.HashMap; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Instruction { public static enum Type { - NOP(0x00), - RETURN(0x01), - THROW(0x02), - THROW_SYNTAX(0x03), - DELETE(0x04), - TRY_START(0x05), - TRY_END(0x06), + NOP(0), + RETURN(1), + THROW(2), + THROW_SYNTAX(3), + DELETE(4), + TRY_START(5), + TRY_END(6), - CALL(0x10), - CALL_MEMBER(0x11), - CALL_NEW(0x12), - JMP_IF(0x13), - JMP_IFN(0x14), - JMP(0x15), + CALL(7), + CALL_NEW(8), + JMP_IF(9), + JMP_IFN(10), + JMP(11), - PUSH_UNDEFINED(0x20), - PUSH_NULL(0x21), - PUSH_BOOL(0x22), - PUSH_NUMBER(0x23), - PUSH_STRING(0x24), - DUP(0x25), - DISCARD(0x26), + PUSH_UNDEFINED(12), + PUSH_NULL(13), + PUSH_BOOL(14), + PUSH_NUMBER(15), + PUSH_STRING(16), - LOAD_FUNC(0x30), - LOAD_ARR(0x31), - LOAD_OBJ(0x32), - LOAD_GLOB(0x33), - LOAD_INTRINSICS(0x34), - LOAD_REGEX(0x35), + LOAD_VAR(17), + LOAD_MEMBER(18), + LOAD_GLOB(20), - LOAD_VAR(0x40), - LOAD_MEMBER(0x41), - LOAD_ARGS(0x42), - LOAD_THIS(0x43), - STORE_VAR(0x48), - STORE_MEMBER(0x49), + LOAD_FUNC(21), + LOAD_ARR(22), + LOAD_OBJ(23), + STORE_SELF_FUNC(24), + LOAD_REGEX(25), - DEF_PROP(0x50), - KEYS(0x51), - TYPEOF(0x52), - OPERATION(0x53), + DUP(26), - GLOB_GET(0x60), - GLOB_SET(0x61), - GLOB_DEF(0x62), + STORE_VAR(27), + STORE_MEMBER(28), + DISCARD(29), - STACK_ALLOC(0x70), - STACK_FREE(0x71); + MAKE_VAR(30), + DEF_PROP(31), + KEYS(32), + + TYPEOF(33), + OPERATION(34); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -116,128 +110,117 @@ public class Instruction { return params[i].equals(arg); } - // public void write(DataOutputStream writer) throws IOException { - // var rawType = type.numeric; + public void write(DataOutputStream writer) throws IOException { + var rawType = type.numeric; - // switch (type) { - // case KEYS: - // case PUSH_BOOL: - // case STORE_MEMBER: - // case GLOB_SET: - // rawType |= (boolean)get(0) ? 128 : 0; break; - // case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; - // default: - // } + switch (type) { + case KEYS: + case PUSH_BOOL: + case STORE_MEMBER: rawType |= (boolean)get(0) ? 128 : 0; break; + case STORE_VAR: rawType |= (boolean)get(1) ? 128 : 0; break; + case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break; + default: + } - // writer.writeByte(rawType); + writer.writeByte(rawType); - // switch (type) { - // case CALL: - // case CALL_NEW: - // case CALL_MEMBER: - // writer.writeInt(get(0)); - // writer.writeUTF(get(1)); - // break; - // case DUP: writer.writeInt(get(0)); break; - // case JMP: writer.writeInt(get(0)); break; - // case JMP_IF: writer.writeInt(get(0)); break; - // case JMP_IFN: writer.writeInt(get(0)); break; - // case LOAD_ARR: writer.writeInt(get(0)); break; - // case LOAD_FUNC: { - // writer.writeInt(params.length - 1); + switch (type) { + case CALL: writer.writeInt(get(0)); break; + case CALL_NEW: writer.writeInt(get(0)); break; + case DUP: writer.writeInt(get(0)); break; + case JMP: writer.writeInt(get(0)); break; + case JMP_IF: writer.writeInt(get(0)); break; + case JMP_IFN: writer.writeInt(get(0)); break; + case LOAD_ARR: writer.writeInt(get(0)); break; + case LOAD_FUNC: { + writer.writeInt(params.length - 1); - // for (var i = 0; i < params.length; i++) { - // writer.writeInt(get(i + 1)); - // } + for (var i = 0; i < params.length; i++) { + writer.writeInt(get(i + 1)); + } - // writer.writeInt(get(0)); - // writer.writeUTF(get(0)); - // break; - // } - // case LOAD_REGEX: writer.writeUTF(get(0)); break; - // case LOAD_VAR: writer.writeInt(get(0)); break; - // case GLOB_DEF: writer.writeUTF(get(0)); break; - // case GLOB_GET: writer.writeUTF(get(0)); break; - // case GLOB_SET: - // writer.writeUTF(get(0)); - // break; - // case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; - // case PUSH_NUMBER: writer.writeDouble(get(0)); break; - // case PUSH_STRING: writer.writeUTF(get(0)); break; - // case STORE_VAR: writer.writeInt(get(0)); break; - // case THROW_SYNTAX: writer.writeUTF(get(0)); - // case TRY_START: - // writer.writeInt(get(0)); - // writer.writeInt(get(1)); - // writer.writeInt(get(2)); - // break; - // case TYPEOF: - // if (params.length > 0) writer.writeUTF(get(0)); - // break; - // default: - // } - // } + writer.writeInt(get(0)); + break; + } + case LOAD_REGEX: writer.writeUTF(get(0)); break; + case LOAD_VAR: writer.writeInt(get(0)); break; + case MAKE_VAR: writer.writeUTF(get(0)); break; + case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; + case PUSH_NUMBER: writer.writeDouble(get(0)); break; + case PUSH_STRING: writer.writeUTF(get(0)); break; + case STORE_SELF_FUNC: writer.writeInt(get(0)); break; + case STORE_VAR: writer.writeInt(get(0)); break; + case THROW_SYNTAX: writer.writeUTF(get(0)); + case TRY_START: + writer.writeInt(get(0)); + writer.writeInt(get(1)); + writer.writeInt(get(2)); + break; + case TYPEOF: + if (params.length > 0) writer.writeUTF(get(0)); + break; + default: + } + } private Instruction(Type type, Object ...params) { this.type = type; this.params = params; } - // public static Instruction read(DataInputStream stream) throws IOException { - // var rawType = stream.readUnsignedByte(); - // var type = Type.fromNumeric(rawType & 127); - // var flag = (rawType & 128) != 0; + public static Instruction read(DataInputStream stream) throws IOException { + var rawType = stream.readUnsignedByte(); + var type = Type.fromNumeric(rawType & 127); + var flag = (rawType & 128) != 0; - // switch (type) { - // case CALL: return call(stream.readInt(), stream.readUTF()); - // case CALL_NEW: return callNew(stream.readInt(), stream.readUTF()); - // case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF()); - // case DEF_PROP: return defProp(); - // case DELETE: return delete(); - // case DISCARD: return discard(); - // case DUP: return dup(stream.readInt()); - // case JMP: return jmp(stream.readInt()); - // case JMP_IF: return jmpIf(stream.readInt()); - // case JMP_IFN: return jmpIfNot(stream.readInt()); - // case KEYS: return keys(flag); - // case LOAD_ARR: return loadArr(stream.readInt()); - // case LOAD_FUNC: { - // var captures = new int[stream.readInt()]; + switch (type) { + case CALL: return call(stream.readInt()); + case CALL_NEW: return callNew(stream.readInt()); + case DEF_PROP: return defProp(); + case DELETE: return delete(); + case DISCARD: return discard(); + case DUP: return dup(stream.readInt()); + case JMP: return jmp(stream.readInt()); + case JMP_IF: return jmpIf(stream.readInt()); + case JMP_IFN: return jmpIfNot(stream.readInt()); + case KEYS: return keys(flag); + case LOAD_ARR: return loadArr(stream.readInt()); + case LOAD_FUNC: { + var captures = new int[stream.readInt()]; - // for (var i = 0; i < captures.length; i++) { - // captures[i] = stream.readInt(); - // } + for (var i = 0; i < captures.length; i++) { + captures[i] = stream.readInt(); + } - // return loadFunc(stream.readInt(), stream.readUTF(), captures); - // } - // case LOAD_GLOB: return loadGlob(); - // case LOAD_MEMBER: return loadMember(); - // case LOAD_OBJ: return loadObj(); - // case LOAD_REGEX: return loadRegex(stream.readUTF(), null); - // case LOAD_VAR: return loadVar(stream.readInt()); - // case GLOB_DEF: return globDef(stream.readUTF()); - // case GLOB_GET: return globGet(stream.readUTF()); - // case GLOB_SET: return globSet(stream.readUTF(), flag); - // case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); - // case PUSH_NULL: return pushNull(); - // case PUSH_UNDEFINED: return pushUndefined(); - // case PUSH_BOOL: return pushValue(flag); - // case PUSH_NUMBER: return pushValue(stream.readDouble()); - // case PUSH_STRING: return pushValue(stream.readUTF()); - // case RETURN: return ret(); - // case STORE_MEMBER: return storeMember(flag); - // case STORE_VAR: return storeVar(stream.readInt(), flag); - // case THROW: return throwInstr(); - // case THROW_SYNTAX: return throwSyntax(stream.readUTF()); - // case TRY_END: return tryEnd(); - // case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt()); - // case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); - // case NOP: - // if (flag) return null; - // else return nop(); - // default: return null; - // } - // } + return loadFunc(stream.readInt(), captures); + } + case LOAD_GLOB: return loadGlob(); + case LOAD_MEMBER: return loadMember(); + case LOAD_OBJ: return loadObj(); + case LOAD_REGEX: return loadRegex(stream.readUTF(), null); + case LOAD_VAR: return loadVar(stream.readInt()); + case MAKE_VAR: return makeVar(stream.readUTF()); + case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte())); + case PUSH_NULL: return pushNull(); + case PUSH_UNDEFINED: return pushUndefined(); + case PUSH_BOOL: return pushValue(flag); + case PUSH_NUMBER: return pushValue(stream.readDouble()); + case PUSH_STRING: return pushValue(stream.readUTF()); + case RETURN: return ret(); + case STORE_MEMBER: return storeMember(flag); + case STORE_SELF_FUNC: return storeSelfFunc(stream.readInt()); + case STORE_VAR: return storeVar(stream.readInt(), flag); + case THROW: return throwInstr(); + case THROW_SYNTAX: return throwSyntax(stream.readUTF()); + case TRY_END: return tryEnd(); + case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt()); + case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof(); + case NOP: + if (flag) return null; + else return nop(); + default: return null; + } + } public static Instruction tryStart(int catchStart, int finallyStart, int end) { return new Instruction(Type.TRY_START, catchStart, finallyStart, end); @@ -268,23 +251,11 @@ public class Instruction { return new Instruction(Type.NOP, params); } - public static Instruction call(int argn, String name) { - return new Instruction(Type.CALL, argn, name); - } public static Instruction call(int argn) { - return call(argn, ""); - } - public static Instruction callMember(int argn, String name) { - return new Instruction(Type.CALL_MEMBER, argn, name); - } - public static Instruction callMember(int argn) { - return new Instruction(Type.CALL_MEMBER, argn, ""); - } - public static Instruction callNew(int argn, String name) { - return new Instruction(Type.CALL_NEW, argn, name); + return new Instruction(Type.CALL, argn); } public static Instruction callNew(int argn) { - return new Instruction(Type.CALL_NEW, argn, ""); + return new Instruction(Type.CALL_NEW, argn); } public static Instruction jmp(int offset) { return new Instruction(Type.JMP, offset); @@ -312,32 +283,15 @@ public class Instruction { return new Instruction(Type.PUSH_STRING, val); } - public static Instruction globDef(String name) { - return new Instruction(Type.GLOB_GET, name); + public static Instruction makeVar(String name) { + return new Instruction(Type.MAKE_VAR, name); } - - public static Instruction globGet(String name) { - return new Instruction(Type.GLOB_GET, name); - } - public static Instruction globSet(String name, boolean keep, boolean define) { - return new Instruction(Type.GLOB_SET, name, keep, define); - } - - public static Instruction loadVar(int i) { + public static Instruction loadVar(Object i) { return new Instruction(Type.LOAD_VAR, i); } - public static Instruction loadThis() { - return new Instruction(Type.LOAD_THIS); - } - public static Instruction loadArgs() { - return new Instruction(Type.LOAD_ARGS); - } public static Instruction loadGlob() { return new Instruction(Type.LOAD_GLOB); } - public static Instruction loadIntrinsics(String key) { - return new Instruction(Type.LOAD_INTRINSICS, key); - } public static Instruction loadMember() { return new Instruction(Type.LOAD_MEMBER); } @@ -345,13 +299,10 @@ public class Instruction { public static Instruction loadRegex(String pattern, String flags) { return new Instruction(Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(int id, String name, int[] captures) { - if (name == null) name = ""; - - var args = new Object[2 + captures.length]; + public static Instruction loadFunc(int id, int[] captures) { + var args = new Object[1 + captures.length]; args[0] = id; - args[1] = name; - for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i]; + for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i]; return new Instruction(Type.LOAD_FUNC, args); } public static Instruction loadObj() { @@ -367,10 +318,13 @@ public class Instruction { return new Instruction(Type.DUP, count); } - public static Instruction storeVar(int i) { + public static Instruction storeSelfFunc(int i) { + return new Instruction(Type.STORE_SELF_FUNC, i); + } + public static Instruction storeVar(Object i) { return new Instruction(Type.STORE_VAR, i, false); } - public static Instruction storeVar(int i, boolean keep) { + public static Instruction storeVar(Object i, boolean keep) { return new Instruction(Type.STORE_VAR, i, keep); } public static Instruction storeMember() { @@ -402,14 +356,8 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - public static Instruction stackAlloc(int i) { - return new Instruction(Type.STACK_ALLOC, i); - } - public static Instruction stackFree(int i) { - return new Instruction(Type.STACK_FREE, i); - } - - @Override public String toString() { + @Override + public String toString() { var res = type.toString(); for (int i = 0; i < params.length; i++) { diff --git a/src/main/java/me/topchetoeu/jscript/common/Location.java b/src/main/java/me/topchetoeu/jscript/common/Location.java new file mode 100644 index 0000000..e3030b8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/Location.java @@ -0,0 +1,101 @@ +package me.topchetoeu.jscript.common; + +import java.util.ArrayList; + +public class Location implements Comparable { + public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native")); + private int line; + private int start; + private Filename filename; + + public int line() { return line; } + public int start() { return start; } + public Filename filename() { return filename; } + + @Override + public String toString() { + var res = new ArrayList(); + + if (filename != null) res.add(filename.toString()); + if (line >= 0) res.add(line + ""); + if (start >= 0) res.add(start + ""); + + return String.join(":", res); + } + + public Location add(int n, boolean clone) { + if (clone) return new Location(line, start + n, filename); + this.start += n; + return this; + } + public Location add(int n) { + return add(n, false); + } + public Location nextLine() { + line++; + start = 0; + return this; + } + public Location clone() { + return new Location(line, start, filename); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + line; + result = prime * result + start; + result = prime * result + ((filename == null) ? 0 : filename.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Location other = (Location) obj; + if (line != other.line) return false; + if (start != other.start) return false; + if (filename == null && other.filename != null) return false; + else if (!filename.equals(other.filename)) return false; + return true; + } + + @Override + public int compareTo(Location other) { + int a = filename.toString().compareTo(other.filename.toString()); + int b = Integer.compare(line, other.line); + int c = Integer.compare(start, other.start); + + if (a != 0) return a; + if (b != 0) return b; + return c; + } + + public Location(int line, int start, Filename filename) { + this.line = line; + this.start = start; + this.filename = filename; + } + + public static Location parse(String raw) { + int i0 = -1, i1 = -1; + for (var i = raw.length() - 1; i >= 0; i--) { + if (raw.charAt(i) == ':') { + if (i1 == -1) i1 = i; + else if (i0 == -1) { + i0 = i; + break; + } + } + } + + return new Location( + Integer.parseInt(raw.substring(i0 + 1, i1)), + Integer.parseInt(raw.substring(i1 + 1)), + Filename.parse(raw.substring(0, i0)) + ); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/RefTracker.java b/src/main/java/me/topchetoeu/jscript/common/RefTracker.java new file mode 100644 index 0000000..249605f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/RefTracker.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.common; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +public class RefTracker { + public static void onDestroy(Object obj, Runnable runnable) { + var queue = new ReferenceQueue<>(); + var ref = new WeakReference<>(obj, queue); + obj = null; + + var th = new Thread(() -> { + try { + queue.remove(); + ref.get(); + runnable.run(); + } + catch (InterruptedException e) { return; } + }); + th.setDaemon(true); + th.start(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java b/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java new file mode 100644 index 0000000..7bfc608 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/ResultRunnable.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.common; + +public interface ResultRunnable { + T run(); +} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java b/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java deleted file mode 100644 index 6195fa2..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java +++ /dev/null @@ -1,199 +0,0 @@ -package me.topchetoeu.jscript.common.environment; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.function.Supplier; - -public class Environment { - public final Environment parent; - private final Map, Object> map = new HashMap<>(); - private final Set> hidden = new HashSet<>(); - - private final Map, Set> multi = new HashMap<>(); - private final Map, Set> multiHidden = new HashMap<>(); - - @SuppressWarnings("unchecked") - private Set getAll(MultiKey key, boolean forceClone) { - Set parent = null, child = null; - boolean cloned = false; - - if (this.parent != null && !hidden.contains(key)) { - parent = this.parent.getAll(key, false); - if (parent.size() == 0) parent = null; - else if (multiHidden.containsKey(key)) { - parent = new HashSet<>(parent); - parent.removeAll(multiHidden.get(key)); - cloned = true; - } - } - if (multi.containsKey(key)) { - child = (Set)multi.get(key); - if (child.size() == 0) child = null; - } - - if (!forceClone) { - if (parent == null && child == null) return Set.of(); - if (parent == null && child != null) return child; - if (parent != null && child == null) return parent; - } - - if (!cloned) parent = new HashSet<>(); - parent.addAll(child); - return parent; - } - private T getMulti(MultiKey key) { - return key.of(getAll(key, false)); - } - private boolean hasMulti(MultiKey key) { - return getAll(key, false).size() > 0; - } - - @SuppressWarnings("all") - private Environment addMulti(MultiKey key, T value) { - if (!multi.containsKey(key)) { - if (hidden.contains(key)) { - multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true)); - hidden.remove(key); - } - - multi.put((MultiKey)key, new HashSet<>()); - } - - multi.get(key).add(value); - return this; - } - - @SuppressWarnings("unchecked") - public T get(Key key) { - if (key instanceof MultiKey) return getMulti((MultiKey)key); - - if (map.containsKey(key)) return (T)map.get(key); - else if (!hidden.contains(key) && parent != null) return parent.get(key); - else return null; - } - public boolean has(Key key) { - if (key instanceof MultiKey) return hasMulti((MultiKey)key); - - if (map.containsKey(key)) return true; - else if (!hidden.contains(key) && parent != null) return parent.has(key); - else return false; - } - - public boolean hasNotNull(Key key) { - return get(key) != null; - } - - public T get(Key key, T defaultVal) { - if (has(key)) return get(key); - else return defaultVal; - } - public T get(Key key, Supplier defaultVal) { - if (has(key)) return get(key); - else return defaultVal.get(); - } - - @SuppressWarnings("unchecked") - public Environment add(Key key, T val) { - if (key instanceof MultiKey) return add(key, val); - - map.put((Key)key, val); - hidden.remove(key); - return this; - } - public Environment add(Key key) { - return add(key, null); - } - @SuppressWarnings("all") - public Environment addAll(Map, ?> map, boolean iterableAsMulti) { - for (var pair : map.entrySet()) { - if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) { - for (var val : (Iterable)pair.getValue()) { - addMulti((MultiKey)pair.getKey(), val); - } - } - else add((Key)pair.getKey(), pair.getValue()); - } - map.putAll((Map)map); - hidden.removeAll(map.keySet()); - return this; - } - public Environment addAll(Map, ?> map) { - return addAll(map, true); - } - // public Environment addAll(Environment env) { - // this.map.putAll(env.map); - // this.hidden.removeAll(env.map.keySet()); - - // for (var el : env.multi.entrySet()) { - // for (var val : el.getValue()) { - // add(el.getKey(), val); - // } - // } - - // return this; - // } - - @SuppressWarnings("unchecked") - public Environment remove(Key key) { - map.remove(key); - multi.remove(key); - multiHidden.remove(key); - hidden.add((Key)key); - return this; - } - @SuppressWarnings("all") - public Environment remove(MultiKey key, T val) { - if (multi.containsKey(key)) { - multi.get(key).remove(val); - multiHidden.get(key).add(val); - - if (multi.get(key).size() == 0) { - multi.remove(key); - multiHidden.remove(key); - hidden.add((Key)key); - } - } - - return this; - } - - public T init(Key key, T val) { - if (!has(key)) this.add(key, val); - return val; - } - public T initFrom(Key key, Supplier val) { - if (!has(key)) { - var res = val.get(); - this.add(key, res); - return res; - } - else return get(key); - } - - public Environment child() { - return new Environment(this); - } - - public Environment(Environment parent) { - this.parent = parent; - } - public Environment() { - this.parent = null; - } - - public static Environment wrap(Environment env) { - if (env == null) return empty(); - else return env; - } - - public static Environment empty() { - return new Environment(); - } - - public static int nextId() { - return new Random().nextInt(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Key.java b/src/main/java/me/topchetoeu/jscript/common/environment/Key.java deleted file mode 100644 index 1bb96b3..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/environment/Key.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.common.environment; - -public interface Key { - public static Key of() { - return new Key<>() { }; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java b/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java deleted file mode 100644 index 79e8404..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.common.environment; - -import java.util.Set; - -public interface MultiKey extends Key { - public T of(Set values); -} diff --git a/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java b/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java new file mode 100644 index 0000000..b498b85 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/events/DataNotifier.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.common.events; + +public class DataNotifier { + private Notifier notifier = new Notifier(); + private boolean isErr; + private T val; + private RuntimeException err; + + public void error(RuntimeException t) { + err = t; + isErr = true; + notifier.next(); + } + public void next(T val) { + this.val = val; + isErr = false; + notifier.next(); + } + public T await() { + notifier.await(); + + try { + if (isErr) throw err; + else return val; + } + finally { + this.err = null; + this.val = null; + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java b/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java new file mode 100644 index 0000000..eb6ad4d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/common/events/Notifier.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.common.events; + +import me.topchetoeu.jscript.runtime.exceptions.InterruptException; + +public class Notifier { + private boolean ok = false; + + public synchronized void next() { + ok = true; + notifyAll(); + } + public synchronized void await() { + try { + while (!ok) wait(); + ok = false; + } + catch (InterruptedException e) { throw new InterruptException(e); } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java index e93f3f3..2aaa8f1 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java @@ -1,72 +1,147 @@ package me.topchetoeu.jscript.common.json; -import java.math.BigDecimal; -import java.util.Map; +import java.util.HashSet; +import java.util.List; import java.util.stream.Collectors; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; public class JSON { - public static ParseRes parseString(Source src, int i) { - var res = Parsing.parseString(src, i); - if (!res.isSuccess()) return res.chainError(); - return ParseRes.res(JSONElement.string(res.result), res.n); + public static Object toJs(JSONElement val) { + if (val.isBoolean()) return val.bool(); + if (val.isString()) return val.string(); + if (val.isNumber()) return val.number(); + if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList())); + if (val.isMap()) { + var res = new ObjectValue(); + for (var el : val.map().entrySet()) { + res.defineProperty(null, el.getKey(), toJs(el.getValue())); + } + return res; + } + if (val.isNull()) return Values.NULL; + return null; } - public static ParseRes parseNumber(Source src, int i) { - var res = Parsing.parseNumber(src, i, true); - if (!res.isSuccess()) return res.chainError(); - else return ParseRes.res(JSONElement.number(res.result), res.n); + private static JSONElement fromJs(Extensions ext, Object val, HashSet prev) { + if (val instanceof Boolean) return JSONElement.bool((boolean)val); + if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue()); + if (val instanceof String) return JSONElement.string((String)val); + if (val == Values.NULL) return JSONElement.NULL; + if (val instanceof ArrayValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONList(); + + for (var el : ((ArrayValue)val).toArray()) { + var jsonEl = fromJs(ext, el, prev); + if (jsonEl == null) jsonEl = JSONElement.NULL; + res.add(jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val instanceof ObjectValue) { + if (prev.contains(val)) throw new EngineException("Circular dependency in JSON."); + prev.add(val); + + var res = new JSONMap(); + + for (var el : Values.getMembers(ext, val, false, false)) { + var jsonEl = fromJs(ext, Values.getMember(ext, val, el), prev); + if (jsonEl == null) continue; + if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl); + } + + prev.remove(val); + return JSONElement.of(res); + } + if (val == null) return null; + return null; } - public static ParseRes parseLiteral(Source src, int i) { - var id = Parsing.parseIdentifier(src, i); + public static JSONElement fromJs(Extensions ext, Object val) { + return fromJs(ext, val, new HashSet<>()); + } + + public static ParseRes parseIdentifier(List tokens, int i) { + return Parsing.parseIdentifier(tokens, i); + } + public static ParseRes parseString(Filename filename, List tokens, int i) { + var res = Parsing.parseString(filename, tokens, i); + if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); + else return res.transform(); + } + public static ParseRes parseNumber(Filename filename, List tokens, int i) { + var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT); + if (minus) i++; + + var res = Parsing.parseNumber(filename, tokens, i); + if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0)); + else return res.transform(); + } + public static ParseRes parseBool(Filename filename, List tokens, int i) { + var id = parseIdentifier(tokens, i); if (!id.isSuccess()) return ParseRes.failed(); - else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n); - else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n); - else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n); + else if (id.result.equals("true")) return ParseRes.res(true, 1); + else if (id.result.equals("false")) return ParseRes.res(false, 1); else return ParseRes.failed(); } - public static ParseRes parseValue(Source src, int i) { - return ParseRes.first(src, i, - JSON::parseString, - JSON::parseNumber, - JSON::parseLiteral, - JSON::parseMap, - JSON::parseList + public static ParseRes parseValue(Filename filename, List tokens, int i) { + return ParseRes.any( + parseString(filename, tokens, i), + parseNumber(filename, tokens, i), + parseBool(filename, tokens, i), + parseMap(filename, tokens, i), + parseList(filename, tokens, i) ); } - public static ParseRes parseMap(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; + public static ParseRes parseMap(Filename filename, List tokens, int i) { + int n = 0; + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); var values = new JSONMap(); - if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(Map.of()), n + 1); while (true) { - var name = parseString(src, i + n); - if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); + if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } - if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon"); + var name = ParseRes.any( + parseIdentifier(tokens, i + n), + parseString(filename, tokens, i + n), + parseNumber(filename, tokens, i + n) + ); + if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name); + else n += name.n; + + if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) { + return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name); + } n++; - var res = parseValue(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); - values.put(name.result.toString(), res.result); - n += res.n; - n += Parsing.skipEmpty(src, i + n); + var res = parseValue(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); + else n += res.n; - if (src.is(i + n, ",")) n++; - else if (src.is(i + n, "}")) { + values.put(name.result.toString(), JSONElement.of(res.result)); + + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; + else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { n++; break; } @@ -74,23 +149,26 @@ public class JSON { return ParseRes.res(values, n); } - public static ParseRes parseList(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n++, "[]")) return ParseRes.failed(); + public static ParseRes parseList(Filename filename, List tokens, int i) { + int n = 0; + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); var values = new JSONList(); - if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1); while (true) { - var res = parseValue(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); - values.add(res.result); - n += res.n; - n += Parsing.skipEmpty(src, i + n); + if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break; + } - if (src.is(i + n, ",")) n++; - else if (src.is(i + n, "]")) { + var res = parseValue(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); + else n += res.n; + + values.add(JSONElement.of(res.result)); + + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; + else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { n++; break; } @@ -100,21 +178,14 @@ public class JSON { } public static JSONElement parse(Filename filename, String raw) { if (filename == null) filename = new Filename("jscript", "json"); - - var res = parseValue(new Source(null, filename, raw), 0); + var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); else if (res.isError()) throw new SyntaxException(null, res.error); else return JSONElement.of(res.result); } public static String stringify(JSONElement el) { - if (el.isNumber()) { - var d = el.number(); - if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; - if (d == Double.POSITIVE_INFINITY) return "Infinity"; - if (Double.isNaN(d)) return "NaN"; - return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); - } + if (el.isNumber()) return Double.toString(el.number()); if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isNull()) return "null"; if (el.isString()) { diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java index a8d8490..0b3af33 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java @@ -69,7 +69,8 @@ public class JSONElement { return (boolean)value; } - @Override public String toString() { + @Override + public String toString() { if (isMap()) return "{...}"; if (isList()) return "[...]"; if (isString()) return (String)value; diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java index 2e2f1f9..ac0cb49 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java @@ -116,20 +116,32 @@ public class JSONMap implements Map { public JSONMap set(String key, Map val) { elements.put(key, JSONElement.of(val)); return this; } public JSONMap set(String key, Collection val) { elements.put(key, JSONElement.of(val)); return this; } - @Override public int size() { return elements.size(); } - @Override public boolean isEmpty() { return elements.isEmpty(); } - @Override public boolean containsKey(Object key) { return elements.containsKey(key); } - @Override public boolean containsValue(Object value) { return elements.containsValue(value); } - @Override public JSONElement get(Object key) { return elements.get(key); } - @Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } - @Override public JSONElement remove(Object key) { return elements.remove(key); } - @Override public void putAll(Map m) { elements.putAll(m); } + @Override + public int size() { return elements.size(); } + @Override + public boolean isEmpty() { return elements.isEmpty(); } + @Override + public boolean containsKey(Object key) { return elements.containsKey(key); } + @Override + public boolean containsValue(Object value) { return elements.containsValue(value); } + @Override + public JSONElement get(Object key) { return elements.get(key); } + @Override + public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } + @Override + public JSONElement remove(Object key) { return elements.remove(key); } + @Override + public void putAll(Map m) { elements.putAll(m); } - @Override public void clear() { elements.clear(); } + @Override + public void clear() { elements.clear(); } - @Override public Set keySet() { return elements.keySet(); } - @Override public Collection values() { return elements.values(); } - @Override public Set> entrySet() { return elements.entrySet(); } + @Override + public Set keySet() { return elements.keySet(); } + @Override + public Collection values() { return elements.values(); } + @Override + public Set> entrySet() { return elements.entrySet(); } public JSONMap() { } public JSONMap(Map els) { diff --git a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index fea96ff..b6b992a 100644 --- a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -11,10 +11,11 @@ import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Collectors; +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.Scope; +import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.utils.mapping.SourceMap; public class FunctionMap { public static class FunctionMapBuilder { @@ -53,8 +54,8 @@ public class FunctionMap { public FunctionMap build(String[] localNames, String[] captureNames) { return new FunctionMap(sourceMap, breakpoints, localNames, captureNames); } - public FunctionMap build(Scope scope) { - return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); + public FunctionMap build(LocalScopeRecord scope) { + return new FunctionMap(sourceMap, breakpoints, scope.locals(), scope.captures()); } public FunctionMap build() { return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); @@ -103,7 +104,7 @@ public class FunctionMap { var res = new ArrayList(candidates.size()); for (var candidate : candidates.entrySet()) { - var val = correctBreakpoint(Location.of(candidate.getKey(), line, column)); + var val = correctBreakpoint(new Location(line, column, candidate.getKey())); if (val == null) continue; res.add(val); } @@ -130,27 +131,27 @@ public class FunctionMap { return pcToLoc.lastEntry().getValue(); } - // public static FunctionMap apply(FunctionMap funcMap, SourceMap map) { - // var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames); + public FunctionMap apply(SourceMap map) { + var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames); - // for (var el : funcMap.pcToLoc.entrySet()) { - // res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue())); - // } + for (var el : pcToLoc.entrySet()) { + res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue())); + } - // res.bps.putAll(bps); + res.bps.putAll(bps); - // for (var el : bpLocs.entrySet()) { - // for (var loc : el.getValue()) { - // loc = map.toCompiled(loc); - // if (loc == null) continue; + for (var el : bpLocs.entrySet()) { + for (var loc : el.getValue()) { + loc = map.toCompiled(loc); + if (loc == null) continue; - // if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>()); - // res.bpLocs.get(loc.filename()).add(loc); - // } - // } + if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>()); + res.bpLocs.get(loc.filename()).add(loc); + } + } - // return res; - // } + return res; + } public FunctionMap clone() { var res = new FunctionMap(Map.of(), Map.of(), localNames, captureNames); diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java deleted file mode 100644 index 1d86866..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Location.java +++ /dev/null @@ -1,108 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -import java.util.ArrayList; -import java.util.Objects; - -public abstract class Location implements Comparable { - public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1); - - public abstract int line(); - public abstract int start(); - public abstract Filename filename(); - - public final String toString() { - var res = new ArrayList(); - - if (filename() != null) res.add(filename().toString()); - if (line() >= 0) res.add(line() + 1 + ""); - if (start() >= 0) res.add(start() + 1 + ""); - - return String.join(":", res); - } - - public final Location add(int n) { - var self = this; - - return new Location() { - @Override public Filename filename() { return self.filename(); } - @Override public int start() { return self.start() + n; } - @Override public int line() { return self.line(); } - }; - } - public final Location nextLine() { - var self = this; - - return new Location() { - @Override public Filename filename() { return self.filename(); } - @Override public int start() { return 0; } - @Override public int line() { return self.line() + 1; } - }; - } - - @Override public int hashCode() { - return Objects.hash(line(), start(), filename()); - } - @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof Location)) return false; - var other = (Location)obj; - - if (!Objects.equals(this.start(), other.start())) return false; - if (!Objects.equals(this.line(), other.line())) return false; - if (!Objects.equals(this.filename(), other.filename())) return false; - - return true; - } - - @Override public int compareTo(Location other) { - int a = filename().toString().compareTo(other.filename().toString()); - int b = Integer.compare(line(), other.line()); - int c = Integer.compare(start(), other.start()); - - if (a != 0) return a; - if (b != 0) return b; - - return c; - } - - public static Location of(Filename filename, int line, int start) { - return new Location() { - @Override public Filename filename() { return filename; } - @Override public int start() { return start; } - @Override public int line() { return line; } - }; - } - - public static Location of(String raw) { - var i0 = raw.lastIndexOf(':'); - if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1); - - var i1 = raw.lastIndexOf(':', i0); - if (i0 < 0) { - try { - return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1); - } - catch (NumberFormatException e) { - return Location.of(Filename.parse(raw), -1, -1); - } - } - - int start, line; - - try { - start = Integer.parseInt(raw.substring(i1 + 1)); - } - catch (NumberFormatException e) { - return Location.of(Filename.parse(raw), -1, -1); - } - - try { - line = Integer.parseInt(raw.substring(i0 + 1, i1)); - } - catch (NumberFormatException e) { - return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1); - } - - return Location.of(Filename.parse(raw.substring(0, i0)), start, line); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java deleted file mode 100644 index 7c1f553..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java +++ /dev/null @@ -1,75 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -public class ParseRes { - public static enum State { - SUCCESS, - FAILED, - ERROR; - - public boolean isSuccess() { return this == SUCCESS; } - public boolean isFailed() { return this == FAILED; } - public boolean isError() { return this == ERROR; } - } - - public final ParseRes.State state; - public final Location errorLocation; - public final String error; - public final T result; - public final int n; - - private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) { - this.result = result; - this.n = readN; - this.state = state; - this.error = error; - this.errorLocation = errorLocation; - } - - public ParseRes setN(int i) { - if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, null, result, i); - } - public ParseRes addN(int n) { - if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, null, result, this.n + n); - } - public ParseRes chainError() { - if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); - return new ParseRes<>(state, errorLocation, error, null, 0); - } - @SuppressWarnings("unchecked") - public ParseRes chainError(Location loc, String error) { - if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0); - return (ParseRes) this; - } - - public boolean isSuccess() { return state.isSuccess(); } - public boolean isFailed() { return state.isFailed(); } - public boolean isError() { return state.isError(); } - - public static ParseRes failed() { - return new ParseRes(State.FAILED, null, null, null, 0); - } - public static ParseRes error(Location loc, String error) { - return new ParseRes<>(State.ERROR, loc, error, null, 0); - } - public static ParseRes res(T val, int i) { - return new ParseRes<>(State.SUCCESS, null, null, val, i); - } - - @SafeVarargs - @SuppressWarnings("all") - // to hell with all of java's bullshit generics that do jack shit nothing - public static ParseRes first(Source src, int i, Parser ...parsers) { - int n = Parsing.skipEmpty(src, i); - ParseRes error = ParseRes.failed(); - - for (var parser : parsers) { - var res = parser.parse(src, i + n); - if (res.isSuccess()) return res.addN(n); - if (res.isError() && error.isFailed()) error = res.chainError(); - } - - return error; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java deleted file mode 100644 index d40f4f4..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Parser.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -public interface Parser { - public ParseRes parse(Source src, int i); -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java deleted file mode 100644 index 925c3d3..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java +++ /dev/null @@ -1,420 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class Parsing { - public static boolean isDigit(Character c) { - return c != null && c >= '0' && c <= '9'; - } - public static boolean isAny(char c, String alphabet) { - return alphabet.contains(Character.toString(c)); - } - - public static int fromHex(char c) { - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= '0' && c <= '9') return c - '0'; - return -1; - } - - public static int skipEmpty(Source src, int i) { - return skipEmpty(src, i, true); - } - - public static int skipEmpty(Source src, int i, boolean noComments) { - int n = 0; - - if (i == 0 && src.is(0, "#!")) { - while (!src.is(n, '\n')) n++; - n++; - } - - var isSingle = false; - var isMulti = false; - - while (i + n < src.size()) { - if (isSingle) { - if (src.is(i + n, '\n')) { - n++; - isSingle = false; - } - else n++; - } - else if (isMulti) { - if (src.is(i + n, "*/")) { - n += 2; - isMulti = false; - } - else n++; - } - else if (src.is(i + n, "//")) { - n += 2; - isSingle = true; - } - else if (src.is(i + n, "/*")) { - n += 2; - isMulti = true; - } - else if (src.is(i + n, Character::isWhitespace)) { - n++; - } - else break; - } - - return n; - } - - public static ParseRes parseChar(Source src, int i) { - int n = 0; - - if (src.is(i + n, '\\')) { - n++; - char c = src.at(i + n++); - - if (c == 'b') return ParseRes.res('\b', n); - else if (c == 't') return ParseRes.res('\t', n); - else if (c == 'n') return ParseRes.res('\n', n); - else if (c == 'f') return ParseRes.res('\f', n); - else if (c == 'r') return ParseRes.res('\r', n); - else if (c == '0') { - if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); - else return ParseRes.res('\0', n); - } - else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); - else if (c == 'x') { - var newC = 0; - - for (var j = 0; j < 2; j++) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); - - int val = fromHex(src.at(i + n)); - if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); - n++; - - newC = (newC << 4) | val; - } - - return ParseRes.res((char)newC, n); - } - else if (c == 'u') { - var newC = 0; - - for (var j = 0; j < 4; j++) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); - - int val = fromHex(src.at(i + n)); - if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); - n++; - - newC = (newC << 4) | val; - } - - return ParseRes.res((char)newC, n); - } - else if (c == '\n') return ParseRes.res(null, n); - } - - return ParseRes.res(src.at(i + n), n + 1); - } - - public static ParseRes parseIdentifier(Source src, int i) { - var n = skipEmpty(src, i); - var res = new StringBuilder(); - var first = true; - - while (true) { - if (i + n > src.size()) break; - char c = src.at(i + n, '\0'); - - if (first && Parsing.isDigit(c)) break; - if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; - res.append(c); - n++; - first = false; - } - - if (res.length() <= 0) return ParseRes.failed(); - else return ParseRes.res(res.toString(), n); - } - public static ParseRes parseIdentifier(Source src, int i, String test) { - var n = skipEmpty(src, i); - var res = new StringBuilder(); - var first = true; - - while (true) { - if (i + n > src.size()) break; - char c = src.at(i + n, '\0'); - - if (first && Parsing.isDigit(c)) break; - if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; - res.append(c); - n++; - first = false; - } - - if (res.length() <= 0) return ParseRes.failed(); - else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); - else return ParseRes.failed(); - } - public static boolean isIdentifier(Source src, int i, String test) { - return parseIdentifier(src, i, test).isSuccess(); - } - - public static ParseRes parseOperator(Source src, int i, String op) { - var n = skipEmpty(src, i); - - if (src.is(i + n, op)) return ParseRes.res(op, n + op.length()); - else return ParseRes.failed(); - } - - private static ParseRes parseHex(Source src, int i) { - int n = 0; - double res = 0; - - while (true) { - int digit = Parsing.fromHex(src.at(i + n, '\0')); - if (digit < 0) { - if (n <= 0) return ParseRes.failed(); - else return ParseRes.res(res, n); - } - n++; - - res *= 16; - res += digit; - } - } - private static ParseRes parseOct(Source src, int i) { - int n = 0; - double res = 0; - - while (true) { - int digit = src.at(i + n, '\0') - '0'; - if (digit < 0 || digit > 9) break; - if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); - - if (digit < 0) { - if (n <= 0) return ParseRes.failed(); - else return ParseRes.res(res, n); - } - n++; - - res *= 8; - res += digit; - } - - return ParseRes.res(res, n); - } - - public static ParseRes parseString(Source src, int i) { - var n = skipEmpty(src, i); - - char quote; - - if (src.is(i + n, '\'')) quote = '\''; - else if (src.is(i + n, '"')) quote = '"'; - else return ParseRes.failed(); - n++; - - var res = new StringBuilder(); - - while (true) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); - if (src.is(i + n, quote)) { - n++; - return ParseRes.res(res.toString(), n); - } - - var charRes = parseChar(src, i + n); - if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); - n += charRes.n; - - if (charRes.result != null) res.append(charRes.result); - } - } - public static ParseRes parseNumber(Source src, int i, boolean withMinus) { - var n = skipEmpty(src, i); - - double whole = 0; - double fract = 0; - long exponent = 0; - boolean parsedAny = false; - boolean negative = false; - - if (withMinus && src.is(i + n, "-")) { - negative = true; - n++; - } - - if (src.is(i + n, "0x") || src.is(i + n, "0X")) { - n += 2; - - var res = parseHex(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); - n += res.n; - - if (negative) return ParseRes.res(-res.result, n); - else return ParseRes.res(res.result, n); - } - else if (src.is(i + n, "0o") || src.is(i + n, "0O")) { - n += 2; - - var res = parseOct(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); - n += res.n; - - if (negative) return ParseRes.res(-res.result, n); - else return ParseRes.res(res.result, n); - } - else if (src.is(i + n, '0')) { - n++; - parsedAny = true; - if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); - } - - while (src.is(i + n, Parsing::isDigit)) { - parsedAny = true; - whole *= 10; - whole += src.at(i + n++) - '0'; - } - - if (src.is(i + n, '.')) { - parsedAny = true; - n++; - - while (src.is(i + n, Parsing::isDigit)) { - fract += src.at(i + n++) - '0'; - fract /= 10; - } - } - - if (src.is(i + n, 'e') || src.is(i + n, 'E')) { - n++; - parsedAny = true; - boolean expNegative = false; - boolean parsedE = false; - - if (src.is(i + n, '-')) { - expNegative = true; - n++; - } - else if (src.is(i + n, '+')) n++; - - while (src.is(i + n, Parsing::isDigit)) { - parsedE = true; - exponent *= 10; - - if (expNegative) exponent -= src.at(i + n++) - '0'; - else exponent += src.at(i + n++) - '0'; - } - - if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); - } - - if (!parsedAny) { - if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); - return ParseRes.failed(); - } - else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); - else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); - } - public static ParseRes parseFloat(Source src, int i, boolean withMinus) { - var n = skipEmpty(src, i); - - double whole = 0; - double fract = 0; - long exponent = 0; - boolean parsedAny = false; - boolean negative = false; - - if (withMinus && src.is(i + n, "-")) { - negative = true; - n++; - } - - while (src.is(i + n, Parsing::isDigit)) { - parsedAny = true; - whole *= 10; - whole += src.at(i + n++) - '0'; - } - - if (src.is(i + n, '.')) { - parsedAny = true; - n++; - - while (src.is(i + n, Parsing::isDigit)) { - fract += src.at(i + n++) - '0'; - fract /= 10; - } - } - - if (src.is(i + n, 'e') || src.is(i + n, 'E')) { - n++; - parsedAny = true; - boolean expNegative = false; - boolean parsedE = false; - - if (src.is(i + n, '-')) { - expNegative = true; - n++; - } - else if (src.is(i + n, '+')) n++; - - while (src.is(i + n, Parsing::isDigit)) { - parsedE = true; - exponent *= 10; - - if (expNegative) exponent -= src.at(i + n++) - '0'; - else exponent += src.at(i + n++) - '0'; - } - - if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); - } - - if (!parsedAny) { - if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); - return ParseRes.failed(); - } - else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); - else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); - } - public static ParseRes parseInt(Source src, int i, String alphabet, boolean withMinus) { - var n = skipEmpty(src, i); - - double result = 0; - boolean parsedAny = false; - boolean negative = false; - - if (withMinus && src.is(i + n, "-")) { - negative = true; - n++; - } - - if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) { - n += 2; - - var res = parseHex(src, i); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); - n += res.n; - - if (negative) return ParseRes.res(-res.result, n); - else return ParseRes.res(res.result, n); - } - - while (true) { - var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n))); - if (digit < 0) break; - - parsedAny = true; - result += digit; - result *= alphabet.length(); - } - - if (!parsedAny) { - if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); - return ParseRes.failed(); - } - else if (negative) return ParseRes.res(-result, n); - else return ParseRes.res(-result, n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java deleted file mode 100644 index d04c1e4..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Source.java +++ /dev/null @@ -1,75 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -import java.util.function.Predicate; - -import me.topchetoeu.jscript.common.environment.Environment; - -public class Source { - public final Environment env; - public final Filename filename; - public final String src; - - private int[] lineStarts; - - public Location loc(int offset) { - return new SourceLocation(filename, lineStarts, offset); - } - public boolean is(int i, char c) { - return i >= 0 && i < src.length() && src.charAt(i) == c; - } - public boolean is(int i, String src) { - if (i < 0 || i + src.length() > size()) return false; - - for (int j = 0; j < src.length(); j++) { - if (at(i + j) != src.charAt(j)) return false; - } - - return true; - } - public boolean is(int i, Predicate predicate) { - if (i < 0 || i >= src.length()) return false; - return predicate.test(at(i)); - } - public char at(int i) { - return src.charAt(i); - } - public char at(int i, char defaultVal) { - if (i < 0 || i >= src.length()) return defaultVal; - else return src.charAt(i); - } - public int size() { - return src.length(); - } - public String slice(int start, int end) { - return src.substring(start, end); - } - - public Source(Environment env, Filename filename, String src) { - if (env == null) this.env = new Environment(); - else this.env = env; - - this.filename = filename; - this.src = src; - - int n = 1; - lineStarts = new int[16]; - lineStarts[0] = 0; - - for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) { - if (n >= lineStarts.length) { - var newArr = new int[lineStarts.length * 2]; - System.arraycopy(lineStarts, 0, newArr, 0, n); - lineStarts = newArr; - } - - lineStarts[n++] = i + 1; - } - - var newArr = new int[n]; - System.arraycopy(lineStarts, 0, newArr, 0, n); - lineStarts = newArr; - } - public Source(String src) { - this(null, null, src); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java b/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java deleted file mode 100644 index 5819565..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/SourceLocation.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.topchetoeu.jscript.common.parsing; - -import java.util.Objects; - -public class SourceLocation extends Location { - private int[] lineStarts; - private int line; - private int start; - private final Filename filename; - private final int offset; - - private void update() { - if (lineStarts == null) return; - - int a = 0; - int b = lineStarts.length; - - while (true) { - if (a + 1 >= b) break; - var mid = -((-a - b) >> 1); - var el = lineStarts[mid]; - - if (el < offset) a = mid; - else if (el > offset) b = mid; - else { - this.line = mid; - this.start = 0; - this.lineStarts = null; - return; - } - } - - this.line = a; - this.start = offset - lineStarts[a]; - this.lineStarts = null; - return; - } - - @Override public Filename filename() { return filename; } - @Override public int line() { - update(); - return line; - } - @Override public int start() { - update(); - return start; - } - - @Override public int hashCode() { - return Objects.hash(offset); - } - @Override public int compareTo(Location other) { - if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset); - else return super.compareTo(other); - } - @Override public boolean equals(Object obj) { - if (obj instanceof SourceLocation other) return this.offset == other.offset; - else return super.equals(obj); - } - - public SourceLocation(Filename filename, int[] lineStarts, int offset) { - this.filename = filename; - this.lineStarts = lineStarts; - this.offset = offset; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java deleted file mode 100644 index e684c44..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Operation; - -public interface AssignableNode { - public abstract Node toAssign(Node val, Operation operation); -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java new file mode 100644 index 0000000..9602c2e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/AssignableStatement.java @@ -0,0 +1,12 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; + +public abstract class AssignableStatement extends Statement { + public abstract Statement toAssign(Statement val, Operation operation); + + protected AssignableStatement(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index 0a78253..00f6db7 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -1,27 +1,23 @@ package me.topchetoeu.jscript.compilation; import java.util.List; -import java.util.ArrayList; import java.util.LinkedList; -import java.util.function.IntFunction; +import java.util.Vector; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.mapping.FunctionMap; import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.LocalScope; -import me.topchetoeu.jscript.compilation.scope.Scope; +import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; -public final class CompileResult { - public final List> instructions; - public final List children; - public final FunctionMapBuilder map; - public final Environment env; - public int length, assignN; - public final Scope scope; +public class CompileResult { + public final Vector instructions = new Vector<>(); + public final List children = new LinkedList<>(); + public final FunctionMapBuilder map = FunctionMap.builder(); + public final LocalScopeRecord scope; + public int length = 0; public int temp() { instructions.add(null); @@ -29,24 +25,16 @@ public final class CompileResult { } public CompileResult add(Instruction instr) { - instructions.add(i -> instr); - return this; - } - public CompileResult add(IntFunction instr) { instructions.add(instr); return this; } public CompileResult set(int i, Instruction instr) { - instructions.set(i, _i -> instr); - return this; - } - public CompileResult set(int i, IntFunctioninstr) { instructions.set(i, instr); return this; } - // public Instruction get(int i) { - // return instructions.get(i); - // } + public Instruction get(int i) { + return instructions.get(i); + } public int size() { return instructions.size(); } public void setDebug(Location loc, BreakpointType type) { @@ -73,16 +61,6 @@ public final class CompileResult { return child; } - public Instruction[] instructions() { - var res = new Instruction[instructions.size()]; - var i = 0; - for (var suppl : instructions) { - res[i] = suppl.apply(i); - i++; - } - return res; - } - public FunctionMap map() { return map.build(scope); } @@ -91,37 +69,10 @@ public final class CompileResult { for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body(); - var instrRes = new Instruction[instructions.size()]; - var i = 0; - - for (var suppl : instructions) { - instrRes[i] = suppl.apply(i); - i++; - } - - return new FunctionBody( - scope.localsCount() + scope.allocCount(), scope.capturesCount(), - length, assignN, - instrRes, builtChildren - ); + return new FunctionBody(scope.localsCount(), length, instructions.toArray(Instruction[]::new), builtChildren); } - public CompileResult subtarget() { - return new CompileResult(new LocalScope(scope), this); - } - - public CompileResult(Environment env, Scope scope) { + public CompileResult(LocalScopeRecord scope) { this.scope = scope; - instructions = new ArrayList<>(); - children = new LinkedList<>(); - map = FunctionMap.builder(); - this.env = env; - } - private CompileResult(Scope scope, CompileResult parent) { - this.scope = scope; - this.instructions = parent.instructions; - this.children = parent.children; - this.map = parent.map; - this.env = parent.env; } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java deleted file mode 100644 index 0a03de1..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ /dev/null @@ -1,118 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.List; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; - - -public class CompoundNode extends Node { - public final Node[] statements; - public Location end; - - @Override public void resolve(CompileResult target) { - for (var stm : statements) stm.resolve(target); - } - - public void compile(CompileResult target, boolean pollute, boolean alloc, BreakpointType type) { - List statements = new ArrayList(); - - var subtarget = target.subtarget(); - if (alloc) subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); - - for (var stm : this.statements) { - if (stm instanceof FunctionStatementNode func) { - func.compile(subtarget, false); - } - else statements.add(stm); - } - - var polluted = false; - - for (var i = 0; i < statements.size(); i++) { - var stm = statements.get(i); - - if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER); - else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); - } - - subtarget.scope.end(); - if (alloc) subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); - - if (!polluted && pollute) { - target.add(Instruction.pushUndefined()); - } - } - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { - compile(target, pollute, true, type); - } - - public CompoundNode setEnd(Location loc) { - this.end = loc; - return this; - } - - public CompoundNode(Location loc, Node ...statements) { - super(loc); - this.statements = statements; - } - - public static ParseRes parseComma(Source src, int i, Node prev, int precedence) { - if (precedence > 1) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, ",")) return ParseRes.failed(); - n++; - - var curr = JavaScript.parseExpression(src, i + n, 2); - if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma"); - n += curr.n; - - if (prev instanceof CompoundNode) { - var children = new ArrayList(); - children.addAll(List.of(((CompoundNode)prev).statements)); - children.add(curr.result); - - return ParseRes.res(new CompoundNode(loc, children.toArray(Node[]::new)), n); - } - else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n); - } - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; - - var statements = new ArrayList(); - - while (true) { - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - if (src.is(i + n, ";")) { - n++; - continue; - } - - var res = JavaScript.parseStatement(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement"); - n += res.n; - - statements.add(res.result); - } - - return ParseRes.res(new CompoundNode(loc, statements.toArray(Node[]::new)).setEnd(src.loc(i + n - 1)), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java new file mode 100644 index 0000000..06eed66 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -0,0 +1,64 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.List; +import java.util.Vector; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.values.FunctionStatement; + +public class CompoundStatement extends Statement { + public final Statement[] statements; + public final boolean separateFuncs; + public Location end; + + @Override public boolean pure() { + for (var stm : statements) { + if (!stm.pure()) return false; + } + + return true; + } + + @Override + public void declare(CompileResult target) { + for (var stm : statements) stm.declare(target); + } + + @Override + public void compile(CompileResult target, boolean pollute, BreakpointType type) { + List statements = new Vector(); + if (separateFuncs) for (var stm : this.statements) { + if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) { + stm.compile(target, false); + } + else statements.add(stm); + } + else statements = List.of(this.statements); + + var polluted = false; + + for (var i = 0; i < statements.size(); i++) { + var stm = statements.get(i); + + if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER); + else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER); + } + + if (!polluted && pollute) { + target.add(Instruction.pushUndefined()); + } + } + + public CompoundStatement setEnd(Location loc) { + this.end = loc; + return this; + } + + public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) { + super(loc); + this.separateFuncs = separateFuncs; + this.statements = statements; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java b/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java deleted file mode 100644 index e0cb484..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/DeferredIntSupplier.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.function.IntSupplier; - -public final class DeferredIntSupplier implements IntSupplier { - private int value; - private boolean set; - - public void set(int val) { - if (set) throw new RuntimeException("A deferred int supplier may be set only once"); - value = val; - set = true; - } - - @Override public int getAsInt() { - if (!set) throw new RuntimeException("Deferred int supplier accessed too early"); - return value; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java deleted file mode 100644 index 66c88e4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ /dev/null @@ -1,145 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.scope.FunctionScope; -import me.topchetoeu.jscript.compilation.scope.LocalScope; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public abstract class FunctionNode extends Node { - public final CompoundNode body; - public final Parameters params; - public final Location end; - - public abstract String name(); - - // @Override public void declare(CompileResult target) { - // if (varName != null && statement) target.scope.define(varName); - // } - - // public static void checkBreakAndCont(CompileResult target, int start) { - // for (int i = start; i < target.size(); i++) { - // if (target.get(i).type == Type.NOP) { - // if (target.get(i).is(0, "break") ) { - // throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop."); - // } - // if (target.get(i).is(0, "cont")) { - // throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop."); - // } - // } - // } - // } - - protected void compileLoadFunc(CompileResult target, int[] captures, String name) { - target.add(Instruction.loadFunc(target.children.size(), name, captures)); - } - - private CompileResult compileBody(CompileResult target, String name, boolean storeSelf, boolean pollute, BreakpointType bp) { - var env = target.env.child() - .remove(LabelContext.BREAK_CTX) - .remove(LabelContext.CONTINUE_CTX); - - var funcScope = new FunctionScope(target.scope); - var subtarget = new CompileResult(env, new LocalScope(funcScope)); - - for (var param : params.params) { - // TODO: Implement default values - // TODO: Implement argument location - if (funcScope.hasArg(param.name)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); - var i = funcScope.defineParam(param.name, param.loc); - - if (param.node != null) { - var end = new DeferredIntSupplier(); - - subtarget.add(_i -> Instruction.loadVar(i.index())); - subtarget.add(Instruction.pushUndefined()); - subtarget.add(Instruction.operation(Operation.EQUALS)); - subtarget.add(_i -> Instruction.jmpIfNot(end.getAsInt() - _i)); - param.node.compile(subtarget, pollute); - subtarget.add(_i -> Instruction.storeVar(i.index())); - - end.set(subtarget.size()); - } - } - - body.resolve(subtarget); - body.compile(subtarget, false, false, BreakpointType.NONE); - - subtarget.length = params.length; - subtarget.assignN = params.params.size(); - subtarget.scope.end(); - funcScope.end(); - - if (pollute) compileLoadFunc(target, funcScope.getCaptureIndices(), name); - - return target.addChild(subtarget); - } - - public void compile(CompileResult target, boolean pollute, boolean storeSelf, String name, BreakpointType bp) { - if (this.name() != null) name = this.name(); - - compileBody(target, name, storeSelf, pollute, bp); - } - public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); - public void compile(CompileResult target, boolean pollute, String name) { - compile(target, pollute, name, BreakpointType.NONE); - } - @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { - compile(target, pollute, (String)null, bp); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, (String)null, BreakpointType.NONE); - } - - public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) { - super(loc); - - this.end = end; - this.params = params; - this.body = body; - } - - public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { - if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name); - else stm.compile(target, pollute); - } - public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp); - else stm.compile(target, pollute, bp); - } - - public static ParseRes parseFunction(Source src, int i, boolean statement) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); - n += 8; - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - var params = JavaScript.parseParameters(src, i + n); - if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list"); - n += params.n; - - var body = CompoundNode.parse(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function"); - n += body.n; - - if (statement) return ParseRes.res(new FunctionStatementNode( - loc, src.loc(i + n - 1), - params.result, body.result, name.result - ), n); - else return ParseRes.res(new FunctionValueNode( - loc, src.loc(i + n - 1), - params.result, body.result, name.result - ), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java deleted file mode 100644 index 183fcd9..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class FunctionStatementNode extends FunctionNode { - public final String name; - - @Override public String name() { return name; } - - @Override public void resolve(CompileResult target) { - target.scope.define(name, false, end); - } - - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - compile(target, true, false, this.name, bp); - target.add(VariableNode.toSet(target, end, this.name, pollute, true)); - } - - public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { - super(loc, end, params, body); - this.name = name; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java deleted file mode 100644 index 89f96dd..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; - -public class FunctionValueNode extends FunctionNode { - public final String name; - - @Override public String name() { return name; } - - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - compile(target, pollute, true, name, bp); - } - - public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { - super(loc, end, params, body); - this.name = name; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java deleted file mode 100644 index 7b58aae..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ /dev/null @@ -1,328 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.Set; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.control.BreakNode; -import me.topchetoeu.jscript.compilation.control.ContinueNode; -import me.topchetoeu.jscript.compilation.control.DebugNode; -import me.topchetoeu.jscript.compilation.control.DeleteNode; -import me.topchetoeu.jscript.compilation.control.DoWhileNode; -import me.topchetoeu.jscript.compilation.control.ForInNode; -import me.topchetoeu.jscript.compilation.control.ForNode; -import me.topchetoeu.jscript.compilation.control.ForOfNode; -import me.topchetoeu.jscript.compilation.control.IfNode; -import me.topchetoeu.jscript.compilation.control.ReturnNode; -import me.topchetoeu.jscript.compilation.control.SwitchNode; -import me.topchetoeu.jscript.compilation.control.ThrowNode; -import me.topchetoeu.jscript.compilation.control.TryNode; -import me.topchetoeu.jscript.compilation.control.WhileNode; -import me.topchetoeu.jscript.compilation.scope.GlobalScope; -import me.topchetoeu.jscript.compilation.scope.LocalScope; -import me.topchetoeu.jscript.compilation.values.ArgumentsNode; -import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.GlobalThisNode; -import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.RegexNode; -import me.topchetoeu.jscript.compilation.values.ThisNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.BoolNode; -import me.topchetoeu.jscript.compilation.values.constants.NullNode; -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; -import me.topchetoeu.jscript.compilation.values.operations.CallNode; -import me.topchetoeu.jscript.compilation.values.operations.ChangeNode; -import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; -import me.topchetoeu.jscript.compilation.values.operations.IndexNode; -import me.topchetoeu.jscript.compilation.values.operations.OperationNode; -import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class JavaScript { - static final Set reserved = Set.of( - "true", "false", "void", "null", "this", "if", "else", "try", "catch", - "finally", "for", "do", "while", "switch", "case", "default", "new", - "function", "var", "return", "throw", "typeof", "delete", "break", - "continue", "debugger", "implements", "interface", "package", "private", - "protected", "public", "static" - ); - - public static ParseRes parseParens(Source src, int i) { - int n = 0; - - var openParen = Parsing.parseOperator(src, i + n, "("); - if (!openParen.isSuccess()) return openParen.chainError(); - n += openParen.n; - - var res = JavaScript.parseExpression(src, i + n, 0); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); - n += res.n; - - var closeParen = Parsing.parseOperator(src, i + n, ")"); - if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); - n += closeParen.n; - - return ParseRes.res(res.result, n); - } - - public static ParseRes parseSimple(Source src, int i, boolean statement) { - return ParseRes.first(src, i, - (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), - (s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false), - JavaScript::parseLiteral, - StringNode::parse, - RegexNode::parse, - NumberNode::parse, - ChangeNode::parsePrefixDecrease, - ChangeNode::parsePrefixIncrease, - OperationNode::parsePrefix, - ArrayNode::parse, - JavaScript::parseParens, - CallNode::parseNew, - TypeofNode::parse, - DiscardNode::parse, - DeleteNode::parse, - VariableNode::parse - ); - } - - public static ParseRes parseLiteral(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var id = Parsing.parseIdentifier(src, i); - if (!id.isSuccess()) return id.chainError(); - n += id.n; - - if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n); - if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n); - if (id.result.equals("undefined")) return ParseRes.res(new DiscardNode(loc, null), n); - if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); - if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n); - if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); - if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n); - - return ParseRes.failed(); - } - - public static ParseRes parseExpression(Source src, int i, int precedence, boolean statement) { - var n = Parsing.skipEmpty(src, i); - Node prev = null; - - while (true) { - if (prev == null) { - var res = parseSimple(src, i + n, statement); - if (res.isSuccess()) { - n += res.n; - prev = res.result; - } - else if (res.isError()) return res.chainError(); - else break; - } - else { - var _prev = prev; - ParseRes res = ParseRes.first(src, i + n, - (s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence), - (s, j) -> OperationNode.parseIn(s, j, _prev, precedence), - (s, j) -> ChangeNode.parsePostfixIncrease(s, j, _prev, precedence), - (s, j) -> ChangeNode.parsePostfixDecrease(s, j, _prev, precedence), - (s, j) -> OperationNode.parseOperator(s, j, _prev, precedence), - (s, j) -> IfNode.parseTernary(s, j, _prev, precedence), - (s, j) -> IndexNode.parseMember(s, j, _prev, precedence), - (s, j) -> IndexNode.parseIndex(s, j, _prev, precedence), - (s, j) -> CallNode.parseCall(s, j, _prev, precedence), - (s, j) -> CompoundNode.parseComma(s, j, _prev, precedence) - ); - - if (res.isSuccess()) { - n += res.n; - prev = res.result; - continue; - } - else if (res.isError()) return res.chainError(); - - break; - } - } - - if (prev == null) return ParseRes.failed(); - else return ParseRes.res(prev, n); - } - - public static ParseRes parseExpression(Source src, int i, int precedence) { - return parseExpression(src, i, precedence, false); - } - - public static ParseRes parseExpressionStatement(Source src, int i) { - var res = parseExpression(src, i, 0, true); - if (!res.isSuccess()) return res.chainError(); - - var end = JavaScript.parseStatementEnd(src, i + res.n); - if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); - - return res.addN(end.n); - } - - public static ParseRes parseStatement(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1); - if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); - - ParseRes res = ParseRes.first(src, i + n, - VariableDeclareNode::parse, - ReturnNode::parse, - ThrowNode::parse, - ContinueNode::parse, - BreakNode::parse, - DebugNode::parse, - IfNode::parse, - WhileNode::parse, - SwitchNode::parse, - ForNode::parse, - ForInNode::parse, - ForOfNode::parse, - DoWhileNode::parse, - TryNode::parse, - CompoundNode::parse, - (s, j) -> FunctionNode.parseFunction(s, j, true), - JavaScript::parseExpressionStatement - ); - return res.addN(n); - } - - public static ParseRes parseStatementEnd(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - if (i >= src.size()) return ParseRes.res(true, n + 1); - - for (var j = i; j < i + n; j++) { - if (src.is(j, '\n')) return ParseRes.res(true, n); - } - - if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); - if (src.is(i + n, '}')) return ParseRes.res(true, n); - - return ParseRes.failed(); - } - - public static ParseRes parseParameters(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var openParen = Parsing.parseOperator(src, i + n, "("); - if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list"); - n += openParen.n; - - var params = new ArrayList(); - - var closeParen = Parsing.parseOperator(src, i + n, ")"); - n += closeParen.n; - - if (!closeParen.isSuccess()) { - while (true) { - n += Parsing.skipEmpty(src, i + n); - - var paramLoc = src.loc(i); - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an argument or a closing brace"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "=")) { - n++; - - var val = parseExpression(src, i + n, 2); - if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a default value"); - n += val.n; - n += Parsing.skipEmpty(src, i + n); - - params.add(new Parameter(paramLoc, name.result, val.result)); - } - else params.add(new Parameter(paramLoc, name.result, null)); - - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - } - if (src.is(i + n, ")")) { - n++; - break; - } - } - } - - return ParseRes.res(new Parameters(params), n); - } - - public static Node[] parse(Environment env, Filename filename, String raw) { - var src = new Source(env, filename, raw); - var list = new ArrayList(); - int i = 0; - - while (true) { - if (i >= src.size()) break; - - var res = parseStatement(src, i); - - if (res.isError()) throw new SyntaxException(res.errorLocation, res.error); - else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); - - i += res.n; - - list.add(res.result); - } - - return list.toArray(Node[]::new); - } - - public static boolean checkVarName(String name) { - return !JavaScript.reserved.contains(name); - } - - public static CompileResult compile(Environment env, Node ...statements) { - var target = new CompileResult(env, new LocalScope(new GlobalScope())); - var stm = new CompoundNode(null, statements); - - try { - stm.resolve(target); - stm.compile(target, true, false, BreakpointType.NONE); - // FunctionNode.checkBreakAndCont(target, 0); - } - catch (SyntaxException e) { - target = new CompileResult(env, new LocalScope(new GlobalScope())); - - target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); - } - - return target; - } - - public static CompileResult compile(Environment env, Filename filename, String raw) { - return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); - } - public static CompileResult compile(Filename filename, String raw) { - var env = new Environment(); - return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); - } - - public static ParseRes parseLabel(Source src, int i) { - int n = Parsing.skipEmpty(src, i); - - var nameRes = Parsing.parseIdentifier(src, i + n); - if (!nameRes.isSuccess()) return nameRes.chainError(); - n += nameRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ":")) return ParseRes.failed(); - n++; - - return ParseRes.res(nameRes.result, n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java b/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java deleted file mode 100644 index 0dea994..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java +++ /dev/null @@ -1,85 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.function.IntFunction; -import java.util.function.IntSupplier; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class LabelContext { - public static final Key BREAK_CTX = Key.of(); - public static final Key CONTINUE_CTX = Key.of(); - - private final LinkedList list = new LinkedList<>(); - private final HashMap map = new HashMap<>(); - - public IntSupplier get() { - return list.peekLast(); - } - public IntSupplier get(String name) { - return map.get(name); - } - - public IntFunction getJump() { - var res = get(); - if (res == null) return null; - else return i -> Instruction.jmp(res.getAsInt() - i); - } - public IntFunction getJump(String name) { - var res = get(name); - if (res == null) return null; - else return i -> Instruction.jmp(res.getAsInt() - i); - } - - public void push(IntSupplier jumpTarget) { - list.add(jumpTarget); - } - public void push(Location loc, String name, IntSupplier jumpTarget) { - if (name == null) return; - if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name)); - map.put(name, jumpTarget); - } - - public void pushLoop(Location loc, String name, IntSupplier jumpTarget) { - push(jumpTarget); - push(loc, name, jumpTarget); - } - - public void pop() { - list.removeLast(); - } - public void pop(String name) { - if (name == null) return; - map.remove(name); - } - - public void popLoop(String name) { - pop(); - pop(name); - } - - public static LabelContext getBreak(Environment env) { - return env.initFrom(BREAK_CTX, () -> new LabelContext()); - } - public static LabelContext getCont(Environment env) { - return env.initFrom(CONTINUE_CTX, () -> new LabelContext()); - } - - public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) { - LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); - LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget); - } - public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) { - LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); - LabelContext.getCont(env).pushLoop(loc, name, contTarget); - } - public static void popLoop(Environment env, String name) { - LabelContext.getBreak(env).popLoop(name); - LabelContext.getCont(env).popLoop(name); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java b/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java deleted file mode 100644 index 920c6d0..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/NodeChildren.java +++ /dev/null @@ -1,93 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.Function; - -public final class NodeChildren implements Iterable { - public static final class Slot { - private Node node; - private final Function replacer; - - public final void replace(Node node) { - this.node = this.replacer.apply(node); - } - - public Slot(Node nodes, Function replacer) { - this.node = nodes; - this.replacer = replacer; - } - } - - private final Slot[] slots; - - private NodeChildren(Slot[] slots) { - this.slots = slots; - } - - @Override public Iterator iterator() { - return new Iterator() { - private int i = 0; - private Slot[] arr = slots; - - @Override public boolean hasNext() { - if (arr == null) return false; - else if (i >= arr.length) { - arr = null; - return false; - } - else return true; - } - @Override public Node next() { - if (!hasNext()) return null; - return arr[i++].node; - } - }; - } - public Iterable slots() { - return () -> new Iterator() { - private int i = 0; - private Slot[] arr = slots; - - @Override public boolean hasNext() { - if (arr == null) return false; - else if (i >= arr.length) { - arr = null; - return false; - } - else return true; - } - @Override public Slot next() { - if (!hasNext()) return null; - return arr[i++]; - } - }; - } - - public static final class Builder { - private final ArrayList slots = new ArrayList<>(); - - public final Builder add(Slot ...children) { - for (var child : children) { - this.slots.add(child); - } - - return this; - } - public final Builder add(Iterable children) { - for (var child : children) { - this.slots.add(child); - } - - return this; - } - public final Builder add(Node child, Function replacer) { - slots.add(new Slot(child, replacer)); - return this; - } - - public final NodeChildren build() { - return new NodeChildren(slots.toArray(Slot[]::new)); - } - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java deleted file mode 100644 index 9404d5d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/Parameter.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.parsing.Location; - -public final class Parameter { - public final Location loc; - public final String name; - public final Node node; - - public Parameter(Location loc, String name, Node node) { - this.name = name; - this.node = node; - this.loc = loc; - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java deleted file mode 100644 index c104a38..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public final class Parameters { - public final int length; - public final List params; - public final Set names; - - public Parameters(List params) { - this.names = new HashSet<>(); - var len = params.size(); - - for (var i = params.size() - 1; i >= 0; i--) { - if (params.get(i).node == null) break; - len--; - } - - for (var param : params) { - this.names.add(param.name); - } - - this.params = params; - this.length = len; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Node.java b/src/main/java/me/topchetoeu/jscript/compilation/Statement.java similarity index 55% rename from src/main/java/me/topchetoeu/jscript/compilation/Node.java rename to src/main/java/me/topchetoeu/jscript/compilation/Statement.java index 7da1c3e..de9d23b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/Node.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/Statement.java @@ -1,12 +1,13 @@ package me.topchetoeu.jscript.compilation; +import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -public abstract class Node { - private Location loc; +public abstract class Statement { + private Location _loc; - public void resolve(CompileResult target) {} + public boolean pure() { return false; } + public void declare(CompileResult target) { } public void compile(CompileResult target, boolean pollute, BreakpointType type) { int start = target.size(); @@ -17,10 +18,10 @@ public abstract class Node { compile(target, pollute, BreakpointType.NONE); } - public Location loc() { return loc; } - public void setLoc(Location loc) { this.loc = loc; } + public Location loc() { return _loc; } + public void setLoc(Location loc) { _loc = loc; } - protected Node(Location loc) { - this.loc = loc; + protected Statement(Location loc) { + this._loc = loc; } } \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java new file mode 100644 index 0000000..523e10a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/ThrowSyntaxStatement.java @@ -0,0 +1,18 @@ +package me.topchetoeu.jscript.compilation; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class ThrowSyntaxStatement extends Statement { + public final String name; + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.throwSyntax(name)); + } + + public ThrowSyntaxStatement(SyntaxException e) { + super(e.loc); + this.name = e.msg; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java deleted file mode 100644 index 69b91f8..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ /dev/null @@ -1,115 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.List; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class VariableDeclareNode extends Node { - public static class Pair { - public final String name; - public final Node value; - public final Location location; - - public Pair(String name, Node value, Location location) { - this.name = name; - this.value = value; - this.location = location; - } - } - - public final List values; - - @Override public void resolve(CompileResult target) { - for (var entry : values) { - target.scope.define(entry.name, false, entry.location); - } - } - @Override public void compile(CompileResult target, boolean pollute) { - for (var entry : values) { - if (entry.name == null) continue; - - if (entry.value != null) { - FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); - target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); - } - else { - target.add(_i -> { - var i = target.scope.get(entry.name, true); - - if (i == null) return Instruction.globDef(entry.name); - else return Instruction.nop(); - }); - } - } - - if (pollute) target.add(Instruction.pushUndefined()); - } - - public VariableDeclareNode(Location loc, List values) { - super(loc); - this.values = values; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "var")) return ParseRes.failed(); - n += 3; - - var res = new ArrayList(); - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new VariableDeclareNode(loc, res), n); - } - - while (true) { - var nameLoc = src.loc(i + n); - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); - n += name.n; - - if (!JavaScript.checkVarName(name.result)) { - return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); - } - - Node val = null; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "=")) { - n++; - - var valRes = JavaScript.parseExpression(src, i + n, 2); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); - - n += valRes.n; - n += Parsing.skipEmpty(src, i + n); - val = valRes.result; - } - - res.add(new Pair(name.result, val, nameLoc)); - - if (src.is(i + n, ",")) { - n++; - continue; - } - - end = JavaScript.parseStatementEnd(src, i + n); - - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new VariableDeclareNode(loc, res), n); - } - else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java new file mode 100644 index 0000000..1f4b6a4 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.List; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.values.FunctionStatement; + +public class VariableDeclareStatement extends Statement { + public static class Pair { + public final String name; + public final Statement value; + public final Location location; + + public Pair(String name, Statement value, Location location) { + this.name = name; + this.value = value; + this.location = location; + } + } + + public final List values; + + @Override + public void declare(CompileResult target) { + for (var key : values) { + target.scope.define(key.name); + } + } + @Override + public void compile(CompileResult target, boolean pollute) { + for (var entry : values) { + if (entry.name == null) continue; + var key = target.scope.getKey(entry.name); + + if (key instanceof String) target.add(Instruction.makeVar((String)key)); + + if (entry.value != null) { + FunctionStatement.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); + target.add(Instruction.storeVar(key)); + } + } + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public VariableDeclareStatement(Location loc, List values) { + super(loc); + this.values = values; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java deleted file mode 100644 index b74e2a4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class BreakNode extends Node { - public final String label; - - @Override public void compile(CompileResult target, boolean pollute) { - var res = LabelContext.getBreak(target.env).getJump(); - if (res == null) { - if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); - else throw new SyntaxException(loc(), "Illegal break statement"); - } - target.add(res); - - // target.add(Instruction.nop("break", label)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public BreakNode(Location loc, String label) { - super(loc); - this.label = label; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed(); - n += 5; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new BreakNode(loc, null), n); - } - - var label = Parsing.parseIdentifier(src, i + n); - if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); - n += label.n; - - end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new BreakNode(loc, label.result), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement"); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java new file mode 100644 index 0000000..eafae48 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class BreakStatement extends Statement { + public final String label; + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.nop("break", label)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public BreakStatement(Location loc, String label) { + super(loc); + this.label = label; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java deleted file mode 100644 index afc94a5..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java +++ /dev/null @@ -1,58 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class ContinueNode extends Node { - public final String label; - - @Override public void compile(CompileResult target, boolean pollute) { - var res = LabelContext.getCont(target.env).getJump(); - if (res == null) { - if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); - else throw new SyntaxException(loc(), "Illegal continue statement"); - } - target.add(res); - - // () -> Instruction.nop("cont", label)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ContinueNode(Location loc, String label) { - super(loc); - this.label = label; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed(); - n += 8; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ContinueNode(loc, null), n); - } - - var label = Parsing.parseIdentifier(src, i + n); - if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); - n += label.n; - - end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ContinueNode(loc, label.result), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement"); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java new file mode 100644 index 0000000..dfc3abd --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ContinueStatement extends Statement { + public final String label; + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.nop("cont", label)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ContinueStatement(Location loc, String label) { + super(loc); + this.label = label; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java deleted file mode 100644 index 9464e9d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -public class DebugNode extends Node { - @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.debug()); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public DebugNode(Location loc) { - super(loc); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed(); - n += 8; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new DebugNode(loc), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement"); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java new file mode 100644 index 0000000..20d23ef --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java @@ -0,0 +1,18 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class DebugStatement extends Statement { + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.debug()); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public DebugStatement(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java deleted file mode 100644 index 6df811d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.BoolNode; -import me.topchetoeu.jscript.compilation.values.operations.IndexNode; - -public class DeleteNode extends Node { - public final Node key; - public final Node value; - - @Override public void compile(CompileResult target, boolean pollute) { - value.compile(target, true); - key.compile(target, true); - - target.add(Instruction.delete()); - if (pollute) target.add(Instruction.pushValue(true)); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed(); - n += 6; - - var valRes = JavaScript.parseExpression(src, i + n, 15); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'"); - n += valRes.n; - - if (valRes.result instanceof IndexNode) { - var index = (IndexNode)valRes.result; - return ParseRes.res(new DeleteNode(loc, index.index, index.object), n); - } - else if (valRes.result instanceof VariableNode) { - return ParseRes.error(src.loc(i + n), "A variable may not be deleted"); - } - else return ParseRes.res(new BoolNode(loc, true), n); - } - - public DeleteNode(Location loc, Node key, Node value) { - super(loc); - this.key = key; - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java new file mode 100644 index 0000000..9355182 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java @@ -0,0 +1,26 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class DeleteStatement extends Statement { + public final Statement key; + public final Statement value; + + @Override + public void compile(CompileResult target, boolean pollute) { + value.compile(target, true); + key.compile(target, true); + + target.add(Instruction.delete()); + if (pollute) target.add(Instruction.pushValue(true)); + } + + public DeleteStatement(Location loc, Statement key, Statement value) { + super(loc); + this.key = key; + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java deleted file mode 100644 index c07a004..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java +++ /dev/null @@ -1,85 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; - -public class DoWhileNode extends Node { - public final Node condition, body; - public final String label; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - } - - @Override public void compile(CompileResult target, boolean pollute) { - int start = target.size(); - var end = new DeferredIntSupplier(); - var mid = new DeferredIntSupplier(); - - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - mid.set(target.size()); - condition.compile(target, true, BreakpointType.STEP_OVER); - int endI = target.size(); - end.set(endI + 1); - - // WhileNode.replaceBreaks(target, label, start, mid - 1, mid, end + 1); - target.add(Instruction.jmpIf(start - endI)); - } - - public DoWhileNode(Location loc, String label, Node condition, Node body) { - super(loc); - this.label = label; - this.condition = condition; - this.body = body; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var labelRes = JavaScript.parseLabel(src, i + n); - n += labelRes.n; - - if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); - n += 2; - - var bodyRes = JavaScript.parseStatement(src, i + n); - if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); - n += bodyRes.n; - - if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); - n += 5; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); - n++; - - var condRes = JavaScript.parseExpression(src, i + n, 0); - if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition."); - n += condRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition."); - n++; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement"); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java new file mode 100644 index 0000000..2417902 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java @@ -0,0 +1,36 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class DoWhileStatement extends Statement { + public final Statement condition, body; + public final String label; + + @Override + public void declare(CompileResult target) { + body.declare(target); + } + + @Override + public void compile(CompileResult target, boolean pollute) { + int start = target.size(); + body.compile(target, false, BreakpointType.STEP_OVER); + int mid = target.size(); + condition.compile(target, true, BreakpointType.STEP_OVER); + int end = target.size(); + + WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1); + target.add(Instruction.jmpIf(start - end)); + } + + public DoWhileStatement(Location loc, String label, Statement condition, Statement body) { + super(loc); + this.label = label; + this.condition = condition; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java deleted file mode 100644 index e2c2735..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ /dev/null @@ -1,116 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class ForInNode extends Node { - public final String varName; - public final boolean isDeclaration; - public final Node object, body; - public final String label; - public final Location varLocation; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - if (isDeclaration) target.scope.define(varName, false, loc()); - } - - @Override public void compile(CompileResult target, boolean pollute) { - object.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.keys(true)); - - int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - int mid = target.temp(); - - target.add(Instruction.pushValue("value")).setLocation(varLocation); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(VariableNode.toSet(target, loc(), varName, pollute, isDeclaration)); - target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); - - var end = new DeferredIntSupplier(); - - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - int endI = target.size(); - - // WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - endI)); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(endI - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForInNode(Location loc, Location varLocation, String label, boolean isDecl, String varName, Node object, Node body) { - super(loc); - this.varLocation = varLocation; - this.label = label; - this.isDeclaration = isDecl; - this.varName = varName; - this.object = object; - this.body = body; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); - n += 3; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); - n++; - n += Parsing.skipEmpty(src, i + n); - - var isDecl = false; - - if (Parsing.isIdentifier(src, i + n, "var")) { - isDecl = true; - n += 3; - } - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-in loop"); - var nameLoc = src.loc(i + n); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); - n += 2; - - var obj = JavaScript.parseExpression(src, i + n, 0); - if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); - n += obj.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); - n++; - - var bodyRes = JavaScript.parseStatement(src, i + n); - if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); - n += bodyRes.n; - - return ParseRes.res(new ForInNode(loc, nameLoc, label.result, isDecl, name.result, obj.result, bodyRes.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java new file mode 100644 index 0000000..216509a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ForInStatement extends Statement { + public final String varName; + public final boolean isDeclaration; + public final Statement varValue, object, body; + public final String label; + public final Location varLocation; + + @Override + public void declare(CompileResult target) { + body.declare(target); + if (isDeclaration) target.scope.define(varName); + } + + @Override + public void compile(CompileResult target, boolean pollute) { + var key = target.scope.getKey(varName); + + if (key instanceof String) target.add(Instruction.makeVar((String)key)); + + if (varValue != null) { + varValue.compile(target, true); + target.add(Instruction.storeVar(target.scope.getKey(varName))); + } + + object.compile(target, true, BreakpointType.STEP_OVER); + target.add(Instruction.keys(true)); + + int start = target.size(); + target.add(Instruction.dup()); + target.add(Instruction.pushUndefined()); + target.add(Instruction.operation(Operation.EQUALS)); + int mid = target.temp(); + + target.add(Instruction.pushValue("value")).setLocation(varLocation); + target.add(Instruction.loadMember()).setLocation(varLocation); + target.add(Instruction.storeVar(key)).setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); + + body.compile(target, false, BreakpointType.STEP_OVER); + + int end = target.size(); + + WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); + + target.add(Instruction.jmp(start - end)); + target.add(Instruction.discard()); + target.set(mid, Instruction.jmpIf(end - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { + super(loc); + this.varLocation = varLocation; + this.label = label; + this.isDeclaration = isDecl; + this.varName = varName; + this.varValue = varValue; + this.object = object; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java deleted file mode 100644 index 8a7c3d3..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ /dev/null @@ -1,124 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.VariableDeclareNode; -import me.topchetoeu.jscript.compilation.values.operations.DiscardNode; - -public class ForNode extends Node { - public final Node declaration, assignment, condition, body; - public final String label; - - @Override public void resolve(CompileResult target) { - declaration.resolve(target); - body.resolve(target); - } - @Override public void compile(CompileResult target, boolean pollute) { - declaration.compile(target, false, BreakpointType.STEP_OVER); - - int start = target.size(); - condition.compile(target, true, BreakpointType.STEP_OVER); - int mid = target.temp(); - - var end = new DeferredIntSupplier(); - - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - // int beforeAssign = target.size(); - assignment.compile(target, false, BreakpointType.STEP_OVER); - int endI = target.size(); - end.set(endI); - - // WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); - - target.add(Instruction.jmp(start - endI)); - target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { - super(loc); - this.label = label; - this.declaration = declaration; - this.condition = condition; - this.assignment = assignment; - this.body = body; - } - - private static ParseRes parseSemicolon(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, ";")) return ParseRes.failed(); - else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1); - } - private static ParseRes parseCondition(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var res = JavaScript.parseExpression(src, i + n, 0); - if (!res.isSuccess()) return res.chainError(); - n += res.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon"); - else return ParseRes.res(res.result, n + 1); - } - private static ParseRes parseUpdater(Source src, int i) { - return JavaScript.parseExpression(src, i, 0); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var labelRes = JavaScript.parseLabel(src, i + n); - n += labelRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); - n += 3; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'"); - n++; - - ParseRes decl = ParseRes.first(src, i + n, - ForNode::parseSemicolon, - VariableDeclareNode::parse, - ForNode::parseCondition - ); - if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression"); - n += decl.n; - - ParseRes cond = ParseRes.first(src, i + n, - ForNode::parseSemicolon, - ForNode::parseCondition - ); - if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition"); - n += cond.n; - - var update = parseUpdater(src, i + n); - if (update.isError()) return update.chainError(); - n += update.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater"); - n++; - - var body = JavaScript.parseStatement(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body."); - n += body.n; - - return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java deleted file mode 100644 index 87ffb45..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java +++ /dev/null @@ -1,125 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class ForOfNode extends Node { - public final String varName; - public final boolean isDeclaration; - public final Node iterable, body; - public final String label; - public final Location varLocation; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - if (isDeclaration) target.scope.define(varName, false, varLocation); - } - - @Override public void compile(CompileResult target, boolean pollute) { - iterable.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.dup()); - target.add(Instruction.loadIntrinsics("it_key")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - - int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.dup()); - target.add(Instruction.pushValue("next")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - target.add(Instruction.dup()); - target.add(Instruction.pushValue("done")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - int mid = target.temp(); - - target.add(Instruction.pushValue("value")); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(VariableNode.toSet(target, varLocation, varName, false, isDeclaration)); - - var end = new DeferredIntSupplier(); - - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - int endI = target.size(); - end.set(endI); - - // WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - endI)); - target.add(Instruction.discard()); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(endI - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForOfNode(Location loc, Location varLocation, String label, boolean isDecl, String varName, Node object, Node body) { - super(loc); - this.varLocation = varLocation; - this.label = label; - this.isDeclaration = isDecl; - this.varName = varName; - this.iterable = object; - this.body = body; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); - n += 3; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); - n++; - n += Parsing.skipEmpty(src, i + n); - - var isDecl = false; - - if (Parsing.isIdentifier(src, i + n, "var")) { - isDecl = true; - n += 3; - } - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-of loop"); - var nameLoc = src.loc(i + n); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); - n += 2; - - var obj = JavaScript.parseExpression(src, i + n, 0); - if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); - n += obj.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); - n++; - - var bodyRes = JavaScript.parseStatement(src, i + n); - if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); - n += bodyRes.n; - - return ParseRes.res(new ForOfNode(loc, nameLoc, label.result, isDecl, name.result, obj.result, bodyRes.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java new file mode 100644 index 0000000..fba5fc7 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java @@ -0,0 +1,73 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ForOfStatement extends Statement { + public final String varName; + public final boolean isDeclaration; + public final Statement iterable, body; + public final String label; + public final Location varLocation; + + @Override + public void declare(CompileResult target) { + body.declare(target); + if (isDeclaration) target.scope.define(varName); + } + + @Override + public void compile(CompileResult target, boolean pollute) { + var key = target.scope.getKey(varName); + + if (key instanceof String) target.add(Instruction.makeVar((String)key)); + + iterable.compile(target, true, BreakpointType.STEP_OVER); + target.add(Instruction.dup()); + target.add(Instruction.loadVar("Symbol")); + target.add(Instruction.pushValue("iterator")); + target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.call(0)).setLocation(iterable.loc()); + + int start = target.size(); + target.add(Instruction.dup()); + target.add(Instruction.dup()); + target.add(Instruction.pushValue("next")); + target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.call(0)).setLocation(iterable.loc()); + target.add(Instruction.dup()); + target.add(Instruction.pushValue("done")); + target.add(Instruction.loadMember()).setLocation(iterable.loc()); + int mid = target.temp(); + + target.add(Instruction.pushValue("value")); + target.add(Instruction.loadMember()).setLocation(varLocation); + target.add(Instruction.storeVar(key)).setLocationAndDebug(iterable.loc(), BreakpointType.STEP_OVER); + + body.compile(target, false, BreakpointType.STEP_OVER); + + int end = target.size(); + + WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); + + target.add(Instruction.jmp(start - end)); + target.add(Instruction.discard()); + target.add(Instruction.discard()); + target.set(mid, Instruction.jmpIf(end - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ForOfStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) { + super(loc); + this.varLocation = varLocation; + this.label = label; + this.isDeclaration = isDecl; + this.varName = varName; + this.iterable = object; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java new file mode 100644 index 0000000..9a59613 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForStatement.java @@ -0,0 +1,45 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ForStatement extends Statement { + public final Statement declaration, assignment, condition, body; + public final String label; + + @Override + public void declare(CompileResult target) { + declaration.declare(target); + body.declare(target); + } + @Override + public void compile(CompileResult target, boolean pollute) { + declaration.compile(target, false, BreakpointType.STEP_OVER); + + int start = target.size(); + condition.compile(target, true, BreakpointType.STEP_OVER); + int mid = target.temp(); + body.compile(target, false, BreakpointType.STEP_OVER); + int beforeAssign = target.size(); + assignment.compile(target, false, BreakpointType.STEP_OVER); + int end = target.size(); + + WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); + + target.add(Instruction.jmp(start - end)); + target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) { + super(loc); + this.label = label; + this.declaration = declaration; + this.condition = condition; + this.assignment = assignment; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java deleted file mode 100644 index d6a07f5..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java +++ /dev/null @@ -1,131 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; - -public class IfNode extends Node { - public final Node condition, body, elseBody; - public final String label; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - if (elseBody != null) elseBody.resolve(target); - } - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { - condition.compile(target, true, breakpoint); - - if (elseBody == null) { - int start = target.temp(); - var end = new DeferredIntSupplier(); - - LabelContext.getBreak(target.env).push(loc(), label, end); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.getBreak(target.env).pop(label); - - int endI = target.size(); - end.set(endI); - - target.set(start, Instruction.jmpIfNot(endI - start)); - } - else { - int start = target.temp(); - var end = new DeferredIntSupplier(); - - LabelContext.getBreak(target.env).push(loc(), label, end); - body.compile(target, false, BreakpointType.STEP_OVER); - - int mid = target.temp(); - - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.getBreak(target.env).pop(label); - - int endI = target.size(); - end.set(endI); - - target.set(start, Instruction.jmpIfNot(mid - start + 1)); - target.set(mid, Instruction.jmp(endI - mid)); - } - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, BreakpointType.STEP_IN); - } - - public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) { - super(loc); - this.condition = condition; - this.body = body; - this.elseBody = elseBody; - this.label = label; - } - - public static ParseRes parseTernary(Source src, int i, Node prev, int precedence) { - if (precedence > 2) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, "?")) return ParseRes.failed(); - var loc = src.loc(i + n); - n++; - - var a = JavaScript.parseExpression(src, i + n, 2); - if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); - n += a.n; - n += Parsing.skipEmpty(src, i); - - if (!src.is(i + n, ":")) return ParseRes.failed(); - n++; - - var b = JavaScript.parseExpression(src, i + n, 2); - if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); - n += b.n; - - return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n); - } - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed(); - n += 2; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'."); - n++; - - var condRes = JavaScript.parseExpression(src, i + n, 0); - if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition."); - n += condRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition."); - n++; - - var res = JavaScript.parseStatement(src, i + n); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body."); - n += res.n; - - var elseKw = Parsing.parseIdentifier(src, i + n, "else"); - if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n); - n += elseKw.n; - - var elseRes = JavaScript.parseStatement(src, i + n); - if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); - n += elseRes.n; - - return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java new file mode 100644 index 0000000..ba37671 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/IfStatement.java @@ -0,0 +1,48 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class IfStatement extends Statement { + public final Statement condition, body, elseBody; + + @Override + public void declare(CompileResult target) { + body.declare(target); + if (elseBody != null) elseBody.declare(target); + } + + @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { + condition.compile(target, true, breakpoint); + + if (elseBody == null) { + int i = target.temp(); + body.compile(target, pollute, breakpoint); + int endI = target.size(); + target.set(i, Instruction.jmpIfNot(endI - i)); + } + else { + int start = target.temp(); + body.compile(target, pollute, breakpoint); + int mid = target.temp(); + elseBody.compile(target, pollute, breakpoint); + int end = target.size(); + + target.set(start, Instruction.jmpIfNot(mid - start + 1)); + target.set(mid, Instruction.jmp(end - mid)); + } + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, BreakpointType.STEP_IN); + } + + public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) { + super(loc); + this.condition = condition; + this.body = body; + this.elseBody = elseBody; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java deleted file mode 100644 index 3caccea..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java +++ /dev/null @@ -1,50 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -public class ReturnNode extends Node { - public final Node value; - - @Override public void compile(CompileResult target, boolean pollute) { - if (value == null) target.add(Instruction.pushUndefined()); - else value.compile(target, true); - target.add(Instruction.ret()).setLocation(loc()); - } - - public ReturnNode(Location loc, Node value) { - super(loc); - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed(); - n += 6; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ReturnNode(loc, null), n); - } - - var val = JavaScript.parseExpression(src, i + n, 0); - if (val.isError()) return val.chainError(); - n += val.n; - - end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ReturnNode(loc, val.result), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement or a return value"); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java new file mode 100644 index 0000000..4e915fd --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ReturnStatement extends Statement { + public final Statement value; + + @Override + public void compile(CompileResult target, boolean pollute) { + if (value == null) target.add(Instruction.pushUndefined()); + else value.compile(target, true); + target.add(Instruction.ret()).setLocation(loc()); + } + + public ReturnStatement(Location loc, Statement value) { + super(loc); + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java deleted file mode 100644 index f41a997..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java +++ /dev/null @@ -1,203 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import java.util.ArrayList; -import java.util.HashMap; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; - -public class SwitchNode extends Node { - public static class SwitchCase { - public final Node value; - public final int statementI; - - public SwitchCase(Node value, int statementI) { - this.value = value; - this.statementI = statementI; - } - } - - public final Node value; - public final SwitchCase[] cases; - public final Node[] body; - public final int defaultI; - public final String label; - - @Override public void resolve(CompileResult target) { - for (var stm : body) stm.resolve(target); - } - - @Override public void compile(CompileResult target, boolean pollute) { - var caseToStatement = new HashMap(); - var statementToIndex = new HashMap(); - - value.compile(target, true, BreakpointType.STEP_OVER); - - var subtarget = target.subtarget(); - subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.allocCount())); - - // TODO: create a jump map - for (var ccase : cases) { - subtarget.add(Instruction.dup()); - ccase.value.compile(subtarget, true); - subtarget.add(Instruction.operation(Operation.EQUALS)); - caseToStatement.put(subtarget.temp(), ccase.statementI); - } - - int start = subtarget.temp(); - var end = new DeferredIntSupplier(); - - LabelContext.getBreak(target.env).push(loc(), label, end); - for (var stm : body) { - statementToIndex.put(statementToIndex.size(), subtarget.size()); - stm.compile(subtarget, false, BreakpointType.STEP_OVER); - } - LabelContext.getBreak(target.env).pop(label); - - subtarget.scope.end(); - subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); - - int endI = subtarget.size(); - end.set(endI); - subtarget.add(Instruction.discard()); - if (pollute) subtarget.add(Instruction.pushUndefined()); - - if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start)); - else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); - - // for (int i = start; i < end; i++) { - // var instr = target.get(i); - // if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { - // target.set(i, Instruction.jmp(end - i)); - // } - // } - for (var el : caseToStatement.entrySet()) { - var i = statementToIndex.get(el.getValue()); - if (i == null) i = endI; - subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); - } - - } - - public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) { - super(loc); - this.label = label; - this.value = value; - this.defaultI = defaultI; - this.cases = cases; - this.body = body; - } - - private static ParseRes parseSwitchCase(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); - n += 4; - - var val = JavaScript.parseExpression(src, i + n, 0); - if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'"); - n += val.n; - - if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); - n++; - - return ParseRes.res(val.result, n); - } - private static ParseRes parseDefaultCase(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed(); - n += 7; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'"); - n++; - - return ParseRes.res(null, n); - } - @SuppressWarnings("unused") - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed(); - n += 6; - n += Parsing.skipEmpty(src, i + n); - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'"); - n++; - - var val = JavaScript.parseExpression(src, i + n, 0); - if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value"); - n += val.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value"); - n++; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value"); - n++; - n += Parsing.skipEmpty(src, i + n); - - var statements = new ArrayList(); - var cases = new ArrayList(); - var defaultI = -1; - - while (true) { - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - if (src.is(i + n, ";")) { - n++; - continue; - } - - ParseRes caseRes = ParseRes.first(src, i + n, - SwitchNode::parseDefaultCase, - SwitchNode::parseSwitchCase - ); - - // Parsing::parseStatement - - if (caseRes.isSuccess()) { - n += caseRes.n; - - if (caseRes.result == null) defaultI = statements.size(); - else cases.add(new SwitchCase(caseRes.result, statements.size())); - continue; - } - if (caseRes.isError()) return caseRes.chainError(); - - var stm = JavaScript.parseStatement(src, i + n); - if (stm.isSuccess()) { - n += stm.n; - statements.add(stm.result); - continue; - } - else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'"); - } - - return ParseRes.res(new SwitchNode( - loc, label.result, val.result, defaultI, - cases.toArray(SwitchCase[]::new), - statements.toArray(Node[]::new) - ), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java new file mode 100644 index 0000000..bf4988c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -0,0 +1,83 @@ +package me.topchetoeu.jscript.compilation.control; + +import java.util.HashMap; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.Instruction.Type; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class SwitchStatement extends Statement { + public static class SwitchCase { + public final Statement value; + public final int statementI; + + public SwitchCase(Statement value, int statementI) { + this.value = value; + this.statementI = statementI; + } + } + + public final Statement value; + public final SwitchCase[] cases; + public final Statement[] body; + public final int defaultI; + + @Override + public void declare(CompileResult target) { + for (var stm : body) stm.declare(target); + } + + @Override + public void compile(CompileResult target, boolean pollute) { + var caseToStatement = new HashMap(); + var statementToIndex = new HashMap(); + + value.compile(target, true, BreakpointType.STEP_OVER); + + for (var ccase : cases) { + target.add(Instruction.dup()); + ccase.value.compile(target, true); + target.add(Instruction.operation(Operation.EQUALS)); + caseToStatement.put(target.temp(), ccase.statementI); + } + + int start = target.temp(); + + for (var stm : body) { + statementToIndex.put(statementToIndex.size(), target.size()); + stm.compile(target, false, BreakpointType.STEP_OVER); + } + + int end = target.size(); + target.add(Instruction.discard()); + if (pollute) target.add(Instruction.pushUndefined()); + + if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start)); + else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); + + for (int i = start; i < end; i++) { + var instr = target.get(i); + if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { + target.set(i, Instruction.jmp(end - i)); + } + } + for (var el : caseToStatement.entrySet()) { + var i = statementToIndex.get(el.getValue()); + if (i == null) i = end; + target.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); + } + + } + + public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { + super(loc); + this.value = value; + this.defaultI = defaultI; + this.cases = cases; + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java deleted file mode 100644 index 2c6cb37..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java +++ /dev/null @@ -1,49 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -public class ThrowNode extends Node { - public final Node value; - - @Override public void compile(CompileResult target, boolean pollute) { - value.compile(target, true); - target.add(Instruction.throwInstr()).setLocation(loc()); - } - - public ThrowNode(Location loc, Node value) { - super(loc); - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); - n += 5; - - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ThrowNode(loc, null), n); - } - - var val = JavaScript.parseExpression(src, i + n, 0); - if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); - n += val.n; - - end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new ThrowNode(loc, val.result), n); - } - else return end.chainError(src.loc(i + n), "Expected end of statement"); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java new file mode 100644 index 0000000..156f7ae --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ThrowStatement extends Statement { + public final Statement value; + + @Override + public void compile(CompileResult target, boolean pollute) { + value.compile(target, true); + target.add(Instruction.throwInstr()).setLocation(loc()); + } + + public ThrowStatement(Location loc, Statement value) { + super(loc); + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java deleted file mode 100644 index 04e4650..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java +++ /dev/null @@ -1,134 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; - -public class TryNode extends Node { - public final CompoundNode tryBody; - public final CompoundNode catchBody; - public final CompoundNode finallyBody; - public final String captureName; - public final String label; - - @Override public void resolve(CompileResult target) { - tryBody.resolve(target); - catchBody.resolve(target); - finallyBody.resolve(target); - } - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { - int replace = target.temp(); - var endSuppl = new DeferredIntSupplier(); - - int start = replace + 1, catchStart = -1, finallyStart = -1; - - LabelContext.getBreak(target.env).push(loc(), label, endSuppl); - - tryBody.compile(target, false); - target.add(Instruction.tryEnd()); - - if (catchBody != null) { - catchStart = target.size() - start; - - if (captureName != null) { - var subtarget = target.subtarget(); - subtarget.scope.defineStrict(captureName, false, catchBody.loc()); - catchBody.compile(subtarget, false); - subtarget.scope.end(); - } - else catchBody.compile(target, false); - - target.add(Instruction.tryEnd()); - } - - if (finallyBody != null) { - finallyStart = target.size() - start; - finallyBody.compile(target, false); - } - - LabelContext.getBreak(target.env).pop(label); - - endSuppl.set(target.size()); - - target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); - target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); - - if (pollute) target.add(Instruction.pushUndefined()); - } - - public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) { - super(loc); - this.tryBody = tryBody; - this.catchBody = catchBody; - this.finallyBody = finallyBody; - this.captureName = captureName; - this.label = label; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var labelRes = JavaScript.parseLabel(src, i + n); - n += labelRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed(); - n += 3; - - var tryBody = CompoundNode.parse(src, i + n); - if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body"); - n += tryBody.n; - n += Parsing.skipEmpty(src, i + n); - - String capture = null; - CompoundNode catchBody = null, finallyBody = null; - - if (Parsing.isIdentifier(src, i + n, "catch")) { - n += 5; - n += Parsing.skipEmpty(src, i + n); - if (src.is(i + n, "(")) { - n++; - var nameRes = Parsing.parseIdentifier(src, i + n); - if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name"); - capture = nameRes.result; - n += nameRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name"); - n++; - } - - var bodyRes = CompoundNode.parse(src, i + n); - if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body"); - n += bodyRes.n; - n += Parsing.skipEmpty(src, i + n); - - catchBody = bodyRes.result; - } - - if (Parsing.isIdentifier(src, i + n, "finally")) { - n += 7; - - var bodyRes = CompoundNode.parse(src, i + n); - if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body"); - n += bodyRes.n; - n += Parsing.skipEmpty(src, i + n); - finallyBody = bodyRes.result; - } - - if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally"); - - return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java new file mode 100644 index 0000000..41ce48f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -0,0 +1,58 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class TryStatement extends Statement { + public final Statement tryBody; + public final Statement catchBody; + public final Statement finallyBody; + public final String name; + + @Override + public void declare(CompileResult target) { + tryBody.declare(target); + if (catchBody != null) catchBody.declare(target); + if (finallyBody != null) finallyBody.declare(target); + } + + @Override + public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { + int replace = target.temp(); + + int start = replace + 1, catchStart = -1, finallyStart = -1; + + tryBody.compile(target, false); + target.add(Instruction.tryEnd()); + + if (catchBody != null) { + catchStart = target.size() - start; + target.scope.define(name, true); + catchBody.compile(target, false); + target.scope.undefine(); + target.add(Instruction.tryEnd()); + } + + if (finallyBody != null) { + finallyStart = target.size() - start; + finallyBody.compile(target, false); + target.add(Instruction.tryEnd()); + } + + target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); + target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); + + if (pollute) target.add(Instruction.pushUndefined()); + } + + public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { + super(loc); + this.tryBody = tryBody; + this.catchBody = catchBody; + this.finallyBody = finallyBody; + this.name = name; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java deleted file mode 100644 index 55787df..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java +++ /dev/null @@ -1,92 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; - -public class WhileNode extends Node { - public final Node condition, body; - public final String label; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - } - @Override public void compile(CompileResult target, boolean pollute) { - int start = target.size(); - condition.compile(target, true); - int mid = target.temp(); - - var end = new DeferredIntSupplier(); - - - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - var endI = target.size(); - end.set(endI + 1); - - // replaceBreaks(target, label, mid + 1, end, start, end + 1); - - target.add(Instruction.jmp(start - end.getAsInt())); - target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public WhileNode(Location loc, String label, Node condition, Node body) { - super(loc); - this.label = label; - this.condition = condition; - this.body = body; - } - - // public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) { - // for (int i = start; i < end; i++) { - // var instr = target.get(i); - // if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { - // target.set(i, Instruction.jmp(continuePoint - i)); - // } - // if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { - // target.set(i, Instruction.jmp(breakPoint - i)); - // } - // } - // } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); - n += 5; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); - n++; - - var cond = JavaScript.parseExpression(src, i + n, 0); - if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition."); - n += cond.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); - n++; - - var body = JavaScript.parseStatement(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body."); - n += body.n; - - return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java new file mode 100644 index 0000000..459e4ef --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.compilation.control; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.Instruction.Type; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class WhileStatement extends Statement { + public final Statement condition, body; + public final String label; + + @Override + public void declare(CompileResult target) { + body.declare(target); + } + @Override + public void compile(CompileResult target, boolean pollute) { + int start = target.size(); + condition.compile(target, true); + int mid = target.temp(); + body.compile(target, false, BreakpointType.STEP_OVER); + + int end = target.size(); + + replaceBreaks(target, label, mid + 1, end, start, end + 1); + + target.add(Instruction.jmp(start - end)); + target.set(mid, Instruction.jmpIfNot(end - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public WhileStatement(Location loc, String label, Statement condition, Statement body) { + super(loc); + this.label = label; + this.condition = condition; + this.body = body; + } + + public static void replaceBreaks(CompileResult target, String label, int start, int end, int continuePoint, int breakPoint) { + for (int i = start; i < end; i++) { + var instr = target.get(i); + if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) { + target.set(i, Instruction.jmp(continuePoint - i)); + } + if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) { + target.set(i, Instruction.jmp(breakPoint - i)); + } + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java new file mode 100644 index 0000000..3e06258 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Operator.java @@ -0,0 +1,113 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.Operation; + +public enum Operator { + MULTIPLY("*", Operation.MULTIPLY, 13), + DIVIDE("/", Operation.DIVIDE, 12), + MODULO("%", Operation.MODULO, 12), + SUBTRACT("-", Operation.SUBTRACT, 11), + ADD("+", Operation.ADD, 11), + SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10), + SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10), + USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10), + GREATER(">", Operation.GREATER, 9), + LESS("<", Operation.LESS, 9), + GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9), + LESS_EQUALS("<=", Operation.LESS_EQUALS, 9), + NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8), + LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8), + EQUALS("==", Operation.LOOSE_EQUALS, 8), + LOOSE_EQUALS("===", Operation.EQUALS, 8), + AND("&", Operation.AND, 7), + XOR("^", Operation.XOR, 6), + OR("|", Operation.OR, 5), + LAZY_AND("&&", 4), + LAZY_OR("||", 3), + ASSIGN_SHIFT_LEFT("<<=", 2, true), + ASSIGN_SHIFT_RIGHT(">>=", 2, true), + ASSIGN_USHIFT_RIGHT(">>>=", 2, true), + ASSIGN_AND("&=", 2, true), + ASSIGN_OR("|=", 2, true), + ASSIGN_XOR("^=", 2, true), + ASSIGN_MODULO("%=", 2, true), + ASSIGN_DIVIDE("/=", 2, true), + ASSIGN_MULTIPLY("*=", 2, true), + ASSIGN_SUBTRACT("-=", 2, true), + ASSIGN_ADD("+=", 2, true), + ASSIGN("=", 2, true), + SEMICOLON(";"), + COLON(":"), + PAREN_OPEN("("), + PAREN_CLOSE(")"), + BRACKET_OPEN("["), + BRACKET_CLOSE("]"), + BRACE_OPEN("{"), + BRACE_CLOSE("}"), + DOT("."), + COMMA(","), + NOT("!"), + QUESTION("?"), + INVERSE("~"), + INCREASE("++"), + DECREASE("--"); + + public final String readable; + public final Operation operation; + public final int precedence; + public final boolean reverse; + private static final Map ops = new HashMap<>(); + + static { + for (var el : Operator.values()) { + ops.put(el.readable, el); + } + } + + public boolean isAssign() { return precedence == 2; } + + public static Operator parse(String val) { + return ops.get(val); + } + + private Operator() { + this.readable = null; + this.operation = null; + this.precedence = -1; + this.reverse = false; + } + private Operator(String value) { + this.readable = value; + this.operation = null; + this.precedence = -1; + this.reverse = false; + } + private Operator(String value, int precedence) { + this.readable = value; + this.operation = null; + this.precedence = precedence; + this.reverse = false; + } + private Operator(String value, int precedence, boolean reverse) { + this.readable = value; + this.operation = null; + this.precedence = precedence; + this.reverse = reverse; + } + + private Operator(String value, Operation funcName, int precedence) { + this.readable = value; + this.operation = funcName; + this.precedence = precedence; + this.reverse = false; + } + private Operator(String value, Operation funcName, int precedence, boolean reverse) { + this.readable = value; + this.operation = funcName; + this.precedence = precedence; + this.reverse = reverse; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java new file mode 100644 index 0000000..982c504 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java @@ -0,0 +1,100 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser; + +public class ParseRes { + public static enum State { + SUCCESS, + FAILED, + ERROR; + + public boolean isSuccess() { return this == SUCCESS; } + public boolean isFailed() { return this == FAILED; } + public boolean isError() { return this == ERROR; } + } + + public final ParseRes.State state; + public final String error; + public final T result; + public final int n; + + private ParseRes(ParseRes.State state, String error, T result, int readN) { + this.result = result; + this.n = readN; + this.state = state; + this.error = error; + } + + public ParseRes setN(int i) { + if (!state.isSuccess()) return this; + return new ParseRes<>(state, null, result, i); + } + public ParseRes addN(int i) { + if (!state.isSuccess()) return this; + return new ParseRes<>(state, null, result, this.n + i); + } + public ParseRes transform() { + if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); + return new ParseRes<>(state, error, null, 0); + } + public TestRes toTest() { + if (isSuccess()) return TestRes.res(n); + else if (isError()) return TestRes.error(null, error); + else return TestRes.failed(); + } + + public boolean isSuccess() { return state.isSuccess(); } + public boolean isFailed() { return state.isFailed(); } + public boolean isError() { return state.isError(); } + + public static ParseRes failed() { + return new ParseRes(State.FAILED, null, null, 0); + } + public static ParseRes error(Location loc, String error) { + if (loc != null) error = loc + ": " + error; + return new ParseRes<>(State.ERROR, error, null, 0); + } + public static ParseRes error(Location loc, String error, ParseRes other) { + if (loc != null) error = loc + ": " + error; + if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0); + return new ParseRes<>(State.ERROR, other.error, null, 0); + } + public static ParseRes res(T val, int i) { + return new ParseRes<>(State.SUCCESS, null, val, i); + } + + @SafeVarargs + public static ParseRes any(ParseRes ...parsers) { + return any(List.of(parsers)); + } + public static ParseRes any(List> parsers) { + ParseRes best = null; + ParseRes error = ParseRes.failed(); + + for (var parser : parsers) { + if (parser.isSuccess()) { + if (best == null || best.n < parser.n) best = parser; + } + else if (parser.isError() && error.isFailed()) error = parser.transform(); + } + + if (best != null) return best; + else return error; + } + @SafeVarargs + public static ParseRes first(String filename, List tokens, Map> named, Parser ...parsers) { + ParseRes error = ParseRes.failed(); + + for (var parser : parsers) { + var res = parser.parse(null, tokens, 0); + if (res.isSuccess()) return res; + else if (res.isError() && error.isFailed()) error = res.transform(); + } + + return error; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java new file mode 100644 index 0000000..def1449 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java @@ -0,0 +1,1933 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.compilation.*; +import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; +import me.topchetoeu.jscript.compilation.control.*; +import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; +import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; +import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; +import me.topchetoeu.jscript.compilation.values.*; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +// TODO: this has to be rewritten +public class Parsing { + public static interface Parser { + ParseRes parse(Filename filename, List tokens, int i); + } + + private static class ObjProp { + public final String name; + public final String access; + public final FunctionStatement func; + + public ObjProp(String name, String access, FunctionStatement func) { + this.name = name; + this.access = access; + this.func = func; + } + } + + public static final HashMap> functions = new HashMap<>(); + + private static final HashSet reserved = new HashSet(); + static { + reserved.add("true"); + reserved.add("false"); + reserved.add("void"); + reserved.add("null"); + reserved.add("this"); + reserved.add("if"); + reserved.add("else"); + reserved.add("try"); + reserved.add("catch"); + reserved.add("finally"); + reserved.add("for"); + reserved.add("do"); + reserved.add("while"); + reserved.add("switch"); + reserved.add("case"); + reserved.add("default"); + reserved.add("new"); + reserved.add("function"); + reserved.add("var"); + reserved.add("return"); + reserved.add("throw"); + reserved.add("typeof"); + reserved.add("delete"); + reserved.add("break"); + reserved.add("continue"); + reserved.add("debugger"); + reserved.add("implements"); + reserved.add("interface"); + reserved.add("package"); + reserved.add("private"); + reserved.add("protected"); + reserved.add("public"); + reserved.add("static"); + // Although ES5 allow these, we will comply to ES6 here + reserved.add("const"); + reserved.add("let"); + // These are allowed too, however our parser considers them keywords + reserved.add("undefined"); + reserved.add("arguments"); + reserved.add("globalThis"); + reserved.add("window"); + reserved.add("self"); + // We allow yield and await, because they're part of the custom async and generator functions + } + + public static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + public static boolean isWhitespace(char c) { + return isAny(c, " \t\r\n"); + } + public static boolean isLetter(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + public static boolean isAlphanumeric(char c) { + return isLetter(c) || isDigit(c); + } + public static boolean isAny(char c, String alphabet) { + return alphabet.contains(Character.toString(c)); + } + + private static final int CURR_NONE = 0; + private static final int CURR_NUMBER = 1; + private static final int CURR_FLOAT = 11; + private static final int CURR_SCIENTIFIC_NOT = 12; + private static final int CURR_NEG_SCIENTIFIC_NOT = 13; + private static final int CURR_HEX = 14; + private static final int CURR_STRING = 2; + private static final int CURR_LITERAL = 3; + private static final int CURR_OPERATOR = 4; + private static final int CURR_REGEX = 6; + private static final int CURR_REGEX_FLAGS = 7; + private static final int CURR_MULTI_COMMENT = 8; + private static final int CURR_SINGLE_COMMENT = 9; + + private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List tokens) { + var res = currToken.toString(); + + switch (currStage) { + case CURR_STRING: tokens.add(new RawToken(res, TokenType.STRING, line, lastStart)); break; + case CURR_REGEX_FLAGS: tokens.add(new RawToken(res, TokenType.REGEX, line, lastStart)); break; + case CURR_NUMBER: + case CURR_HEX: + case CURR_NEG_SCIENTIFIC_NOT: + case CURR_SCIENTIFIC_NOT: + case CURR_FLOAT: + tokens.add(new RawToken(res, TokenType.NUMBER, line, lastStart)); break; + case CURR_LITERAL: tokens.add(new RawToken(res, TokenType.LITERAL, line, lastStart)); break; + case CURR_OPERATOR: tokens.add(new RawToken(res, TokenType.OPERATOR, line, lastStart)); break; + } + + currToken.delete(0, currToken.length()); + } + + // This method is so long because we're tokenizing the string using an iterative approach + // instead of a recursive descent parser. This is mainly done for performance reasons. + private static ArrayList splitTokens(Filename filename, String raw) { + var tokens = new ArrayList(); + var currToken = new StringBuilder(64); + + // Those are state variables, and will be reset every time a token has ended parsing + boolean lastEscape = false, inBrackets = false; + + int line = 1, start = 1, lastStart = 1, parenI = 0; + var loc = new Location(line, lastStart, filename); + int currStage = CURR_NONE; + + // when we want to continue parsing a token, we will execute continue;, which will skip + // the token end logic + loop: for (int i = 0; i < raw.length(); i++) { + char c = raw.charAt(i); + + start++; + + switch (currStage) { + case CURR_STRING: + currToken.append(c); + + if (!lastEscape) { + if (c == '\n') throw new SyntaxException(loc, "Can't have a multiline string."); + else if (c == '\\') { + lastEscape = true; + continue; + } + else if (c != currToken.charAt(0)) continue; + } + else { + lastEscape = false; + continue; + } + break; + case CURR_REGEX: + currToken.append(c); + if (!lastEscape) { + if (c == '\\') lastEscape = true; + if (c == '/' & parenI == 0 & !inBrackets) { + currStage = CURR_REGEX_FLAGS; + continue; + } + if (c == '[') inBrackets = true; + if (c == ']') inBrackets = false; + if (c == '(' && !inBrackets) parenI++; + if (c == ')' && !inBrackets) parenI--; + } + else lastEscape = false; + continue; + case CURR_REGEX_FLAGS: + if (isAny(c, "dgimsuy")) { + currToken.append(c); + continue; + } + i--; start--; + break; + case CURR_NUMBER: + if (c == '.') currStage = CURR_FLOAT; + else if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; + else if ((c == 'x' || c == 'X') && currToken.toString().equals("0")) currStage = CURR_HEX; + else if (!isDigit(c)) { + i--; start--; + break; + } + currToken.append(c); + continue; + case CURR_FLOAT: + if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; + else if (!isDigit(c)) { + i--; start--; + break; + } + currToken.append(c); + continue; + case CURR_SCIENTIFIC_NOT: + if (c == '-') { + if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT; + } + if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) { + i--; start--; + break; + } + currToken.append(c); + continue; + case CURR_NEG_SCIENTIFIC_NOT: + if (isDigit(c)) currToken.append(c); + else { + i--; start--; + break; + } + continue; + case CURR_HEX: + if (isDigit(c) || isAny(c, "ABCDEFabcdef")) currToken.append(c); + else { + i--; start--; + break; + } + continue; + case CURR_SINGLE_COMMENT: + currToken.delete(0, currToken.length()); + if (c != '\n') continue; + else { + line++; + start = 1; + } + break; + case CURR_MULTI_COMMENT: + if (c == '\n') line++; + if (!(currToken.charAt(0) == '*' && c == '/')) { + currToken.delete(0, currToken.length()); + currToken.append(c); + continue; + } + break; + case CURR_LITERAL: + if (isAlphanumeric(c) || c == '_' || c == '$') { + currToken.append(c); + continue; + } + else { i--; start--; } + break; + case CURR_OPERATOR: { + // here we do several things: + // - detect a comment + // - detect a regular expression + // - detect a float number (.xxxx) + // - read an operator greedily + + // this variable keeps track of whether we're still reading an operator + boolean ok = false; + if (currToken.length() == 1) { + // double operators + if (currToken.charAt(0) == c && isAny(c, "&|=+-<>")) ok = true; + // assignments + else if (c == '=' && isAny(currToken.charAt(0), "&|^+-/*%!<>")) ok = true; + // detect float numbers + else if (isDigit(c) && currToken.charAt(0) == '.') { + currStage = CURR_FLOAT; + currToken.append(c); + continue; + } + else if (currToken.charAt(0) == '/') { + // single line comments + if (c == '/') { + currStage = CURR_SINGLE_COMMENT; + continue; + } + // multiline comments + else if (c == '*') { + currStage = CURR_MULTI_COMMENT; + continue; + } + // regular expressions + else { + // regular expressions must be in the start of a file, or be followed by a + // newline, or an operator + // this is because of expressions like 1 / 2 / 3 (/ 2 /) will get recognized as regex + // still, the closing paren must be ignored, because in an expression, we can't have a value, following a paren + var prevToken = tokens.size() == 0 ? null : tokens.get(tokens.size() - 1); + if (tokens.size() == 0 || ( + prevToken.line < line || + prevToken.type == TokenType.OPERATOR && !prevToken.value.equals(")") || + prevToken.value.equals("return") || + prevToken.value.equals("throe") + )) { + // we look for a second / on the same line + // if we don't find one, we determine the current operator + // to be a division + for (int j = i; j < raw.length(); j++) { + if (raw.charAt(j) == '/') { + i--; start--; + currStage = CURR_REGEX; + continue loop; + } + if (raw.charAt(j) == '\n') break; + } + } + } + } + } + if (currToken.length() == 2) { + var a = currToken.charAt(0); + var b = currToken.charAt(1); + if (( + a == '=' && b == '=' || + a == '!' && b == '=' || + a == '<' && b == '<' || + a == '>' && b == '>' || + a == '>' && b == '>' + ) && c == '=') ok = true; + if (a == '>' && b == '>' && c == '>') ok = true; + } + if ( + currToken.length() == 3 && + currToken.charAt(0) == '>' && + currToken.charAt(1) == '>' && + currToken.charAt(2) == '>' && + c == '=' + ) ok = true; + + if (ok) { + currToken.append(c); + continue; + } + else { i--; start--; } + break; + } + default: + // here we detect what type of token we're reading + if (isAny(c, " \t\n\r")) { + if (c == '\n') { + line++; + start = 1; + } + } + else if (isDigit(c)) { + currToken.append(c); + currStage = CURR_NUMBER; + continue; + } + else if (isAlphanumeric(c) || c == '_' || c == '$') { + currToken.append(c); + currStage = CURR_LITERAL; + continue; + } + else if (isAny(c, "+-/*%=!&|^(){}[];.,<>!:~?")) { + currToken.append(c); + currStage = CURR_OPERATOR; + continue; + } + else if (c == '"' || c == '\'') { + currToken.append(c); + currStage = CURR_STRING; + continue; + } + else throw new SyntaxException(new Location(line, start, filename), String.format("Unrecognized character %s.", c)); + } + + // if we got here, we know that we have encountered the end of a token + addToken(currToken, currStage, line, lastStart, filename, tokens); + lastEscape = inBrackets = false; + currStage = CURR_NONE; + lastStart = start; + } + + // here, we save a leftover token (if any) + switch (currStage) { + case CURR_STRING: throw new SyntaxException(new Location(line, start, filename), "Unterminated string literal."); + case CURR_REGEX: throw new SyntaxException(new Location(line, start, filename), "Incomplete regex."); + } + addToken(currToken, currStage, line, lastStart, filename, tokens); + + return tokens; + } + + public static int fromHex(char c) { + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= '0' && c <= '9') return c - '0'; + return -1; + } + + private static boolean inBounds(List tokens, int i) { + return i >= 0 && i < tokens.size(); + } + + private static String parseString(Location loc, String literal) { + var res = new StringBuilder(); + + for (var i = 1; i < literal.length() - 1; i++) { + if (literal.charAt(i) == '\\') { + char c = literal.charAt(++i); + if (c == 'b') res.append('\b'); + else if (c == 't') res.append('\t'); + else if (c == 'n') res.append('\n'); + else if (c == 'f') res.append('\f'); + else if (c == 'r') res.append('\r'); + else if (c == '0') { + if (i + 1 >= literal.length()) res.append((char)0); + c = literal.charAt(i + 1); + if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); + res.append((char)0); + } + else if (c >= '1' && c <= '9') { + throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); + } + else if (c == 'x') { + var newC = 0; + i++; + for (var j = 0; j < 2; j++) { + if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); + int val = fromHex(literal.charAt(i++)); + if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); + newC = (newC << 4) | val; + } + i--; + + res.append((char)newC); + } + else if (c == 'u') { + var newC = 0; + i++; + for (var j = 0; j < 4; j++) { + if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); + int val = fromHex(literal.charAt(i++)); + if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); + newC = (newC << 4) | val; + } + i--; + + res.append((char)newC); + } + else res.append(c); + } + else res.append(literal.charAt(i)); + } + + return res.toString(); + } + private static String parseRegex(Location loc, String literal) { + var res = new StringBuilder(); + + int end = literal.lastIndexOf('/'); + + for (var i = 1; i < end; i++) { + if (literal.charAt(i) == '\\') { + char c = literal.charAt(++i); + if (c == 'b') res.append('\b'); + else if (c == 't') res.append('\t'); + else if (c == 'n') res.append('\n'); + else if (c == 'f') res.append('\f'); + else if (c == 'r') res.append('\r'); + else if (c == '0') { + if (i + 1 >= literal.length()) res.append((char)0); + c = literal.charAt(i + 1); + if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); + res.append((char)0); + } + else if (c >= '1' && c <= '9') { + res.append((char)(c - '0')); + i++; + } + else if (c == 'x') { + var newC = 0; + i++; + for (var j = 0; j < 2; j++) { + if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); + int val = fromHex(literal.charAt(i++)); + if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); + newC = (newC << 4) | val; + } + i--; + + res.append((char)newC); + } + else if (c == 'u') { + var newC = 0; + i++; + for (var j = 0; j < 4; j++) { + if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); + int val = fromHex(literal.charAt(i++)); + if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); + newC = (newC << 4) | val; + } + i--; + + res.append((char)newC); + } + else res.append("\\" + c); + } + else res.append(literal.charAt(i)); + } + + return '/' + res.toString() + literal.substring(end); + } + + private static double parseHex(String literal) { + double res = 0; + + for (int i = 2; i < literal.length(); i++) { + res *= 16; + int dig = fromHex(literal.charAt(i)); + res += dig; + } + + return res; + } + + public static Double parseNumber(boolean octals, String value) { + if (value.startsWith("0x") || value.startsWith("0X")) { + if (value.length() == 2) return null; + return parseHex(value); + } + if (value.endsWith("e") || value.endsWith("E") || value.endsWith("-")) return null; + + int i = 0; + double res = 0, dotDivisor = 1; + boolean e = false, dot = false; + int exponent = 0; + + for (; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '.') { dot = true; break; } + if (c == 'e') { e = true; break; } + if (!isDigit(c)) break; + + res = res * 10 + c - '0'; + } + + if (dot) for (i++; i < value.length(); i++) { + char c = value.charAt(i); + if (c == 'e') { e = true; break; } + if (!isDigit(c)) break; + + res += (c - '0') / (dotDivisor *= 10); + } + + if (e) for (i++; i < value.length(); i++) { + char c = value.charAt(i); + if (!isDigit(c)) break; + exponent = 10 * exponent + c - '0'; + } + + if (exponent < 0) for (int j = 0; j < -exponent; j++) res /= 10; + else for (int j = 0; j < exponent; j++) res *= 10; + + return res; + } + private static double parseNumber(Location loc, String value) { + var res = parseNumber(false, value); + if (res == null) + throw new SyntaxException(loc, "Invalid number format."); + else return res; + } + + private static List parseTokens(Filename filename, Collection tokens) { + var res = new ArrayList(); + + for (var el : tokens) { + var loc = new Location(el.line, el.start, filename); + switch (el.type) { + case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break; + case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value), el.value)); break; + case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value), el.value)); break; + case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value), el.value)); break; + case OPERATOR: + Operator op = Operator.parse(el.value); + if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value)); + res.add(Token.operator(el.line, el.start, op)); + break; + } + } + + return res; + } + + public static List tokenize(Filename filename, String raw) { + return parseTokens(filename, splitTokens(filename, raw)); + } + + public static Location getLoc(Filename filename, List tokens, int i) { + if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename); + if (i >= tokens.size()) i = tokens.size() - 1; + return new Location(tokens.get(i).line, tokens.get(i).start, filename); + } + public static int getLines(List tokens) { + if (tokens.size() == 0) return 1; + return tokens.get(tokens.size() - 1).line; + } + + public static ParseRes parseIdentifier(List tokens, int i) { + if (inBounds(tokens, i)) { + if (tokens.get(i).isIdentifier()) { + return ParseRes.res(tokens.get(i).identifier(), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } + public static ParseRes parseOperator(List tokens, int i) { + if (inBounds(tokens, i)) { + if (tokens.get(i).isOperator()) { + return ParseRes.res(tokens.get(i).operator(), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } + + public static boolean isIdentifier(List tokens, int i, String lit) { + if (inBounds(tokens, i)) { + if (tokens.get(i).isIdentifier(lit)) { + return true; + } + else return false; + } + else return false; + } + public static boolean isOperator(List tokens, int i, Operator op) { + if (inBounds(tokens, i)) { + if (tokens.get(i).isOperator(op)) { + return true; + } + else return false; + } + else return false; + } + public static boolean isStatementEnd(List tokens, int i) { + if (isOperator(tokens, i, Operator.SEMICOLON)) return true; + if (isOperator(tokens, i, Operator.BRACE_CLOSE)) return true; + if (i < 0) return false; + if (i >= tokens.size()) return true; + return getLoc(null, tokens, i).line() > getLoc(null, tokens, i - 1).line(); + } + public static boolean checkVarName(String name) { + return !reserved.contains(name); + } + + public static ParseRes parseString(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + if (inBounds(tokens, i)) { + if (tokens.get(i).isString()) { + return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } + public static ParseRes parseNumber(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + if (inBounds(tokens, i)) { + if (tokens.get(i).isNumber()) { + return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } + public static ParseRes parseRegex(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + if (inBounds(tokens, i)) { + if (tokens.get(i).isRegex()) { + var val = tokens.get(i).regex(); + var index = val.lastIndexOf('/'); + var first = val.substring(1, index); + var second = val.substring(index + 1); + return ParseRes.res(new RegexStatement(loc, first, second), 1); + } + else return ParseRes.failed(); + } + return ParseRes.failed(); + } + + public static ParseRes parseArray(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + + var values = new ArrayList(); + + // Java allows labels, so me uses labels + loop: while (true) { + if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break; + } + + while (isOperator(tokens, i + n, Operator.COMMA)) { + n++; + values.add(null); + if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break loop; + } + } + + var res = parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an array element.", res); + else n += res.n; + + values.add(res.result); + + if (isOperator(tokens, i + n, Operator.COMMA)) n++; + else if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break; + } + } + + return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); + } + + public static ParseRes> parseParamList(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); + + var args = new ArrayList(); + + if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + } + else { + while (true) { + var argRes = parseIdentifier(tokens, i + n); + if (argRes.isSuccess()) { + args.add(argRes.result); + n++; + if (isOperator(tokens, i + n, Operator.COMMA)) { + n++; + } + if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + break; + } + } + else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); + } + } + + return ParseRes.res(args, n); + } + + public static ParseRes parsePropName(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + + if (inBounds(tokens, i)) { + var token = tokens.get(i); + + if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1); + else return ParseRes.error(loc, "Expected identifier, string or number literal."); + } + else return ParseRes.failed(); + } + public static ParseRes parseObjectProp(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var accessRes = parseIdentifier(tokens, i + n++); + if (!accessRes.isSuccess()) return ParseRes.failed(); + var access = accessRes.result; + if (!access.equals("get") && !access.equals("set")) return ParseRes.failed(); + + var nameRes = parsePropName(filename, tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a property name after '" + access + "'."); + var name = nameRes.result; + n += nameRes.n; + + var argsRes = parseParamList(filename, tokens, i + n); + if (!argsRes.isSuccess()) return ParseRes.error(loc, "Expected an argument list.", argsRes); + n += argsRes.n; + + var res = parseCompound(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); + n += res.n; + + var end = getLoc(filename, tokens, i + n - 1); + + return ParseRes.res(new ObjProp( + name, access, + new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) + ), n); + } + public static ParseRes parseObject(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + + var values = new LinkedHashMap(); + var getters = new LinkedHashMap(); + var setters = new LinkedHashMap(); + + if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); + } + + while (true) { + var propRes = parseObjectProp(filename, tokens, i + n); + + if (propRes.isSuccess()) { + n += propRes.n; + if (propRes.result.access.equals("set")) { + setters.put(propRes.result.name, propRes.result.func); + } + else { + getters.put(propRes.result.name, propRes.result.func); + } + } + else { + var nameRes = parsePropName(filename, tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a field name.", propRes); + n += nameRes.n; + + if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected a colon."); + + var valRes = parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in array list.", valRes); + n += valRes.n; + + values.put(nameRes.result, valRes.result); + } + + if (isOperator(tokens, i + n, Operator.COMMA)) { + n++; + if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + continue; + } + else if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + else ParseRes.error(loc, "Expected a comma or a closing brace."); + } + + return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); + } + public static ParseRes parseNew(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + var n = 0; + if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 18); + n += valRes.n; + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'new' keyword.", valRes); + var callRes = parseCall(filename, tokens, i + n, valRes.result, 0); + n += callRes.n; + if (callRes.isError()) return callRes.transform(); + else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); + var call = (CallStatement)callRes.result; + + return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n); + } + public static ParseRes parseTypeof(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + var n = 0; + if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 15); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'typeof' keyword.", valRes); + n += valRes.n; + + return ParseRes.res(new TypeofStatement(loc, valRes.result), n); + } + public static ParseRes parseVoid(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + var n = 0; + if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 14); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); + n += valRes.n; + + return ParseRes.res(new DiscardStatement(loc, valRes.result), n); + } + public static ParseRes parseDelete(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 15); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'delete'.", valRes); + n += valRes.n; + + if (valRes.result instanceof IndexStatement) { + var index = (IndexStatement)valRes.result; + return ParseRes.res(new DeleteStatement(loc, index.index, index.object), n); + } + else if (valRes.result instanceof VariableStatement) { + return ParseRes.error(loc, "A variable may not be deleted."); + } + else { + return ParseRes.res(new ConstantStatement(loc, true), n); + } + } + + public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isIdentifier(tokens, i + n++, "function")) return ParseRes.failed(); + + var nameRes = parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess() && statement) return ParseRes.error(loc, "A statement function requires a name, one is not present."); + var name = nameRes.result; + n += nameRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); + + var args = new ArrayList(); + + if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + } + else { + while (true) { + var argRes = parseIdentifier(tokens, i + n); + if (argRes.isSuccess()) { + args.add(argRes.result); + n++; + if (isOperator(tokens, i + n, Operator.COMMA)) { + n++; + } + if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + break; + } + } + else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); + } + } + + var res = parseCompound(filename, tokens, i + n); + n += res.n; + var end = getLoc(filename, tokens, i + n - 1); + + if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n); + else return ParseRes.error(loc, "Expected a compound statement for function.", res); + } + + public static ParseRes parseUnary(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var opState = parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + var op = opState.result; + + Operation operation = null; + + if (op == Operator.ADD) operation = Operation.POS; + else if (op == Operator.SUBTRACT) operation = Operation.NEG; + else if (op == Operator.INVERSE) operation = Operation.INVERSE; + else if (op == Operator.NOT) operation = Operation.NOT; + else return ParseRes.failed(); + + var res = parseValue(filename, tokens, n + i, 14); + + if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); + else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res); + } + public static ParseRes parsePrefixChange(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var opState = parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + + int change = 0; + + if (opState.result == Operator.INCREASE) change = 1; + else if (opState.result == Operator.DECREASE) change = -1; + else return ParseRes.failed(); + + var res = parseValue(filename, tokens, i + n, 15); + if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); + } + public static ParseRes parseParens(Filename filename, List tokens, int i) { + int n = 0; + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); + + var res = parseValue(filename, tokens, i + n, 0); + if (!res.isSuccess()) return res; + n += res.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.failed(); + + return ParseRes.res(res.result, n); + } + public static ParseRes parseSimple(Filename filename, List tokens, int i, boolean statement) { + var res = new ArrayList>(); + + if (!statement) { + res.add(parseObject(filename, tokens, i)); + res.add(parseFunction(filename, tokens, i, false)); + } + + res.addAll(List.of( + parseVariable(filename, tokens, i), + parseLiteral(filename, tokens, i), + parseString(filename, tokens, i), + parseRegex(filename, tokens, i), + parseNumber(filename, tokens, i), + parseUnary(filename, tokens, i), + parseArray(filename, tokens, i), + parsePrefixChange(filename, tokens, i), + parseParens(filename, tokens, i), + parseNew(filename, tokens, i), + parseTypeof(filename, tokens, i), + parseVoid(filename, tokens, i), + parseDelete(filename, tokens, i) + )); + + return ParseRes.any(res); + } + + public static ParseRes parseVariable(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + var literal = parseIdentifier(tokens, i); + + if (!literal.isSuccess()) return ParseRes.failed(); + + if (!checkVarName(literal.result)) { + if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); + if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); + } + + return ParseRes.res(new VariableStatement(loc, literal.result), 1); + } + public static ParseRes parseLiteral(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + var id = parseIdentifier(tokens, i); + if (!id.isSuccess()) return id.transform(); + + if (id.result.equals("true")) { + return ParseRes.res(new ConstantStatement(loc, true), 1); + } + if (id.result.equals("false")) { + return ParseRes.res(new ConstantStatement(loc, false), 1); + } + if (id.result.equals("undefined")) { + return ParseRes.res(ConstantStatement.ofUndefined(loc), 1); + } + if (id.result.equals("null")) { + return ParseRes.res(ConstantStatement.ofNull(loc), 1); + } + if (id.result.equals("this")) { + return ParseRes.res(new VariableIndexStatement(loc, 0), 1); + } + if (id.result.equals("arguments")) { + return ParseRes.res(new VariableIndexStatement(loc, 1), 1); + } + if (id.result.equals("globalThis") || id.result.equals("window") || id.result.equals("self")) { + return ParseRes.res(new GlobalThisStatement(loc), 1); + } + return ParseRes.failed(); + } + public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 18) return ParseRes.failed(); + + if (!isOperator(tokens, i + n++, Operator.DOT)) return ParseRes.failed(); + + var literal = parseIdentifier(tokens, i + n++); + if (!literal.isSuccess()) return ParseRes.error(loc, "Expected an identifier after member access."); + + return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); + } + public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 18) return ParseRes.failed(); + + if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in index expression.", valRes); + n += valRes.n; + + if (!isOperator(tokens, i + n++, Operator.BRACKET_CLOSE)) return ParseRes.error(loc, "Expected a closing bracket."); + + return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); + } + public static ParseRes parseAssign(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + int n = 0 ; + + if (precedence > 2) return ParseRes.failed(); + + var opRes = parseOperator(tokens, i + n++); + if (opRes.state != State.SUCCESS) return ParseRes.failed(); + + var op = opRes.result; + if (!op.isAssign()) return ParseRes.failed(); + + if (!(prev instanceof AssignableStatement)) { + return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); + } + + var res = parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res); + n += res.n; + + Operation operation = null; + + if (op == Operator.ASSIGN_ADD) operation = Operation.ADD; + if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT; + if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY; + if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE; + if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO; + if (op == Operator.ASSIGN_OR) operation = Operation.OR; + if (op == Operator.ASSIGN_XOR) operation = Operation.XOR; + if (op == Operator.ASSIGN_AND) operation = Operation.AND; + if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT; + if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT; + if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT; + + return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); + } + public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 17) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); + + var args = new ArrayList(); + boolean prevArg = false; + + while (true) { + var argRes = parseValue(filename, tokens, i + n, 2); + if (argRes.isSuccess()) { + args.add(argRes.result); + n += argRes.n; + prevArg = true; + } + else if (argRes.isError()) return argRes.transform(); + else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) { + prevArg = false; + n++; + } + else if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + break; + } + else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren."); + } + + return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); + } + public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 15) return ParseRes.failed(); + + var opState = parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + + int change = 0; + + if (opState.result == Operator.INCREASE) change = 1; + else if (opState.result == Operator.DECREASE) change = -1; + else return ParseRes.failed(); + + if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); + } + public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 9) return ParseRes.failed(); + if (!isIdentifier(tokens, i + n++, "instanceof")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 10); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); + } + public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 9) return ParseRes.failed(); + if (!isIdentifier(tokens, i + n++, "in")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 10); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); + } + public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 1) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.COMMA)) return ParseRes.failed(); + + var res = parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); + n += res.n; + + return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n); + } + public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 2) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.QUESTION)) return ParseRes.failed(); + + var a = parseValue(filename, tokens, i + n, 2); + if (!a.isSuccess()) return ParseRes.error(loc, "Expected a value after the ternary operator.", a); + n += a.n; + + if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); + + var b = parseValue(filename, tokens, i + n, 2); + if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b); + n += b.n; + + return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); + } + public static ParseRes parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = getLoc(filename, tokens, i); + var n = 0; + + var opRes = parseOperator(tokens, i + n++); + if (!opRes.isSuccess()) return ParseRes.failed(); + var op = opRes.result; + + if (op.precedence < precedence) return ParseRes.failed(); + if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence); + + var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1)); + if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res); + n += res.n; + + if (op == Operator.LAZY_AND) { + return ParseRes.res(new LazyAndStatement(loc, prev, res.result), n); + } + if (op == Operator.LAZY_OR) { + return ParseRes.res(new LazyOrStatement(loc, prev, res.result), n); + } + + return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); + } + + public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) { + Statement prev = null; + int n = 0; + + while (true) { + if (prev == null) { + var res = parseSimple(filename, tokens, i + n, statement); + if (res.isSuccess()) { + n += res.n; + prev = res.result; + } + else if (res.isError()) return res.transform(); + else break; + } + else { + var res = ParseRes.any( + parseOperator(filename, tokens, i + n, prev, precedence), + parseMember(filename, tokens, i + n, prev, precedence), + parseIndex(filename, tokens, i + n, prev, precedence), + parseCall(filename, tokens, i + n, prev, precedence), + parsePostfixChange(filename, tokens, i + n, prev, precedence), + parseInstanceof(filename, tokens, i + n, prev, precedence), + parseIn(filename, tokens, i + n, prev, precedence), + parseComma(filename, tokens, i + n, prev, precedence), + parseTernary(filename, tokens, i + n, prev, precedence) + ); + + if (res.isSuccess()) { + n += res.n; + prev = res.result; + continue; + } + else if (res.isError()) return res.transform(); + + break; + } + } + + if (prev == null) return ParseRes.failed(); + else return ParseRes.res(prev, n); + } + public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence) { + return parseValue(filename, tokens, i, precedence, false); + } + + public static ParseRes parseValueStatement(Filename filename, List tokens, int i) { + var valRes = parseValue(filename, tokens, i, 0, true); + if (!valRes.isSuccess()) return valRes.transform(); + + // valRes.result.setLoc(loc); + var res = ParseRes.res(valRes.result, valRes.n); + + if (isStatementEnd(tokens, i + res.n)) { + if (isOperator(tokens, i + res.n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else if (isIdentifier(tokens, i, "const") || isIdentifier(tokens, i, "let")) { + return ParseRes.error(getLoc(filename, tokens, i), "Detected the usage of 'const'/'let'. Please, use 'var' instead."); + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); + } + public static ParseRes parseVariableDeclare(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); + + var res = new ArrayList(); + + if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), 2); + else return ParseRes.res(new VariableDeclareStatement(loc, res), 1); + } + + while (true) { + var nameLoc = getLoc(filename, tokens, i + n); + var nameRes = parseIdentifier(tokens, i + n++); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); + + if (!checkVarName(nameRes.result)) { + return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", nameRes.result)); + } + + Statement val = null; + + if (isOperator(tokens, i + n, Operator.ASSIGN)) { + n++; + var valRes = parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); + n += valRes.n; + val = valRes.result; + } + + res.add(new Pair(nameRes.result, val, nameLoc)); + + if (isOperator(tokens, i + n, Operator.COMMA)) { + n++; + continue; + } + else if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), n + 1); + else return ParseRes.res(new VariableDeclareStatement(loc, res), n); + } + else return ParseRes.error(loc, "Expected a comma or end of statement."); + } + } + + public static ParseRes parseReturn(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); + + if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new ReturnStatement(loc, null), 2); + else return ParseRes.res(new ReturnStatement(loc, null), 1); + } + + var valRes = parseValue(filename, tokens, i + n, 0); + n += valRes.n; + if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes); + + var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); + + if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else + return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + } + public static ParseRes parseThrow(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 0); + n += valRes.n; + if (valRes.isError()) return ParseRes.error(loc, "Expected a throw value.", valRes); + + var res = ParseRes.res(new ThrowStatement(loc, valRes.result), n); + + if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + } + + public static ParseRes parseBreak(Filename filename, List tokens, int i) { + if (!isIdentifier(tokens, i, "break")) return ParseRes.failed(); + + if (isStatementEnd(tokens, i + 1)) { + if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 2); + else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 1); + } + + var labelRes = parseIdentifier(tokens, i + 1); + if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); + var label = labelRes.result; + + if (isStatementEnd(tokens, i + 2)) { + if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 3); + else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 2); + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); + } + public static ParseRes parseContinue(Filename filename, List tokens, int i) { + if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed(); + + if (isStatementEnd(tokens, i + 1)) { + if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 2); + else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 1); + } + + var labelRes = parseIdentifier(tokens, i + 1); + if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); + var label = labelRes.result; + + if (isStatementEnd(tokens, i + 2)) { + if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 3); + else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 2); + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); + } + public static ParseRes parseDebug(Filename filename, List tokens, int i) { + if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); + + if (isStatementEnd(tokens, i + 1)) { + if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2); + else return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 1); + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); + } + + public static ParseRes parseCompound(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + + var statements = new ArrayList(); + + while (true) { + if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + if (isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + continue; + } + + var res = parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) { + return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); + } + n += res.n; + + statements.add(res.result); + } + + return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); + } + public static ParseRes parseLabel(List tokens, int i) { + int n = 0; + + var nameRes = parseIdentifier(tokens, i + n++); + if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); + + return ParseRes.res(nameRes.result, n); + } + public static ParseRes parseIf(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isIdentifier(tokens, i + n++, "if")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'if'."); + + var condRes = parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); + n += condRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition."); + + var res = parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); + n += res.n; + + if (!isIdentifier(tokens, i + n, "else")) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); + n++; + + var elseRes = parseStatement(filename, tokens, i + n); + if (!elseRes.isSuccess()) return ParseRes.error(loc, "Expected an else body.", elseRes); + n += elseRes.n; + + return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); + } + public static ParseRes parseWhile(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var labelRes = parseLabel(tokens, i + n); + n += labelRes.n; + + if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); + + var condRes = parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + n += condRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + + var res = parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a while body.", res); + n += res.n; + + return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); + } + public static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isIdentifier(tokens, i + n++, "case")) return ParseRes.failed(); + + var valRes = parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'case'.", valRes); + n += valRes.n; + + if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected colons after 'case' value."); + + return ParseRes.res(valRes.result, n); + } + public static ParseRes parseDefaultCase(List tokens, int i) { + if (!isIdentifier(tokens, i, "default")) return ParseRes.failed(); + if (!isOperator(tokens, i + 1, Operator.COLON)) return ParseRes.error(getLoc(null, tokens, i), "Expected colons after 'default'."); + + return ParseRes.res(null, 2); + } + public static ParseRes parseSwitch(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isIdentifier(tokens, i + n++, "switch")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'switch'."); + + var valRes = parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a switch value.", valRes); + n += valRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after switch value."); + if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.error(loc, "Expected an opening brace after switch value."); + + var statements = new ArrayList(); + var cases = new ArrayList(); + var defaultI = -1; + + while (true) { + if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + if (isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + continue; + } + + var defaultRes = parseDefaultCase(tokens, i + n); + var caseRes = parseSwitchCase(filename, tokens, i + n); + + if (defaultRes.isSuccess()) { + defaultI = statements.size(); + n += defaultRes.n; + } + else if (caseRes.isSuccess()) { + cases.add(new SwitchCase(caseRes.result, statements.size())); + n += caseRes.n; + } + else if (defaultRes.isError()) return defaultRes.transform(); + else if (caseRes.isError()) return defaultRes.transform(); + else { + var res = ParseRes.any( + parseStatement(filename, tokens, i + n), + parseCompound(filename, tokens, i + n) + ); + if (!res.isSuccess()) { + return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); + } + n += res.n; + statements.add(res.result); + } + } + + return ParseRes.res(new SwitchStatement( + loc, valRes.result, defaultI, + cases.toArray(SwitchCase[]::new), + statements.toArray(Statement[]::new) + ), n); + } + public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var labelRes = parseLabel(tokens, i + n); + n += labelRes.n; + + if (!isIdentifier(tokens, i + n++, "do")) return ParseRes.failed(); + var bodyRes = parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a do-while body.", bodyRes); + n += bodyRes.n; + + if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.error(loc, "Expected 'while' keyword."); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); + + var condRes = parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + n += condRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + + var res = ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); + + if (isStatementEnd(tokens, i + n)) { + if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon."); + } + public static ParseRes parseFor(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var labelRes = parseLabel(tokens, i + n); + n += labelRes.n; + + if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + Statement decl, cond, inc; + + if (isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + decl = new CompoundStatement(loc, false); + } + else { + var declRes = ParseRes.any( + parseVariableDeclare(filename, tokens, i + n), + parseValueStatement(filename, tokens, i + n) + ); + if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); + n += declRes.n; + decl = declRes.result; + } + + if (isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + cond = new ConstantStatement(loc, 1); + } + else { + var condRes = parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", condRes); + n += condRes.n; + if (!isOperator(tokens, i + n++, Operator.SEMICOLON)) return ParseRes.error(loc, "Expected a semicolon.", condRes); + cond = condRes.result; + } + + if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + inc = new CompoundStatement(loc, false); + } + else { + var incRes = parseValue(filename, tokens, i + n, 0); + if (!incRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", incRes); + n += incRes.n; + inc = incRes.result; + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + } + + + var res = parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a for body.", res); + n += res.n; + + return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); + } + public static ParseRes parseForIn(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var labelRes = parseLabel(tokens, i + n); + var isDecl = false; + n += labelRes.n; + + if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + if (isIdentifier(tokens, i + n, "var")) { + isDecl = true; + n++; + } + + var nameRes = parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); + var nameLoc = getLoc(filename, tokens, i + n); + n += nameRes.n; + + Statement varVal = null; + + if (isOperator(tokens, i + n, Operator.ASSIGN)) { + n++; + + var valRes = parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); + n += nameRes.n; + + varVal = valRes.result; + } + + if (!isIdentifier(tokens, i + n++, "in")) { + if (varVal == null) { + if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + } + return ParseRes.error(loc, "Expected 'in' keyword after variable declaration."); + } + + var objRes = parseValue(filename, tokens, i + n, 0); + if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); + n += objRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + + + var bodyRes = parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); + n += bodyRes.n; + + return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); + } + public static ParseRes parseForOf(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + var labelRes = parseLabel(tokens, i + n); + var isDecl = false; + n += labelRes.n; + + if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + if (isIdentifier(tokens, i + n, "var")) { + isDecl = true; + n++; + } + + var nameRes = parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); + var nameLoc = getLoc(filename, tokens, i + n); + n += nameRes.n; + + if (!isIdentifier(tokens, i + n++, "of")) { + if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + else return ParseRes.error(loc, "Expected 'of' keyword after variable declaration."); + } + + var objRes = parseValue(filename, tokens, i + n, 0); + if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); + n += objRes.n; + + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + + + var bodyRes = parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); + n += bodyRes.n; + + return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n); + } + public static ParseRes parseCatch(Filename filename, List tokens, int i) { + var loc = getLoc(filename, tokens, i); + int n = 0; + + if (!isIdentifier(tokens, i + n++, "try")) return ParseRes.failed(); + + var res = parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); + n += res.n; + + String name = null; + Statement catchBody = null, finallyBody = null; + + + if (isIdentifier(tokens, i + n, "catch")) { + n++; + if (isOperator(tokens, i + n, Operator.PAREN_OPEN)) { + n++; + var nameRes = parseIdentifier(tokens, i + n++); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a catch variable name."); + name = nameRes.result; + if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after catch variable name."); + } + + var catchRes = parseStatement(filename, tokens, i + n); + if (!catchRes.isSuccess()) return ParseRes.error(loc, "Expected a catch body.", catchRes); + n += catchRes.n; + catchBody = catchRes.result; + } + + if (isIdentifier(tokens, i + n, "finally")) { + n++; + var finallyRes = parseStatement(filename, tokens, i + n); + if (!finallyRes.isSuccess()) return ParseRes.error(loc, "Expected a finally body.", finallyRes); + n += finallyRes.n; + finallyBody = finallyRes.result; + } + + return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); + } + + public static ParseRes parseStatement(Filename filename, List tokens, int i) { + if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1); + if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); + return ParseRes.any( + parseVariableDeclare(filename, tokens, i), + parseReturn(filename, tokens, i), + parseThrow(filename, tokens, i), + parseContinue(filename, tokens, i), + parseBreak(filename, tokens, i), + parseDebug(filename, tokens, i), + parseIf(filename, tokens, i), + parseWhile(filename, tokens, i), + parseSwitch(filename, tokens, i), + parseFor(filename, tokens, i), + parseForIn(filename, tokens, i), + parseForOf(filename, tokens, i), + parseDoWhile(filename, tokens, i), + parseCatch(filename, tokens, i), + parseCompound(filename, tokens, i), + parseFunction(filename, tokens, i, true), + parseValueStatement(filename, tokens, i) + ); + } + + public static Statement[] parse(Filename filename, String raw) { + var tokens = tokenize(filename, raw); + var list = new ArrayList(); + int i = 0; + + while (true) { + if (i >= tokens.size()) break; + + var res = Parsing.parseStatement(filename, tokens, i); + + if (res.isError()) throw new SyntaxException(getLoc(filename, tokens, i), res.error); + else if (res.isFailed()) throw new SyntaxException(getLoc(filename, tokens, i), "Unexpected syntax."); + + i += res.n; + + list.add(res.result); + } + + return list.toArray(Statement[]::new); + } + + public static CompileResult compile(Statement ...statements) { + var target = new CompileResult(new LocalScopeRecord()); + var stm = new CompoundStatement(null, true, statements); + + target.scope.define("this"); + target.scope.define("arguments"); + + try { + stm.compile(target, true); + FunctionStatement.checkBreakAndCont(target, 0); + } + catch (SyntaxException e) { + target = new CompileResult(new LocalScopeRecord()); + + target.scope.define("this"); + target.scope.define("arguments"); + + target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); + } + + target.add(Instruction.ret()).setLocation(stm.loc()); + + return target; + } + public static CompileResult compile(Filename filename, String raw) { + return compile(parse(filename, raw)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java new file mode 100644 index 0000000..e300d6b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/RawToken.java @@ -0,0 +1,15 @@ +package me.topchetoeu.jscript.compilation.parsing; + +public class RawToken { + public final String value; + public final TokenType type; + public final int line; + public final int start; + + public RawToken(String value, TokenType type, int line, int start) { + this.value = value; + this.type = type; + this.line = line; + this.start = start; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java new file mode 100644 index 0000000..c399a9e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java @@ -0,0 +1,45 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; + +public class TestRes { + public final State state; + public final String error; + public final int i; + + private TestRes(ParseRes.State state, String error, int i) { + this.i = i; + this.state = state; + this.error = error; + } + + public TestRes add(int n) { + return new TestRes(state, null, this.i + n); + } + public ParseRes transform() { + if (isSuccess()) throw new RuntimeException("Can't transform a TestRes that hasn't failed."); + else if (isError()) return ParseRes.error(null, error); + else return ParseRes.failed(); + } + + public boolean isSuccess() { return state.isSuccess(); } + public boolean isFailed() { return state.isFailed(); } + public boolean isError() { return state.isError(); } + + public static TestRes failed() { + return new TestRes(State.FAILED, null, 0); + } + public static TestRes error(Location loc, String error) { + if (loc != null) error = loc + ": " + error; + return new TestRes(State.ERROR, error, 0); + } + public static TestRes error(Location loc, String error, TestRes other) { + if (loc != null) error = loc + ": " + error; + if (!other.isError()) return new TestRes(State.ERROR, error, 0); + return new TestRes(State.ERROR, other.error, 0); + } + public static TestRes res(int i) { + return new TestRes(State.SUCCESS, null, i); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java new file mode 100644 index 0000000..a3f4f0a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/Token.java @@ -0,0 +1,58 @@ +package me.topchetoeu.jscript.compilation.parsing; + +public class Token { + public final Object value; + public final String rawValue; + public final boolean isString; + public final boolean isRegex; + public final int line; + public final int start; + + private Token(int line, int start, Object value, String rawValue, boolean isString, boolean isRegex) { + this.value = value; + this.rawValue = rawValue; + this.line = line; + this.start = start; + this.isString = isString; + this.isRegex = isRegex; + } + private Token(int line, int start, Object value, String rawValue) { + this.value = value; + this.rawValue = rawValue; + this.line = line; + this.start = start; + this.isString = false; + this.isRegex = false; + } + + public boolean isString() { return isString; } + public boolean isRegex() { return isRegex; } + public boolean isNumber() { return value instanceof Number; } + public boolean isIdentifier() { return !isString && !isRegex && value instanceof String; } + public boolean isOperator() { return value instanceof Operator; } + + public boolean isIdentifier(String lit) { return !isString && !isRegex && value.equals(lit); } + public boolean isOperator(Operator op) { return value.equals(op); } + + public String string() { return (String)value; } + public String regex() { return (String)value; } + public double number() { return (double)value; } + public String identifier() { return (String)value; } + public Operator operator() { return (Operator)value; } + + public static Token regex(int line, int start, String val, String rawValue) { + return new Token(line, start, val, rawValue, false, true); + } + public static Token string(int line, int start, String val, String rawValue) { + return new Token(line, start, val, rawValue, true, false); + } + public static Token number(int line, int start, double val, String rawValue) { + return new Token(line, start, val, rawValue); + } + public static Token identifier(int line, int start, String val) { + return new Token(line, start, val, val); + } + public static Token operator(int line, int start, Operator val) { + return new Token(line, start, val, val.readable); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java new file mode 100644 index 0000000..a34e821 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/parsing/TokenType.java @@ -0,0 +1,9 @@ +package me.topchetoeu.jscript.compilation.parsing; + +enum TokenType { + REGEX, + STRING, + NUMBER, + LITERAL, + OPERATOR, +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java deleted file mode 100644 index 3e70aa5..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import java.util.HashMap; - -import me.topchetoeu.jscript.common.parsing.Location; - -public class FunctionScope extends Scope { - private final VariableList captures = new VariableList().setIndexMap(v -> ~v); - private final VariableList specials = new VariableList(); - private final VariableList locals = new VariableList(specials); - private HashMap childToParent = new HashMap<>(); - - private void removeCapture(String name) { - var res = captures.remove(name); - if (res != null) childToParent.remove(res); - } - - @Override public VariableDescriptor define(String name, boolean readonly, Location loc) { - var old = locals.get(name); - if (old != null) return old; - - removeCapture(name); - return locals.add(name, readonly); - } - @Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) { - if (locals.has(name)) throw alreadyDefinedErr(loc, name); - else if (parent == null) throw new RuntimeException("Strict variables may be defined only in local scopes"); - else return parent.defineStrict(name, readonly, loc); - } - public VariableDescriptor defineParam(String name, Location loc) { - return specials.add(name, false); - } - public boolean hasArg(String name) { - return specials.has(name); - } - - public VariableDescriptor get(String name, boolean capture, boolean skipSelf) { - if (!skipSelf) { - if (specials.has(name)) return specials.get(name); - if (locals.has(name)) return locals.get(name); - if (captures.has(name)) return captures.get(name); - } - - var parentVar = parent.get(name, true); - if (parentVar == null) return null; - - var childVar = captures.add(parentVar); - - childToParent.put(childVar, parentVar); - - return childVar; - } - - @Override public VariableDescriptor get(String name, boolean capture) { - return get(name, capture, false); - } - - @Override public boolean has(String name) { - if (specials.has(name)) return true; - if (locals.has(name)) return true; - if (captures.has(name)) return true; - if (parent != null) return parent.has(name); - - return false; - } - - @Override public boolean end() { - if (!super.end()) return false; - - captures.freeze(); - locals.freeze(); - return true; - } - - @Override public int localsCount() { - return locals.size() + specials.size(); - } - @Override public int capturesCount() { - return captures.size(); - } - @Override public int allocCount() { - return 0; - } - - public int offset() { - return captures.size() + locals.size(); - } - - public int[] getCaptureIndices() { - var res = new int[captures.size()]; - var i = 0; - - for (var el : captures) { - assert childToParent.containsKey(el); - res[i] = childToParent.get(el).index(); - } - - return res; - } - - public FunctionScope() { - super(); - } - public FunctionScope(Scope parent) { - super(parent); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java deleted file mode 100644 index 6151a8e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/GlobalScope.java +++ /dev/null @@ -1,33 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import me.topchetoeu.jscript.common.parsing.Location; - -public final class GlobalScope extends Scope { - @Override public VariableDescriptor define(String name, boolean readonly, Location loc) { - return null; - } - @Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) { - return null; - } - @Override public VariableDescriptor get(String name, boolean capture) { - return null; - } - @Override public int offset() { - return 0; - } - @Override public boolean has(String name) { - return false; - } - - @Override public int localsCount() { - return 0; - } - @Override public int capturesCount() { - return 0; - } - @Override public int allocCount() { - return 0; - } - - public GlobalScope() { super(); } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java deleted file mode 100644 index c348168..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScope.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import me.topchetoeu.jscript.common.parsing.Location; - -public final class LocalScope extends Scope { - private final VariableList locals = new VariableList(); - - @Override public int offset() { - if (parent != null) return parent.offset() + locals.size(); - else return locals.size(); - } - - @Override public VariableDescriptor define(String name, boolean readonly, Location loc) { - if (locals.has(name)) throw alreadyDefinedErr(loc, name); - - return parent.define(name, readonly, loc); - } - @Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) { - if (locals.has(name)) throw alreadyDefinedErr(loc, name); - return locals.add(name, readonly); - } - - @Override public VariableDescriptor get(String name, boolean capture) { - var res = locals.get(name); - - if (res != null) return res; - if (parent != null) return parent.get(name, capture); - - return null; - } - - @Override public boolean has(String name) { - if (locals.has(name)) return true; - if (parent != null) return parent.has(name); - - return false; - } - - @Override public boolean end() { - if (!super.end()) return false; - - this.locals.freeze(); - return true; - } - - @Override public int localsCount() { - if (parent == null) return 0; - else return parent.localsCount(); - } - @Override public int capturesCount() { - if (parent == null) return 0; - else return parent.capturesCount(); - } - @Override public int allocCount() { - return locals.size(); - } - - public Iterable all() { - return () -> locals.iterator(); - } - - - public LocalScope(Scope parent) { - super(parent); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java new file mode 100644 index 0000000..070168d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/LocalScopeRecord.java @@ -0,0 +1,77 @@ +package me.topchetoeu.jscript.compilation.scope; + +import java.util.ArrayList; + +public class LocalScopeRecord implements ScopeRecord { + public final LocalScopeRecord parent; + + private final ArrayList captures = new ArrayList<>(); + private final ArrayList locals = new ArrayList<>(); + + public String[] captures() { + return captures.toArray(String[]::new); + } + public String[] locals() { + return locals.toArray(String[]::new); + } + + public LocalScopeRecord child() { + return new LocalScopeRecord(this); + } + + public int localsCount() { + return locals.size(); + } + public int capturesCount() { + return captures.size(); + } + + public int[] getCaptures() { + var buff = new int[captures.size()]; + var i = 0; + + for (var name : captures) { + var index = parent.getKey(name); + if (index instanceof Integer) buff[i++] = (int)index; + } + + var res = new int[i]; + System.arraycopy(buff, 0, res, 0, i); + + return res; + } + + public Object getKey(String name) { + var capI = captures.indexOf(name); + var locI = locals.lastIndexOf(name); + if (locI >= 0) return locI; + if (capI >= 0) return ~capI; + if (parent != null) { + var res = parent.getKey(name); + if (res != null && res instanceof Integer) { + captures.add(name); + return -captures.size(); + } + } + + return name; + } + public Object define(String name, boolean force) { + if (!force && locals.contains(name)) return locals.indexOf(name); + locals.add(name); + return locals.size() - 1; + } + public Object define(String name) { + return define(name, false); + } + public void undefine() { + locals.remove(locals.size() - 1); + } + + public LocalScopeRecord() { + this.parent = null; + } + public LocalScopeRecord(LocalScopeRecord parent) { + this.parent = parent; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java deleted file mode 100644 index c41c2bd..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ /dev/null @@ -1,73 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public abstract class Scope { - public final Scope parent; - private boolean active = true; - private Scope child; - - protected final SyntaxException alreadyDefinedErr(Location loc, String name) { - return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); - } - - /** - * Defines an ES5-style variable - * @returns The index supplier of the variable if it is a local, or null if it is a global - * @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope - * @throws RuntimeException If the scope is finalized or has an active child - */ - public abstract VariableDescriptor define(String name, boolean readonly, Location loc); - /** - * Defines an ES2015-style variable - * @param readonly True if const, false if let - * @return The index supplier of the variable - * @throws SyntaxException If any variable with the same name exists in the current scope - * @throws RuntimeException If the scope is finalized or has an active child - */ - public abstract VariableDescriptor defineStrict(String name, boolean readonly, Location loc); - /** - * Gets the index supplier of the given variable name, or null if it is a global - * - * @param capture Used to signal to the scope that the variable is going to be captured. - * Not passing this could lead to a local variable being optimized out as an ES5-style variable, - * which could break the semantics of a capture - */ - public abstract VariableDescriptor get(String name, boolean capture); - public abstract boolean has(String name); - /** - * Gets the index offset from this scope to its children - */ - public abstract int offset(); - - public abstract int localsCount(); - public abstract int capturesCount(); - public abstract int allocCount(); - - public boolean end() { - if (!active) return false; - - this.active = false; - if (this.parent != null) { - assert this.parent.child == this; - this.parent.child = null; - } - - return true; - } - - public final boolean active() { return active; } - public final Scope child() { return child; } - - public Scope() { - this.parent = null; - } - public Scope(Scope parent) { - if (!parent.active) throw new RuntimeException("Parent is not active"); - if (parent.child != null) throw new RuntimeException("Parent has an active child"); - - this.parent = parent; - this.parent.child = this; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java new file mode 100644 index 0000000..fe08968 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/ScopeRecord.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.compilation.scope; + +public interface ScopeRecord { + public Object getKey(String name); + public Object define(String name); + public LocalScopeRecord child(); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableDescriptor.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableDescriptor.java deleted file mode 100644 index 29b3bb5..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableDescriptor.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -public abstract class VariableDescriptor { - public final boolean readonly; - public final String name; - - public abstract int index(); - - public VariableDescriptor(String name, boolean readonly) { - this.name = name; - this.readonly = readonly; - } - - public static VariableDescriptor of(String name, boolean readonly, int i) { - return new VariableDescriptor(name, readonly) { - @Override public int index() { return i; } - }; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java deleted file mode 100644 index dc583af..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java +++ /dev/null @@ -1,241 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.function.IntSupplier; -import java.util.function.IntUnaryOperator; - -public final class VariableList implements Iterable { - private class ListVar extends VariableDescriptor { - private ListVar next; - private ListVar prev; - private boolean frozen; - private int index; - - @Override public int index() { - if (frozen) { - if (offset == null) { - return indexConverter == null ? index : indexConverter.applyAsInt(index); - } - else { - return indexConverter == null ? - index + offset.getAsInt() : - indexConverter.applyAsInt(index + offset.getAsInt()); - } - } - - var res = 0; - if (offset != null) res = offset.getAsInt(); - - for (var it = prev; it != null; it = it.prev) { - res++; - } - - return indexConverter == null ? res : indexConverter.applyAsInt(res); - } - - public ListVar freeze() { - if (frozen) return this; - this.frozen = true; - - if (prev == null) return this; - assert prev.frozen; - - this.index = prev.index + 1; - this.next = null; - this.prev = null; - - return this; - } - - public ListVar(String name, boolean readonly, ListVar next, ListVar prev) { - super(name, readonly); - - this.next = next; - this.prev = prev; - } - } - - private ListVar first, last; - - private HashMap map = new HashMap<>(); - - private HashMap frozenMap = null; - private ArrayList frozenList = null; - - private final IntSupplier offset; - private IntUnaryOperator indexConverter = null; - - public boolean frozen() { - if (frozenMap != null) { - assert frozenList != null; - assert frozenMap != null; - assert map == null; - assert first == null; - assert last == null; - - return true; - } - else { - assert frozenList == null; - assert frozenMap == null; - assert map != null; - - return false; - } - } - - public VariableDescriptor add(VariableDescriptor val) { - return add(val.name, val.readonly); - } - public VariableDescriptor add(String name, boolean readonly) { - if (frozen()) throw new RuntimeException("The scope has been frozen"); - if (map.containsKey(name)) return map.get(name); - - var res = new ListVar(name, readonly, null, last); - - if (last != null) { - assert first != null; - - last.next = res; - res.prev = last; - - last = res; - } - else { - first = last = res; - } - - map.put(name, res); - - return res; - } - public VariableDescriptor remove(String name) { - if (frozen()) throw new RuntimeException("The scope has been frozen"); - - var el = map.get(name); - if (el == null) return null; - - if (el.prev != null) { - assert el != first; - el.prev.next = el.next; - } - else { - assert el == first; - first = first.next; - } - - if (el.next != null) { - assert el != last; - el.next.prev = el.prev; - } - else { - assert el == last; - last = last.prev; - } - - el.next = null; - el.prev = null; - - return el; - } - - public VariableDescriptor get(String name) { - if (frozen()) return frozenMap.get(name); - else return map.get(name); - } - public VariableDescriptor get(int i) { - if (frozen()) { - if (i < 0 || i >= frozenList.size()) return null; - return frozenList.get(i); - } - else { - if (i < 0 || i >= map.size()) return null; - - if (i < map.size() / 2) { - var it = first; - for (var j = 0; j < i; it = it.next, j++); - return it; - } - else { - var it = last; - for (var j = map.size() - 1; j >= i; it = it.prev, j--); - return it; - } - } - } - - public boolean has(String name) { - return this.get(name) != null; - } - - public int size() { - if (frozen()) return frozenList.size(); - else return map.size(); - } - - public void freeze() { - if (frozen()) return; - - frozenMap = new HashMap<>(); - frozenList = new ArrayList<>(); - - for (var it = first; it != null; ) { - frozenMap.put(it.name, it); - frozenList.add(it); - - var tmp = it; - it = it.next; - tmp.freeze(); - } - - map = null; - first = last = null; - } - - @Override public Iterator iterator() { - if (frozen()) return frozenList.iterator(); - else return new Iterator() { - private ListVar curr = first; - - @Override public boolean hasNext() { - return curr != null; - } - @Override public VariableDescriptor next() { - if (curr == null) return null; - - var res = curr; - curr = curr.next; - return res; - } - }; - } - - public VariableDescriptor[] toArray() { - var res = new VariableDescriptor[size()]; - var i = 0; - - for (var el : this) res[i++] = el; - - return res; - } - - public VariableList setIndexMap(IntUnaryOperator map) { - indexConverter = map; - return this; - } - - public VariableList(IntSupplier offset) { - this.offset = offset; - } - public VariableList(int offset) { - this.offset = () -> offset; - } - public VariableList(VariableList prev) { - this.offset = prev::size; - } - public VariableList() { - this.offset = null; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java deleted file mode 100644 index 104340f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - - -public class ArgumentsNode extends Node { - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadArgs()); - } - - public ArgumentsNode(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java deleted file mode 100644 index 148a2a4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java +++ /dev/null @@ -1,82 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.ArrayList; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - - -public class ArrayNode extends Node { - public final Node[] statements; - - @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadArr(statements.length)); - - for (var i = 0; i < statements.length; i++) { - var el = statements[i]; - if (el != null) { - target.add(Instruction.dup()); - target.add(Instruction.pushValue(i)); - el.compile(target, true); - target.add(Instruction.storeMember()); - } - } - - if (!pollute) target.add(Instruction.discard()); - } - - public ArrayNode(Location loc, Node[] statements) { - super(loc); - this.statements = statements; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "[")) return ParseRes.failed(); - n++; - - var values = new ArrayList(); - - loop: while (true) { - n += Parsing.skipEmpty(src, i + n); - if (src.is(i + n, "]")) { - n++; - break; - } - - while (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - values.add(null); - - if (src.is(i + n, "]")) { - n++; - break loop; - } - } - - var res = JavaScript.parseExpression(src, i + n, 2); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element."); - n += res.n; - n += Parsing.skipEmpty(src, i + n); - - values.add(res.result); - - if (src.is(i + n, ",")) n++; - else if (src.is(i + n, "]")) { - n++; - break; - } - } - - return ParseRes.res(new ArrayNode(loc, values.toArray(Node[]::new)), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java new file mode 100644 index 0000000..e24e50b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -0,0 +1,40 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ArrayStatement extends Statement { + public final Statement[] statements; + + @Override public boolean pure() { + for (var stm : statements) { + if (!stm.pure()) return false; + } + + return true; + } + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadArr(statements.length)); + + for (var i = 0; i < statements.length; i++) { + var el = statements[i]; + if (el != null) { + target.add(Instruction.dup()); + target.add(Instruction.pushValue(i)); + el.compile(target, true); + target.add(Instruction.storeMember()); + } + } + + if (!pollute) target.add(Instruction.discard()); + } + + public ArrayStatement(Location loc, Statement[] statements) { + super(loc); + this.statements = statements; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java new file mode 100644 index 0000000..412eacc --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -0,0 +1,43 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class CallStatement extends Statement { + public final Statement func; + public final Statement[] args; + public final boolean isNew; + + @Override + public void compile(CompileResult target, boolean pollute, BreakpointType type) { + if (isNew) func.compile(target, true); + else if (func instanceof IndexStatement) { + ((IndexStatement)func).compile(target, true, true); + } + else { + target.add(Instruction.pushUndefined()); + func.compile(target, true); + } + + for (var arg : args) arg.compile(target, true); + + if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type); + else target.add(Instruction.call(args.length)).setLocationAndDebug(loc(), type); + + if (!pollute) target.add(Instruction.discard()); + } + @Override + public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, BreakpointType.STEP_IN); + } + + public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) { + super(loc); + this.isNew = isNew; + this.func = func; + this.args = args; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java new file mode 100644 index 0000000..26ae605 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.compilation.AssignableStatement; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ChangeStatement extends Statement { + public final AssignableStatement value; + public final double addAmount; + public final boolean postfix; + + @Override + public void compile(CompileResult target, boolean pollute) { + value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); + if (!pollute) target.add(Instruction.discard()); + else if (postfix) { + target.add(Instruction.pushValue(addAmount)); + target.add(Instruction.operation(Operation.SUBTRACT)); + } + } + + public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { + super(loc); + this.value = value; + this.addAmount = addAmount; + this.postfix = postfix; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java new file mode 100644 index 0000000..2def204 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java @@ -0,0 +1,47 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ConstantStatement extends Statement { + public final Object value; + public final boolean isNull; + + @Override public boolean pure() { return true; } + + @Override + public void compile(CompileResult target, boolean pollute) { + if (pollute) { + if (isNull) target.add(Instruction.pushNull()); + else if (value instanceof Double) target.add(Instruction.pushValue((Double)value)); + else if (value instanceof String) target.add(Instruction.pushValue((String)value)); + else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value)); + else target.add(Instruction.pushUndefined()); + } + } + + private ConstantStatement(Location loc, Object val, boolean isNull) { + super(loc); + this.value = val; + this.isNull = isNull; + } + + public ConstantStatement(Location loc, boolean val) { + this(loc, val, false); + } + public ConstantStatement(Location loc, String val) { + this(loc, val, false); + } + public ConstantStatement(Location loc, double val) { + this(loc, val, false); + } + + public static ConstantStatement ofUndefined(Location loc) { + return new ConstantStatement(loc, null, false); + } + public static ConstantStatement ofNull(Location loc) { + return new ConstantStatement(loc, null, true); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java new file mode 100644 index 0000000..40ad0cd --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class DiscardStatement extends Statement { + public final Statement value; + + @Override public boolean pure() { return value.pure(); } + + @Override + public void compile(CompileResult target, boolean pollute) { + value.compile(target, false); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public DiscardStatement(Location loc, Statement val) { + super(loc); + this.value = val; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java new file mode 100644 index 0000000..db7e331 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -0,0 +1,127 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.Instruction.Type; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundStatement; +import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; + +public class FunctionStatement extends Statement { + public final CompoundStatement body; + public final String varName; + public final String[] args; + public final boolean statement; + public final Location end; + + @Override public boolean pure() { return varName == null && statement; } + + @Override + public void declare(CompileResult target) { + if (varName != null && statement) target.scope.define(varName); + } + + public static void checkBreakAndCont(CompileResult target, int start) { + for (int i = start; i < target.size(); i++) { + if (target.get(i).type == Type.NOP) { + if (target.get(i).is(0, "break") ) { + throw new SyntaxException(target.map.toLocation(i), "Break was placed outside a loop."); + } + if (target.get(i).is(0, "cont")) { + throw new SyntaxException(target.map.toLocation(i), "Continue was placed outside a loop."); + } + } + } + } + + private CompileResult compileBody(CompileResult target, boolean pollute, BreakpointType bp) { + for (var i = 0; i < args.length; i++) { + for (var j = 0; j < i; j++) { + if (args[i].equals(args[j])) { + throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'."); + } + } + } + + var subtarget = new CompileResult(target.scope.child()); + + subtarget.scope.define("this"); + var argsVar = subtarget.scope.define("arguments"); + + if (args.length > 0) { + for (var i = 0; i < args.length; i++) { + subtarget.add(Instruction.loadVar(argsVar)); + subtarget.add(Instruction.pushValue(i)); + subtarget.add(Instruction.loadMember()); + subtarget.add(Instruction.storeVar(subtarget.scope.define(args[i]))); + } + } + + if (!statement && this.varName != null) { + subtarget.add(Instruction.storeSelfFunc((int)subtarget.scope.define(this.varName))).setLocationAndDebug(loc(), bp); + } + + body.declare(subtarget); + body.compile(subtarget, false); + subtarget.length = args.length; + subtarget.add(Instruction.ret()).setLocation(end); + checkBreakAndCont(subtarget, 0); + + if (pollute) target.add(Instruction.loadFunc(target.children.size(), subtarget.scope.getCaptures())); + return target.addChild(subtarget); + } + + public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + if (this.varName != null) name = this.varName; + + var hasVar = this.varName != null && statement; + var hasName = name != null; + + compileBody(target, pollute || hasVar || hasName, bp); + + if (hasName) { + if (pollute || hasVar) target.add(Instruction.dup()); + target.add(Instruction.pushValue("name")); + target.add(Instruction.pushValue(name)); + target.add(Instruction.storeMember()); + } + + if (hasVar) { + var key = target.scope.getKey(this.varName); + + if (key instanceof String) target.add(Instruction.makeVar((String)key)); + target.add(Instruction.storeVar(target.scope.getKey(this.varName), false)); + } + } + public void compile(CompileResult target, boolean pollute, String name) { + compile(target, pollute, name, BreakpointType.NONE); + } + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { + compile(target, pollute, (String)null, bp); + } + @Override public void compile(CompileResult target, boolean pollute) { + compile(target, pollute, (String)null, BreakpointType.NONE); + } + + public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) { + super(loc); + + this.end = end; + this.varName = varName; + this.statement = statement; + + this.args = args; + this.body = body; + } + + public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name) { + if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name); + else stm.compile(target, pollute); + } + public static void compileWithName(Statement stm, CompileResult target, boolean pollute, String name, BreakpointType bp) { + if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp); + else stm.compile(target, pollute, bp); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java deleted file mode 100644 index 11823a3..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - - -public class GlobalThisNode extends Node { - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadGlob()); - } - - public GlobalThisNode(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java new file mode 100644 index 0000000..b43cfc2 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisStatement.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class GlobalThisStatement extends Statement { + @Override public boolean pure() { return true; } + + @Override + public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadGlob()); + } + + public GlobalThisStatement(Location loc) { + super(loc); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java similarity index 68% rename from src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java rename to src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java index e9e2e3d..1716336 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexAssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexAssignStatement.java @@ -1,19 +1,20 @@ -package me.topchetoeu.jscript.compilation.values.operations; +package me.topchetoeu.jscript.compilation.values; import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.Statement; -public class IndexAssignNode extends Node { - public final Node object; - public final Node index; - public final Node value; +public class IndexAssignStatement extends Statement { + public final Statement object; + public final Statement index; + public final Statement value; public final Operation operation; - @Override public void compile(CompileResult target, boolean pollute) { + @Override + public void compile(CompileResult target, boolean pollute) { if (operation != null) { object.compile(target, true); index.compile(target, true); @@ -34,7 +35,7 @@ public class IndexAssignNode extends Node { } } - public IndexAssignNode(Location loc, Node object, Node index, Node value, Operation operation) { + public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) { super(loc); this.object = object; this.index = index; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java new file mode 100644 index 0000000..58315dc --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.AssignableStatement; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class IndexStatement extends AssignableStatement { + public final Statement object; + public final Statement index; + + @Override + public Statement toAssign(Statement val, Operation operation) { + return new IndexAssignStatement(loc(), object, index, val, operation); + } + public void compile(CompileResult target, boolean dupObj, boolean pollute) { + object.compile(target, true); + if (dupObj) target.add(Instruction.dup()); + + index.compile(target, true); + target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN); + if (!pollute) target.add(Instruction.discard()); + } + @Override + public void compile(CompileResult target, boolean pollute) { + compile(target, false, pollute); + } + + public IndexStatement(Location loc, Statement object, Statement index) { + super(loc); + this.object = object; + this.index = index; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java new file mode 100644 index 0000000..306ec25 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class LazyAndStatement extends Statement { + public final Statement first, second; + + @Override public boolean pure() { return first.pure() && second.pure(); } + + @Override + public void compile(CompileResult target, boolean pollute) { + first.compile(target, true); + if (pollute) target.add(Instruction.dup()); + int start = target.temp(); + if (pollute) target.add(Instruction.discard()); + second.compile(target, pollute); + target.set(start, Instruction.jmpIfNot(target.size() - start)); + } + + public LazyAndStatement(Location loc, Statement first, Statement second) { + super(loc); + this.first = first; + this.second = second; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java new file mode 100644 index 0000000..b1461ed --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class LazyOrStatement extends Statement { + public final Statement first, second; + + @Override public boolean pure() { return first.pure() && second.pure(); } + + @Override + public void compile(CompileResult target, boolean pollute) { + first.compile(target, true); + if (pollute) target.add(Instruction.dup()); + int start = target.temp(); + if (pollute) target.add(Instruction.discard()); + second.compile(target, pollute); + target.set(start, Instruction.jmpIf(target.size() - start)); + } + + public LazyOrStatement(Location loc, Statement first, Statement second) { + super(loc); + this.first = first; + this.second = second; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java deleted file mode 100644 index 9382b81..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ /dev/null @@ -1,180 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.FunctionNode; -import me.topchetoeu.jscript.compilation.FunctionValueNode; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - - -public class ObjectNode extends Node { - public static class ObjProp { - public final String name; - public final String access; - public final FunctionValueNode func; - - public ObjProp(String name, String access, FunctionValueNode func) { - this.name = name; - this.access = access; - this.func = func; - } - } - - public final Map map; - public final Map getters; - public final Map setters; - - @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadObj()); - - for (var el : map.entrySet()) { - target.add(Instruction.dup()); - target.add(Instruction.pushValue(el.getKey())); - var val = el.getValue(); - FunctionNode.compileWithName(val, target, true, el.getKey().toString()); - target.add(Instruction.storeMember()); - } - - var keys = new ArrayList(); - keys.addAll(getters.keySet()); - keys.addAll(setters.keySet()); - - for (var key : keys) { - target.add(Instruction.pushValue((String)key)); - - if (getters.containsKey(key)) getters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - if (setters.containsKey(key)) setters.get(key).compile(target, true); - else target.add(Instruction.pushUndefined()); - - target.add(Instruction.defProp()); - } - - if (!pollute) target.add(Instruction.discard()); - } - - public ObjectNode(Location loc, Map map, Map getters, Map setters) { - super(loc); - this.map = map; - this.getters = getters; - this.setters = setters; - } - - private static ParseRes parsePropName(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var res = ParseRes.first(src, i + n, - Parsing::parseIdentifier, - Parsing::parseString, - (s, j) -> Parsing.parseNumber(s, j, false) - ); - n += res.n; - - if (!res.isSuccess()) return res.chainError(); - return ParseRes.res(res.result.toString(), n); - } - private static ParseRes parseObjectProp(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var access = Parsing.parseIdentifier(src, i + n); - if (!access.isSuccess()) return ParseRes.failed(); - if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); - n += access.n; - - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); - n += name.n; - - var params = JavaScript.parseParameters(src, i + n); - if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); - n += params.n; - - var body = CompoundNode.parse(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); - n += body.n; - - var end = src.loc(i + n - 1); - - return ParseRes.res(new ObjProp( - name.result, access.result, - new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString()) - ), n); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; - n += Parsing.skipEmpty(src, i + n); - - var values = new LinkedHashMap(); - var getters = new LinkedHashMap(); - var setters = new LinkedHashMap(); - - if (src.is(i + n, "}")) { - n++; - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); - } - - while (true) { - var prop = parseObjectProp(src, i + n); - - if (prop.isSuccess()) { - n += prop.n; - - if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func); - else getters.put(prop.result.name, prop.result.func); - } - else { - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name"); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); - n++; - - var valRes = JavaScript.parseExpression(src, i + n, 2); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); - n += valRes.n; - - values.put(name.result, valRes.result); - } - - n += Parsing.skipEmpty(src, i + n); - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - - continue; - } - else if (src.is(i + n, "}")) { - n++; - break; - } - else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java new file mode 100644 index 0000000..f5af390 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -0,0 +1,61 @@ +package me.topchetoeu.jscript.compilation.values; + +import java.util.ArrayList; +import java.util.Map; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class ObjectStatement extends Statement { + public final Map map; + public final Map getters; + public final Map setters; + + @Override public boolean pure() { + for (var el : map.values()) { + if (!el.pure()) return false; + } + + return true; + } + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadObj()); + + for (var el : map.entrySet()) { + target.add(Instruction.dup()); + target.add(Instruction.pushValue(el.getKey())); + var val = el.getValue(); + FunctionStatement.compileWithName(val, target, true, el.getKey().toString()); + target.add(Instruction.storeMember()); + } + + var keys = new ArrayList(); + keys.addAll(getters.keySet()); + keys.addAll(setters.keySet()); + + for (var key : keys) { + target.add(Instruction.pushValue((String)key)); + + if (getters.containsKey(key)) getters.get(key).compile(target, true); + else target.add(Instruction.pushUndefined()); + + if (setters.containsKey(key)) setters.get(key).compile(target, true); + else target.add(Instruction.pushUndefined()); + + target.add(Instruction.defProp()); + } + + if (!pollute) target.add(Instruction.discard()); + } + + public ObjectStatement(Location loc, Map map, Map getters, Map setters) { + super(loc); + this.map = map; + this.getters = getters; + this.setters = setters; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java new file mode 100644 index 0000000..cf66133 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -0,0 +1,36 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class OperationStatement extends Statement { + public final Statement[] args; + public final Operation operation; + + @Override public boolean pure() { + for (var el : args) { + if (!el.pure()) return false; + } + + return true; + } + + @Override + public void compile(CompileResult target, boolean pollute) { + for (var arg : args) { + arg.compile(target, true); + } + + if (pollute) target.add(Instruction.operation(operation)); + else target.add(Instruction.discard()); + } + + public OperationStatement(Location loc, Operation operation, Statement ...args) { + super(loc); + this.operation = operation; + this.args = args; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java deleted file mode 100644 index 7ac7c51..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java +++ /dev/null @@ -1,76 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class RegexNode extends Node { - public final String pattern, flags; - - @Override public void compile(CompileResult target, boolean pollute) { - target.add(Instruction.loadRegex(pattern, flags)); - if (!pollute) target.add(Instruction.discard()); - } - - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, '/')) return ParseRes.failed(); - var loc = src.loc(i + n); - n++; - - var source = new StringBuilder(); - var flags = new StringBuilder(); - - var inBrackets = false; - - while (true) { - if (src.is(i + n, '[')) { - n++; - inBrackets = true; - source.append(src.at(i + n)); - continue; - } - else if (src.is(i + n, ']')) { - n++; - inBrackets = false; - source.append(src.at(i + n)); - continue; - } - else if (src.is(i + n, '/') && !inBrackets) { - n++; - break; - } - - var charRes = Parsing.parseChar(src, i + n); - if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed"); - source.append(charRes.result); - n++; - } - - while (true) { - char c = src.at(i + n, '\0'); - - if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) { - if (flags.indexOf(c + "") >= 0) return ParseRes.error(src.loc(i + n), "The flags of a regular expression may not be repeated"); - flags.append(c); - } - else break; - - n++; - } - - return ParseRes.res(new RegexNode(loc, source.toString(), flags.toString()), n); - } - - public RegexNode(Location loc, String pattern, String flags) { - super(loc); - this.pattern = pattern; - this.flags = flags; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java new file mode 100644 index 0000000..fe2bdb8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java @@ -0,0 +1,25 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class RegexStatement extends Statement { + public final String pattern, flags; + + // Not really pure, since a function is called, but can be ignored. + @Override public boolean pure() { return true; } + + @Override + public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.loadRegex(pattern, flags)); + if (!pollute) target.add(Instruction.discard()); + } + + public RegexStatement(Location loc, String pattern, String flags) { + super(loc); + this.pattern = pattern; + this.flags = flags; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java deleted file mode 100644 index ad74007..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - - -public class ThisNode extends Node { - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadThis()); - } - - public ThisNode(Location loc) { - super(loc); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java new file mode 100644 index 0000000..11ab78d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java @@ -0,0 +1,32 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class TypeofStatement extends Statement { + public final Statement value; + + // Not really pure, since a variable from the global scope could be accessed, + // which could lead to code execution, that would get omitted + @Override public boolean pure() { return true; } + + @Override + public void compile(CompileResult target, boolean pollute) { + if (value instanceof VariableStatement) { + var i = target.scope.getKey(((VariableStatement)value).name); + if (i instanceof String) { + target.add(Instruction.typeof((String)i)); + return; + } + } + value.compile(target, pollute); + target.add(Instruction.typeof()); + } + + public TypeofStatement(Location loc, Statement value) { + super(loc); + this.value = value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java new file mode 100644 index 0000000..31bf6c9 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableAssignStatement.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class VariableAssignStatement extends Statement { + public final String name; + public final Statement value; + public final Operation operation; + + @Override public boolean pure() { return false; } + + @Override + public void compile(CompileResult target, boolean pollute) { + var i = target.scope.getKey(name); + if (operation != null) { + target.add(Instruction.loadVar(i)); + FunctionStatement.compileWithName(value, target, true, name); + target.add(Instruction.operation(operation)); + target.add(Instruction.storeVar(i, pollute)); + } + else { + FunctionStatement.compileWithName(value, target, true, name); + target.add(Instruction.storeVar(i, pollute)); + } + } + + public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { + super(loc); + this.name = name; + this.value = val; + this.operation = operation; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java new file mode 100644 index 0000000..db3e4f0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableIndexStatement.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class VariableIndexStatement extends Statement { + public final int index; + + @Override public boolean pure() { return true; } + + @Override + public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.loadVar(index)); + } + + public VariableIndexStatement(Location loc, int i) { + super(loc); + this.index = i; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java deleted file mode 100644 index 1ced342..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ /dev/null @@ -1,96 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import java.util.function.IntFunction; -import java.util.function.Supplier; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.AssignableNode; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.operations.VariableAssignNode; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; - -public class VariableNode extends Node implements AssignableNode { - public final String name; - - // @Override public EvalResult evaluate(CompileResult target) { - // var i = target.scope.getKey(name); - - // if (i instanceof String) return EvalResult.NONE; - // else return EvalResult.UNKNOWN; - // } - - @Override public Node toAssign(Node val, Operation operation) { - return new VariableAssignNode(loc(), name, val, operation); - } - - @Override public void compile(CompileResult target, boolean pollute) { - var i = target.scope.get(name, true); - - if (i == null) { - target.add(_i -> { - if (target.scope.has(name)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name)); - return Instruction.globGet(name); - }); - - if (!pollute) target.add(Instruction.discard()); - } - else if (pollute) { - target.add(Instruction.loadVar(i.index())); - } - } - - public static IntFunction toGet(CompileResult target, Location loc, String name, Supplier onGlobal) { - var i = target.scope.get(name, true); - - if (i == null) return _i -> { - if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name)); - else return onGlobal.get(); - }; - else return _i -> Instruction.loadVar(i.index()); - } - public static IntFunction toGet(CompileResult target, Location loc, String name) { - return toGet(target, loc, name, () -> Instruction.globGet(name)); - } - - - public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { - var i = target.scope.get(name, true); - - if (i == null) return _i -> { - if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name)); - else return Instruction.globSet(name, keep, define); - }; - else return _i -> Instruction.storeVar(i.index(), keep); - - } - - public VariableNode(Location loc, String name) { - super(loc); - this.name = name; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var literal = Parsing.parseIdentifier(src, i); - if (!literal.isSuccess()) return literal.chainError(); - n += literal.n; - - if (!JavaScript.checkVarName(literal.result)) { - if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); - if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); - if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported."); - return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result)); - } - - return ParseRes.res(new VariableNode(loc, literal.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java new file mode 100644 index 0000000..1ae3d2d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.compilation.values; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.compilation.AssignableStatement; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class VariableStatement extends AssignableStatement { + public final String name; + + @Override public boolean pure() { return false; } + + @Override + public Statement toAssign(Statement val, Operation operation) { + return new VariableAssignStatement(loc(), name, val, operation); + } + + @Override + public void compile(CompileResult target, boolean pollute) { + var i = target.scope.getKey(name); + target.add(Instruction.loadVar(i)); + if (!pollute) target.add(Instruction.discard()); + } + + public VariableStatement(Location loc, String name) { + super(loc); + this.name = name; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java deleted file mode 100644 index 8a5462b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.constants; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class BoolNode extends Node { - public final boolean value; - - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.pushValue(value)); - } - - public BoolNode(Location loc, boolean value) { - super(loc); - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java deleted file mode 100644 index cae8bd6..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.constants; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class NullNode extends Node { - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.pushNull()); - } - - public NullNode(Location loc) { super(loc); } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java deleted file mode 100644 index 5afd40e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.constants; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class NumberNode extends Node { - public final double value; - - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.pushValue(value)); - } - - public NumberNode(Location loc, double value) { - super(loc); - this.value = value; - } - - public static double power(double a, long b) { - if (b == 0) return 1; - if (b == 1) return a; - if (b < 0) return 1 / power(a, -b); - - if ((b & 1) == 0) return power(a * a, b / 2); - else return a * power(a * a, b / 2); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var res = Parsing.parseNumber(src, i + n, false); - if (res.isSuccess()) return ParseRes.res(new NumberNode(loc, res.result), n + res.n); - else return res.chainError(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java deleted file mode 100644 index e50d82b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.constants; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; - -public class StringNode extends Node { - public final String value; - - @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.pushValue(value)); - } - - public StringNode(Location loc, String value) { - super(loc); - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var res = Parsing.parseString(src, i + n); - if (res.isSuccess()) return ParseRes.res(new StringNode(loc, res.result), n + res.n); - else return res.chainError(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java deleted file mode 100644 index bdd3b74..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java +++ /dev/null @@ -1,188 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import java.util.ArrayList; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.ArgumentsNode; -import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.ThisNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.BoolNode; -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; - -public class CallNode extends Node { - public static boolean ATTACH_NAME = true; - - public final Node func; - public final Node[] args; - public final boolean isNew; - - private String generateName(Node func, Node index) { - String res = "(intermediate value)"; - boolean shouldParen = false; - - if (func instanceof ObjectNode) { - var obj = (ObjectNode)func; - - shouldParen = true; - - if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}"; - else res = "{(intermediate value)}"; - } - else if (func instanceof StringNode) { - res = JSON.stringify(JSONElement.string(((StringNode)func).value)); - } - else if (func instanceof NumberNode) { - res = JSON.stringify(JSONElement.number(((NumberNode)func).value)); - } - else if (func instanceof BoolNode) { - res = ((BoolNode)func).value ? "true" : "false"; - } - else if (func instanceof VariableNode) { - res = ((VariableNode)func).name; - } - else if (func instanceof ThisNode) { - res = "this"; - } - else if (func instanceof ArgumentsNode) { - res = "arguments"; - } - // else if (func instanceof VariableIndexNode) { - // var i = ((VariableIndexNode)func).index; - - // if (i == 0) res = "this"; - // else if (i == 1) res = "arguments"; - // } - else if (func instanceof ArrayNode) { - var els = new ArrayList(); - - for (var el : ((ArrayNode)func).statements) { - if (el != null) els.add(generateName(el, null)); - else els.add("(intermediate value)"); - } - - res = "[" + String.join(",", els) + "]"; - } - - if (index == null) return res; - - if (shouldParen) res = "(" + res + ")"; - - if (index instanceof StringNode) { - var val = ((StringNode)index).value; - var bracket = JSON.stringify(JSONElement.string(val)); - - if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]"; - if (Parsing.parseIdentifier(new Source(val), 0).n != val.length()) return res + "[" + bracket + "]"; - - return res + "." + val; - } - - return res + "[" + generateName(index, null) + "]"; - } - - @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { - if (!isNew && func instanceof IndexNode) { - var obj = ((IndexNode)func).object; - var index = ((IndexNode)func).index; - String name = ""; - - obj.compile(target, true); - index.compile(target, true); - for (var arg : args) arg.compile(target, true); - - if (ATTACH_NAME) name = generateName(obj, index); - - target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type); - } - else { - String name = ""; - - func.compile(target, true); - for (var arg : args) arg.compile(target, true); - - if (ATTACH_NAME) name = generateName(func, null); - - if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type); - else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type); - } - if (!pollute) target.add(Instruction.discard()); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, pollute, BreakpointType.STEP_IN); - } - - public CallNode(Location loc, boolean isNew, Node func, Node ...args) { - super(loc); - this.isNew = isNew; - this.func = func; - this.args = args; - } - - public static ParseRes parseCall(Source src, int i, Node prev, int precedence) { - if (precedence > 17) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "(")) return ParseRes.failed(); - n++; - - var args = new ArrayList(); - boolean prevArg = false; - - while (true) { - var argRes = JavaScript.parseExpression(src, i + n, 2); - n += argRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (argRes.isSuccess()) { - args.add(argRes.result); - prevArg = true; - } - else if (argRes.isError()) return argRes.chainError(); - else if (prevArg && src.is(i + n, ",")) { - prevArg = false; - n++; - } - else if (src.is(i + n, ")")) { - n++; - break; - } - else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren"); - else return ParseRes.error(src.loc(i + n), "Expected an expression or a closing paren"); - } - - return ParseRes.res(new CallNode(loc, false, prev, args.toArray(Node[]::new)), n); - } - public static ParseRes parseNew(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed(); - n += 3; - - var valRes = JavaScript.parseExpression(src, i + n, 18); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword."); - n += valRes.n; - - var callRes = CallNode.parseCall(src, i + n, valRes.result, 0); - if (callRes.isFailed()) return ParseRes.res(new CallNode(loc, true, valRes.result), n); - if (callRes.isError()) return callRes.chainError(); - n += callRes.n; - - return ParseRes.res(new CallNode(loc, true, callRes.result.func, callRes.result.args), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java deleted file mode 100644 index e8b403a..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ /dev/null @@ -1,87 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.AssignableNode; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; - -public class ChangeNode extends Node { - public final AssignableNode value; - public final double addAmount; - public final boolean postfix; - - @Override public void compile(CompileResult target, boolean pollute) { - value.toAssign(new NumberNode(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); - if (!pollute) target.add(Instruction.discard()); - else if (postfix) { - target.add(Instruction.pushValue(addAmount)); - target.add(Instruction.operation(Operation.SUBTRACT)); - } - } - - public ChangeNode(Location loc, AssignableNode value, double addAmount, boolean postfix) { - super(loc); - this.value = value; - this.addAmount = addAmount; - this.postfix = postfix; - } - - public static ParseRes parsePrefixIncrease(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "++")) return ParseRes.failed(); - n += 2; - - var res = JavaScript.parseExpression(src, i + n, 15); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); - else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, 1, false), n + res.n); - } - public static ParseRes parsePrefixDecrease(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "--")) return ParseRes.failed(); - n += 2; - - var res = JavaScript.parseExpression(src, i + n, 15); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); - else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, -1, false), n + res.n); - } - - public static ParseRes parsePostfixIncrease(Source src, int i, Node prev, int precedence) { - if (precedence > 15) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "++")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); - n += 2; - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, 1, true), n); - } - public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { - if (precedence > 15) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "--")) return ParseRes.failed(); - if (!(prev instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); - n += 2; - - return ParseRes.res(new ChangeNode(loc, (AssignableNode)prev, -1, true), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java deleted file mode 100644 index a68ebb6..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java +++ /dev/null @@ -1,48 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - - -public class DiscardNode extends Node { - public final Node value; - - // @Override public EvalResult evaluate(CompileResult target) { - // if (value == null) return EvalResult.FALSY; - // var res = value.evaluate(target); - - // if (res.isPure) return EvalResult.FALSY; - // else if (res.never) return EvalResult.NEVER; - // else return EvalResult.FALSY_IMPURE; - // } - - @Override public void compile(CompileResult target, boolean pollute) { - if (value != null) value.compile(target, false); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public DiscardNode(Location loc, Node val) { - super(loc); - this.value = val; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed(); - n += 4; - - var valRes = JavaScript.parseExpression(src, i + n, 14); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword."); - n += valRes.n; - - return ParseRes.res(new DiscardNode(loc, valRes.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java deleted file mode 100644 index 85a6da4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.AssignableNode; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; - -public class IndexNode extends Node implements AssignableNode { - public final Node object; - public final Node index; - - @Override public Node toAssign(Node val, Operation operation) { - return new IndexAssignNode(loc(), object, index, val, operation); - } - public void compile(CompileResult target, boolean dupObj, boolean pollute) { - object.compile(target, true); - if (dupObj) target.add(Instruction.dup()); - - index.compile(target, true); - target.add(Instruction.loadMember()).setLocationAndDebug(loc(), BreakpointType.STEP_IN); - if (!pollute) target.add(Instruction.discard()); - } - @Override public void compile(CompileResult target, boolean pollute) { - compile(target, false, pollute); - } - - public IndexNode(Location loc, Node object, Node index) { - super(loc); - this.object = object; - this.index = index; - } - - public static ParseRes parseIndex(Source src, int i, Node prev, int precedence) { - if (precedence > 18) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "[")) return ParseRes.failed(); - n++; - - var valRes = JavaScript.parseExpression(src, i + n, 0); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression"); - n += valRes.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket"); - n++; - - return ParseRes.res(new IndexNode(loc, prev, valRes.result), n); - } - public static ParseRes parseMember(Source src, int i, Node prev, int precedence) { - if (precedence > 18) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, ".")) return ParseRes.failed(); - n++; - - var literal = Parsing.parseIdentifier(src, i + n); - if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access."); - n += literal.n; - - return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java deleted file mode 100644 index a63506b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java +++ /dev/null @@ -1,53 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -public class LazyAndNode extends Node { - public final Node first, second; - - // @Override public EvalResult evaluate(CompileResult target) { - // var firstRes = first.evaluate(target); - // if (firstRes.falsy) return firstRes; - // if (!firstRes.isPure) return firstRes; - - // return second.evaluate(target); - // } - - @Override public void compile(CompileResult target, boolean pollute) { - first.compile(target, true); - if (pollute) target.add(Instruction.dup()); - int start = target.temp(); - if (pollute) target.add(Instruction.discard()); - second.compile(target, pollute); - target.set(start, Instruction.jmpIfNot(target.size() - start)); - } - - public LazyAndNode(Location loc, Node first, Node second) { - super(loc); - this.first = first; - this.second = second; - } - - - public static ParseRes parse(Source src, int i, Node prev, int precedence) { - if (precedence < 4) return ParseRes.failed(); - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, "&&")) return ParseRes.failed(); - var loc = src.loc(i + n); - n += 2; - - var res = JavaScript.parseExpression(src, i + n, 4); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator."); - n += res.n; - - return ParseRes.res(new LazyAndNode(loc, prev, res.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java deleted file mode 100644 index ac374ce..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - - -public class LazyOrNode extends Node { - public final Node first, second; - - // @Override public EvalResult evaluate(CompileResult target) { - // var firstRes = first.evaluate(target); - // if (firstRes.truthy) return firstRes; - // if (!firstRes.isPure) return firstRes; - - // return second.evaluate(target); - // } - - @Override public void compile(CompileResult target, boolean pollute) { - first.compile(target, true); - if (pollute) target.add(Instruction.dup()); - int start = target.temp(); - if (pollute) target.add(Instruction.discard()); - second.compile(target, pollute); - target.set(start, Instruction.jmpIf(target.size() - start)); - } - - public LazyOrNode(Location loc, Node first, Node second) { - super(loc); - this.first = first; - this.second = second; - } - - - public static ParseRes parse(Source src, int i, Node prev, int precedence) { - if (precedence < 3) return ParseRes.failed(); - var n = Parsing.skipEmpty(src, i); - - if (!src.is(i + n, "||")) return ParseRes.failed(); - var loc = src.loc(i + n); - n += 2; - - var res = JavaScript.parseExpression(src, i + n, 4); - if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator."); - n += res.n; - - return ParseRes.res(new LazyOrNode(loc, prev, res.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java deleted file mode 100644 index 57039c6..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ /dev/null @@ -1,226 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.AssignableNode; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -public class OperationNode extends Node { - private static interface OperatorFactory { - String token(); - int precedence(); - ParseRes construct(Source src, int i, Node prev); - } - - private static class NormalOperatorFactory implements OperatorFactory { - public final String token; - public final int precedence; - public final Operation operation; - - @Override public int precedence() { return precedence; } - @Override public String token() { return token; } - @Override public ParseRes construct(Source src, int i, Node prev) { - var loc = src.loc(i); - - var other = JavaScript.parseExpression(src, i, precedence + 1); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); - return ParseRes.res(new OperationNode(loc, operation, prev, (Node)other.result), other.n); - } - - public NormalOperatorFactory(String token, int precedence, Operation operation) { - this.token = token; - this.precedence = precedence; - this.operation = operation; - } - } - private static class AssignmentOperatorFactory implements OperatorFactory { - public final String token; - public final int precedence; - public final Operation operation; - - @Override public int precedence() { return precedence; } - @Override public String token() { return token; } - @Override public ParseRes construct(Source src, int i, Node prev) { - var loc = src.loc(i); - - if (!(prev instanceof AssignableNode)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); - - var other = JavaScript.parseExpression(src, i, precedence); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token)); - return ParseRes.res(((AssignableNode)prev).toAssign(other.result, operation), other.n); - } - - public AssignmentOperatorFactory(String token, int precedence, Operation operation) { - this.token = token; - this.precedence = precedence; - this.operation = operation; - } - } - private static class LazyAndFactory implements OperatorFactory { - @Override public int precedence() { return 4; } - @Override public String token() { return "&&"; } - @Override public ParseRes construct(Source src, int i, Node prev) { - var loc = src.loc(i); - - var other = JavaScript.parseExpression(src, i, 5); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '&&'"); - return ParseRes.res(new LazyAndNode(loc, prev, (Node)other.result), other.n); - } - } - private static class LazyOrFactory implements OperatorFactory { - @Override public int precedence() { return 5; } - @Override public String token() { return "||"; } - @Override public ParseRes construct(Source src, int i, Node prev) { - var loc = src.loc(i); - - var other = JavaScript.parseExpression(src, i, 6); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), "Expected a value after '||'"); - return ParseRes.res(new LazyOrNode(loc, prev, (Node)other.result), other.n); - } - } - - public final Node[] args; - public final Operation operation; - - @Override public void compile(CompileResult target, boolean pollute) { - for (var arg : args) { - arg.compile(target, true); - } - - if (pollute) target.add(Instruction.operation(operation)); - else target.add(Instruction.discard()); - } - - public OperationNode(Location loc, Operation operation, Node ...args) { - super(loc); - this.operation = operation; - this.args = args; - } - - private static final Map factories = Set.of( - new NormalOperatorFactory("*", 13, Operation.MULTIPLY), - new NormalOperatorFactory("/", 12, Operation.DIVIDE), - new NormalOperatorFactory("%", 12, Operation.MODULO), - new NormalOperatorFactory("-", 11, Operation.SUBTRACT), - new NormalOperatorFactory("+", 11, Operation.ADD), - new NormalOperatorFactory(">>", 10, Operation.SHIFT_RIGHT), - new NormalOperatorFactory("<<", 10, Operation.SHIFT_LEFT), - new NormalOperatorFactory(">>>", 10, Operation.USHIFT_RIGHT), - new NormalOperatorFactory(">", 9, Operation.GREATER), - new NormalOperatorFactory("<", 9, Operation.LESS), - new NormalOperatorFactory(">=", 9, Operation.GREATER_EQUALS), - new NormalOperatorFactory("<=", 9, Operation.LESS_EQUALS), - new NormalOperatorFactory("!=", 8, Operation.LOOSE_NOT_EQUALS), - new NormalOperatorFactory("!==", 8, Operation.NOT_EQUALS), - new NormalOperatorFactory("==", 8, Operation.LOOSE_EQUALS), - new NormalOperatorFactory("===", 8, Operation.EQUALS), - new NormalOperatorFactory("&", 7, Operation.AND), - new NormalOperatorFactory("^", 6, Operation.XOR), - new NormalOperatorFactory("|", 5, Operation.OR), - - new AssignmentOperatorFactory("=", 2, null), - new AssignmentOperatorFactory("*=", 2, Operation.MULTIPLY), - new AssignmentOperatorFactory("/=", 2, Operation.DIVIDE), - new AssignmentOperatorFactory("%=", 2, Operation.MODULO), - new AssignmentOperatorFactory("-=", 2, Operation.SUBTRACT), - new AssignmentOperatorFactory("+=", 2, Operation.ADD), - new AssignmentOperatorFactory(">>=", 2, Operation.SHIFT_RIGHT), - new AssignmentOperatorFactory("<<=", 2, Operation.SHIFT_LEFT), - new AssignmentOperatorFactory(">>>=", 2, Operation.USHIFT_RIGHT), - new AssignmentOperatorFactory("&=", 2, Operation.AND), - new AssignmentOperatorFactory("^=", 2, Operation.XOR), - new AssignmentOperatorFactory("|=", 2, Operation.OR), - - new LazyAndFactory(), - new LazyOrFactory() - ).stream().collect(Collectors.toMap(v -> v.token(), v -> v)); - - private static final List operatorsByLength = factories.keySet().stream().sorted((a, b) -> -a.compareTo(b)).collect(Collectors.toList()); - - public static ParseRes parsePrefix(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - Operation operation = null; - String op; - - if (src.is(i + n, op = "+")) operation = Operation.POS; - else if (src.is(i + n, op = "-")) operation = Operation.NEG; - else if (src.is(i + n, op = "~")) operation = Operation.INVERSE; - else if (src.is(i + n, op = "!")) operation = Operation.NOT; - else return ParseRes.failed(); - - n++; - - var res = JavaScript.parseExpression(src, i + n, 14); - - if (res.isSuccess()) return ParseRes.res(new OperationNode(loc, operation, res.result), n + res.n); - else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op)); - } - public static ParseRes parseInstanceof(Source src, int i, Node prev, int precedence) { - if (precedence > 9) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var kw = Parsing.parseIdentifier(src, i + n, "instanceof"); - if (!kw.isSuccess()) return kw.chainError(); - n += kw.n; - - var valRes = JavaScript.parseExpression(src, i + n, 10); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'."); - n += valRes.n; - - return ParseRes.res(new OperationNode(loc, Operation.INSTANCEOF, prev, valRes.result), n); - } - public static ParseRes parseIn(Source src, int i, Node prev, int precedence) { - if (precedence > 9) return ParseRes.failed(); - - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var kw = Parsing.parseIdentifier(src, i + n, "in"); - if (!kw.isSuccess()) return kw.chainError(); - n += kw.n; - - var valRes = JavaScript.parseExpression(src, i + n, 10); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'."); - n += valRes.n; - - return ParseRes.res(new OperationNode(loc, Operation.IN, valRes.result, prev), n); - } - public static ParseRes parseOperator(Source src, int i, Node prev, int precedence) { - var n = Parsing.skipEmpty(src, i); - - for (var token : operatorsByLength) { - var factory = factories.get(token); - - if (!src.is(i + n, token)) continue; - if (factory.precedence() < precedence) ParseRes.failed(); - - n += token.length(); - n += Parsing.skipEmpty(src, i + n); - - var res = factory.construct(src, i + n, prev); - return res.addN(n); - // var res = Parsing.parseValue(src, i + n, prec + 1); - // if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", token)); - // n += res.n; - - // return ParseRes.res(new OperationStatement(loc, factories.get(token), prev, res.result), n); - } - - return ParseRes.failed(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java deleted file mode 100644 index 15f8387..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java +++ /dev/null @@ -1,57 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; - -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class TypeofNode extends Node { - public final Node value; - - // @Override public EvalResult evaluate(CompileResult target) { - // if (value instanceof VariableNode) { - // var i = target.scope.getKey(((VariableNode)value).name); - // if (i instanceof String) return EvalResult.NONE; - // } - - // return EvalResult.UNKNOWN; - // } - - @Override public void compile(CompileResult target, boolean pollute) { - if (value instanceof VariableNode varNode) { - target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); - if (!pollute) target.add(Instruction.discard()); - - return; - } - - value.compile(target, pollute); - target.add(Instruction.typeof()); - if (!pollute) target.add(Instruction.discard()); - } - - public TypeofNode(Location loc, Node value) { - super(loc); - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed(); - n += 6; - - var valRes = JavaScript.parseExpression(src, i + n, 15); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword."); - n += valRes.n; - - return ParseRes.res(new TypeofNode(loc, valRes.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java deleted file mode 100644 index 940a27c..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.topchetoeu.jscript.compilation.values.operations; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.FunctionNode; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -public class VariableAssignNode extends Node { - public final String name; - public final Node value; - public final Operation operation; - - @Override public void compile(CompileResult target, boolean pollute) { - if (operation != null) { - target.add(VariableNode.toGet(target, loc(), name)); - FunctionNode.compileWithName(value, target, true, name); - target.add(Instruction.operation(operation)); - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); - } - else { - FunctionNode.compileWithName(value, target, true, name); - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); - } - } - - public VariableAssignNode(Location loc, String name, Node val, Operation operation) { - super(loc); - this.name = name; - this.value = val; - this.operation = operation; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java b/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java new file mode 100644 index 0000000..cfe9708 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/ArrayLib.java @@ -0,0 +1,456 @@ +package me.topchetoeu.jscript.lib; + +import java.util.Iterator; +import java.util.Stack; + +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Array") +public class ArrayLib { + private static int normalizeI(int len, int i, boolean clamp) { + if (i < 0) i += len; + if (clamp) { + if (i < 0) i = 0; + if (i > len) i = len; + } + return i; + } + + @Expose(value = "length", type = ExposeType.GETTER) + public static int __getLength(Arguments args) { + return args.self(ArrayValue.class).size(); + } + @Expose(value = "length", type = ExposeType.SETTER) + public static void __setLength(Arguments args) { + args.self(ArrayValue.class).setSize(args.getInt(0)); + } + + @Expose public static ObjectValue __values(Arguments args) { + return __iterator(args); + } + @Expose public static ObjectValue __keys(Arguments args) { + return Values.toJSIterator(args.ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < args.self(ArrayValue.class).size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return i++; + } + }); + } + @Expose public static ObjectValue __entries(Arguments args) { + return Values.toJSIterator(args.ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < args.self(ArrayValue.class).size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return new ArrayValue(args.ctx, i, args.self(ArrayValue.class).get(i++)); + } + }); + } + + @Expose(value = "@@Symbol.iterator") + public static ObjectValue __iterator(Arguments args) { + return Values.toJSIterator(args.ctx, args.self(ArrayValue.class)); + } + @Expose(value = "@@Symbol.asyncIterator") + public static ObjectValue __asyncIterator(Arguments args) { + return Values.toJSAsyncIterator(args.ctx, args.self(ArrayValue.class).iterator()); + } + + @Expose public static ArrayValue __concat(Arguments args) { + // TODO: Fully implement with non-array spreadable objects + var arrs = args.slice(-1); + var size = 0; + + for (int i = 0; i < arrs.n(); i++) { + if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size(); + else i++; + } + + var res = new ArrayValue(size); + + for (int i = 0, j = 0; i < arrs.n(); i++) { + if (arrs.get(i) instanceof ArrayValue) { + var arrEl = arrs.convert(i, ArrayValue.class); + int n = arrEl.size(); + arrEl.copyTo(res, 0, j, n); + j += n; + } + else { + res.set(args.ctx, j++, arrs.get(i)); + } + } + + return res; + } + @Expose public static ArrayValue __sort(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + + var defaultCmp = new NativeFunction("", _args -> { + return _args.getString(0).compareTo(_args.getString(1)); + }); + + arr.sort((a, b) -> { + var res = Values.toNumber(args.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, null, a, b)); + if (res < 0) return -1; + if (res > 0) return 1; + return 0; + }); + return arr; + } + + @Expose public static ArrayValue __fill(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1, 0), true); + var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true); + + for (; start < end; start++) arr.set(args.ctx, start, val); + + return arr; + } + @Expose public static boolean __every(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (var i = 0; i < arr.size(); i++) { + if (arr.has(i) && !Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) return false; + } + + return true; + } + @Expose public static boolean __some(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (var i = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) return true; + } + + return false; + } + @Expose public static ArrayValue __filter(Arguments args) { + var arr = args.self(ArrayValue.class); + var res = new ArrayValue(arr.size()); + + for (int i = 0, j = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) res.set(args.ctx, j++, arr.get(i)); + } + + return res; + } + @Expose public static ArrayValue __map(Arguments args) { + var arr = args.self(ArrayValue.class); + var res = new ArrayValue(arr.size()); + res.setSize(arr.size()); + + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i)) res.set(args.ctx, i, Values.call(args.ctx, args.get(0), args.get(1), arr.get(i), i, arr)); + } + return res; + } + @Expose public static void __forEach(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i)) func.call(args.ctx, thisArg, arr.get(i), i, arr); + } + } + + @Expose public static Object __reduce(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var res = args.get(1); + var i = 0; + + if (args.n() < 2) { + for (; i < arr.size(); i++) { + if (arr.has(i)){ + res = arr.get(i++); + break; + } + } + } + + for (; i < arr.size(); i++) { + if (arr.has(i)) { + res = func.call(args.ctx, null, res, arr.get(i), i, arr); + } + } + + return res; + } + @Expose public static Object __reduceRight(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var res = args.get(1); + var i = arr.size(); + + if (args.n() < 2) { + while (!arr.has(i--) && i >= 0) { + res = arr.get(i); + } + } + else i--; + + for (; i >= 0; i--) { + if (arr.has(i)) { + res = func.call(args.ctx, null, res, arr.get(i), i, arr); + } + } + + return res; + } + + @Expose public static ArrayValue __flat(Arguments args) { + var arr = args.self(ArrayValue.class); + var depth = args.getInt(0, 1); + var res = new ArrayValue(arr.size()); + var stack = new Stack(); + var depths = new Stack(); + + stack.push(arr); + depths.push(-1); + + while (!stack.empty()) { + var el = stack.pop(); + int d = depths.pop(); + + if ((d == -1 || d < depth) && el instanceof ArrayValue) { + var arrEl = (ArrayValue)el; + for (int i = arrEl.size() - 1; i >= 0; i--) { + if (!arrEl.has(i)) continue; + stack.push(arrEl.get(i)); + depths.push(d + 1); + } + } + else res.set(args.ctx, res.size(), el); + } + + return res; + } + @Expose public static ArrayValue __flatMap(Arguments args) { + return __flat(new Arguments(args.ctx, __map(args), 1)); + } + + @Expose public static Object __find(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return arr.get(i); + } + + return null; + } + @Expose public static Object __findLast(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (var i = arr.size() - 1; i >= 0; i--) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return arr.get(i); + } + + return null; + } + + @Expose public static int __findIndex(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return i; + } + + return -1; + } + @Expose public static int __findLastIndex(Arguments args) { + var arr = args.self(ArrayValue.class); + + for (var i = arr.size() - 1; i >= 0; i--) { + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return i; + } + + return -1; + } + + @Expose public static int __indexOf(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1), true); + + for (int i = start; i < arr.size(); i++) { + if (Values.strictEquals(args.ctx, arr.get(i), val)) return i; + } + + return -1; + } + @Expose public static int __lastIndexOf(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1), true); + + for (int i = arr.size(); i >= start; i--) { + if (Values.strictEquals(args.ctx, arr.get(i), val)) return i; + } + + return -1; + } + + @Expose public static boolean __includes(Arguments args) { + return __indexOf(args) >= 0; + } + + @Expose public static Object __pop(Arguments args) { + var arr = args.self(ArrayValue.class); + if (arr.size() == 0) return null; + + var val = arr.get(arr.size() - 1); + arr.shrink(1); + return val; + } + @Expose public static int __push(Arguments args) { + var arr = args.self(ArrayValue.class); + var values = args.args; + + arr.copyFrom(args.ctx, values, 0, arr.size(), values.length); + return arr.size(); + } + + @Expose public static Object __shift(Arguments args) { + var arr = args.self(ArrayValue.class); + + if (arr.size() == 0) return null; + var val = arr.get(0); + + arr.move(1, 0, arr.size()); + arr.shrink(1); + return val; + } + @Expose public static int __unshift(Arguments args) { + var arr = args.self(ArrayValue.class); + var values = args.slice(0).args; + + arr.move(0, values.length, arr.size()); + arr.copyFrom(args.ctx, values, 0, 0, values.length); + return arr.size(); + } + + @Expose public static ArrayValue __slice(Arguments args) { + var arr = args.self(ArrayValue.class); + var start = normalizeI(arr.size(), args.getInt(0), true); + var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true); + + var res = new ArrayValue(end - start); + arr.copyTo(res, start, 0, end - start); + return res; + } + + @Expose public static ArrayValue __splice(Arguments args) { + var arr = args.self(ArrayValue.class); + var start = normalizeI(arr.size(), args.getInt(0), true); + var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true); + var items = args.slice(2).args; + + if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; + + var size = arr.size() - deleteCount + items.length; + var res = new ArrayValue(deleteCount); + arr.copyTo(res, start, 0, deleteCount); + arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount); + arr.copyFrom(args.ctx, items, 0, start, items.length); + arr.setSize(size); + + return res; + } + @Expose public static String __toString(Arguments args) { + return __join(new Arguments(args.ctx, args.self, ",")); + } + + @Expose public static String __join(Arguments args) { + var arr = args.self(ArrayValue.class); + var sep = args.getString(0, ", "); + var res = new StringBuilder(); + var comma = false; + + for (int i = 0; i < arr.size(); i++) { + if (!arr.has(i)) continue; + + if (comma) res.append(sep); + comma = true; + + var el = arr.get(i); + if (el == null || el == Values.NULL) continue; + + res.append(Values.toString(args.ctx, el)); + } + + return res.toString(); + } + + @Expose(target = ExposeTarget.STATIC) + public static boolean __isArray(Arguments args) { + return args.get(0) instanceof ArrayValue; + } + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __of(Arguments args) { + return new ArrayValue(args.ctx, args.slice(0).args); + } + + @ExposeConstructor public static ArrayValue __constructor(Arguments args) { + ArrayValue res; + + if (args.n() == 1 && args.get(0) instanceof Number) { + var len = args.getInt(0); + res = new ArrayValue(len); + res.setSize(len); + } + else { + var val = args.args; + res = new ArrayValue(val.length); + res.copyFrom(args.ctx, val, 0, 0, val.length); + } + + return res; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java new file mode 100644 index 0000000..2151164 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -0,0 +1,87 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.lib.PromiseLib.Handle; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("AsyncFunction") +public class AsyncFunctionLib extends FunctionValue { + public final CodeFunction func; + + private static class AsyncHelper { + public PromiseLib promise = new PromiseLib(); + public Frame frame; + + private boolean awaiting = false; + + private void next(Context ctx, Object inducedValue, EngineException inducedError) { + Object res = null; + + frame.onPush(); + awaiting = false; + while (!awaiting) { + try { + res = frame.next(inducedValue, Values.NO_RETURN, inducedError); + inducedValue = Values.NO_RETURN; + inducedError = null; + + if (res != Values.NO_RETURN) { + promise.fulfill(ctx, res); + break; + } + } + catch (EngineException e) { + promise.reject(ctx, e); + break; + } + } + frame.onPop(); + + if (awaiting) { + PromiseLib.handle(ctx, frame.pop(), new Handle() { + @Override + public void onFulfil(Object val) { + next(ctx, val, null); + } + @Override + public void onReject(EngineException err) { + next(ctx, Values.NO_RETURN, err); + } + }.defer(ctx)); + } + } + + public Object await(Arguments args) { + this.awaiting = true; + return args.get(0); + } + } + + @Override + public Object call(Extensions ext, Object thisArg, Object ...args) { + var handler = new AsyncHelper(); + var ctx = Context.of(ext); + + var newArgs = new Object[args.length + 1]; + newArgs[0] = new NativeFunction("await", handler::await); + System.arraycopy(args, 0, newArgs, 1, args.length); + + handler.frame = new Frame(ctx, thisArg, newArgs, (CodeFunction)func); + handler.next(ctx, Values.NO_RETURN, null); + return handler.promise; + } + + public AsyncFunctionLib(FunctionValue func) { + super(func.name, func.length); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + this.func = (CodeFunction)func; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java new file mode 100644 index 0000000..43e7689 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java @@ -0,0 +1,34 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("AsyncGeneratorFunction") +public class AsyncGeneratorFunctionLib extends FunctionValue { + public final CodeFunction func; + + @Override + public Object call(Extensions ext, Object thisArg, Object ...args) { + var handler = new AsyncGeneratorLib(); + + var newArgs = new Object[args.length + 2]; + newArgs[0] = new NativeFunction("await", handler::await); + newArgs[1] = new NativeFunction("yield", handler::yield); + System.arraycopy(args, 0, newArgs, 2, args.length); + + handler.frame = new Frame(Context.of(ext), thisArg, newArgs, func); + return handler; + } + + public AsyncGeneratorFunctionLib(CodeFunction func) { + super(func.name, func.length); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + this.func = func; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java new file mode 100644 index 0000000..32778ac --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -0,0 +1,107 @@ +package me.topchetoeu.jscript.lib; + +import java.util.Map; + +import me.topchetoeu.jscript.lib.PromiseLib.Handle; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("AsyncGenerator") +public class AsyncGeneratorLib { + private int state = 0; + private boolean done = false; + private PromiseLib currPromise; + public Frame frame; + + private void next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { + if (done) { + if (inducedError != null) throw inducedError; + currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of( + "done", true, + "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn + ))); + return; + } + + Object res = null; + state = 0; + + frame.onPush(); + while (state == 0) { + try { + res = frame.next(inducedValue, inducedReturn, inducedError); + inducedValue = inducedReturn = Values.NO_RETURN; + inducedError = null; + + if (res != Values.NO_RETURN) { + var obj = new ObjectValue(); + obj.defineProperty(ctx, "done", true); + obj.defineProperty(ctx, "value", res); + currPromise.fulfill(ctx, obj); + break; + } + } + catch (EngineException e) { + currPromise.reject(ctx, e); + break; + } + } + frame.onPop(); + + if (state == 1) { + PromiseLib.handle(ctx, frame.pop(), new Handle() { + @Override public void onFulfil(Object val) { + next(ctx, val, Values.NO_RETURN, null); + } + @Override public void onReject(EngineException err) { + next(ctx, Values.NO_RETURN, Values.NO_RETURN, err); + } + }.defer(ctx)); + } + else if (state == 2) { + var obj = new ObjectValue(); + obj.defineProperty(ctx, "done", false); + obj.defineProperty(ctx, "value", frame.pop()); + currPromise.fulfill(ctx, obj); + } + } + + @Override + public String toString() { + if (done) return "Generator [closed]"; + if (state != 0) return "Generator [suspended]"; + return "Generator [running]"; + } + + public Object await(Arguments args) { + this.state = 1; + return args.get(0); + } + public Object yield(Arguments args) { + this.state = 2; + return args.get(0); + } + + @Expose public PromiseLib __next(Arguments args) { + this.currPromise = new PromiseLib(); + if (args.has(0)) next(args.ctx, args.get(0), Values.NO_RETURN, null); + else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); + return this.currPromise; + } + @Expose public PromiseLib __return(Arguments args) { + this.currPromise = new PromiseLib(); + next(args.ctx, Values.NO_RETURN, args.get(0), null); + return this.currPromise; + } + @Expose public PromiseLib __throw(Arguments args) { + this.currPromise = new PromiseLib(); + next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setExtensions(args.ctx)); + return this.currPromise; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java b/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java new file mode 100644 index 0000000..019a061 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/BooleanLib.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Boolean") +public class BooleanLib { + public static final BooleanLib TRUE = new BooleanLib(true); + public static final BooleanLib FALSE = new BooleanLib(false); + + public final boolean value; + + @Override public String toString() { + return value + ""; + } + + public BooleanLib(boolean val) { + this.value = val; + } + + @ExposeConstructor public static Object __constructor(Arguments args) { + var val = args.getBoolean(0); + if (args.self instanceof ObjectValue) return val ? TRUE : FALSE; + else return val; + } + @Expose public static String __toString(Arguments args) { + return args.self(Boolean.class) ? "true" : "false"; + } + @Expose public static boolean __valueOf(Arguments args) { + if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value; + return args.self(Boolean.class); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java b/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java new file mode 100644 index 0000000..2a7c205 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/ConsoleLib.java @@ -0,0 +1,38 @@ +package me.topchetoeu.jscript.lib; + +import java.io.IOException; + +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.filesystem.File; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Console") +public class ConsoleLib { + public static interface Writer { + void writeLine(String val) throws IOException; + } + + private File file; + + @Expose + public void __log(Arguments args) { + var res = new StringBuilder(); + var first = true; + + for (var el : args.args) { + if (!first) res.append(" "); + first = false; + res.append(Values.toReadable(args.ctx, el).getBytes()); + } + + for (var line : res.toString().split("\n", -1)) { + file.write(line.getBytes()); + } + } + + public ConsoleLib(File file) { + this.file = file; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/DateLib.java b/src/main/java/me/topchetoeu/jscript/lib/DateLib.java new file mode 100644 index 0000000..b4b5082 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/DateLib.java @@ -0,0 +1,266 @@ +package me.topchetoeu.jscript.lib; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Date") +public class DateLib { + private Calendar normal; + private Calendar utc; + + private void updateUTC() { + if (utc == null || normal == null) return; + utc.setTimeInMillis(normal.getTimeInMillis()); + } + private void updateNormal() { + if (utc == null || normal == null) return; + normal.setTimeInMillis(utc.getTimeInMillis()); + } + private void invalidate() { + normal = utc = null; + } + + @Expose public double __getYear() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.YEAR) - 1900; + } + @Expose public double __setYeard(Arguments args) { + var real = args.getDouble(0); + if (real >= 0 && real <= 99) real = real + 1900; + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.YEAR, (int)real); + updateUTC(); + return __getTime(); + } + + @Expose public double __getFullYear() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.YEAR); + } + @Expose public double __getMonth() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.MONTH); + } + @Expose public double __getDate() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.DAY_OF_MONTH); + } + @Expose public double __getDay() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.DAY_OF_WEEK); + } + @Expose public double __getHours() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.HOUR_OF_DAY); + } + @Expose public double __getMinutes() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.MINUTE); + } + @Expose public double __getSeconds() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.SECOND); + } + @Expose public double __getMilliseconds() { + if (normal == null) return Double.NaN; + return normal.get(Calendar.MILLISECOND); + } + + @Expose public double __getUTCFullYear() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.YEAR); + } + @Expose public double __getUTCMonth() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.MONTH); + } + @Expose public double __getUTCDate() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.DAY_OF_MONTH); + } + @Expose public double __getUTCDay() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.DAY_OF_WEEK); + } + @Expose public double __getUTCHours() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.HOUR_OF_DAY); + } + @Expose public double __getUTCMinutes() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.MINUTE); + } + @Expose public double __getUTCSeconds() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.SECOND); + } + @Expose public double __getUTCMilliseconds() { + if (utc == null) return Double.NaN; + return utc.get(Calendar.MILLISECOND); + } + + @Expose public double __setFullYear(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.YEAR, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setMonthd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.MONTH, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setDated(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.DAY_OF_MONTH, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setDayd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.DAY_OF_WEEK, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setHoursd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.HOUR_OF_DAY, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setMinutesd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.MINUTE, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setSecondsd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.SECOND, (int)real); + updateUTC(); + return __getTime(); + } + @Expose public double __setMillisecondsd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else normal.set(Calendar.MILLISECOND, (int)real); + updateUTC(); + return __getTime(); + } + + @Expose public double __setUTCFullYeard(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.YEAR, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCMonthd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.MONTH, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCDated(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.DAY_OF_MONTH, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCDayd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.DAY_OF_WEEK, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCHoursd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.HOUR_OF_DAY, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCMinutesd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.MINUTE, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCSecondsd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.SECOND, (int)real); + updateNormal(); + return __getTime(); + } + @Expose public double __setUTCMillisecondsd(Arguments args) { + var real = args.getDouble(0); + if (Double.isNaN(real)) invalidate(); + else utc.set(Calendar.MILLISECOND, (int)real); + updateNormal(); + return __getTime(); + } + + @Expose public double __getTime() { + if (utc == null) return Double.NaN; + return utc.getTimeInMillis(); + } + @Expose public double __getTimezoneOffset() { + if (normal == null) return Double.NaN; + return normal.getTimeZone().getRawOffset() / 60000; + } + + @Expose public double __valueOf() { + if (normal == null) return Double.NaN; + else return normal.getTimeInMillis(); + } + + @Expose public String __toString() { + return normal.getTime().toString(); + } + + @Override public String toString() { + return __toString(); + } + + public DateLib(long timestamp) { + normal = Calendar.getInstance(); + utc = Calendar.getInstance(); + normal.setTimeInMillis(timestamp); + utc.setTimeZone(TimeZone.getTimeZone("UTC")); + utc.setTimeInMillis(timestamp); + } + + public DateLib() { + this(new Date().getTime()); + } + + @ExposeConstructor public static DateLib init(Arguments args) { + if (args.has(0)) return new DateLib(args.getLong(0)); + else return new DateLib(); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __now() { + return new DateLib().__getTime(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java b/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java new file mode 100644 index 0000000..04ec153 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/EncodingLib.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; + +import me.topchetoeu.jscript.common.Buffer; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Encoding") +public class EncodingLib { + private static final String HEX = "0123456789ABCDEF"; + + public static String encodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var bytes = str.getBytes(); + var sb = new StringBuilder(bytes.length); + + for (byte c : bytes) { + if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c); + else { + sb.append('%'); + sb.append(HEX.charAt(c / 16)); + sb.append(HEX.charAt(c % 16)); + } + } + + return sb.toString(); + } + public static String decodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var res = new Buffer(); + var bytes = str.getBytes(); + + for (var i = 0; i < bytes.length; i++) { + var c = bytes[i]; + if (c == '%') { + if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed."); + var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]); + if (!Parsing.isAny((char)b, keepAlphabet)) { + i += 2; + res.append((byte)b); + continue; + } + } + res.append(c); + } + + return new String(res.data()); + } + + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __encode(Arguments args) { + var res = new ArrayValue(); + for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el); + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static String __decode(Arguments args) { + var raw = args.convert(0, ArrayList.class); + var res = new byte[raw.size()]; + for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, raw.get(i)); + return new String(res); + } + + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURIComponent(Arguments args) { + return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()"); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURIComponent(Arguments args) { + return decodeUriAny(args.getString(0), ""); + } + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURI(Arguments args) { + return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()"); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURI(Arguments args) { + return decodeUriAny(args.getString(0), ",/?:@&=+$#."); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java new file mode 100644 index 0000000..abda9c3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/ErrorLib.java @@ -0,0 +1,54 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.exceptions.ConvertException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Error") +public class ErrorLib { + private static String toString(Context ctx, Object name, Object message) { + if (name == null) name = ""; + else name = Values.toString(ctx, name).trim(); + if (message == null) message = ""; + else message = Values.toString(ctx, message).trim(); + StringBuilder res = new StringBuilder(); + + if (!name.equals("")) res.append(name); + if (!message.equals("") && !name.equals("")) res.append(": "); + if (!message.equals("")) res.append(message); + + return res.toString(); + } + + @ExposeField public static final String __name = "Error"; + + @Expose public static String __toString(Arguments args) { + if (args.self instanceof ObjectValue) return toString(args.ctx, + Values.getMember(args.ctx, args.self, "name"), + Values.getMember(args.ctx, args.self, "message") + ); + else return "[Invalid error]"; + } + + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { + var target = new ObjectValue(); + var message = args.getString(0, ""); + + try { + target = args.self(ObjectValue.class); + } + catch (ConvertException e) {} + + target.setPrototype(PlaceholderProto.ERROR); + target.defineProperty(args.ctx, "message", Values.toString(args.ctx, message)); + + return target; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FileLib.java b/src/main/java/me/topchetoeu/jscript/lib/FileLib.java new file mode 100644 index 0000000..95b7baa --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/FileLib.java @@ -0,0 +1,84 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.filesystem.File; +import me.topchetoeu.jscript.utils.filesystem.FilesystemException; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("File") +public class FileLib { + public final File fd; + + @Expose public PromiseLib __pointer(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + try { + return fd.seek(0, 1); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose public PromiseLib __length(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + try { + long curr = fd.seek(0, 1); + long res = fd.seek(0, 2); + fd.seek(curr, 0); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + + @Expose public PromiseLib __read(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var n = args.getInt(0); + try { + var buff = new byte[n]; + var res = new ArrayValue(); + int resI = fd.read(buff); + + for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose public PromiseLib __write(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var val = args.convert(0, ArrayValue.class); + try { + var res = new byte[val.size()]; + + for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i)); + fd.write(res); + + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose public PromiseLib __close(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + fd.close(); + return null; + }); + } + @Expose public PromiseLib __seek(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var ptr = args.getLong(0); + var whence = args.getInt(1); + + try { + return fd.seek(ptr, whence); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + + public FileLib(File fd) { + this.fd = fd; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java new file mode 100644 index 0000000..6582495 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -0,0 +1,193 @@ +package me.topchetoeu.jscript.lib; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Stack; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.filesystem.ActionType; +import me.topchetoeu.jscript.utils.filesystem.EntryType; +import me.topchetoeu.jscript.utils.filesystem.ErrorReason; +import me.topchetoeu.jscript.utils.filesystem.File; +import me.topchetoeu.jscript.utils.filesystem.FileStat; +import me.topchetoeu.jscript.utils.filesystem.Filesystem; +import me.topchetoeu.jscript.utils.filesystem.FilesystemException; +import me.topchetoeu.jscript.utils.filesystem.Mode; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Filesystem") +public class FilesystemLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_SET = 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_CUR = 1; + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_END = 2; + + private static Filesystem fs(Context ctx) { + var fs = Filesystem.get(ctx); + if (fs != null) return fs; + throw EngineException.ofError("Current environment doesn't have a file system."); + } + + @Expose(target = ExposeTarget.STATIC) + public static String __normalize(Arguments args) { + return fs(args.ctx).normalize(args.convert(String.class)); + } + + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __open(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var _mode = Mode.parse(args.getString(1)); + + try { + if (fs.stat(path).type != EntryType.FILE) { + throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN).setPath(path); + } + + return new FileLib(fs.open(path, _mode)); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __ls(Arguments args) { + + return Values.toJSAsyncIterator(args.ctx, new Iterator<>() { + private boolean failed, done; + private File file; + private String nextLine; + + private void update() { + if (done) return; + if (!failed) { + if (file == null) { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + + if (fs.stat(path).type != EntryType.FOLDER) { + throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN); + } + + file = fs.open(path, Mode.READ); + } + + if (nextLine == null) { + while (true) { + nextLine = file.readLine(); + if (nextLine == null) { + done = true; + return; + } + nextLine = nextLine.trim(); + if (!nextLine.equals("")) break; + } + } + } + } + + @Override + public boolean hasNext() { + try { + update(); + return !done && !failed; + } + catch (FilesystemException e) { throw e.toEngineException(); } + } + @Override + public String next() { + try { + update(); + var res = nextLine; + nextLine = null; + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + } + }); + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __mkdir(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { + fs(args.ctx).create(args.getString(0), EntryType.FOLDER); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __mkfile(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { + fs(args.ctx).create(args.getString(0), EntryType.FILE); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __rm(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var recursive = args.getBoolean(1); + + if (!recursive) fs.create(path, EntryType.NONE); + else { + var stack = new Stack(); + stack.push(path); + + while (!stack.empty()) { + var currPath = stack.pop(); + FileStat stat; + + try { stat = fs.stat(currPath); } + catch (FilesystemException e) { continue; } + + if (stat.type == EntryType.FOLDER) { + for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el); + } + else fs.create(currPath, EntryType.NONE); + } + } + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __stat(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var stat = fs.stat(path); + var res = new ObjectValue(); + + res.defineProperty(args.ctx, "type", stat.type.name); + res.defineProperty(args.ctx, "mode", stat.mode.name); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __exists(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { fs(args.ctx).stat(args.getString(0)); return true; } + catch (FilesystemException e) { return false; } + }); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java new file mode 100644 index 0000000..e50a927 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/FunctionLib.java @@ -0,0 +1,83 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.runtime.Compiler; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Function") +public class FunctionLib { + private static int i; + + @Expose public static Object __apply(Arguments args) { + return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray()); + } + @Expose public static Object __call(Arguments args) { + return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.slice(1).args); + } + @Expose public static FunctionValue __bind(Arguments args) { + var self = args.self(FunctionValue.class); + var thisArg = args.get(0); + var bindArgs = args.slice(1).args; + + return new NativeFunction(self.name + " (bound)", callArgs -> { + Object[] resArgs; + + if (args.n() == 0) resArgs = bindArgs; + else { + resArgs = new Object[bindArgs.length + callArgs.n()]; + System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length); + System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n()); + } + + return self.call(callArgs.ctx, thisArg, resArgs); + }); + } + @Expose public static String __toString(Arguments args) { + return args.self.toString(); + } + + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __async(Arguments args) { + return new AsyncFunctionLib(args.convert(0, FunctionValue.class)); + } + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __asyncGenerator(Arguments args) { + return new AsyncGeneratorFunctionLib(args.convert(0, CodeFunction.class)); + } + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __generator(Arguments args) { + return new GeneratorFunctionLib(args.convert(0, CodeFunction.class)); + } + + @ExposeConstructor + public static Object __constructor(Arguments args) { + var compiler = Compiler.get(args); + + var parts = args.convert(String.class); + if (parts.length == 0) parts = new String[] { "" }; + + var src = "return function("; + + for (var i = 0; i < parts.length - 1; i++) { + if (i != 0) src += ","; + src += parts[i]; + } + + src += "){" + parts[parts.length - 1] + "}"; + + var body = compiler.compile(new Filename("jscript", "func/" + i++), src); + var func = new CodeFunction(Context.clean(args.ctx), "", body, new ValueVariable[0]); + return Values.call(args, func, null); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java b/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java new file mode 100644 index 0000000..7075dae --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java @@ -0,0 +1,32 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("GeneratorFunction") +public class GeneratorFunctionLib extends FunctionValue { + public final CodeFunction func; + + @Override public Object call(Extensions ext, Object thisArg, Object ...args) { + var handler = new GeneratorLib(); + + var newArgs = new Object[args.length + 1]; + newArgs[0] = new NativeFunction("yield", handler::yield); + System.arraycopy(args, 0, newArgs, 1, args.length); + + handler.frame = new Frame(Context.of(ext), thisArg, newArgs, func); + return handler; + } + + public GeneratorFunctionLib(CodeFunction func) { + super(func.name, func.length); + if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); + this.func = func; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java new file mode 100644 index 0000000..9849c7d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -0,0 +1,78 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Generator") +public class GeneratorLib { + private boolean yielding = true; + private boolean done = false; + public Frame frame; + + private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { + if (done) { + if (inducedError != Values.NO_RETURN) throw inducedError; + var res = new ObjectValue(); + res.defineProperty(ctx, "done", true); + res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn); + return res; + } + + Object res = null; + yielding = false; + + frame.onPush(); + while (!yielding) { + try { + res = frame.next(inducedValue, inducedReturn, inducedError); + inducedReturn = Values.NO_RETURN; + inducedError = null; + if (res != Values.NO_RETURN) { + done = true; + break; + } + } + catch (EngineException e) { + done = true; + throw e; + } + } + frame.onPop(); + + if (done) frame = null; + else res = frame.pop(); + + var obj = new ObjectValue(); + obj.defineProperty(ctx, "done", done); + obj.defineProperty(ctx, "value", res); + return obj; + } + + @Expose public ObjectValue __next(Arguments args) { + if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); + else return next(args.ctx, args.get(0), Values.NO_RETURN, null); + } + @Expose public ObjectValue __throw(Arguments args) { + return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setExtensions(args.ctx)); + } + @Expose public ObjectValue __return(Arguments args) { + return next(args.ctx, Values.NO_RETURN, args.get(0), null); + } + + @Override public String toString() { + if (done) return "Generator [closed]"; + if (yielding) return "Generator [suspended]"; + return "Generator [running]"; + } + + public Object yield(Arguments args) { + this.yielding = true; + return args.get(0); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/Internals.java b/src/main/java/me/topchetoeu/jscript/lib/Internals.java new file mode 100644 index 0000000..67564c6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/Internals.java @@ -0,0 +1,222 @@ +package me.topchetoeu.jscript.lib; + +import java.util.HashMap; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.filesystem.Filesystem; +import me.topchetoeu.jscript.utils.filesystem.Mode; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; +import me.topchetoeu.jscript.utils.modules.ModuleRepo; + +public class Internals { + private static final Key> THREADS = new Key<>(); + private static final Key I = new Key<>(); + + @Expose(target = ExposeTarget.STATIC) + public static Object __require(Arguments args) { + var repo = ModuleRepo.get(args.ctx); + + if (repo != null) { + var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0)); + res.load(args.ctx); + return res.value(); + } + + else throw EngineException.ofError("Modules are not supported."); + } + + @Expose(target = ExposeTarget.STATIC) + public static Thread __setTimeout(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var delay = args.getDouble(1); + var arguments = args.slice(2).args; + + if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop"); + + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + try { Thread.sleep(ms, ns); } + catch (InterruptedException e) { return; } + + args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.extensions), null, arguments), false); + }); + + thread.start(); + var i = args.ctx.init(I, 1); + args.ctx.add(I, i + 1); + args.ctx.init(THREADS, new HashMap()).put(i, thread); + + return thread; + } + @Expose(target = ExposeTarget.STATIC) + public static Thread __setInterval(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var delay = args.getDouble(1); + var arguments = args.slice(2).args; + + if (!args.ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop"); + + var thread = new Thread(() -> { + var ms = (long)delay; + var ns = (int)((delay - ms) * 10000000); + + while (true) { + try { + Thread.sleep(ms, ns); + } + catch (InterruptedException e) { return; } + + args.ctx.get(EventLoop.KEY).pushMsg(() -> func.call(new Context(args.ctx.extensions), null, arguments), false); + } + }); + thread.start(); + var i = args.ctx.init(I, 1); + args.ctx.add(I, i + 1); + args.ctx.init(THREADS, new HashMap()).put(i, thread); + + return thread; + } + + @Expose(target = ExposeTarget.STATIC) + public static void __clearTimeout(Arguments args) { + var i = args.getInt(0); + HashMap map = args.ctx.get(THREADS); + if (map == null) return; + + var thread = map.get(i); + if (thread == null) return; + + thread.interrupt(); + map.remove(i); + } + @Expose(target = ExposeTarget.STATIC) + public static void __clearInterval(Arguments args) { + __clearTimeout(args); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __parseInt(Arguments args) { + return NumberLib.__parseInt(args); + } + @Expose(target = ExposeTarget.STATIC) + public static double __parseFloat(Arguments args) { + return NumberLib.__parseFloat(args); + } + + @Expose(target = ExposeTarget.STATIC) + public static boolean __isNaN(Arguments args) { + return NumberLib.__isNaN(args); + } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFinite(Arguments args) { + return NumberLib.__isFinite(args); + } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isInfinite(Arguments args) { + return NumberLib.__isInfinite(args); + } + + @Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER) + public static FileLib __stdin(Arguments args) { + return new FileLib(Filesystem.get(args.ctx).open("std://in", Mode.READ)); + } + @Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER) + public static FileLib __stdout(Arguments args) { + return new FileLib(Filesystem.get(args.ctx).open("std://out", Mode.READ)); + } + @Expose(target = ExposeTarget.STATIC, type = ExposeType.GETTER) + public static FileLib __stderr(Arguments args) { + return new FileLib(Filesystem.get(args.ctx).open("std://err", Mode.READ)); + } + + @ExposeField(target = ExposeTarget.STATIC) + public static double __NaN = Double.NaN; + @ExposeField(target = ExposeTarget.STATIC) + public static double __Infinity = Double.POSITIVE_INFINITY; + + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURIComponent(Arguments args) { + return EncodingLib.__encodeURIComponent(args); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURIComponent(Arguments args) { + return EncodingLib.__decodeURIComponent(args); + } + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURI(Arguments args) { + return EncodingLib.__encodeURI(args); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURI(Arguments args) { + return EncodingLib.__decodeURI(args); + } + + public static Environment apply(Environment env) { + var wp = new NativeWrapperProvider(); + var glob = new GlobalScope(wp.getNamespace(Internals.class)); + + glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); + glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); + glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); + glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); + + glob.define(null, false, wp.getConstr(FileLib.class)); + + glob.define(null, false, wp.getConstr(DateLib.class)); + glob.define(null, false, wp.getConstr(ObjectLib.class)); + glob.define(null, false, wp.getConstr(FunctionLib.class)); + glob.define(null, false, wp.getConstr(ArrayLib.class)); + + glob.define(null, false, wp.getConstr(BooleanLib.class)); + glob.define(null, false, wp.getConstr(NumberLib.class)); + glob.define(null, false, wp.getConstr(StringLib.class)); + glob.define(null, false, wp.getConstr(SymbolLib.class)); + + glob.define(null, false, wp.getConstr(PromiseLib.class)); + glob.define(null, false, wp.getConstr(RegExpLib.class)); + glob.define(null, false, wp.getConstr(MapLib.class)); + glob.define(null, false, wp.getConstr(SetLib.class)); + + glob.define(null, false, wp.getConstr(ErrorLib.class)); + glob.define(null, false, wp.getConstr(SyntaxErrorLib.class)); + glob.define(null, false, wp.getConstr(TypeErrorLib.class)); + glob.define(null, false, wp.getConstr(RangeErrorLib.class)); + + env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class)); + env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class)); + env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class)); + + env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class)); + env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class)); + env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class)); + env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class)); + + env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class)); + env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class)); + env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class)); + env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class)); + + env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class)); + Values.setPrototype(new Context(), wp.getProto(ObjectLib.class), null); + + env.add(NativeWrapperProvider.KEY, wp); + env.add(GlobalScope.KEY, glob); + + + return env; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java b/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java new file mode 100644 index 0000000..31a1e11 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/JSONLib.java @@ -0,0 +1,24 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("JSON") +public class JSONLib { + @Expose(target = ExposeTarget.STATIC) + public static Object __parse(Arguments args) { + try { + return JSON.toJs(JSON.parse(null, args.getString(0))); + } + catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } + } + @Expose(target = ExposeTarget.STATIC) + public static String __stringify(Arguments args) { + return JSON.stringify(JSON.fromJs(args.ctx, args.get(0))); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/MapLib.java b/src/main/java/me/topchetoeu/jscript/lib/MapLib.java new file mode 100644 index 0000000..849bf00 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/MapLib.java @@ -0,0 +1,87 @@ +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Map") +public class MapLib { + private LinkedHashMap map = new LinkedHashMap<>(); + + @Expose("@@Symbol.iterator") + public ObjectValue __iterator(Arguments args) { + return this.__entries(args); + } + + @Expose public void __clear() { + map.clear(); + } + @Expose public boolean __delete(Arguments args) { + var key = args.get(0); + if (map.containsKey(key)) { + map.remove(key); + return true; + } + return false; + } + + @Expose public ObjectValue __entries(Arguments args) { + return Values.toJSIterator(args.ctx, map + .entrySet() + .stream() + .map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue())) + .collect(Collectors.toList()) + ); + } + @Expose public ObjectValue __keys(Arguments args) { + return Values.toJSIterator(args.ctx, map.keySet()); + } + @Expose public ObjectValue __values(Arguments args) { + return Values.toJSIterator(args.ctx, map.values()); + } + + @Expose public Object __get(Arguments args) { + return map.get(args.get(0)); + } + @Expose public MapLib __set(Arguments args) { + map.put(args.get(0), args.get(1)); + return this; + } + @Expose public boolean __has(Arguments args) { + return map.containsKey(args.get(0)); + } + + @Expose(type = ExposeType.GETTER) + public int __size() { + return map.size(); + } + + @Expose public void __forEach(Arguments args) { + var keys = new ArrayList<>(map.keySet()); + + for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), map.get(el), el, args.self); + } + + public MapLib(Context ctx, Object iterable) { + for (var el : Values.fromJSIterator(ctx, iterable)) { + try { + map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); + } + catch (IllegalArgumentException e) { } + } + } + + @ExposeConstructor public static MapLib __constructor(Arguments args) { + return new MapLib(args.ctx, args.get(0)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/MathLib.java b/src/main/java/me/topchetoeu/jscript/lib/MathLib.java new file mode 100644 index 0000000..a0b5d53 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/MathLib.java @@ -0,0 +1,211 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Math") +public class MathLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final double __E = Math.E; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __PI = Math.PI; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __SQRT2 = Math.sqrt(2); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __SQRT1_2 = Math.sqrt(.5); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LN2 = Math.log(2); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LN10 = Math.log(10); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LOG2E = Math.log(Math.E) / __LN2; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LOG10E = Math.log10(Math.E); + + @Expose(target = ExposeTarget.STATIC) + public static double __asin(Arguments args) { + return Math.asin(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __acos(Arguments args) { + return Math.acos(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atan(Arguments args) { + return Math.atan(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atan2(Arguments args) { + var x = args.getDouble(1); + var y = args.getDouble(0); + + if (x == 0) { + if (y == 0) return Double.NaN; + return Math.signum(y) * Math.PI / 2; + } + else { + var val = Math.atan(y / x); + if (x > 0) return val; + else if (y < 0) return val - Math.PI; + else return val + Math.PI; + } + + } + + @Expose(target = ExposeTarget.STATIC) + public static double __asinh(Arguments args) { + var x = args.getDouble(0); + return Math.log(x + Math.sqrt(x * x + 1)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __acosh(Arguments args) { + var x = args.getDouble(0); + return Math.log(x + Math.sqrt(x * x - 1)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atanh(Arguments args) { + var x = args.getDouble(0); + + if (x <= -1 || x >= 1) return Double.NaN; + return .5 * Math.log((1 + x) / (1 - x)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __sin(Arguments args) { + return Math.sin(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cos(Arguments args) { + return Math.cos(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __tan(Arguments args) { + return Math.tan(args.getDouble(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __sinh(Arguments args) { + return Math.sinh(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cosh(Arguments args) { + return Math.cosh(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __tanh(Arguments args) { + return Math.tanh(args.getDouble(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __sqrt(Arguments args) { + return Math.sqrt(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cbrt(Arguments args) { + return Math.cbrt(args.getDouble(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __hypot(Arguments args) { + var res = 0.; + for (var i = 0; i < args.n(); i++) { + var val = args.getDouble(i); + res += val * val; + } + return Math.sqrt(res); + } + @Expose(target = ExposeTarget.STATIC) + public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); } + + @Expose(target = ExposeTarget.STATIC) + public static double __exp(Arguments args) { + return Math.exp(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __expm1(Arguments args) { + return Math.expm1(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); } + + @Expose(target = ExposeTarget.STATIC) + public static double __log(Arguments args) { + return Math.log(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log10(Arguments args) { + return Math.log10(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log1p(Arguments args) { + return Math.log1p(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log2(Arguments args) { + return Math.log(args.getDouble(0)) / __LN2; + } + + @Expose(target = ExposeTarget.STATIC) + public static double __ceil(Arguments args) { + return Math.ceil(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __floor(Arguments args) { + return Math.floor(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __round(Arguments args) { + return Math.round(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static float __fround(Arguments args) { + return (float)args.getDouble(0); + } + @Expose(target = ExposeTarget.STATIC) + public static double __trunc(Arguments args) { + var x = args.getDouble(0); + return Math.floor(Math.abs(x)) * Math.signum(x); + } + @Expose(target = ExposeTarget.STATIC) + public static double __abs(Arguments args) { + return Math.abs(args.getDouble(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __max(Arguments args) { + var res = Double.NEGATIVE_INFINITY; + + for (var i = 0; i < args.n(); i++) { + var el = args.getDouble(i); + if (el > res) res = el; + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static double __min(Arguments args) { + var res = Double.POSITIVE_INFINITY; + + for (var i = 0; i < args.n(); i++) { + var el = args.getDouble(i); + if (el < res) res = el; + } + + return res; + } + + @Expose(target = ExposeTarget.STATIC) + public static double __sign(Arguments args) { + return Math.signum(args.getDouble(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __random() { return Math.random(); } + @Expose(target = ExposeTarget.STATIC) + public static int __clz32(Arguments args) { + return Integer.numberOfLeadingZeros(args.getInt(0)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java b/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java new file mode 100644 index 0000000..07eb00b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/NumberLib.java @@ -0,0 +1,103 @@ +package me.topchetoeu.jscript.lib; + +import java.text.NumberFormat; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Number") +public class NumberLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final double __EPSILON = Math.ulp(1.0); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MAX_SAFE_INTEGER = 9007199254740991.; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER; + // lmao big number go brrr + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MIN_VALUE = -__MAX_VALUE; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __NaN = 0. / 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __NEGATIVE_INFINITY = -1. / 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __POSITIVE_INFINITY = 1. / 0; + + public final double value; + + @Override public String toString() { return value + ""; } + + public NumberLib(double val) { + this.value = val; + } + + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isSafeInteger(Arguments args) { + return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER; + } + + @Expose(target = ExposeTarget.STATIC) + public static double __parseFloat(Arguments args) { + return args.getDouble(0); + } + @Expose(target = ExposeTarget.STATIC) + public static double __parseInt(Arguments args) { + var radix = args.getInt(1, 10); + + if (radix < 2 || radix > 36) return Double.NaN; + else { + long res = 0; + + for (var c : args.getString(0).toCharArray()) { + var digit = 0; + + if (c >= '0' && c <= '9') digit = c - '0'; + else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10; + else break; + + if (digit > radix) break; + + res *= radix; + res += digit; + } + + return res; + } + } + + @ExposeConstructor public static Object __constructor(Arguments args) { + if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0)); + else return args.getDouble(0); + } + @Expose public static String __toString(Arguments args) { + return Values.toString(args.ctx, args.self); + } + @Expose public static String __toFixed(Arguments args) { + var digits = args.getInt(0, 0); + + var nf = NumberFormat.getNumberInstance(); + nf.setMinimumFractionDigits(digits); + nf.setMaximumFractionDigits(digits); + + return nf.format(args.getDouble(-1)); + } + @Expose public static double __valueOf(Arguments args) { + if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value; + else return Values.toNumber(args.ctx, args.self); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java b/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java new file mode 100644 index 0000000..c9b2964 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/ObjectLib.java @@ -0,0 +1,273 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Object") +public class ObjectLib { + @Expose(target = ExposeTarget.STATIC) + public static Object __assign(Arguments args) { + for (var obj : args.slice(1).args) { + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key)); + } + } + return args.get(0); + } + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __create(Arguments args) { + var obj = new ObjectValue(); + Values.setPrototype(args.ctx, obj, args.get(0)); + + if (args.n() >= 1) { + var newArgs = new Object[args.n()]; + System.arraycopy(args.args, 1, args, 1, args.n() - 1); + newArgs[0] = obj; + + __defineProperties(new Arguments(args.ctx, null, newArgs)); + } + + return obj; + } + + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __defineProperty(Arguments args) { + var obj = args.convert(0, ObjectValue.class); + var key = args.get(1); + var attrib = args.convert(2, ObjectValue.class); + + var hasVal = Values.hasMember(args.ctx, attrib, "value", false); + var hasGet = Values.hasMember(args.ctx, attrib, "get", false); + var hasSet = Values.hasMember(args.ctx, attrib, "set", false); + + if (hasVal) { + if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); + if (!obj.defineProperty( + args.ctx, key, + Values.getMember(args.ctx, attrib, "value"), + Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable")) + )) throw EngineException.ofType("Can't define property '" + key + "'."); + } + else { + var get = Values.getMember(args.ctx, attrib, "get"); + var set = Values.getMember(args.ctx, attrib, "set"); + if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function."); + if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function."); + + if (!obj.defineProperty( + args.ctx, key, + (FunctionValue)get, (FunctionValue)set, + Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable")) + )) throw EngineException.ofType("Can't define property '" + key + "'."); + } + + return obj; + } + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __defineProperties(Arguments args) { + var obj = args.convert(0, ObjectValue.class); + var attrib = args.get(1); + + for (var key : Values.getMembers(null, attrib, false, false)) { + __defineProperty(new Arguments(args.ctx, null, obj, key, Values.getMember(args.ctx, attrib, key))); + } + + return obj; + } + + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __keys(Arguments args) { + var obj = args.get(0); + var all = args.getBoolean(1); + var res = new ArrayValue(); + + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __entries(Arguments args) { + var res = new ArrayValue(); + var obj = args.get(0); + var all = args.getBoolean(1); + + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, obj, key))); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __values(Arguments args) { + var res = new ArrayValue(); + var obj = args.get(0); + var all = args.getBoolean(1); + + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key)); + } + + return res; + } + + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getOwnPropertyDescriptor(Arguments args) { + return Values.getMemberDescriptor(args.ctx, args.get(0), args.get(1)); + } + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getOwnPropertyDescriptors(Arguments args) { + var res = new ObjectValue(); + var obj = args.get(0); + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, obj, key)); + } + return res; + } + + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __getOwnPropertyNames(Arguments args) { + var res = new ArrayValue(); + var obj = args.get(0); + var all = args.getBoolean(1); + + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __getOwnPropertySymbols(Arguments args) { + var obj = args.get(0); + var res = new ArrayValue(); + + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + if (key instanceof Symbol) res.set(args.ctx, res.size(), key); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static boolean __hasOwn(Arguments args) { + return Values.hasMember(args.ctx, args.get(0), args.get(1), true); + } + + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getPrototypeOf(Arguments args) { + return Values.getPrototype(args.ctx, args.get(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static Object __setPrototypeOf(Arguments args) { + Values.setPrototype(args.ctx, args.get(0), args.get(1)); + return args.get(0); + } + + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __fromEntries(Arguments args) { + var res = new ObjectValue(); + + for (var el : Values.fromJSIterator(args.ctx, args.get(0))) { + if (el instanceof ArrayValue) { + res.defineProperty(args.ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); + } + } + + return res; + } + + @Expose(target = ExposeTarget.STATIC) + public static Object __preventExtensions(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions(); + return args.get(0); + } + @Expose(target = ExposeTarget.STATIC) + public static Object __seal(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal(); + return args.get(0); + } + @Expose(target = ExposeTarget.STATIC) + public static Object __freeze(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze(); + return args.get(0); + } + + @Expose(target = ExposeTarget.STATIC) + public static boolean __isExtensible(Arguments args) { + var obj = args.get(0); + if (!(obj instanceof ObjectValue)) return false; + return ((ObjectValue)obj).extensible(); + } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isSealed(Arguments args) { + var obj = args.get(0); + + if (!(obj instanceof ObjectValue)) return true; + var _obj = (ObjectValue)obj; + + if (_obj.extensible()) return false; + + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; + } + + return true; + } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFrozen(Arguments args) { + var obj = args.get(0); + + if (!(obj instanceof ObjectValue)) return true; + var _obj = (ObjectValue)obj; + + if (_obj.extensible()) return false; + + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; + if (_obj.memberWritable(key)) return false; + } + + return true; + } + + @Expose + public static Object __valueOf(Arguments args) { + return args.self; + } + @Expose + public static String __toString(Arguments args) { + var name = Values.getMember(args.ctx, args.self, Symbol.get("Symbol.typeName")); + if (name == null) name = "Unknown"; + else name = Values.toString(args.ctx, name); + + return "[object " + name + "]"; + } + @Expose + public static boolean __hasOwnProperty(Arguments args) { + return Values.hasMember(args.ctx, args.self, args.get(0), true); + } + + @ExposeConstructor + public static Object __constructor(Arguments args) { + var arg = args.get(0); + if (arg == null || arg == Values.NULL) return new ObjectValue(); + else if (arg instanceof Boolean) return new BooleanLib((boolean)arg); + else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue()); + else if (arg instanceof String) return new StringLib((String)arg); + else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg); + else return arg; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java b/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java new file mode 100644 index 0000000..66829de --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/PromiseLib.java @@ -0,0 +1,404 @@ +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.ResultRunnable; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.InterruptException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Promise") +public class PromiseLib { + public static interface Handle { + void onFulfil(Object val); + void onReject(EngineException err); + + default Handle defer(Extensions loop) { + var self = this; + + return new Handle() { + @Override public void onFulfil(Object val) { + if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop"); + loop.get(EventLoop.KEY).pushMsg(() -> self.onFulfil(val), true); + } + @Override public void onReject(EngineException val) { + if (!loop.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop"); + loop.get(EventLoop.KEY).pushMsg(() -> self.onReject(val), true); + } + }; + } + } + + private static final int STATE_PENDING = 0; + private static final int STATE_FULFILLED = 1; + private static final int STATE_REJECTED = 2; + + private List handles = new ArrayList<>(); + + private int state = STATE_PENDING; + private boolean handled = false; + private Object val; + + private void resolveSynchronized(Context ctx, Object val, int newState) { + this.val = val; + this.state = newState; + + for (var handle : handles) { + if (newState == STATE_FULFILLED) handle.onFulfil(val); + if (newState == STATE_REJECTED) { + handle.onReject((EngineException)val); + handled = true; + } + } + + if (state == STATE_REJECTED && !handled) { + Values.printError(((EngineException)val).setExtensions(ctx), "(in promise)"); + } + + handles = null; + + // ctx.get(EventLoop.KEY).pushMsg(() -> { + // if (!ctx.hasNotNull(EventLoop.KEY)) throw EngineException.ofError("No event loop"); + + + // handles = null; + // }, true); + + } + private synchronized void resolve(Context ctx, Object val, int newState) { + if (this.state != STATE_PENDING || newState == STATE_PENDING) return; + + handle(ctx, val, new Handle() { + @Override public void onFulfil(Object val) { + resolveSynchronized(ctx, val, newState); + } + @Override public void onReject(EngineException err) { + resolveSynchronized(ctx, val, STATE_REJECTED); + } + }); + } + + public synchronized void fulfill(Context ctx, Object val) { + resolve(ctx, val, STATE_FULFILLED); + } + public synchronized void reject(Context ctx, EngineException val) { + resolve(ctx, val, STATE_REJECTED); + } + + private void handle(Handle handle) { + if (state == STATE_FULFILLED) handle.onFulfil(val); + else if (state == STATE_REJECTED) { + handle.onReject((EngineException)val); + handled = true; + } + else handles.add(handle); + } + + @Override public String toString() { + if (state == STATE_PENDING) return "Promise (pending)"; + else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; + else return "Promise (rejected)"; + } + + public PromiseLib() { + this.state = STATE_PENDING; + this.val = null; + } + + public static PromiseLib await(Context ctx, ResultRunnable runner) { + var res = new PromiseLib(); + + new Thread(() -> { + try { + res.fulfill(ctx, runner.run()); + } + catch (EngineException e) { + res.reject(ctx, e); + } + catch (Exception e) { + if (e instanceof InterruptException) throw e; + else { + res.reject(ctx, EngineException.ofError("Native code failed with " + e.getMessage())); + } + } + }, "Promisifier").start(); + + return res; + } + public static PromiseLib await(Context ctx, Runnable runner) { + return await(ctx, () -> { + runner.run(); + return null; + }); + } + + public static void handle(Context ctx, Object obj, Handle handle) { + if (Values.isWrapper(obj, PromiseLib.class)) { + var promise = Values.wrapper(obj, PromiseLib.class); + handle(ctx, promise, handle); + return; + } + if (obj instanceof PromiseLib) { + ((PromiseLib)obj).handle(handle); + return; + } + + var rethrow = new boolean[1]; + + try { + var then = Values.getMember(ctx, obj, "then"); + + if (then instanceof FunctionValue) Values.call(ctx, then, obj, + new NativeFunction(args -> { + try { handle.onFulfil(args.get(0)); } + catch (Exception e) { + rethrow[0] = true; + throw e; + } + return null; + }), + new NativeFunction(args -> { + try { handle.onReject(new EngineException(args.get(0))); } + catch (Exception e) { + rethrow[0] = true; + throw e; + } + return null; + }) + ); + else handle.onFulfil(obj); + + return; + } + catch (Exception e) { + if (rethrow[0]) throw e; + } + + handle.onFulfil(obj); + } + + public static PromiseLib ofResolved(Context ctx, Object value) { + var res = new PromiseLib(); + res.fulfill(ctx, value); + return res; + } + public static PromiseLib ofRejected(Context ctx, EngineException value) { + var res = new PromiseLib(); + res.reject(ctx, value); + return res; + } + + @Expose(value = "resolve", target = ExposeTarget.STATIC) + public static PromiseLib __ofResolved(Arguments args) { + return ofResolved(args.ctx, args.get(0)); + } + @Expose(value = "reject", target = ExposeTarget.STATIC) + public static PromiseLib __ofRejected(Arguments args) { + return ofRejected(args.ctx, new EngineException(args.get(0)).setExtensions(args.ctx)); + } + + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __any(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + + if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setExtensions(args.ctx)); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var errors = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (res.state != STATE_PENDING) break; + + handle(args.ctx, val, new Handle() { + public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + public void onReject(EngineException err) { + errors.set(args.ctx, index, err.value); + n[0]--; + if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setExtensions(args.ctx)); + } + }); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __race(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var res = new PromiseLib(); + + for (var i = 0; i < promises.size(); i++) { + var val = promises.get(i); + if (res.state != STATE_PENDING) break; + + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + @Override public void onReject(EngineException err) { res.reject(args.ctx, err); } + }); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __all(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + var val = promises.get(i); + + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { + result.set(args.ctx, index, val); + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, result); + } + @Override public void onReject(EngineException err) { + res.reject(args.ctx, err); + } + }); + } + + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __allSettled(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + + handle(args.ctx, promises.get(i), new Handle() { + @Override public void onFulfil(Object val) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "fulfilled"); + desc.defineProperty(args.ctx, "value", val); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + @Override public void onReject(EngineException err) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "reject"); + desc.defineProperty(args.ctx, "value", err.value); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + }); + } + + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; + } + + @Expose + public static Object __then(Arguments args) { + var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx)); + + return res; + } + @Expose + public static Object __catch(Arguments args) { + return __then(new Arguments(args.ctx, args.self, null, args.get(0))); + } + @Expose + public static Object __finally(Arguments args) { + var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { + func.call(args.ctx); + res.fulfill(args.ctx, val); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { + func.call(args.ctx); + res.reject(args.ctx, err); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx)); + + return res; + } + + @ExposeConstructor + public static PromiseLib __constructor(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var res = new PromiseLib(); + + try { + func.call( + args.ctx, null, + new NativeFunction(null, _args -> { + res.fulfill(_args.ctx, _args.get(0)); + return null; + }), + new NativeFunction(null, _args -> { + res.reject(_args.ctx, new EngineException(_args.get(0)).setExtensions(_args.ctx)); + return null; + }) + ); + } + catch (EngineException e) { + res.reject(args.ctx, e); + } + + return res; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java new file mode 100644 index 0000000..78877ce --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("RangeError") +public class RangeErrorLib extends ErrorLib { + @ExposeField public static final String __name = "RangeError"; + + @ExposeConstructor public static ObjectValue constructor(Arguments args) { + var target = ErrorLib.__constructor(args); + target.setPrototype(PlaceholderProto.RANGE_ERROR); + return target; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java b/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java new file mode 100644 index 0000000..47c8b92 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/RegExpLib.java @@ -0,0 +1,354 @@ +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.regex.Pattern; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeWrapper; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("RegExp") +public class RegExpLib { + // I used Regex to analyze Regex + private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); + private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); + + private Pattern pattern; + private String[] namedGroups; + private int flags; + + public int lastI = 0; + public final String source; + public final boolean hasIndices; + public final boolean global; + public final boolean sticky; + + @Expose(type = ExposeType.GETTER) + public int __lastIndex() { return lastI; } + @Expose(type = ExposeType.SETTER) + public void __setLastIndex(Arguments args) { lastI = args.getInt(0); } + @Expose(type = ExposeType.GETTER) + public String __source() { return source; } + + @Expose(type = ExposeType.GETTER) + public boolean __ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __multiline() { return (flags & Pattern.MULTILINE) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __dotAll() { return (flags & Pattern.DOTALL) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __global() { return global; } + @Expose(type = ExposeType.GETTER) + public boolean __sticky() { return sticky; } + @Expose(type = ExposeType.GETTER) + public final String __flags() { + String res = ""; + if (hasIndices) res += 'd'; + if (global) res += 'g'; + if (__ignoreCase()) res += 'i'; + if (__multiline()) res += 'm'; + if (__dotAll()) res += 's'; + if (__unicode()) res += 'u'; + if (sticky) res += 'y'; + return res; + } + + @Expose public Object __exec(Arguments args) { + var str = args.getString(0); + var matcher = pattern.matcher(str); + if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { + lastI = 0; + return Values.NULL; + } + if (sticky || global) { + lastI = matcher.end(); + if (matcher.end() == matcher.start()) lastI++; + } + + var obj = new ArrayValue(); + ObjectValue groups = null; + + for (var el : namedGroups) { + if (groups == null) groups = new ObjectValue(); + try { groups.defineProperty(null, el, matcher.group(el)); } + catch (IllegalArgumentException e) { } + } + + + for (int i = 0; i < matcher.groupCount() + 1; i++) { + obj.set(null, i, matcher.group(i)); + } + obj.defineProperty(null, "groups", groups); + obj.defineProperty(null, "index", matcher.start()); + obj.defineProperty(null, "input", str); + + if (hasIndices) { + var indices = new ArrayValue(); + for (int i = 0; i < matcher.groupCount() + 1; i++) { + indices.set(null, i, new ArrayValue(null, matcher.start(i), matcher.end(i))); + } + var groupIndices = new ObjectValue(); + for (var el : namedGroups) { + groupIndices.defineProperty(null, el, new ArrayValue(null, matcher.start(el), matcher.end(el))); + } + indices.defineProperty(null, "groups", groupIndices); + obj.defineProperty(null, "indices", indices); + } + + return obj; + } + + @Expose public boolean __test(Arguments args) { + return this.__exec(args) != Values.NULL; + } + + @Expose("@@Symbol.match") public Object __match(Arguments args) { + if (this.global) { + var res = new ArrayValue(); + Object val; + while ((val = this.__exec(args)) != Values.NULL) { + res.set(args.ctx, res.size(), Values.getMember(args.ctx, val, 0)); + } + lastI = 0; + return res; + } + else { + var res = this.__exec(args); + if (!this.sticky) this.lastI = 0; + return res; + } + } + + @Expose("@@Symbol.matchAll") public Object __matchAll(Arguments args) { + var pattern = this.toGlobal(); + + return Values.toJSIterator(args.ctx, new Iterator() { + private Object val = null; + private boolean updated = false; + + private void update() { + if (!updated) val = pattern.__exec(args); + } + @Override public boolean hasNext() { + update(); + return val != Values.NULL; + } + @Override public Object next() { + update(); + updated = false; + return val; + } + }); + } + + @Expose("@@Symbol.split") public ArrayValue __split(Arguments args) { + var pattern = this.toGlobal(); + var target = args.getString(0); + var hasLimit = args.get(1) != null; + var lim = args.getInt(1); + var sensible = args.getBoolean(2); + + Object match; + int lastEnd = 0; + var res = new ArrayValue(); + + while ((match = pattern.__exec(args)) != Values.NULL) { + var added = new ArrayList(); + var arrMatch = (ArrayValue)match; + int index = (int)Values.toNumber(args.ctx, Values.getMember(args.ctx, match, "index")); + var matchVal = (String)arrMatch.get(0); + + if (index >= target.length()) break; + + if (matchVal.length() == 0 || index - lastEnd > 0) { + added.add(target.substring(lastEnd, pattern.lastI)); + if (pattern.lastI < target.length()) { + for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i)); + } + } + else { + for (var i = 1; i < arrMatch.size(); i++) added.add((String)arrMatch.get(i)); + } + + if (sensible) { + if (hasLimit && res.size() + added.size() >= lim) break; + else for (var i = 0; i < added.size(); i++) res.set(args.ctx, res.size(), added.get(i)); + } + else { + for (var i = 0; i < added.size(); i++) { + if (hasLimit && res.size() >= lim) return res; + else res.set(args.ctx, res.size(), added.get(i)); + } + } + lastEnd = pattern.lastI; + } + if (lastEnd < target.length()) { + res.set(args.ctx, res.size(), target.substring(lastEnd)); + } + return res; + } + + @Expose("@@Symbol.replace") public String __replace(Arguments args) { + var pattern = this.toIndexed(); + var target = args.getString(0); + var replacement = args.get(1); + Object match; + var lastEnd = 0; + var res = new StringBuilder(); + + while ((match = pattern.__exec(args)) != Values.NULL) { + var indices = (ArrayValue)((ArrayValue)Values.getMember(args.ctx, match, "indices")).get(0); + var arrMatch = (ArrayValue)match; + + var start = ((Number)indices.get(0)).intValue(); + var end = ((Number)indices.get(1)).intValue(); + + res.append(target.substring(lastEnd, start)); + if (replacement instanceof FunctionValue) { + var callArgs = new Object[arrMatch.size() + 2]; + callArgs[0] = target.substring(start, end); + arrMatch.copyTo(callArgs, 1, 1, arrMatch.size() - 1); + callArgs[callArgs.length - 2] = start; + callArgs[callArgs.length - 1] = target; + res.append(Values.toString(args.ctx, ((FunctionValue)replacement).call(args.ctx, null, callArgs))); + } + else { + res.append(Values.toString(args.ctx, replacement)); + } + lastEnd = end; + if (!pattern.global) break; + } + if (lastEnd < target.length()) { + res.append(target.substring(lastEnd)); + } + return res.toString(); + } + + // [Symbol.search](target, reverse, start) { + // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; + // if (!reverse) { + // pattern.lastIndex = (start as any) | 0; + // const res = pattern.exec(target); + // if (res) return res.index; + // else return -1; + // } + // else { + // start ??= target.length; + // start |= 0; + // let res: RegExpResult | null = null; + // while (true) { + // const tmp = pattern.exec(target); + // if (tmp === null || tmp.index > start) break; + // res = tmp; + // } + // if (res && res.index <= start) return res.index; + // else return -1; + // } + // }, + + public RegExpLib toGlobal() { + return new RegExpLib(pattern, namedGroups, flags, source, hasIndices, true, sticky); + } + public RegExpLib toIndexed() { + return new RegExpLib(pattern, namedGroups, flags, source, true, global, sticky); + } + + public String toString() { + return "/" + source + "/" + __flags(); + } + + public RegExpLib(String pattern, String flags) { + if (pattern == null || pattern.equals("")) pattern = "(?:)"; + if (flags == null || flags.equals("")) flags = ""; + + this.flags = 0; + this.hasIndices = flags.contains("d"); + this.global = flags.contains("g"); + this.sticky = flags.contains("y"); + this.source = pattern; + + if (flags.contains("i")) this.flags |= Pattern.CASE_INSENSITIVE; + if (flags.contains("m")) this.flags |= Pattern.MULTILINE; + if (flags.contains("s")) this.flags |= Pattern.DOTALL; + if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS; + + if (pattern.equals("{(\\d+)}")) pattern = "\\{([0-9]+)\\}"; + this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags); + + var matcher = NAMED_PATTERN.matcher(source); + var groups = new ArrayList(); + + while (matcher.find()) { + if (!checkEscaped(source, matcher.start() - 1)) { + groups.add(matcher.group(1)); + } + } + + namedGroups = groups.toArray(String[]::new); + } + + private RegExpLib(Pattern pattern, String[] namedGroups, int flags, String source, boolean hasIndices, boolean global, boolean sticky) { + this.pattern = pattern; + this.namedGroups = namedGroups; + this.flags = flags; + this.source = source; + this.hasIndices = hasIndices; + this.global = global; + this.sticky = sticky; + } + public RegExpLib(String pattern) { this(pattern, null); } + public RegExpLib() { this(null, null); } + + @ExposeConstructor + public static RegExpLib __constructor(Arguments args) { + return new RegExpLib(cleanupPattern(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); + } + @Expose(target = ExposeTarget.STATIC) + public static RegExpLib __escape(Arguments args) { + return escape(Values.toString(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); + } + + private static String cleanupPattern(Context ctx, Object val) { + if (val == null) return "(?:)"; + if (val instanceof RegExpLib) return ((RegExpLib)val).source; + if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) { + return ((RegExpLib)((NativeWrapper)val).wrapped).source; + } + var res = Values.toString(ctx, val); + if (res.equals("")) return "(?:)"; + return res; + } + private static String cleanupFlags(Context ctx, Object val) { + if (val == null) return ""; + return Values.toString(ctx, val); + } + + private static boolean checkEscaped(String s, int pos) { + int n = 0; + + while (true) { + if (pos <= 0) break; + if (s.charAt(pos) != '\\') break; + n++; + pos--; + } + + return (n % 2) != 0; + } + + public static RegExpLib escape(String raw, String flags) { + return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SetLib.java b/src/main/java/me/topchetoeu/jscript/lib/SetLib.java new file mode 100644 index 0000000..273a27c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/SetLib.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.lib; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Set") +public class SetLib { + private LinkedHashSet set = new LinkedHashSet<>(); + + @Expose("@@Symbol.iterator") + public ObjectValue __iterator(Arguments args) { + return this.__values(args); + } + + @Expose public ObjectValue __entries(Arguments args) { + return Values.toJSIterator(args.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList())); + } + @Expose public ObjectValue __keys(Arguments args) { + return Values.toJSIterator(args.ctx, set); + } + @Expose public ObjectValue __values(Arguments args) { + return Values.toJSIterator(args.ctx, set); + } + + @Expose public Object __add(Arguments args) { + return set.add(args.get(0)); + } + @Expose public boolean __delete(Arguments args) { + return set.remove(args.get(0)); + } + @Expose public boolean __has(Arguments args) { + return set.contains(args.get(0)); + } + + @Expose public void __clear() { + set.clear(); + } + + @Expose(type = ExposeType.GETTER) + public int __size() { + return set.size(); + } + + @Expose public void __forEach(Arguments args) { + var keys = new ArrayList<>(set); + + for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), el, el, args.self); + } + + public SetLib(Context ctx, Object iterable) { + for (var el : Values.fromJSIterator(ctx, iterable)) set.add(el); + } + + @ExposeConstructor + public static SetLib __constructor(Arguments args) { + return new SetLib(args.ctx, args.get(0)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/StringLib.java b/src/main/java/me/topchetoeu/jscript/lib/StringLib.java new file mode 100644 index 0000000..93317a3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/StringLib.java @@ -0,0 +1,291 @@ +package me.topchetoeu.jscript.lib; + +import java.util.regex.Pattern; + +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.ExposeType; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +// TODO: implement index wrapping properly +@WrapperName("String") +public class StringLib { + public final String value; + + @Override public String toString() { return value; } + + public StringLib(String val) { + this.value = val; + } + + private static String passThis(Arguments args, String funcName) { + var val = args.self; + if (Values.isWrapper(val, StringLib.class)) return Values.wrapper(val, StringLib.class).value; + else if (val instanceof String) return (String)val; + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); + } + private static int normalizeI(int i, int len, boolean clamp) { + if (i < 0) i += len; + if (clamp) { + if (i < 0) i = 0; + if (i > len) i = len; + } + return i; + } + + @Expose(type = ExposeType.GETTER) + public static int __length(Arguments args) { + return passThis(args, "length").length(); + } + + @Expose public static String __substring(Arguments args) { + var val = passThis(args, "substring"); + var start = Math.max(0, Math.min(val.length(), args.getInt(0))); + var end = Math.max(0, Math.min(val.length(), args.getInt(1, val.length()))); + + if (end < start) { + var tmp = end; + end = start; + start = tmp; + } + + return val.substring(start, end); + } + @Expose public static String __substr(Arguments args) { + var val = passThis(args, "substr"); + var start = normalizeI(args.getInt(0), val.length(), true); + int end = normalizeI(args.getInt(1, val.length() - start) + start, val.length(), true); + return val.substring(start, end); + } + + @Expose public static String __toLowerCase(Arguments args) { + return passThis(args, "toLowerCase").toLowerCase(); + } + @Expose public static String __toUpperCase(Arguments args) { + return passThis(args, "toUpperCase").toUpperCase(); + } + + @Expose public static String __charAt(Arguments args) { + return passThis(args, "charAt").charAt(args.getInt(0)) + ""; + } + @Expose public static double __charCodeAt(Arguments args) { + var str = passThis(args, "charCodeAt"); + var i = args.getInt(0); + if (i < 0 || i >= str.length()) return Double.NaN; + else return str.charAt(i); + } + @Expose public static double __codePointAt(Arguments args) { + var str = passThis(args, "codePointAt"); + var i = args.getInt(0); + if (i < 0 || i >= str.length()) return Double.NaN; + else return str.codePointAt(i); + } + + @Expose public static boolean __startsWith(Arguments args) { + return passThis(args, "startsWith").startsWith(args.getString(0), args.getInt(1)); + } + @Expose public static boolean __endsWith(Arguments args) { + return passThis(args, "endsWith").lastIndexOf(args.getString(0), args.getInt(1)) >= 0; + } + + @Expose public static int __indexOf(Arguments args) { + var val = passThis(args, "indexOf"); + var term = args.get(0); + var start = args.getInt(1); + var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search")); + + if (search instanceof FunctionValue) { + return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, false, start)); + } + else return val.indexOf(Values.toString(args.ctx, term), start); + } + @Expose public static int __lastIndexOf(Arguments args) { + var val = passThis(args, "lastIndexOf"); + var term = args.get(0); + var start = args.getInt(1); + var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search")); + + if (search instanceof FunctionValue) { + return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, true, start)); + } + else return val.lastIndexOf(Values.toString(args.ctx, term), start); + } + + @Expose public static boolean __includes(Arguments args) { + return __indexOf(args) >= 0; + } + + @Expose public static String __replace(Arguments args) { + var val = passThis(args, "replace"); + var term = args.get(0); + var replacement = args.get(1); + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); + + if (replace instanceof FunctionValue) { + return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); + } + else return val.replaceFirst(Pattern.quote(Values.toString(args.ctx, term)), Values.toString(args.ctx, replacement)); + } + @Expose public static String __replaceAll(Arguments args) { + var val = passThis(args, "replaceAll"); + var term = args.get(0); + var replacement = args.get(1); + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); + + if (replace instanceof FunctionValue) { + return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); + } + else return val.replace(Values.toString(args.ctx, term), Values.toString(args.ctx, replacement)); + } + + @Expose public static ArrayValue __match(Arguments args) { + var val = passThis(args, "match"); + var term = args.get(0); + + FunctionValue match; + + try { + var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.match")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else if (args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), ""); + _match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.match")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else throw EngineException.ofError("Regular expressions don't support matching."); + } + else throw EngineException.ofError("Regular expressions not supported."); + } + catch (IllegalArgumentException e) { return new ArrayValue(args.ctx, ""); } + + var res = match.call(args.ctx, term, val); + if (res instanceof ArrayValue) return (ArrayValue)res; + else return new ArrayValue(args.ctx, ""); + } + @Expose public static Object __matchAll(Arguments args) { + var val = passThis(args, "matchAll"); + var term = args.get(0); + + FunctionValue match = null; + + try { + var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.matchAll")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + } + catch (IllegalArgumentException e) { } + + if (match == null && args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), "g"); + var _match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.matchAll")); + if (_match instanceof FunctionValue) match = (FunctionValue)_match; + else throw EngineException.ofError("Regular expressions don't support matching."); + } + else throw EngineException.ofError("Regular expressions not supported."); + + return match.call(args.ctx, term, val); + } + + @Expose public static ArrayValue __split(Arguments args) { + var val = passThis(args, "split"); + var term = args.get(0); + var lim = args.get(1); + var sensible = args.getBoolean(2); + + if (lim != null) lim = Values.toNumber(args.ctx, lim); + + if (term != null && term != Values.NULL && !(term instanceof String)) { + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); + if (replace instanceof FunctionValue) { + var tmp = ((FunctionValue)replace).call(args.ctx, term, val, lim, sensible); + + if (tmp instanceof ArrayValue) { + var parts = new ArrayValue(((ArrayValue)tmp).size()); + for (int i = 0; i < parts.size(); i++) parts.set(args.ctx, i, Values.toString(args.ctx, ((ArrayValue)tmp).get(i))); + return parts; + } + } + } + + String[] parts; + var pattern = Pattern.quote(Values.toString(args.ctx, term)); + + if (lim == null) parts = val.split(pattern); + else if ((double)lim < 1) return new ArrayValue(); + else if (sensible) parts = val.split(pattern, (int)(double)lim); + else { + var limit = (int)(double)lim; + parts = val.split(pattern, limit + 1); + ArrayValue res; + + if (parts.length > limit) res = new ArrayValue(limit); + else res = new ArrayValue(parts.length); + + for (var i = 0; i < parts.length && i < limit; i++) res.set(args.ctx, i, parts[i]); + + return res; + } + + var res = new ArrayValue(parts.length); + var i = 0; + + for (; i < parts.length; i++) { + if (lim != null && (double)lim <= i) break; + res.set(args.ctx, i, parts[i]); + } + + return res; + } + + @Expose public static String __slice(Arguments args) { + var self = passThis(args, "slice"); + var start = normalizeI(args.getInt(0), self.length(), false); + var end = normalizeI(args.getInt(1, self.length()), self.length(), false); + + return __substring(new Arguments(args.ctx, self, start, end)); + } + + @Expose public static String __concat(Arguments args) { + var res = new StringBuilder(passThis(args, "concat")); + + for (var el : args.convert(String.class)) res.append(el); + + return res.toString(); + } + @Expose public static String __trim(Arguments args) { + return passThis(args, "trim").trim(); + } + @Expose public static String __trimStart(Arguments args) { + return passThis(args, "trimStart").replaceAll("^\\s+", ""); + } + @Expose public static String __trimEnd(Arguments args) { + return passThis(args, "trimEnd").replaceAll("\\s+$", ""); + } + + @ExposeConstructor public static Object __constructor(Arguments args) { + var val = args.getString(0, ""); + if (args.self instanceof ObjectValue) return new StringLib(val); + else return val; + } + @Expose public static String __toString(Arguments args) { + return passThis(args, "toString"); + } + @Expose public static String __valueOf(Arguments args) { + return passThis(args, "valueOf"); + } + + @Expose(target = ExposeTarget.STATIC) + public static String __fromCharCode(Arguments args) { + var val = args.convertInt(); + char[] arr = new char[val.length]; + for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; + return new String(arr); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java b/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java new file mode 100644 index 0000000..9de6a74 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/SymbolLib.java @@ -0,0 +1,81 @@ +package me.topchetoeu.jscript.lib; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.ExposeTarget; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("Symbol") +public class SymbolLib { + private static final Map symbols = new HashMap<>(); + + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __typeName = Symbol.get("Symbol.typeName"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __replace = Symbol.get("Symbol.replace"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __match = Symbol.get("Symbol.match"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __matchAll = Symbol.get("Symbol.matchAll"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __split = Symbol.get("Symbol.split"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __search = Symbol.get("Symbol.search"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __iterator = Symbol.get("Symbol.iterator"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __asyncIterator = Symbol.get("Symbol.asyncIterator"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __cause = Symbol.get("Symbol.cause"); + + public final Symbol value; + + private static Symbol passThis(Arguments args, String funcName) { + var val = args.self; + if (Values.isWrapper(val, SymbolLib.class)) return Values.wrapper(val, SymbolLib.class).value; + else if (val instanceof Symbol) return (Symbol)val; + else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); + } + + public SymbolLib(Symbol val) { + this.value = val; + } + + @Expose public static String __toString(Arguments args) { + return passThis(args, "toString").value; + } + @Expose public static Symbol __valueOf(Arguments args) { + return passThis(args, "valueOf"); + } + + @ExposeConstructor + public static Object __constructor(Arguments args) { + if (args.self instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); + if (args.get(0) == null) return new Symbol(""); + else return new Symbol(args.getString(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static Symbol __for(Arguments args) { + var key = args.getString(0); + if (symbols.containsKey(key)) return symbols.get(key); + else { + var sym = new Symbol(key); + symbols.put(key, sym); + return sym; + } + } + @Expose(target = ExposeTarget.STATIC) + public static String __keyFor(Arguments args) { + return passThis(new Arguments(args.ctx, args.get(0)), "keyFor").value; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java new file mode 100644 index 0000000..1557729 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("SyntaxError") +public class SyntaxErrorLib extends ErrorLib { + @ExposeField public static final String __name = "SyntaxError"; + + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { + var target = ErrorLib.__constructor(args); + target.setPrototype(PlaceholderProto.SYNTAX_ERROR); + return target; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java b/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java new file mode 100644 index 0000000..960229f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/ThrowableLib.java @@ -0,0 +1,18 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.Expose; + +public class ThrowableLib { + @Expose public static String __message(Arguments args) { + if (args.self instanceof Throwable) return ((Throwable)args.self).getMessage(); + else return null; + } + @Expose public static String __name(Arguments args) { + return args.self.getClass().getSimpleName(); + } + + @Expose public static String __toString(Arguments args) { + return __name(args) + ": " + __message(args); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java new file mode 100644 index 0000000..98107ae --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; +import me.topchetoeu.jscript.utils.interop.Arguments; +import me.topchetoeu.jscript.utils.interop.ExposeConstructor; +import me.topchetoeu.jscript.utils.interop.ExposeField; +import me.topchetoeu.jscript.utils.interop.WrapperName; + +@WrapperName("TypeError") +public class TypeErrorLib extends ErrorLib { + @ExposeField public static final String __name = "TypeError"; + + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { + var target = ErrorLib.__constructor(args); + target.setPrototype(PlaceholderProto.TYPE_ERROR); + return target; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java b/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java deleted file mode 100644 index df5a7e3..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/ArgumentsValue.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; - -public class ArgumentsValue extends ArrayValue { - public final Frame frame; - - public ArgumentsValue(Frame frame, Value... args) { - super(args); - this.frame = frame; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Childable.java b/src/main/java/me/topchetoeu/jscript/runtime/Childable.java new file mode 100644 index 0000000..f1f358e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Childable.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.runtime; + +public interface Childable { + Object child(); +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java index de430b2..d332da5 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -1,47 +1,23 @@ package me.topchetoeu.jscript.runtime; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.CodeFunction; public interface Compiler { - public static final Compiler DEFAULT = (env, filename, raw) -> { - var res = JavaScript.compile(env, filename, raw); - var body = res.body(); - DebugContext.get(env).onSource(filename, raw); - registerFunc(env, body, res); + public Key KEY = new Key<>(); - return body; - }; + public FunctionBody compile(Filename filename, String source); - public Key KEY = Key.of(); - - public FunctionBody compile(Environment env, Filename filename, String source); - - public static Compiler get(Environment ext) { - return ext.get(KEY, (env, filename, src) -> { + public static Compiler get(Extensions ext) { + return ext.get(KEY, (filename, src) -> { throw EngineException.ofError("No compiler attached to engine."); }); } - static void registerFunc(Environment env, FunctionBody body, CompileResult res) { - var map = res.map(); - - DebugContext.get(env).onFunctionLoad(body, map); - - for (var i = 0; i < body.children.length; i++) { - registerFunc(env, body.children[i], res.children.get(i)); - } - } - - public static CodeFunction compileFunc(Environment env, Filename filename, String raw) { - return new CodeFunction(env, filename.toString(), get(env).compile(env, filename, raw), new Value[0][]); + public static CodeFunction compile(Environment env, Filename filename, String raw) { + return new CodeFunction(env, filename.toString(), Compiler.get(env).compile(filename, raw), new ValueVariable[0]); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Context.java b/src/main/java/me/topchetoeu/jscript/runtime/Context.java new file mode 100644 index 0000000..e20a62b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Context.java @@ -0,0 +1,98 @@ +package me.topchetoeu.jscript.runtime; + +import java.util.Iterator; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; + +public class Context implements Extensions { + public final Context parent; + public final Extensions extensions; + public final Frame frame; + public final int stackSize; + + @Override public void add(Key key, T obj) { + if (extensions != null) extensions.add(key, obj); + } + @Override public T get(Key key) { + if (extensions != null && extensions.has(key)) return extensions.get(key); + return null; + } + @Override public boolean has(Key key) { + return extensions != null && extensions.has(key); + } + @Override public boolean remove(Key key) { + var res = false; + if (extensions != null) res |= extensions.remove(key); + return res; + } + @Override public Iterable> keys() { + if (extensions == null) return List.of(); + else return extensions.keys(); + } + + public FunctionValue compile(Filename filename, String raw) { + DebugContext.get(this).onSource(filename, raw); + var result = new CodeFunction(extensions, filename.toString(), Compiler.get(this).compile(filename, raw), new ValueVariable[0]); + return result; + } + + public Context pushFrame(Frame frame) { + var res = new Context(this, frame.function.extensions, frame, stackSize + 1); + return res; + } + + public Iterable frames() { + var self = this; + return () -> new Iterator() { + private Context curr = self; + + private void update() { + while (curr != null && curr.frame == null) curr = curr.parent; + } + + @Override public boolean hasNext() { + update(); + return curr != null; + } + @Override public Frame next() { + update(); + var res = curr.frame; + curr = curr.parent; + return res; + } + }; + } + + private Context(Context parent, Extensions ext, Frame frame, int stackSize) { + this.parent = parent; + this.extensions = ext; + this.frame = frame; + this.stackSize = stackSize; + + if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) { + throw EngineException.ofRange("Stack overflow!"); + } + } + + public Context() { + this(null, null, null, 0); + } + public Context(Extensions ext) { + this(null, clean(ext), null, 0); + } + + public static Context of(Extensions ext) { + if (ext instanceof Context) return (Context)ext; + return new Context(ext); + } + public static Extensions clean(Extensions ext) { + if (ext instanceof Context) return clean(((Context)ext).extensions); + else return ext; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java b/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java new file mode 100644 index 0000000..cb2e75e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Copyable.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.runtime; + +public interface Copyable { + Object copy(); +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java index e3cb05e..9e2d6d1 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Engine.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Engine.java @@ -1,24 +1,24 @@ package me.topchetoeu.jscript.runtime; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; import java.util.concurrent.PriorityBlockingQueue; -import java.util.function.Supplier; +import me.topchetoeu.jscript.common.ResultRunnable; +import me.topchetoeu.jscript.common.events.DataNotifier; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -public final class Engine implements EventLoop { +public class Engine implements EventLoop { private static class Task implements Comparable> { - public final Supplier runnable; - public final CompletableFuture notifier = new CompletableFuture(); + public final ResultRunnable runnable; + public final DataNotifier notifier = new DataNotifier<>(); public final boolean micro; - public Task(Supplier runnable, boolean micro) { + public Task(ResultRunnable runnable, boolean micro) { this.runnable = runnable; this.micro = micro; } - @Override public int compareTo(Task other) { + @Override + public int compareTo(Task other) { return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1); } } @@ -26,7 +26,8 @@ public final class Engine implements EventLoop { private PriorityBlockingQueue> tasks = new PriorityBlockingQueue<>(); private Thread thread; - @Override public Future pushMsg(Supplier runnable, boolean micro) { + @Override + public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { var msg = new Task(runnable, micro); tasks.add(msg); return msg.notifier; @@ -39,15 +40,15 @@ public final class Engine implements EventLoop { var task = tasks.take(); try { - ((Task)task).notifier.complete(task.runnable.get()); + ((Task)task).notifier.next(task.runnable.run()); } catch (RuntimeException e) { if (e instanceof InterruptException) throw e; - task.notifier.completeExceptionally(e); + task.notifier.error(e); } } catch (InterruptedException | InterruptException e) { - for (var msg : tasks) msg.notifier.cancel(false); + for (var msg : tasks) msg.notifier.error(new InterruptException(e)); break; } } @@ -74,4 +75,7 @@ public final class Engine implements EventLoop { public boolean isRunning() { return this.thread != null; } + + public Engine() { + } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Environment.java b/src/main/java/me/topchetoeu/jscript/runtime/Environment.java new file mode 100644 index 0000000..4e232d8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Environment.java @@ -0,0 +1,61 @@ +package me.topchetoeu.jscript.runtime; + +import java.util.HashMap; + +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; + +@SuppressWarnings("unchecked") +public class Environment implements Extensions { + public static final Key COMPILE_FUNC = new Key<>(); + + public static final Key REGEX_CONSTR = new Key<>(); + public static final Key MAX_STACK_COUNT = new Key<>(); + public static final Key HIDE_STACK = new Key<>(); + + public static final Key OBJECT_PROTO = new Key<>(); + public static final Key FUNCTION_PROTO = new Key<>(); + public static final Key ARRAY_PROTO = new Key<>(); + public static final Key BOOL_PROTO = new Key<>(); + public static final Key NUMBER_PROTO = new Key<>(); + public static final Key STRING_PROTO = new Key<>(); + public static final Key SYMBOL_PROTO = new Key<>(); + public static final Key ERROR_PROTO = new Key<>(); + public static final Key SYNTAX_ERR_PROTO = new Key<>(); + public static final Key TYPE_ERR_PROTO = new Key<>(); + public static final Key RANGE_ERR_PROTO = new Key<>(); + + private HashMap, Object> data = new HashMap<>(); + + @Override public void add(Key key, T obj) { + data.put(key, obj); + } + @Override public T get(Key key) { + return (T)data.get(key); + } + @Override public boolean remove(Key key) { + if (data.containsKey(key)) { + data.remove(key); + return true; + } + return false; + } + @Override public boolean has(Key key) { + return data.containsKey(key); + } + @Override public Iterable> keys() { + return data.keySet(); + } + + public static FunctionValue regexConstructor(Extensions ext) { + return ext.init(REGEX_CONSTR, new NativeFunction("RegExp", args -> { + throw EngineException.ofError("Regular expressions not supported.").setExtensions(args.ctx); + })); + } + + public Context context() { + return new Context(this); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java index 2c548ba..6046971 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -1,36 +1,37 @@ package me.topchetoeu.jscript.runtime; -import java.util.concurrent.Future; -import java.util.function.Supplier; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.ResultRunnable; +import me.topchetoeu.jscript.common.events.DataNotifier; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; public interface EventLoop { - public static final Key KEY = Key.of(); + public static final Key KEY = new Key<>(); - public static EventLoop get(Environment ext) { + public static EventLoop get(Extensions ext) { if (ext.hasNotNull(KEY)) return ext.get(KEY); else return new EventLoop() { - @Override public Future pushMsg(Supplier runnable, boolean micro) { + @Override public DataNotifier pushMsg(ResultRunnable runnable, boolean micro) { throw EngineException.ofError("No event loop attached to environment."); } }; } - public Future pushMsg(Supplier runnable, boolean micro); - public default Future pushMsg(Runnable runnable, boolean micro) { + public DataNotifier pushMsg(ResultRunnable runnable, boolean micro); + public default DataNotifier pushMsg(Runnable runnable, boolean micro) { return pushMsg(() -> { runnable.run(); return null; }, micro); } - public default Future pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { - return pushMsg(() -> func.call(env, thisArg, args), micro); + public default DataNotifier pushMsg(boolean micro, Extensions ext, FunctionValue func, Object thisArg, Object ...args) { + return pushMsg(() -> { + return func.call(Context.of(ext), thisArg, args); + }, micro); } - public default Future pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) { - return pushMsg(() -> Compiler.compileFunc(env, filename, raw).call(env, thisArg, args), micro); + public default DataNotifier pushMsg(boolean micro, Extensions ext, Filename filename, String raw, Object thisArg, Object ...args) { + return pushMsg(() -> { + var ctx = Context.of(ext); + return ctx.compile(filename, raw).call(Context.of(ext), thisArg, args); + }, micro); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java b/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java new file mode 100644 index 0000000..3410e3e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Extensions.java @@ -0,0 +1,77 @@ +package me.topchetoeu.jscript.runtime; + +import java.util.List; + +public interface Extensions extends Childable, Copyable { + public static Extensions EMPTY = new Extensions() { + @Override public void add(Key key, T obj) { } + @Override public boolean remove(Key key) { return false; } + + @Override public T get(Key key) { return null; } + @Override public boolean has(Key key) { return false; } + @Override public Iterable> keys() { return List.of(); } + }; + + T get(Key key); + void add(Key key, T obj); + Iterable> keys(); + + boolean has(Key key); + boolean remove(Key key); + + default void add(Key key) { + add(key, null); + } + + default boolean hasNotNull(Key key) { + return has(key) && get(key) != null; + } + + default T get(Key key, T defaultVal) { + if (has(key)) return get(key); + else return defaultVal; + } + + default T init(Key key, T val) { + if (has(key)) return get(key); + else { + add(key, val); + return val; + } + } + @SuppressWarnings("unchecked") + default void addAll(Extensions source) { + if (source == null) return; + for (var key : source.keys()) { + add((Key)key, (Object)source.get(key)); + } + } + + @Override + @SuppressWarnings("unchecked") + default Extensions copy() { + var res = new Environment(); + for (var key : keys()) { + var val = get(key); + if (val instanceof Copyable) val = ((Copyable)val).copy(); + res.add((Key)key, val); + } + return res; + } + @Override + @SuppressWarnings("unchecked") + default Extensions child() { + var res = new Environment(); + for (var key : keys()) { + var val = get(key); + if (val instanceof Childable) val = ((Childable)val).child(); + res.add((Key)key, val); + } + return res; + } + + public static Extensions wrap(Extensions ext) { + if (ext == null) return EMPTY; + else return ext; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index f91ba76..7cdb025 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,28 +1,21 @@ package me.topchetoeu.jscript.runtime; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Stack; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.objects.ScopeValue; - -public final class Frame { - public static final Key KEY = Key.of(); +import me.topchetoeu.jscript.runtime.scope.LocalScope; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.ScopeValue; +import me.topchetoeu.jscript.runtime.values.Values; +public class Frame { public static enum TryState { TRY, CATCH, @@ -67,12 +60,12 @@ public final class Frame { private static class PendingResult { public final boolean isReturn, isJump, isThrow; - public final Value value; + public final Object value; public final EngineException error; public final int ptr; public final Instruction instruction; - private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Value value, EngineException error, int ptr) { + private PendingResult(Instruction instr, boolean isReturn, boolean isJump, boolean isThrow, Object value, EngineException error, int ptr) { this.instruction = instr; this.isReturn = isReturn; this.isJump = isJump; @@ -85,7 +78,7 @@ public final class Frame { public static PendingResult ofNone() { return new PendingResult(null, false, false, false, null, null, 0); } - public static PendingResult ofReturn(Value value, Instruction instr) { + public static PendingResult ofReturn(Object value, Instruction instr) { return new PendingResult(instr, true, false, false, value, null, 0); } public static PendingResult ofThrow(EngineException error, Instruction instr) { @@ -96,30 +89,16 @@ public final class Frame { } } - /** - * A list of one-element arrays of values. This is so that we can pass captures to other functions - */ - public final Value[][] captures; - public final List locals = new ArrayList<>(); - public final Value self; - public final Value argsVal; - public final Value[] args; - public final boolean isNew; + public final LocalScope scope; + public final Object thisArg; + public final Object[] args; public final Stack tryStack = new Stack<>(); public final CodeFunction function; - public final Environment env; - private final DebugContext dbg; - - public Value[] getVar(int i) { - if (i < 0) return captures[~i]; - else return locals.get(i); - } - - public Value[] stack = new Value[32]; + public final Context ctx; + public Object[] stack = new Object[32]; public int stackPtr = 0; public int codePtr = 0; - public boolean jumpFlag = false; - public boolean popTryFlag = false; + public boolean jumpFlag = false, popTryFlag = false; public void addTry(int start, int end, int catchStart, int finallyStart) { var err = tryStack.empty() ? null : tryStack.peek().error; @@ -128,70 +107,63 @@ public final class Frame { tryStack.add(res); } - public Value peek() { + public Object peek() { return peek(0); } - public Value peek(int offset) { - return stack[stackPtr - 1 - offset]; + public Object peek(int offset) { + if (stackPtr <= offset) return null; + else return stack[stackPtr - 1 - offset]; } - public Value pop() { + public Object pop() { + if (stackPtr == 0) return null; return stack[--stackPtr]; } - public Value[] take(int n) { - Value[] res = new Value[n]; - System.arraycopy(stack, stackPtr - n, res, 0, n); - stackPtr -= n; + public Object[] take(int n) { + int srcI = stackPtr - n; + if (srcI < 0) srcI = 0; + + int dstI = n + srcI - stackPtr; + int copyN = stackPtr - srcI; + + Object[] res = new Object[n]; + System.arraycopy(stack, srcI, res, dstI, copyN); + stackPtr -= copyN; return res; } - public void push(Value val) { + public void push(Object val) { if (stack.length <= stackPtr) { - var newStack = new Value[stack.length * 2]; + var newStack = new Object[stack.length * 2]; System.arraycopy(stack, 0, newStack, 0, stack.length); stack = newStack; } - - stack[stackPtr++] = val; - } - public void replace(Value val) { - stack[stackPtr - 1] = val; + stack[stackPtr++] = Values.normalize(ctx, val); } - // for the love of christ don't touch this - /** - * This is provided only for optimization-sike. All parameters must be null except at most one, otherwise undefined behavior - */ - public final Value next(Value value, Value returnValue, EngineException error) { - if (value != null) push(value); + public Object next(Object value, Object returnValue, EngineException error) { + if (value != Values.NO_RETURN) push(value); Instruction instr = null; - if (codePtr != function.body.instructions.length) instr = function.body.instructions[codePtr]; + if (codePtr >= 0 && codePtr < function.body.instructions.length) instr = function.body.instructions[codePtr]; - if (returnValue == null && error == null) { + if (returnValue == Values.NO_RETURN && error == null) { try { - if (Thread.interrupted()) throw new InterruptException(); + if (Thread.currentThread().isInterrupted()) throw new InterruptException(); - if (instr == null) { - if (stackPtr > 0) returnValue = stack[stackPtr - 1]; - else returnValue = Value.UNDEFINED; - } + if (instr == null) returnValue = null; else { - dbg.onInstruction(env, this, instr); + DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.NO_RETURN, null, false); try { this.jumpFlag = this.popTryFlag = false; - returnValue = InstructionRunner.exec(env, instr, this); + returnValue = InstructionRunner.exec(ctx, instr, this); } catch (EngineException e) { - error = e.add(env, function.name, dbg.getMapOrEmpty(function).toLocation(codePtr, true)); + error = e.add(ctx, function.name, DebugContext.get(ctx).getMapOrEmpty(function).toLocation(codePtr, true)); } } } catch (EngineException e) { error = e; } - catch (RuntimeException e) { - System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true)); - throw e; - } } while (!tryStack.empty()) { @@ -203,7 +175,7 @@ public final class Frame { if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error); else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr)); } - else if (returnValue != null) { + else if (returnValue != Values.NO_RETURN) { if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr)); } else if (jumpFlag && !tryCtx.inBounds(codePtr)) { @@ -215,12 +187,12 @@ public final class Frame { if (newCtx != tryCtx) { switch (newCtx.state) { case CATCH: - if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value }); + if (tryCtx.state != TryState.CATCH) scope.catchVars.add(new ValueVariable(false, error.value)); codePtr = tryCtx.catchStart; stackPtr = tryCtx.restoreStackPtr; break; case FINALLY: - if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); + if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); codePtr = tryCtx.finallyStart; stackPtr = tryCtx.restoreStackPtr; default: @@ -229,14 +201,13 @@ public final class Frame { tryStack.pop(); tryStack.push(newCtx); } - error = null; - returnValue = null; + returnValue = Values.NO_RETURN; break; } else { popTryFlag = false; - if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); + if (tryCtx.state == TryState.CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { codePtr = tryCtx.finallyStart; @@ -249,7 +220,7 @@ public final class Frame { tryStack.pop(); codePtr = tryCtx.end; if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction; - if (!jumpFlag && returnValue == null && error == null) { + if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) { if (tryCtx.result.isJump) { codePtr = tryCtx.result.ptr; jumpFlag = true; @@ -268,174 +239,91 @@ public final class Frame { if (error != null) { var caught = false; - for (var frame : dbg.getStackFrames()) { + for (var frame : ctx.frames()) { for (var tryCtx : frame.tryStack) { if (tryCtx.state == TryState.TRY) caught = true; } } - dbg.onInstruction(env, this, instr, null, error, caught); + DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught); throw error; } - if (returnValue != null) { - dbg.onInstruction(env, this, instr, returnValue, null, false); + if (returnValue != Values.NO_RETURN) { + DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false); return returnValue; } - return null; - } - - /** - * Executes the next instruction in the frame - */ - public final Value next() { - return next(null, null, null); - } - /** - * Induces a value on the stack (as if it were returned by the last function call) - * and executes the next instruction in the frame. - * - * @param value The value to induce - */ - public final Value next(Value value) { - return next(value, null, null); - } - /** - * Induces a thrown error and executes the next instruction. - * Note that this is different than just throwing the error outside the - * function, as the function executed could have a try-catch which - * would otherwise handle the error - * - * @param error The error to induce - */ - public final Value induceError(EngineException error) { - return next(null, null, error); - } - /** - * Induces a return, as if there was a return statement before - * the currently executed instruction and executes the next instruction. - * Note that this is different than just returning the value outside the - * function, as the function executed could have a try-catch which - * would otherwise handle the error - * - * @param value The retunr value to induce - */ - public final Value induceReturn(Value value) { - return next(null, value, null); + return Values.NO_RETURN; } public void onPush() { - DebugContext.get(env).onFramePush(env, this); + DebugContext.get(ctx).onFramePush(ctx, this); } public void onPop() { - DebugContext.get(env).onFramePop(env, this); + DebugContext.get(ctx.parent).onFramePop(ctx.parent, this); } - /** - * Gets an object proxy of the local locals - */ public ObjectValue getLocalScope() { - throw new RuntimeException("Not supported"); + var names = new String[scope.locals.length]; + var map = DebugContext.get(ctx).getMapOrEmpty(function); - // var names = new String[locals.locals.length]; - // var map = DebugContext.get(env).getMapOrEmpty(function); + for (int i = 0; i < scope.locals.length; i++) { + var name = "local_" + (i - 2); - // for (int i = 0; i < locals.locals.length; i++) { - // var name = "local_" + (i - 2); + if (i == 0) name = "this"; + else if (i == 1) name = "arguments"; + else if (i < map.localNames.length) name = map.localNames[i]; - // if (i == 0) name = "this"; - // else if (i == 1) name = "arguments"; - // else if (i < map.localNames.length) name = map.localNames[i]; + names[i] = name; + } - // names[i] = name; - // } - - // return new ScopeValue(locals, names); + return new ScopeValue(scope.locals, names); } - /** - * Gets an object proxy of the capture locals - */ public ObjectValue getCaptureScope() { - // throw new RuntimeException("Not supported"); + var names = new String[scope.captures.length]; + var map = DebugContext.get(ctx).getMapOrEmpty(function); - var names = new String[captures.length]; - var map = DebugContext.get(env).getMapOrEmpty(function); - - for (int i = 0; i < captures.length; i++) { + for (int i = 0; i < scope.captures.length; i++) { var name = "capture_" + (i - 2); if (i < map.captureNames.length) name = map.captureNames[i]; names[i] = name; } - return new ScopeValue(captures, names); + return new ScopeValue(scope.captures, names); } - /** - * Gets an array proxy of the local locals - */ public ObjectValue getValStackScope() { return new ObjectValue() { - @Override public Member getOwnMember(Environment env, KeyCache key) { - var res = super.getOwnMember(env, key); - if (res != null) return res; - - var num = key.toNumber(env); - var i = key.toInt(env); - - if (num != i || i < 0 || i >= stackPtr) return null; - else return new FieldMember(false, true, true) { - @Override public Value get(Environment env, Value self) { return stack[i]; } - @Override public boolean set(Environment env, Value val, Value self) { - stack[i] = val; - return true; - } - }; + @Override + protected Object getField(Extensions ext, Object key) { + var i = (int)Values.toNumber(ext, key); + if (i < 0 || i >= stackPtr) return null; + else return stack[i]; } - @Override public Map getOwnMembers(Environment env) { - var res = new LinkedHashMap(); - - for (var i = 0; i < stackPtr; i++) { - var _i = i; - res.put(i + "", new FieldMember(false, true, true) { - @Override public Value get(Environment env, Value self) { return stack[_i]; } - @Override public boolean set(Environment env, Value val, Value self) { - stack[_i] = val; - return true; - } - }); - } - + @Override + protected boolean hasField(Extensions ext, Object key) { + return true; + } + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + for (var i = 0; i < stackPtr; i++) res.add(i); return res; } }; } - public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) { - this.env = env; - this.dbg = DebugContext.get(env); + public Frame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { + this.args = args.clone(); + this.scope = new LocalScope(func.body.localsN, func.captures); + this.scope.get(0).set(null, thisArg); + var argsObj = new ArrayValue(); + for (var i = 0; i < args.length; i++) { + argsObj.set(ctx, i, args[i]); + } + this.scope.get(1).value = argsObj; + + this.thisArg = thisArg; this.function = func; - this.isNew = isNew; - - this.self = thisArg; - this.args = args; - this.argsVal = new ArgumentsValue(this, args); - this.captures = func.captures; - - var i = 0; - - for (; i < func.body.argsN && i < args.length; i++) { - this.locals.add(new Value[] { args[i] }); - } - for (; i < args.length; i++) { - this.locals.add(new Value[] { Value.UNDEFINED }); - } - - for (i = 0; i < func.body.localsN; i++) { - this.locals.add(new Value[] { Value.UNDEFINED }); - } - - // this.locals = new LocalScope(func.body.localsN, func.captures); - // this.locals.get(0).set(thisArg); - // this.locals.get(1).value = new ArgumentsValue(this, args); - + this.ctx = ctx.pushFrame(this); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 48ecd51..7a31109 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -1,104 +1,91 @@ package me.topchetoeu.jscript.runtime; -import java.util.ArrayList; import java.util.Collections; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; public class InstructionRunner { - private static Value execReturn(Environment env, Instruction instr, Frame frame) { + private static Object execReturn(Extensions ext, Instruction instr, Frame frame) { return frame.pop(); } - private static Value execThrow(Environment env, Instruction instr, Frame frame) { + private static Object execThrow(Extensions ext, Instruction instr, Frame frame) { throw new EngineException(frame.pop()); } - private static Value execThrowSyntax(Environment env, Instruction instr, Frame frame) { + private static Object execThrowSyntax(Extensions ext, Instruction instr, Frame frame) { throw EngineException.ofSyntax((String)instr.get(0)); } - private static Value execCall(Environment env, Instruction instr, Frame frame) { + private static Object execCall(Extensions ext, Instruction instr, Frame frame) { var callArgs = frame.take(instr.get(0)); var func = frame.pop(); + var thisArg = frame.pop(); - frame.push(func.call(env, false, instr.get(1), Value.UNDEFINED, callArgs)); + frame.push(Values.call(ext, func, thisArg, callArgs)); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execCallMember(Environment env, Instruction instr, Frame frame) { - var callArgs = frame.take(instr.get(0)); - var index = frame.pop(); - var obj = frame.pop(); - - frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs)); - - frame.codePtr++; - return null; - } - private static Value execCallNew(Environment env, Instruction instr, Frame frame) { + private static Object execCallNew(Extensions ext, Instruction instr, Frame frame) { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - frame.push(funcObj.callNew(env, instr.get(1), callArgs)); + frame.push(Values.callNew(ext, funcObj, callArgs)); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execDefProp(Environment env, Instruction instr, Frame frame) { - var setterVal = frame.pop(); - var getterVal = frame.pop(); - var key = frame.pop(); + private static Object execMakeVar(Extensions ext, Instruction instr, Frame frame) { + var name = (String)instr.get(0); + GlobalScope.get(ext).define(ext, name); + frame.codePtr++; + return Values.NO_RETURN; + } + private static Object execDefProp(Extensions ext, Instruction instr, Frame frame) { + var setter = frame.pop(); + var getter = frame.pop(); + var name = frame.pop(); var obj = frame.pop(); - FunctionValue getter, setter; - - if (getterVal == Value.UNDEFINED) getter = null; - else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal; - else throw EngineException.ofType("Getter must be a function or undefined."); - - if (setterVal == Value.UNDEFINED) setter = null; - else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal; - else throw EngineException.ofType("Setter must be a function or undefined."); - - obj.defineOwnMember(env, key, new PropertyMember(getter, setter, true, true)); + if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined."); + if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined."); + if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object."); + ((ObjectValue)obj).defineProperty(ext, name, (FunctionValue)getter, (FunctionValue)setter, false, false); frame.push(obj); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execKeys(Environment env, Instruction instr, Frame frame) { + private static Object execKeys(Extensions ext, Instruction instr, Frame frame) { var val = frame.pop(); - var members = new ArrayList<>(val.getMembers(env, false, true).keySet()); + var members = Values.getMembers(ext, val, false, false); Collections.reverse(members); frame.push(null); for (var el : members) { + if (el instanceof Symbol) continue; var obj = new ObjectValue(); - obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el))); + obj.defineProperty(ext, "value", el); frame.push(obj); } frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execTryStart(Environment env, Instruction instr, Frame frame) { + private static Object execTryStart(Extensions ext, Instruction instr, Frame frame) { int start = frame.codePtr + 1; int catchStart = (int)instr.get(0); int finallyStart = (int)instr.get(1); @@ -107,14 +94,14 @@ public class InstructionRunner { int end = (int)instr.get(2) + start; frame.addTry(start, end, catchStart, finallyStart); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execTryEnd(Environment env, Instruction instr, Frame frame) { + private static Object execTryEnd(Extensions ext, Instruction instr, Frame frame) { frame.popTryFlag = true; - return null; + return Values.NO_RETURN; } - private static Value execDup(Environment env, Instruction instr, Frame frame) { + private static Object execDup(Extensions ext, Instruction instr, Frame frame) { int count = instr.get(0); for (var i = 0; i < count; i++) { @@ -122,390 +109,220 @@ public class InstructionRunner { } frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadValue(Environment env, Instruction instr, Frame frame) { + private static Object execLoadValue(Extensions ext, Instruction instr, Frame frame) { switch (instr.type) { - case PUSH_UNDEFINED: frame.push(Value.UNDEFINED); break; - case PUSH_NULL: frame.push(Value.NULL); break; - case PUSH_BOOL: frame.push(BoolValue.of(instr.get(0))); break; - case PUSH_NUMBER: frame.push(new NumberValue(instr.get(0))); break; - case PUSH_STRING: frame.push(new StringValue(instr.get(0))); break; - default: + case PUSH_UNDEFINED: frame.push(null); break; + case PUSH_NULL: frame.push(Values.NULL); break; + default: frame.push(instr.get(0)); break; } frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { - int i = instr.get(0); + private static Object execLoadVar(Extensions ext, Instruction instr, Frame frame) { + var i = instr.get(0); - frame.push(frame.getVar(i)[0]); - frame.codePtr++; + if (i instanceof String) frame.push(GlobalScope.get(ext).get(ext, (String)i)); + else frame.push(frame.scope.get((int)i).get(ext)); - return null; - } - private static Value execLoadObj(Environment env, Instruction instr, Frame frame) { - var obj = new ObjectValue(); - obj.setPrototype(Value.OBJECT_PROTO); - frame.push(obj); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) { - frame.push(Value.global(env)); + private static Object execLoadObj(Extensions ext, Instruction instr, Frame frame) { + frame.push(new ObjectValue()); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadIntrinsics(Environment env, Instruction instr, Frame frame) { - frame.push(Value.intrinsics(env).get((String)instr.get(0))); + private static Object execLoadGlob(Extensions ext, Instruction instr, Frame frame) { + frame.push(GlobalScope.get(ext).obj); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadArr(Environment env, Instruction instr, Frame frame) { + private static Object execLoadArr(Extensions ext, Instruction instr, Frame frame) { var res = new ArrayValue(); res.setSize(instr.get(0)); frame.push(res); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) { + private static Object execLoadFunc(Extensions ext, Instruction instr, Frame frame) { int id = instr.get(0); - String name = instr.get(1); - var captures = new Value[instr.params.length - 2][]; + var captures = new ValueVariable[instr.params.length - 1]; - for (var i = 2; i < instr.params.length; i++) { - captures[i - 2] = frame.getVar(instr.get(i)); + for (var i = 1; i < instr.params.length; i++) { + captures[i - 1] = frame.scope.get(instr.get(i)); } - var func = new CodeFunction(env, name, frame.function.body.children[id], captures); + var func = new CodeFunction(ext, "", frame.function.body.children[id], captures); frame.push(func); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadMember(Environment env, Instruction instr, Frame frame) { + private static Object execLoadMember(Extensions ext, Instruction instr, Frame frame) { var key = frame.pop(); var obj = frame.pop(); try { - frame.push(obj.getMember(env, key)); + frame.push(Values.getMember(ext, obj, key)); } catch (IllegalArgumentException e) { throw EngineException.ofType(e.getMessage()); } frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) { - if (env.hasNotNull(Value.REGEX_CONSTR)) { - frame.push(env.get(Value.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1))); + private static Object execLoadRegEx(Extensions ext, Instruction instr, Frame frame) { + if (ext.hasNotNull(Environment.REGEX_CONSTR)) { + frame.push(Values.callNew(ext, ext.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1))); } else { throw EngineException.ofSyntax("Regex is not supported."); } - frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execDiscard(Environment env, Instruction instr, Frame frame) { + private static Object execDiscard(Extensions ext, Instruction instr, Frame frame) { frame.pop(); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execStoreMember(Environment env, Instruction instr, Frame frame) { + private static Object execStoreMember(Extensions ext, Instruction instr, Frame frame) { var val = frame.pop(); var key = frame.pop(); var obj = frame.pop(); - if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'."); + if (!Values.setMember(ext, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); if ((boolean)instr.get(0)) frame.push(val); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execStoreVar(Environment env, Instruction instr, Frame frame) { + private static Object execStoreVar(Extensions ext, Instruction instr, Frame frame) { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); - int i = instr.get(0); + var i = instr.get(0); + + if (i instanceof String) GlobalScope.get(ext).set(ext, (String)i, val); + else frame.scope.get((int)i).set(ext, val); - frame.getVar(i)[0] = val; frame.codePtr++; - - return null; + return Values.NO_RETURN; } - - private static Value execJmp(Environment env, Instruction instr, Frame frame) { + private static Object execStoreSelfFunc(Extensions ext, Instruction instr, Frame frame) { + frame.scope.locals[(int)instr.get(0)].set(ext, frame.function); + frame.codePtr++; + return Values.NO_RETURN; + } + + private static Object execJmp(Extensions ext, Instruction instr, Frame frame) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; - return null; + return Values.NO_RETURN; } - private static Value execJmpIf(Environment env, Instruction instr, Frame frame) { - if (frame.pop().toBoolean()) { + private static Object execJmpIf(Extensions ext, Instruction instr, Frame frame) { + if (Values.toBoolean(frame.pop())) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } else frame.codePtr ++; - return null; + return Values.NO_RETURN; } - private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) { - if (!frame.pop().toBoolean()) { + private static Object execJmpIfNot(Extensions ext, Instruction instr, Frame frame) { + if (Values.not(frame.pop())) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; } else frame.codePtr ++; - return null; + return Values.NO_RETURN; } - private static Value execTypeof(Environment env, Instruction instr, Frame frame) { + private static Object execTypeof(Extensions ext, Instruction instr, Frame frame) { String name = instr.get(0); - Value obj; + Object obj; - if (name != null) obj = Value.global(env).getMember(env, name); + if (name != null) { + if (GlobalScope.get(ext).has(ext, name)) { + obj = GlobalScope.get(ext).get(ext, name); + } + else obj = null; + } else obj = frame.pop(); - frame.push(obj.type()); + frame.push(Values.type(obj)); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execNop(Environment env, Instruction instr, Frame frame) { + private static Object execNop(Extensions ext, Instruction instr, Frame frame) { frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execDelete(Environment env, Instruction instr, Frame frame) { + private static Object execDelete(Extensions ext, Instruction instr, Frame frame) { var key = frame.pop(); var val = frame.pop(); - if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'."); + if (!Values.deleteMember(ext, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value execOperation(Environment env, Instruction instr, Frame frame) { + private static Object execOperation(Extensions ext, Instruction instr, Frame frame) { Operation op = instr.get(0); - Value res; - var stack = frame.stack; + var args = new Object[op.operands]; - frame.stackPtr -= 1; - var ptr = frame.stackPtr; + for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); - // for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); - - switch (op) { - case ADD: - res = Value.add(env, stack[ptr - 1], stack[ptr]); - break; - case SUBTRACT: - res = Value.subtract(env, stack[ptr - 1], stack[ptr]); - break; - case DIVIDE: - res = Value.divide(env, stack[ptr - 1], stack[ptr]); - break; - case MULTIPLY: - res = Value.multiply(env, stack[ptr - 1], stack[ptr]); - break; - case MODULO: - res = Value.modulo(env, stack[ptr - 1], stack[ptr]); - break; - - case AND: - res = Value.and(env, stack[ptr - 1], stack[ptr]); - break; - case OR: - res = Value.or(env, stack[ptr - 1], stack[ptr]); - break; - case XOR: - res = Value.xor(env, stack[ptr - 1], stack[ptr]); - break; - - case EQUALS: - res = BoolValue.of(stack[ptr - 1].equals(stack[ptr])); - break; - case NOT_EQUALS: - res = BoolValue.of(!stack[ptr - 1].equals(stack[ptr])); - break; - case LOOSE_EQUALS: - res = BoolValue.of(Value.looseEqual(env, stack[ptr - 1], stack[ptr])); - break; - case LOOSE_NOT_EQUALS: - res = BoolValue.of(!Value.looseEqual(env, stack[ptr - 1], stack[ptr])); - break; - - case GREATER: - res = BoolValue.of(Value.greater(env, stack[ptr - 1], stack[ptr])); - break; - case GREATER_EQUALS: - res = BoolValue.of(Value.greaterOrEqual(env, stack[ptr - 1], stack[ptr])); - break; - case LESS: - res = BoolValue.of(Value.less(env, stack[ptr - 1], stack[ptr])); - break; - case LESS_EQUALS: - res = BoolValue.of(Value.lessOrEqual(env, stack[ptr - 1], stack[ptr])); - break; - - case INVERSE: - res = Value.bitwiseNot(env, stack[ptr++]); - frame.stackPtr++; - break; - case NOT: - res = BoolValue.of(!stack[ptr++].toBoolean()); - frame.stackPtr++; - break; - case POS: - res = stack[ptr++].toNumber(env); - frame.stackPtr++; - break; - case NEG: - res = Value.negative(env, stack[ptr++]); - frame.stackPtr++; - break; - - case SHIFT_LEFT: - res = Value.shiftLeft(env, stack[ptr], stack[ptr]); - break; - case SHIFT_RIGHT: - res = Value.shiftRight(env, stack[ptr], stack[ptr]); - break; - case USHIFT_RIGHT: - res = Value.unsignedShiftRight(env, stack[ptr], stack[ptr]); - break; - - case IN: - res = BoolValue.of(stack[ptr - 1].hasMember(env, stack[ptr], false)); - break; - case INSTANCEOF: - res = BoolValue.of(stack[ptr - 1].isInstanceOf(env, stack[ptr].getMember(env, new StringValue("prototype")))); - break; - - default: return null; - } - - stack[ptr - 1] = res; + frame.push(Values.operation(ext, op, args)); frame.codePtr++; - return null; + return Values.NO_RETURN; } - private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) { - var name = (String)instr.get(0); - - if (!Value.global(env).hasMember(env, name, false)) { - if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name); - } - - frame.codePtr++; - return null; - } - private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { - var name = (String)instr.get(0); - var res = Value.global(env).getMemberOrNull(env, name); - - if (res == null) throw EngineException.ofSyntax(name + " is not defined"); - else frame.push(res); - - frame.codePtr++; - return null; - } - private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) { - var name = (String)instr.get(0); - var keep = (boolean)instr.get(1); - var define = (boolean)instr.get(2); - - var val = keep ? frame.peek() : frame.pop(); - var res = false; - - if (define) res = Value.global(env).setMember(env, name, val); - else res = Value.global(env).setMemberIfExists(env, name, val); - - if (!res) throw EngineException.ofError("Couldn't set variable " + name); - - frame.codePtr++; - return null; - } - - private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) { - frame.push(frame.argsVal); - frame.codePtr++; - return null; - } - private static Value execLoadThis(Environment env, Instruction instr, Frame frame) { - frame.push(frame.self); - frame.codePtr++; - return null; - } - - private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) { - int n = instr.get(0); - - for (var i = 0; i < n; i++) frame.locals.add(new Value[] { Value.UNDEFINED }); - - frame.codePtr++; - return null; - } - private static Value execStackFree(Environment env, Instruction instr, Frame frame) { - int n = instr.get(0); - - for (var i = 0; i < n; i++) { - frame.locals.remove(frame.locals.size() - 1); - } - - frame.codePtr++; - return null; - } - - public static Value exec(Environment env, Instruction instr, Frame frame) { + public static Object exec(Extensions ext, Instruction instr, Frame frame) { switch (instr.type) { - case NOP: return execNop(env, instr, frame); - case RETURN: return execReturn(env, instr, frame); - case THROW: return execThrow(env, instr, frame); - case THROW_SYNTAX: return execThrowSyntax(env, instr, frame); - case CALL: return execCall(env, instr, frame); - case CALL_NEW: return execCallNew(env, instr, frame); - case CALL_MEMBER: return execCallMember(env, instr, frame); - case TRY_START: return execTryStart(env, instr, frame); - case TRY_END: return execTryEnd(env, instr, frame); + case NOP: return execNop(ext, instr, frame); + case RETURN: return execReturn(ext, instr, frame); + case THROW: return execThrow(ext, instr, frame); + case THROW_SYNTAX: return execThrowSyntax(ext, instr, frame); + case CALL: return execCall(ext, instr, frame); + case CALL_NEW: return execCallNew(ext, instr, frame); + case TRY_START: return execTryStart(ext, instr, frame); + case TRY_END: return execTryEnd(ext, instr, frame); - case DUP: return execDup(env, instr, frame); + case DUP: return execDup(ext, instr, frame); case PUSH_UNDEFINED: case PUSH_NULL: case PUSH_STRING: case PUSH_NUMBER: case PUSH_BOOL: - return execLoadValue(env, instr, frame); - case LOAD_VAR: return execLoadVar(env, instr, frame); - case LOAD_OBJ: return execLoadObj(env, instr, frame); - case LOAD_ARR: return execLoadArr(env, instr, frame); - case LOAD_FUNC: return execLoadFunc(env, instr, frame); - case LOAD_MEMBER: return execLoadMember(env, instr, frame); - case LOAD_REGEX: return execLoadRegEx(env, instr, frame); - case LOAD_GLOB: return execLoadGlob(env, instr, frame); - case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame); - case LOAD_ARGS: return execLoadArgs(env, instr, frame); - case LOAD_THIS: return execLoadThis(env, instr, frame); + return execLoadValue(ext, instr, frame); + case LOAD_VAR: return execLoadVar(ext, instr, frame); + case LOAD_OBJ: return execLoadObj(ext, instr, frame); + case LOAD_ARR: return execLoadArr(ext, instr, frame); + case LOAD_FUNC: return execLoadFunc(ext, instr, frame); + case LOAD_MEMBER: return execLoadMember(ext, instr, frame); + case LOAD_REGEX: return execLoadRegEx(ext, instr, frame); + case LOAD_GLOB: return execLoadGlob(ext, instr, frame); - case DISCARD: return execDiscard(env, instr, frame); - case STORE_MEMBER: return execStoreMember(env, instr, frame); - case STORE_VAR: return execStoreVar(env, instr, frame); + case DISCARD: return execDiscard(ext, instr, frame); + case STORE_MEMBER: return execStoreMember(ext, instr, frame); + case STORE_VAR: return execStoreVar(ext, instr, frame); + case STORE_SELF_FUNC: return execStoreSelfFunc(ext, instr, frame); + case MAKE_VAR: return execMakeVar(ext, instr, frame); - case KEYS: return execKeys(env, instr, frame); - case DEF_PROP: return execDefProp(env, instr, frame); - case TYPEOF: return execTypeof(env, instr, frame); - case DELETE: return execDelete(env, instr, frame); + case KEYS: return execKeys(ext, instr, frame); + case DEF_PROP: return execDefProp(ext, instr, frame); + case TYPEOF: return execTypeof(ext, instr, frame); + case DELETE: return execDelete(ext, instr, frame); - case JMP: return execJmp(env, instr, frame); - case JMP_IF: return execJmpIf(env, instr, frame); - case JMP_IFN: return execJmpIfNot(env, instr, frame); + case JMP: return execJmp(ext, instr, frame); + case JMP_IF: return execJmpIf(ext, instr, frame); + case JMP_IFN: return execJmpIfNot(ext, instr, frame); - case OPERATION: return execOperation(env, instr, frame); - - case GLOB_DEF: return exexGlobDef(env, instr, frame); - case GLOB_GET: return exexGlobGet(env, instr, frame); - case GLOB_SET: return exexGlobSet(env, instr, frame); - - case STACK_ALLOC: return execStackAlloc(env, instr, frame); - case STACK_FREE: return execStackFree(env, instr, frame); + case OPERATION: return execOperation(ext, instr, frame); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java b/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java deleted file mode 100644 index b895dea..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/JSONConverter.java +++ /dev/null @@ -1,86 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import java.util.HashSet; -import java.util.stream.Collectors; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.json.JSONElement; -import me.topchetoeu.jscript.common.json.JSONList; -import me.topchetoeu.jscript.common.json.JSONMap; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; - -public class JSONConverter { - public static Value toJs(JSONElement val) { - if (val.isBoolean()) return BoolValue.of(val.bool()); - if (val.isString()) return new StringValue(val.string()); - if (val.isNumber()) return new NumberValue(val.number()); - if (val.isList()) return ArrayValue.of(val.list().stream().map(JSONConverter::toJs).collect(Collectors.toList())); - if (val.isMap()) { - var res = new ObjectValue(); - - for (var el : val.map().entrySet()) { - res.defineOwnMember(null, el.getKey(), FieldMember.of(toJs(el.getValue()))); - } - - return res; - } - if (val.isNull()) return Value.NULL; - return Value.UNDEFINED; - } - - public static JSONElement fromJs(Environment ext, Value val) { - var res = JSONConverter.fromJs(ext, val, new HashSet<>()); - if (res == null) return JSONElement.NULL; - else return res; - } - - public static JSONElement fromJs(Environment env, Value val, HashSet prev) { - if (val instanceof BoolValue) return JSONElement.bool(((BoolValue)val).value); - if (val instanceof NumberValue) return JSONElement.number(((NumberValue)val).value); - if (val instanceof StringValue) return JSONElement.string(((StringValue)val).value); - if (val == Value.NULL) return JSONElement.NULL; - if (val instanceof VoidValue) return null; - - if (val instanceof ArrayValue) { - if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON."); - prev.add(val); - - var res = new JSONList(); - - for (var el : ((ArrayValue)val).toArray()) { - var jsonEl = fromJs(env, el, prev); - if (jsonEl == null) continue; - res.add(jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } - if (val instanceof ObjectValue) { - if (prev.contains(val)) throw EngineException.ofError("Circular dependency in JSON."); - prev.add(val); - - var res = new JSONMap(); - - for (var el : val.getMembers(env, true, true).entrySet()) { - var jsonEl = fromJs(env, el.getValue().get(env, val), prev); - if (jsonEl == null) continue; - res.put(el.getKey(), jsonEl); - } - - prev.remove(val); - return JSONElement.of(res); - } - if (val == null) return null; - return null; - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Key.java b/src/main/java/me/topchetoeu/jscript/runtime/Key.java new file mode 100644 index 0000000..656efc8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/Key.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.runtime; + +public class Key { + +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java deleted file mode 100644 index 21ddfaa..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ /dev/null @@ -1,377 +0,0 @@ -package me.topchetoeu.jscript.runtime; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; - -import me.topchetoeu.jscript.common.Metadata; -import me.topchetoeu.jscript.common.Reading; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.exceptions.InterruptException; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; -import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; - -public class SimpleRepl { - static Thread engineTask; - static Engine engine = new Engine(); - static Environment environment = Environment.empty(); - - static int j = 0; - static String[] args; - - private static void reader() { - try { - try { - try { initGlobals(); } catch (ExecutionException e) { throw e.getCause(); } - } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } - - for (var arg : args) { - try { - var file = Path.of(arg); - var raw = Files.readString(file); - - try { - var res = engine.pushMsg( - false, environment, - Filename.fromFile(file.toFile()), raw, null - ).get(); - - System.err.println(res.toReadable(environment)); - } - catch (ExecutionException e) { throw e.getCause(); } - } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } - } - - for (var i = 0; ; i++) { - try { - var raw = Reading.readline(); - - if (raw == null) break; - - try { - var res = engine.pushMsg( - false, environment, - new Filename("jscript", "repl/" + i + ".js"), raw, - Value.UNDEFINED - ).get(); - System.err.println(res.toReadable(environment)); - } - catch (ExecutionException e) { throw e.getCause(); } - } - catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); } - } - } - catch (IOException e) { - System.out.println(e.toString()); - engine.thread().interrupt(); - } - catch (CancellationException | InterruptedException e) { return; } - catch (Throwable ex) { - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); - } - } - - private static ObjectValue symbolPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "makeSymbol", new NativeFunction(args -> new SymbolValue(args.get(0).toString(args.env).value))); - res.defineOwnMember(env, "getSymbol", new NativeFunction(args -> SymbolValue.get(args.get(0).toString(args.env).value))); - res.defineOwnMember(env, "getSymbolKey", new NativeFunction(args -> ((SymbolValue)args.get(0)).key())); - res.defineOwnMember(env, "getSymbolDescriptor", new NativeFunction(args -> new StringValue(((SymbolValue)args.get(0)).value))); - - return res; - } - - private static ObjectValue numberPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "parseInt", new NativeFunction(args -> { - var radix = args.get(1).toInt(env); - - if (radix != 10 && args.get(0) instanceof NumberValue) { - return new NumberValue(args.get(0).toNumber(env).value - args.get(0).toNumber(env).value % 1); - } - else { - return NumberValue.parseInt(args.get(0).toString(), radix, false); - } - })); - res.defineOwnMember(env, "parseFloat", new NativeFunction(args -> { - if (args.get(0) instanceof NumberValue) { - return args.get(0); - } - else return NumberValue.parseFloat(args.get(0).toString(), false); - })); - res.defineOwnMember(env, "isNaN", new NativeFunction(args -> BoolValue.of(args.get(0).isNaN()))); - res.defineOwnMember(env, "NaN", new NumberValue(Double.NaN)); - res.defineOwnMember(env, "Infinity", new NumberValue(Double.POSITIVE_INFINITY)); - - return res; - } - - private static ObjectValue stringPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "stringBuild", new NativeFunction(args -> { - var parts = ((ArrayValue)args.get(0)).toArray(); - var sb = new StringBuilder(); - - for (var i = 0; i < parts.length; i++) { - sb.append(((StringValue)parts[i]).value); - } - - return new StringValue(sb.toString()); - })); - - res.defineOwnMember(env, "fromCharCode", new NativeFunction(args -> { - var parts = ((ArrayValue)args.get(0)).toArray(); - var sb = new StringBuilder(); - - for (var i = 0; i < parts.length; i++) { - sb.append(((StringValue)parts[i]).value); - } - - return new StringValue(sb.toString()); - })); - - return res; - } - - private static ObjectValue objectPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "defineField", new NativeFunction(args -> { - var obj = (ObjectValue)args.get(0); - var key = args.get(1); - var writable = args.get(2).toBoolean(); - var enumerable = args.get(3).toBoolean(); - var configurable = args.get(4).toBoolean(); - var value = args.get(5); - - obj.defineOwnMember(args.env, key, FieldMember.of(value, enumerable, configurable, writable)); - - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "defineProperty", new NativeFunction(args -> { - var obj = (ObjectValue)args.get(0); - var key = args.get(1); - var enumerable = args.get(2).toBoolean(); - var configurable = args.get(3).toBoolean(); - var getter = args.get(4) instanceof VoidValue ? null : (FunctionValue)args.get(4); - var setter = args.get(5) instanceof VoidValue ? null : (FunctionValue)args.get(5); - - obj.defineOwnMember(args.env, key, new PropertyMember(getter, setter, configurable, enumerable)); - - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "getPrototype", new NativeFunction(args -> { - return args.get(0).getPrototype(env); - })); - res.defineOwnMember(env, "setPrototype", new NativeFunction(args -> { - var proto = args.get(1) instanceof VoidValue ? null : (ObjectValue)args.get(1); - args.get(0).setPrototype(env, proto); - return args.get(0); - })); - return res; - } - - private static ObjectValue functionPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "setCallable", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - func.enableCall = args.get(1).toBoolean(); - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - func.enableNew = args.get(1).toBoolean(); - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { - if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); - else return new StringValue("call"); - })); - res.defineOwnMember(env, "invoke", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - var self = args.get(1); - var funcArgs = (ArrayValue)args.get(2); - - return func.call(env, self, funcArgs.toArray()); - })); - - return res; - } - - private static ObjectValue jsonPrimordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "stringify", new NativeFunction(args -> { - return new StringValue(JSON.stringify(JSONConverter.fromJs(env, args.get(0)))); - })); - res.defineOwnMember(env, "parse", new NativeFunction(args -> { - return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env).value)); - })); - res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - func.enableNew = args.get(1).toBoolean(); - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { - if (((ArgumentsValue)args.get(0)).frame.isNew) return new StringValue("new"); - else return new StringValue("call"); - })); - res.defineOwnMember(env, "invoke", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - var self = args.get(1); - var funcArgs = (ArrayValue)args.get(2); - - return func.call(env, self, funcArgs.toArray()); - })); - - return res; - } - - private static ObjectValue primordials(Environment env) { - var res = new ObjectValue(); - res.setPrototype(null, null); - - res.defineOwnMember(env, "symbol", symbolPrimordials(env)); - res.defineOwnMember(env, "number", numberPrimordials(env)); - res.defineOwnMember(env, "string", stringPrimordials(env)); - res.defineOwnMember(env, "object", objectPrimordials(env)); - res.defineOwnMember(env, "function", functionPrimordials(env)); - res.defineOwnMember(env, "json", jsonPrimordials(env)); - - int[] i = new int[1]; - - res.defineOwnMember(env, "setGlobalPrototype", new NativeFunction(args -> { - var type = args.get(0).toString(env).value; - var obj = (ObjectValue)args.get(1); - - switch (type) { - case "string": - args.env.add(Value.STRING_PROTO, obj); - break; - case "number": - args.env.add(Value.NUMBER_PROTO, obj); - break; - case "boolean": - args.env.add(Value.BOOL_PROTO, obj); - break; - case "symbol": - args.env.add(Value.SYMBOL_PROTO, obj); - break; - case "object": - args.env.add(Value.OBJECT_PROTO, obj); - break; - } - - return Value.UNDEFINED; - })); - res.defineOwnMember(env, "compile", new NativeFunction(args -> { - return Compiler.compileFunc(env, new Filename("jscript", "func" + i[0]++ + ".js"), args.get(0).toString(env).value); - })); - return res; - } - - private static void initEnv() { - // glob.define(null, false, new NativeFunction("go", args -> { - // try { - // var f = Path.of("do.js"); - // var func = Compiler.compile(args.env, new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); - // return func.call(args.env); - // } - // catch (IOException e) { - // throw new EngineException("Couldn't open do.js"); - // } - // })); - - // var fs = new RootFilesystem(PermissionsProvider.get(environment)); - // fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); - // fs.protocols.put("file", new PhysicalFilesystem(".")); - // fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err)); - - // environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS); - // environment.add(Filesystem.KEY, fs); - // environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs)); - // environment.add(Compiler.KEY, new JSCompiler(environment)); - environment.add(EventLoop.KEY, engine); - environment.add(DebugContext.KEY, new DebugContext()); - // environment.add(EventLoop.KEY, engine); - environment.add(Compiler.KEY, Compiler.DEFAULT); - - var glob = Value.global(environment); - - glob.defineOwnMember(null, "exit", new NativeFunction("exit", args -> { - Thread.currentThread().interrupt(); - throw new InterruptException(); - })); - glob.defineOwnMember(null, "print", new NativeFunction("print", args -> { - for (var el : args.args) { - if (el instanceof StringValue) System.out.print(((StringValue)el).value); - else System.out.print(el.toReadable(args.env)); - } - - return null; - })); - } - private static void initEngine() { - // var ctx = new DebugContext(); - // environment.add(DebugContext.KEY, ctx); - - // debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx)); - engineTask = engine.start(); - // debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); - } - private static void initGlobals() throws InterruptedException, ExecutionException { - EventLoop.get(environment).pushMsg( - false, environment, - Filename.parse("jscript://init.js"), Reading.resourceToString("lib/index.js"), - Value.UNDEFINED, Value.global(environment), primordials(environment) - ).get(); - } - - public static void main(String args[]) throws InterruptedException { - System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); - - SimpleRepl.args = args; - var reader = new Thread(SimpleRepl::reader); - - initEnv(); - initEngine(); - - reader.setDaemon(true); - reader.setName("STD Reader"); - reader.start(); - - engine.thread().join(); - // debugTask.interrupt(); - engineTask.interrupt(); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java b/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java new file mode 100644 index 0000000..8665625 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/WrapperProvider.java @@ -0,0 +1,12 @@ +package me.topchetoeu.jscript.runtime; + +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; + +public interface WrapperProvider { + public ObjectValue getProto(Class obj); + public ObjectValue getNamespace(Class obj); + public FunctionValue getConstr(Class obj); + + public WrapperProvider fork(Environment env); +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java index d8f709b..f43e7bc 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugContext.java @@ -5,21 +5,22 @@ import java.util.HashMap; import java.util.List; import java.util.WeakHashMap; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; +import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.mapping.FunctionMap; -import me.topchetoeu.jscript.common.parsing.Filename; -import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.Key; import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; +import me.topchetoeu.jscript.runtime.values.CodeFunction; +import me.topchetoeu.jscript.runtime.values.FunctionValue; public class DebugContext { - public static final Key KEY = Key.of(); - public static final Key IGNORE = Key.of(); + public static final Key KEY = new Key<>(); + public static final Key IGNORE = new Key<>(); private HashMap sources; private WeakHashMap maps; @@ -70,24 +71,15 @@ public class DebugContext { if (maps == null || !(func instanceof CodeFunction)) return FunctionMap.EMPTY; return getMapOrEmpty(((CodeFunction)func).body); } - public List getStackFrames() { - if (debugger == null) return List.of(); - return this.debugger.getStackFrame(); - } - public void onFramePop(Environment env, Frame frame) { - if (debugger != null) debugger.onFramePop(env, frame); + public void onFramePop(Context ctx, Frame frame) { + if (debugger != null) debugger.onFramePop(ctx, frame); } - public void onFramePush(Environment env, Frame frame) { - if (debugger != null) debugger.onFramePush(env, frame); + public void onFramePush(Context ctx, Frame frame) { + if (debugger != null) debugger.onFramePush(ctx, frame); } - - public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { - if (debugger != null) return debugger.onInstruction(env, frame, instruction, returnVal, error, caught); - else return false; - } - public boolean onInstruction(Environment env, Frame frame, Instruction instruction) { - if (debugger != null) return debugger.onInstruction(env, frame, instruction, null, null, false); + public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); else return false; } public void onSource(Filename filename, String source) { @@ -110,26 +102,26 @@ public class DebugContext { this(true); } - public static boolean enabled(Environment exts) { + public static boolean enabled(Extensions exts) { return exts != null && exts.hasNotNull(KEY) && !exts.has(IGNORE); } - public static DebugContext get(Environment exts) { + public static DebugContext get(Extensions exts) { if (enabled(exts)) return exts.get(KEY); else return new DebugContext(false); } - public static List stackTrace(Environment env) { + public static List stackTrace(Context ctx) { var res = new ArrayList(); - var dbgCtx = get(env); + var dbgCtx = get(ctx); - for (var frame : dbgCtx.getStackFrames()) { - var name = frame.function.name; + for (var el : ctx.frames()) { + var name = el.function.name; - var map = dbgCtx.getMapOrEmpty(frame.function); + var map = dbgCtx.getMapOrEmpty(el.function); Location loc = null; if (map != null) { - loc = map.toLocation(frame.codePtr, true); + loc = map.toLocation(el.codePtr, true); if (loc == null) loc = map.start(); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java index 2d67c6f..cb4b786 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/debug/DebugHandler.java @@ -1,12 +1,10 @@ package me.topchetoeu.jscript.runtime.debug; -import java.util.List; - +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.mapping.FunctionMap; -import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.runtime.Context; import me.topchetoeu.jscript.runtime.Frame; import me.topchetoeu.jscript.runtime.exceptions.EngineException; @@ -37,7 +35,7 @@ public interface DebugHandler { /** * Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned. * This function might pause in order to await debugging commands. - * @param env The context of execution + * @param ctx The context of execution * @param frame The frame in which execution is occuring * @param instruction The instruction which was or will be executed * @param returnVal The return value of the instruction, Values.NO_RETURN if none @@ -45,35 +43,32 @@ public interface DebugHandler { * @param caught Whether or not the error has been caught * @return Whether or not the frame should restart (currently does nothing) */ - boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); + boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught); /** * Called immediatly before a frame has been pushed on the frame stack. * This function might pause in order to await debugging commands. - * @param env The context of execution + * @param ctx The context of execution * @param frame The code frame which was pushed */ - void onFramePush(Environment env, Frame frame); + void onFramePush(Context ctx, Frame frame); /** * Called immediatly after a frame has been popped out of the frame stack. * This function might pause in order to await debugging commands. - * @param env The context of execution + * @param ctx The context of execution * @param frame The code frame which was popped out */ - void onFramePop(Environment env, Frame frame); - - List getStackFrame(); + void onFramePop(Context ctx, Frame frame); public static DebugHandler empty() { return new DebugHandler () { - @Override public void onFramePop(Environment env, Frame frame) { } - @Override public void onFramePush(Environment env, Frame frame) { } - @Override public boolean onInstruction(Environment env, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + @Override public void onFramePop(Context ctx, Frame frame) { } + @Override public void onFramePush(Context ctx, Frame frame) { } + @Override public boolean onInstruction(Context ctx, Frame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { return false; } @Override public void onSourceLoad(Filename filename, String source) { } @Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { } - @Override public List getStackFrame() { return List.of(); } }; } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java new file mode 100644 index 0000000..72cb63a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/ConvertException.java @@ -0,0 +1,11 @@ +package me.topchetoeu.jscript.runtime.exceptions; + +public class ConvertException extends RuntimeException { + public final String source, target; + + public ConvertException(String source, String target) { + super(String.format("Cannot convert '%s' to '%s'.", source, target)); + this.source = source; + this.target = target; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java index f9a21f4..20d19bc 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/EngineException.java @@ -3,23 +3,22 @@ package me.topchetoeu.jscript.runtime.exceptions; import java.util.ArrayList; import java.util.List; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue.PrototypeProvider; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.runtime.values.ObjectValue.PlaceholderProto; public class EngineException extends RuntimeException { public static class StackElement { public final Location location; public final String name; - public final Environment ext; + public final Extensions ext; public boolean visible() { - return ext == null || !ext.get(Value.HIDE_STACK, false); + return ext == null || !ext.get(Environment.HIDE_STACK, false); } public String toString() { var res = ""; @@ -31,27 +30,27 @@ public class EngineException extends RuntimeException { return res.trim(); } - public StackElement(Environment ext, Location location, String name) { + public StackElement(Extensions ext, Location location, String name) { if (name != null) name = name.trim(); if (name.equals("")) name = null; if (ext == null) this.ext = null; - else this.ext = ext; + else this.ext = Context.clean(ext); this.location = location; this.name = name; } } - public final Value value; + public final Object value; public EngineException cause; - public Environment env = null; + public Extensions ext = null; public final List stackTrace = new ArrayList<>(); - public EngineException add(Environment env, String name, Location location) { - var el = new StackElement(env, location, name); + public EngineException add(Extensions ext, String name, Location location) { + var el = new StackElement(ext, location, name); if (el.name == null && el.location == null) return this; - setEnvironment(env); + setExtensions(ext); stackTrace.add(el); return this; } @@ -59,70 +58,54 @@ public class EngineException extends RuntimeException { this.cause = cause; return this; } - public EngineException setEnvironment(Environment env) { - if (this.env == null) this.env = env; + public EngineException setExtensions(Extensions ext) { + if (this.ext == null) this.ext = Context.clean(ext); return this; } - public String toString(Environment env) { + public String toString(Extensions ext) { var ss = new StringBuilder(); try { - ss.append(value.toString(env)).append('\n'); + ss.append(Values.toString(ext, value)).append('\n'); } catch (EngineException e) { - var name = value.getMember(env, "name"); - var desc = value.getMember(env, "message"); - - if (name.isPrimitive() && desc.isPrimitive()) { - if (name instanceof VoidValue) ss.append("Error: "); - else ss.append(name.toString(env).value + ": "); - - if (desc instanceof VoidValue) ss.append("An error occurred"); - else ss.append(desc.toString(env).value); - - ss.append("\n"); - } - else ss.append("[Error while stringifying]\n"); + ss.append("[Error while stringifying]\n"); } for (var line : stackTrace) { if (line.visible()) ss.append(" ").append(line.toString()).append("\n"); } - if (cause != null) ss.append("Caused by ").append(cause.toString(env)).append('\n'); + if (cause != null) ss.append("Caused by ").append(cause.toString(ext)).append('\n'); ss.deleteCharAt(ss.length() - 1); return ss.toString(); } - private static ObjectValue err(String name, String msg, PrototypeProvider proto) { - var res = new ObjectValue(); - res.setPrototype(proto); - - if (msg == null) msg = ""; - - if (name != null) res.defineOwnMember(Environment.empty(), "name", FieldMember.of(new StringValue(name))); - res.defineOwnMember(Environment.empty(), "message", FieldMember.of(new StringValue(msg))); + private static Object err(String name, String msg, PlaceholderProto proto) { + var res = new ObjectValue(proto); + if (name != null) res.defineProperty(null, "name", name); + res.defineProperty(null, "message", msg); return res; } - public EngineException(Value error) { - super(error.toReadable(Environment.empty())); + public EngineException(Object error) { + super(error == null ? "null" : error.toString()); this.value = error; this.cause = null; } public static EngineException ofError(String name, String msg) { - return new EngineException(err(name, msg, env -> env.get(Value.ERROR_PROTO))); + return new EngineException(err(name, msg, PlaceholderProto.ERROR)); } public static EngineException ofError(String msg) { - return new EngineException(err(null, msg, env -> env.get(Value.ERROR_PROTO))); + return new EngineException(err(null, msg, PlaceholderProto.ERROR)); } public static EngineException ofSyntax(String msg) { - return new EngineException(err(null, msg, env -> env.get(Value.SYNTAX_ERR_PROTO))); + return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR)); } public static EngineException ofType(String msg) { - return new EngineException(err(null, msg, env -> env.get(Value.TYPE_ERR_PROTO))); + return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR)); } public static EngineException ofRange(String msg) { - return new EngineException(err(null, msg, env -> env.get(Value.RANGE_ERR_PROTO))); + return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR)); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java index 023df78..23bd206 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/exceptions/SyntaxException.java @@ -1,13 +1,13 @@ package me.topchetoeu.jscript.runtime.exceptions; -import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.common.Location; public class SyntaxException extends RuntimeException { public final Location loc; public final String msg; public SyntaxException(Location loc, String msg) { - super(String.format("Syntax error %s: %s", loc, msg)); + super(String.format("Syntax error (at %s): %s", loc, msg)); this.loc = loc; this.msg = msg; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java new file mode 100644 index 0000000..63eed8e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/scope/GlobalScope.java @@ -0,0 +1,81 @@ +package me.topchetoeu.jscript.runtime.scope; + +import java.util.HashSet; +import java.util.Set; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Values; + +public class GlobalScope { + public static final Key KEY = new Key<>(); + + public final ObjectValue obj; + + public boolean has(Extensions ext, String name) { + return Values.hasMember(ext, obj, name, false); + } + + public GlobalScope child() { + var obj = new ObjectValue(); + Values.setPrototype(null, obj, this.obj); + return new GlobalScope(obj); + } + + public Object define(Extensions ext, String name) { + if (Values.hasMember(ext, obj, name, false)) return name; + obj.defineProperty(ext, name, null); + return name; + } + public void define(Extensions ext, String name, Variable val) { + obj.defineProperty(ext, name, + new NativeFunction("get " + name, args -> val.get(args.ctx)), + new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }), + true, true + ); + } + public void define(Extensions ext, String name, boolean readonly, Object val) { + obj.defineProperty(ext, name, val, readonly, true, true); + } + public void define(Extensions ext, String ...names) { + for (var n : names) define(ext, n); + } + public void define(Extensions ext, boolean readonly, FunctionValue val) { + define(ext, val.name, readonly, val); + } + + public Object get(Extensions ext, String name) { + if (!Values.hasMember(ext, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + else return Values.getMember(ext, obj, name); + } + public void set(Extensions ext, String name, Object val) { + if (!Values.hasMember(ext, obj, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); + if (!Values.setMember(ext, obj, name, val)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); + } + + public Set keys() { + var res = new HashSet(); + + for (var key : keys()) { + if (key instanceof String) res.add((String)key); + } + + return res; + } + + public GlobalScope() { + this.obj = new ObjectValue(); + } + public GlobalScope(ObjectValue val) { + this.obj = val; + } + + public static GlobalScope get(Extensions ext) { + if (ext.has(KEY)) return ext.get(KEY); + else return new GlobalScope(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java new file mode 100644 index 0000000..6adce80 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/scope/LocalScope.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.runtime.scope; + +import java.util.ArrayList; + +public class LocalScope { + public final ValueVariable[] captures; + public final ValueVariable[] locals; + public final ArrayList catchVars = new ArrayList<>(); + + public ValueVariable get(int i) { + if (i >= locals.length) return catchVars.get(i - locals.length); + if (i >= 0) return locals[i]; + else return captures[~i]; + } + + + public int size() { + return captures.length + locals.length; + } + + public LocalScope(int n, ValueVariable[] captures) { + locals = new ValueVariable[n]; + this.captures = captures; + + for (int i = 0; i < n; i++) { + locals[i] = new ValueVariable(false, null); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java new file mode 100644 index 0000000..eb988b3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/scope/ValueVariable.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.runtime.scope; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.values.Values; + +public class ValueVariable implements Variable { + public boolean readonly; + public Object value; + + @Override + public boolean readonly() { return readonly; } + + @Override + public Object get(Extensions ext) { + return value; + } + + @Override + public void set(Extensions ext, Object val) { + if (readonly) return; + this.value = Values.normalize(ext, val); + } + + public ValueVariable(boolean readonly, Object val) { + this.readonly = readonly; + this.value = val; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java b/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java new file mode 100644 index 0000000..6d00d80 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/scope/Variable.java @@ -0,0 +1,9 @@ +package me.topchetoeu.jscript.runtime.scope; + +import me.topchetoeu.jscript.runtime.Extensions; + +public interface Variable { + Object get(Extensions ext); + default boolean readonly() { return true; } + default void set(Extensions ext, Object val) { } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java new file mode 100644 index 0000000..a13c014 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/ArrayValue.java @@ -0,0 +1,227 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import me.topchetoeu.jscript.runtime.Extensions; + +// TODO: Make methods generic +public class ArrayValue extends ObjectValue implements Iterable { + private static final Object UNDEFINED = new Object(); + private Object[] values; + private int size; + + private Object[] alloc(int index) { + index++; + if (index < values.length) return values; + if (index < values.length * 2) index = values.length * 2; + + var arr = new Object[index]; + System.arraycopy(values, 0, arr, 0, values.length); + return arr; + } + + public int size() { return size; } + public boolean setSize(int val) { + if (val < 0) return false; + if (size > val) shrink(size - val); + else { + values = alloc(val); + size = val; + } + return true; + } + + public Object get(int i) { + if (i < 0 || i >= size) return null; + var res = values[i]; + if (res == UNDEFINED) return null; + else return res; + } + public void set(Extensions ext, int i, Object val) { + if (i < 0) return; + + values = alloc(i); + + val = Values.normalize(ext, val); + if (val == null) val = UNDEFINED; + values[i] = val; + if (i >= size) size = i + 1; + } + public boolean has(int i) { + return i >= 0 && i < size && values[i] != null; + } + public void remove(int i) { + if (i < 0 || i >= values.length) return; + values[i] = null; + } + public void shrink(int n) { + if (n >= values.length) { + values = new Object[16]; + size = 0; + } + else { + for (int i = 0; i < n; i++) { + values[--size] = null; + } + } + } + + public Object[] toArray() { + Object[] res = new Object[size]; + copyTo(res, 0, 0, size); + return res; + } + public void copyTo(Object[] arr, int sourceStart, int destStart, int count) { + for (var i = 0; i < count; i++) { + if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null; + if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null; + else arr[i + sourceStart] = values[i + destStart]; + } + } + public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) { + if (arr == this) { + move(sourceStart, destStart, count); + return; + } + + // Iterate in reverse to reallocate at most once + if (destStart + count > arr.size) arr.size = destStart + count; + + for (var i = count - 1; i >= 0; i--) { + if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart); + if (values[i + sourceStart] == UNDEFINED) arr.set(null, i + destStart, null); + else if (values[i + sourceStart] == null) arr.remove(i + destStart); + else arr.set(null, i + destStart, values[i + sourceStart]); + } + } + + public void copyFrom(Extensions ext, Object[] arr, int sourceStart, int destStart, int count) { + for (var i = 0; i < count; i++) { + set(ext, i + destStart, arr[i + sourceStart]); + } + } + + public void move(int srcI, int dstI, int n) { + values = alloc(dstI + n); + + System.arraycopy(values, srcI, values, dstI, n); + + if (dstI + n >= size) size = dstI + n; + } + + public void sort(Comparator comparator) { + Arrays.sort(values, 0, size, (a, b) -> { + var _a = 0; + var _b = 0; + + if (a == UNDEFINED) _a = 1; + if (a == null) _a = 2; + + if (b == UNDEFINED) _b = 1; + if (b == null) _b = 2; + + if (_a != 0 || _b != 0) return Integer.compare(_a, _b); + + return comparator.compare(a, b); + }); + } + + @Override + protected Object getField(Extensions ext, Object key) { + if (key instanceof Number) { + var i = ((Number)key).doubleValue(); + if (i >= 0 && i - Math.floor(i) == 0) { + return get((int)i); + } + } + + return super.getField(ext, key); + } + @Override + protected boolean setField(Extensions ext, Object key, Object val) { + if (key instanceof Number) { + var i = Values.number(key); + if (i >= 0 && i - Math.floor(i) == 0) { + set(ext, (int)i, val); + return true; + } + } + + return super.setField(ext, key, val); + } + @Override + protected boolean hasField(Extensions ext, Object key) { + if (key instanceof Number) { + var i = Values.number(key); + if (i >= 0 && i - Math.floor(i) == 0) { + return has((int)i); + } + } + + return super.hasField(ext, key); + } + @Override + protected void deleteField(Extensions ext, Object key) { + if (key instanceof Number) { + var i = Values.number(key); + if (i >= 0 && i - Math.floor(i) == 0) { + remove((int)i); + return; + } + } + + super.deleteField(ext, key); + } + + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + for (var i = 0; i < size(); i++) { + if (has(i)) res.add(i); + } + return res; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return get(i++); + } + }; + } + + public ArrayValue() { + super(PlaceholderProto.ARRAY); + values = new Object[16]; + size = 0; + } + public ArrayValue(int cap) { + super(PlaceholderProto.ARRAY); + values = new Object[cap]; + size = 0; + } + public ArrayValue(Extensions ext, Object ...values) { + this(); + this.values = new Object[values.length]; + size = values.length; + + for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ext, values[i]); + } + + public static ArrayValue of(Extensions ext, Collection values) { + return new ArrayValue(ext, values.toArray(Object[]::new)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java new file mode 100644 index 0000000..2d0df77 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/CodeFunction.java @@ -0,0 +1,50 @@ +package me.topchetoeu.jscript.runtime.values; + +import me.topchetoeu.jscript.common.FunctionBody; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; + +public class CodeFunction extends FunctionValue { + public final FunctionBody body; + public final ValueVariable[] captures; + public Extensions extensions; + + // public Location loc() { + // for (var instr : body.instructions) { + // if (instr.location != null) return instr.location; + // } + // return null; + // } + // public String readable() { + // var loc = loc(); + // if (loc == null) return name; + // else if (name.equals("")) return loc.toString(); + // else return name + "@" + loc; + // } + + @Override + public Object call(Extensions ext, Object thisArg, Object ...args) { + var frame = new Frame(Context.of(ext), thisArg, args, this); + + frame.onPush(); + + try { + while (true) { + var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null); + if (res != Values.NO_RETURN) return res; + } + } + finally { + frame.onPop(); + } + } + + public CodeFunction(Extensions extensions, String name, FunctionBody body, ValueVariable[] captures) { + super(name, body.argsN); + this.captures = captures; + this.extensions = Context.clean(extensions); + this.body = body; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java new file mode 100644 index 0000000..1717f40 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/ConvertHint.java @@ -0,0 +1,6 @@ +package me.topchetoeu.jscript.runtime.values; + +public enum ConvertHint { + TOSTRING, + VALUEOF, +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java new file mode 100644 index 0000000..ced4c9a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/FunctionValue.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.List; + +import me.topchetoeu.jscript.runtime.Extensions; + +public abstract class FunctionValue extends ObjectValue { + public String name = ""; + public int length; + + @Override + public String toString() { + return String.format("function %s(...)", name); + } + + public abstract Object call(Extensions ext, Object thisArg, Object ...args); + public Object call(Extensions ext) { + return call(ext, null); + } + + @Override + protected Object getField(Extensions ext, Object key) { + if ("name".equals(key)) return name; + if ("length".equals(key)) return length; + return super.getField(ext, key); + } + @Override + protected boolean setField(Extensions ext, Object key, Object val) { + if ("name".equals(key)) name = Values.toString(ext, val); + else if ("length".equals(key)) length = (int)Values.toNumber(ext, val); + else return super.setField(ext, key, val); + return true; + } + @Override + protected boolean hasField(Extensions ext, Object key) { + if ("name".equals(key)) return true; + if ("length".equals(key)) return true; + return super.hasField(ext, key); + } + + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + if (includeNonEnumerable) { + res.add("name"); + res.add("length"); + } + return res; + } + + public FunctionValue(String name, int length) { + super(PlaceholderProto.FUNCTION); + + if (name == null) name = ""; + this.length = length; + this.name = name; + + nonConfigurableSet.add("name"); + nonEnumerableSet.add("name"); + nonWritableSet.add("length"); + nonConfigurableSet.add("length"); + nonEnumerableSet.add("length"); + + var proto = new ObjectValue(); + proto.defineProperty(null, "constructor", this, true, false, false); + this.defineProperty(null, "prototype", proto, true, false, false); + } +} + diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java b/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java deleted file mode 100644 index f5660ed..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/KeyCache.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; - -public final class KeyCache { - public final Value value; - private Integer intCache; - private Double doubleCache; - private Boolean booleanCache; - private String stringCache; - - public String toString(Environment env) { - if (stringCache != null) return stringCache; - else return stringCache = value.toString(env).value; - } - public double toNumber(Environment env) { - if (doubleCache != null) return doubleCache; - else return doubleCache = value.toNumber(env).value; - } - public int toInt(Environment env) { - if (intCache != null) return intCache; - else return intCache = (int)toNumber(env); - } - public boolean toBoolean() { - if (booleanCache != null) return booleanCache; - else return booleanCache = value.toBoolean(); - } - public SymbolValue toSymbol() { - if (value instanceof SymbolValue) return (SymbolValue)value; - else return null; - } - public boolean isSymbol() { - return value instanceof SymbolValue; - } - - public KeyCache(Value value) { - this.value = value; - } - public KeyCache(String value) { - this.value = new StringValue(value); - this.stringCache = value; - this.booleanCache = !value.equals(""); - } - public KeyCache(int value) { - this.value = new NumberValue(value); - this.intCache = value; - this.doubleCache = (double)value; - this.booleanCache = value != 0; - } - public KeyCache(double value) { - this.value = new NumberValue(value); - this.intCache = (int)value; - this.doubleCache = value; - this.booleanCache = value != 0; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java deleted file mode 100644 index 92828a0..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java +++ /dev/null @@ -1,130 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; - -public interface Member { - public static final class PropertyMember implements Member { - public final FunctionValue getter; - public final FunctionValue setter; - public final boolean configurable; - public final boolean enumerable; - - @Override public Value get(Environment env, Value self) { - if (getter != null) return getter.call(env, self); - else return Value.UNDEFINED; - } - @Override public boolean set(Environment env, Value val, Value self) { - if (setter == null) return false; - setter.call(env, self, val); - return true; - } - - @Override public boolean configurable() { return configurable; } - @Override public boolean enumerable() { return enumerable; } - - @Override public boolean configure(Environment env, Member newMember, Value self) { - if (!(newMember instanceof PropertyMember)) return false; - var prop = (PropertyMember)newMember; - - if (prop.configurable != configurable) return false; - if (prop.enumerable != enumerable) return false; - - if (prop.getter == getter) return true; - if (prop.setter == setter) return true; - return false; - } - - @Override public ObjectValue descriptor(Environment env, Value self) { - var res = new ObjectValue(); - - if (getter == null) res.defineOwnMember(env, "getter", FieldMember.of(Value.UNDEFINED)); - else res.defineOwnMember(env, "getter", FieldMember.of(getter)); - - if (setter == null) res.defineOwnMember(env, "setter", FieldMember.of(Value.UNDEFINED)); - else res.defineOwnMember(env, "setter", FieldMember.of(setter)); - - res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); - res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); - return res; - } - - public PropertyMember(FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { - this.getter = getter; - this.setter = setter; - this.configurable = configurable; - this.enumerable = enumerable; - } - } - - public static abstract class FieldMember implements Member { - private static class SimpleFieldMember extends FieldMember { - public Value value; - - @Override public Value get(Environment env, Value self) { return value; } - @Override public boolean set(Environment env, Value val, Value self) { - if (!writable) return false; - value = val; - return true; - } - public SimpleFieldMember(Value value, boolean configurable, boolean enumerable, boolean writable) { - super(configurable, enumerable, writable); - this.value = value; - } - } - - public boolean configurable; - public boolean enumerable; - public boolean writable; - - @Override public final boolean configurable() { return configurable; } - @Override public final boolean enumerable() { return enumerable; } - @Override public final boolean configure(Environment env, Member newMember, Value self) { - if (!(newMember instanceof FieldMember)) return false; - var field = (FieldMember)newMember; - - if (field.configurable != configurable) return false; - if (field.enumerable != enumerable) return false; - if (!writable) return field.get(env, self).equals(get(env, self)); - - set(env, field.get(env, self), self); - writable = field.writable; - return true; - } - - @Override public ObjectValue descriptor(Environment env, Value self) { - var res = new ObjectValue(); - res.defineOwnMember(env, "value", FieldMember.of(get(env, self))); - res.defineOwnMember(env, "writable", FieldMember.of(BoolValue.of(writable))); - res.defineOwnMember(env, "enumerable", FieldMember.of(BoolValue.of(enumerable))); - res.defineOwnMember(env, "configurable", FieldMember.of(BoolValue.of(configurable))); - return res; - } - - public FieldMember(boolean configurable, boolean enumerable, boolean writable) { - this.configurable = configurable; - this.enumerable = enumerable; - this.writable = writable; - } - - public static FieldMember of(Value value) { - return new SimpleFieldMember(value, true, true, true); - } - public static FieldMember of(Value value, boolean writable) { - return new SimpleFieldMember(value, true, true, writable); - } - public static FieldMember of(Value value, boolean configurable, boolean enumerable, boolean writable) { - return new SimpleFieldMember(value, configurable, enumerable, writable); - } - } - - public boolean configurable(); - public boolean enumerable(); - public boolean configure(Environment env, Member newMember, Value self); - public ObjectValue descriptor(Environment env, Value self); - - public Value get(Environment env, Value self); - public boolean set(Environment env, Value val, Value self); -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java new file mode 100644 index 0000000..e1145f9 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeFunction.java @@ -0,0 +1,27 @@ +package me.topchetoeu.jscript.runtime.values; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.utils.interop.Arguments; + +public class NativeFunction extends FunctionValue { + public static interface NativeFunctionRunner { + Object run(Arguments args); + } + + public final NativeFunctionRunner action; + + @Override + public Object call(Extensions ext, Object thisArg, Object ...args) { + return action.run(new Arguments(Context.of(ext), thisArg, args)); + } + + public NativeFunction(String name, NativeFunctionRunner action) { + super(name, 0); + this.action = action; + } + public NativeFunction(NativeFunctionRunner action) { + super("", 0); + this.action = action; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java new file mode 100644 index 0000000..938bc73 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/NativeWrapper.java @@ -0,0 +1,81 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.WeakHashMap; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; + +public class NativeWrapper extends ObjectValue { + private static class MapKey { + public final Object key; + + @Override + public int hashCode() { + return System.identityHashCode(key); + } + @Override + public boolean equals(Object obj) { + if (this == null || obj == null) return this == null && obj == null; + if (!(obj instanceof MapKey)) return false; + var other = (MapKey)obj; + + return other.key == key; + } + + public MapKey(Object key) { + this.key = key; + } + } + + private static final Key> WRAPPERS = new Key<>(); + private static final Object NATIVE_PROTO = new Object(); + public final Object wrapped; + + @Override + public ObjectValue getPrototype(Extensions ext) { + if (ext != null && prototype == NATIVE_PROTO) { + var clazz = wrapped.getClass(); + var res = NativeWrapperProvider.get(ext).getProto(clazz); + if (res != null) return res; + } + return super.getPrototype(ext); + } + + @Override + public String toString() { + return wrapped.toString(); + } + @Override + public boolean equals(Object obj) { + return wrapped.equals(obj); + } + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + private NativeWrapper(Object wrapped) { + this.wrapped = wrapped; + prototype = NATIVE_PROTO; + } + + public static NativeWrapper of(Extensions exts, Object wrapped) { + if (exts == null) return new NativeWrapper(wrapped); + var wrappers = exts.get(WRAPPERS); + + if (wrappers == null) { + wrappers = new WeakHashMap<>(); + exts.add(WRAPPERS, wrappers); + } + + var key = new MapKey(wrapped); + + if (wrappers.containsKey(key)) return wrappers.get(key); + + var res = new NativeWrapper(wrapped); + wrappers.put(key, res); + + return res; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java new file mode 100644 index 0000000..c1e0768 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/ObjectValue.java @@ -0,0 +1,354 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.Extensions; + +public class ObjectValue { + public static enum PlaceholderProto { + NONE, + OBJECT, + ARRAY, + FUNCTION, + ERROR, + SYNTAX_ERROR, + TYPE_ERROR, + RANGE_ERROR, + } + public static enum State { + NORMAL, + NO_EXTENSIONS, + SEALED, + FROZEN, + } + + public static class Property { + public final FunctionValue getter; + public final FunctionValue setter; + + public Property(FunctionValue getter, FunctionValue setter) { + this.getter = getter; + this.setter = setter; + } + } + + private static final Object OBJ_PROTO = new Object(); + private static final Object ARR_PROTO = new Object(); + private static final Object FUNC_PROTO = new Object(); + private static final Object ERR_PROTO = new Object(); + private static final Object SYNTAX_ERR_PROTO = new Object(); + private static final Object TYPE_ERR_PROTO = new Object(); + private static final Object RANGE_ERR_PROTO = new Object(); + + protected Object prototype; + + public State state = State.NORMAL; + public LinkedHashMap values = new LinkedHashMap<>(); + public LinkedHashMap properties = new LinkedHashMap<>(); + public LinkedHashSet nonWritableSet = new LinkedHashSet<>(); + public LinkedHashSet nonConfigurableSet = new LinkedHashSet<>(); + public LinkedHashSet nonEnumerableSet = new LinkedHashSet<>(); + + private Property getProperty(Extensions ext, Object key) { + if (properties.containsKey(key)) return properties.get(key); + var proto = getPrototype(ext); + if (proto != null) return proto.getProperty(ext, key); + else return null; + } + + public final boolean memberWritable(Object key) { + if (state == State.FROZEN) return false; + return !values.containsKey(key) || !nonWritableSet.contains(key); + } + public final boolean memberConfigurable(Object key) { + if (state == State.SEALED || state == State.FROZEN) return false; + return !nonConfigurableSet.contains(key); + } + public final boolean memberEnumerable(Object key) { + return !nonEnumerableSet.contains(key); + } + public final boolean extensible() { + return state == State.NORMAL; + } + + public final void preventExtensions() { + if (state == State.NORMAL) state = State.NO_EXTENSIONS; + } + public final void seal() { + if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED; + } + public final void freeze() { + state = State.FROZEN; + } + + public final boolean defineProperty(Extensions ext, Object key, Object val, boolean writable, boolean configurable, boolean enumerable) { + key = Values.normalize(ext, key); val = Values.normalize(ext, val); + boolean reconfigured = + writable != memberWritable(key) || + configurable != memberConfigurable(key) || + enumerable != memberEnumerable(key); + + if (!reconfigured) { + if (!memberWritable(key)) { + var a = values.get(key); + var b = val; + if (a == null || b == null) return a == null && b == null; + return a == b || a.equals(b); + } + values.put(key, val); + return true; + } + + if ( + properties.containsKey(key) && + values.get(key) == val && + !reconfigured + ) return true; + + if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; + if (!memberConfigurable(key)) return false; + + nonWritableSet.remove(key); + nonEnumerableSet.remove(key); + properties.remove(key); + values.remove(key); + + if (!writable) nonWritableSet.add(key); + if (!configurable) nonConfigurableSet.add(key); + if (!enumerable) nonEnumerableSet.add(key); + + values.put(key, val); + return true; + } + public final boolean defineProperty(Extensions ext, Object key, Object val) { + return defineProperty(ext, key, val, true, true, true); + } + public final boolean defineProperty(Extensions ext, Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) { + key = Values.normalize(ext, key); + if ( + properties.containsKey(key) && + properties.get(key).getter == getter && + properties.get(key).setter == setter && + !configurable == nonConfigurableSet.contains(key) && + !enumerable == nonEnumerableSet.contains(key) + ) return true; + if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; + if (!memberConfigurable(key)) return false; + + nonWritableSet.remove(key); + nonEnumerableSet.remove(key); + properties.remove(key); + values.remove(key); + + if (!configurable) nonConfigurableSet.add(key); + if (!enumerable) nonEnumerableSet.add(key); + + properties.put(key, new Property(getter, setter)); + return true; + } + + public ObjectValue getPrototype(Extensions ext) { + if (prototype instanceof ObjectValue || prototype == null) return (ObjectValue)prototype; + + try { + if (prototype == ARR_PROTO) return ext.get(Environment.ARRAY_PROTO); + if (prototype == FUNC_PROTO) return ext.get(Environment.FUNCTION_PROTO); + if (prototype == ERR_PROTO) return ext.get(Environment.ERROR_PROTO); + if (prototype == RANGE_ERR_PROTO) return ext.get(Environment.RANGE_ERR_PROTO); + if (prototype == SYNTAX_ERR_PROTO) return ext.get(Environment.SYNTAX_ERR_PROTO); + if (prototype == TYPE_ERR_PROTO) return ext.get(Environment.TYPE_ERR_PROTO); + return ext.get(Environment.OBJECT_PROTO); + } + catch (NullPointerException e) { return null; } + } + public final boolean setPrototype(PlaceholderProto val) { + if (!extensible()) return false; + switch (val) { + case OBJECT: prototype = OBJ_PROTO; break; + case FUNCTION: prototype = FUNC_PROTO; break; + case ARRAY: prototype = ARR_PROTO; break; + case ERROR: prototype = ERR_PROTO; break; + case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break; + case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break; + case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break; + case NONE: prototype = null; break; + } + return true; + } + + /** + * A method, used to get the value of a field. If a property is bound to + * this key, but not a field, this method should return null. + */ + protected Object getField(Extensions ext, Object key) { + if (values.containsKey(key)) return values.get(key); + var proto = getPrototype(ext); + if (proto != null) return proto.getField(ext, key); + else return null; + } + /** + * Changes the value of a field, that is bound to the given key. If no field is + * bound to this key, a new field should be created with the given value + * @return Whether or not the operation was successful + */ + protected boolean setField(Extensions ext, Object key, Object val) { + if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { + ((FunctionValue)val).name = Values.toString(ext, key); + } + + values.put(key, val); + return true; + } + /** + * Deletes the field bound to the given key. + */ + protected void deleteField(Extensions ext, Object key) { + values.remove(key); + } + /** + * Returns whether or not there is a field bound to the given key. + * This must ignore properties + */ + protected boolean hasField(Extensions ext, Object key) { + return values.containsKey(key); + } + + public final Object getMember(Extensions ext, Object key, Object thisArg) { + key = Values.normalize(ext, key); + + if ("__proto__".equals(key)) { + var res = getPrototype(ext); + return res == null ? Values.NULL : res; + } + + var prop = getProperty(ext, key); + + if (prop != null) { + if (prop.getter == null) return null; + else return prop.getter.call(ext, Values.normalize(ext, thisArg)); + } + else return getField(ext, key); + } + public final boolean setMember(Extensions ext, Object key, Object val, Object thisArg, boolean onlyProps) { + key = Values.normalize(ext, key); val = Values.normalize(ext, val); + + var prop = getProperty(ext, key); + if (prop != null) { + if (prop.setter == null) return false; + prop.setter.call(ext, Values.normalize(ext, thisArg), val); + return true; + } + else if (onlyProps) return false; + else if (!extensible() && !values.containsKey(key)) return false; + else if (key == null) { + values.put(key, val); + return true; + } + else if ("__proto__".equals(key)) return setPrototype(ext, val); + else if (nonWritableSet.contains(key)) return false; + else return setField(ext, key, val); + } + public final boolean hasMember(Extensions ext, Object key, boolean own) { + key = Values.normalize(ext, key); + + if (key != null && "__proto__".equals(key)) return true; + if (hasField(ext, key)) return true; + if (properties.containsKey(key)) return true; + if (own) return false; + var proto = getPrototype(ext); + return proto != null && proto.hasMember(ext, key, own); + } + public final boolean deleteMember(Extensions ext, Object key) { + key = Values.normalize(ext, key); + + if (!memberConfigurable(key)) return false; + properties.remove(key); + nonWritableSet.remove(key); + nonEnumerableSet.remove(key); + deleteField(ext, key); + return true; + } + public final boolean setPrototype(Extensions ext, Object val) { + val = Values.normalize(ext, val); + + if (!extensible()) return false; + if (val == null || val == Values.NULL) { + prototype = null; + return true; + } + else if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + + if (ext != null) { + if (obj == ext.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO; + else if (obj == ext.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO; + else if (obj == ext.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO; + else if (obj == ext.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO; + else if (obj == ext.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO; + else if (obj == ext.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO; + else if (obj == ext.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO; + else prototype = obj; + } + else prototype = obj; + + return true; + } + return false; + } + + public final ObjectValue getMemberDescriptor(Extensions ext, Object key) { + key = Values.normalize(ext, key); + + var prop = properties.get(key); + var res = new ObjectValue(); + + res.defineProperty(ext, "configurable", memberConfigurable(key)); + res.defineProperty(ext, "enumerable", memberEnumerable(key)); + + if (prop != null) { + res.defineProperty(ext, "get", prop.getter); + res.defineProperty(ext, "set", prop.setter); + } + else if (hasField(ext, key)) { + res.defineProperty(ext, "value", values.get(key)); + res.defineProperty(ext, "writable", memberWritable(key)); + } + else return null; + return res; + } + + public List keys(boolean includeNonEnumerable) { + var res = new ArrayList(); + + for (var key : values.keySet()) { + if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; + res.add(key); + } + for (var key : properties.keySet()) { + if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue; + res.add(key); + } + + return res; + } + + public ObjectValue(Extensions ext, Map values) { + this(PlaceholderProto.OBJECT); + for (var el : values.entrySet()) { + defineProperty(ext, el.getKey(), el.getValue()); + } + } + public ObjectValue(PlaceholderProto proto) { + nonConfigurableSet.add("__proto__"); + nonEnumerableSet.add("__proto__"); + setPrototype(proto); + } + public ObjectValue() { + this(PlaceholderProto.OBJECT); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java new file mode 100644 index 0000000..a64f4c2 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/ScopeValue.java @@ -0,0 +1,54 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.HashMap; +import java.util.List; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.scope.ValueVariable; + +public class ScopeValue extends ObjectValue { + public final ValueVariable[] variables; + public final HashMap names = new HashMap<>(); + + @Override + protected Object getField(Extensions ext, Object key) { + if (names.containsKey(key)) return variables[names.get(key)].get(ext); + return super.getField(ext, key); + } + @Override + protected boolean setField(Extensions ext, Object key, Object val) { + if (names.containsKey(key)) { + variables[names.get(key)].set(ext, val); + return true; + } + + var proto = getPrototype(ext); + if (proto != null && proto.hasMember(ext, key, false) && proto.setField(ext, key, val)) return true; + + return super.setField(ext, key, val); + } + @Override + protected void deleteField(Extensions ext, Object key) { + if (names.containsKey(key)) return; + super.deleteField(ext, key); + } + @Override + protected boolean hasField(Extensions ext, Object key) { + if (names.containsKey(key)) return true; + return super.hasField(ext, key); + } + @Override + public List keys(boolean includeNonEnumerable) { + var res = super.keys(includeNonEnumerable); + res.addAll(names.keySet()); + return res; + } + + public ScopeValue(ValueVariable[] variables, String[] names) { + this.variables = variables; + for (var i = 0; i < names.length && i < variables.length; i++) { + this.names.put(names[i], i); + this.nonConfigurableSet.add(names[i]); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java new file mode 100644 index 0000000..efb8f32 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Symbol.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.util.HashMap; + +public final class Symbol { + private static final HashMap registry = new HashMap<>(); + + public final String value; + + public Symbol(String value) { + this.value = value; + } + + @Override + public String toString() { + if (value == null) return "Symbol"; + else return "@@" + value; + } + + public static Symbol get(String name) { + if (registry.containsKey(name)) return registry.get(name); + else { + var res = new Symbol(name); + registry.put(name, res); + return res; + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java deleted file mode 100644 index 358fbf9..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ /dev/null @@ -1,690 +0,0 @@ -package me.topchetoeu.jscript.runtime.values; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; -import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.debug.DebugContext; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; -import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; - -public abstract class Value { - public static enum CompareResult { - NOT_EQUAL, - EQUAL, - LESS, - GREATER; - - public boolean less() { return this == LESS; } - public boolean greater() { return this == GREATER; } - public boolean lessOrEqual() { return this == LESS || this == EQUAL; } - public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } - - public static CompareResult from(int cmp) { - if (cmp < 0) return LESS; - if (cmp > 0) return GREATER; - return EQUAL; - } - } - - public static final Key REGEX_CONSTR = Key.of(); - - public static final Key MAX_STACK_COUNT = Key.of(); - public static final Key HIDE_STACK = Key.of(); - public static final Key OBJECT_PROTO = Key.of(); - public static final Key FUNCTION_PROTO = Key.of(); - public static final Key ARRAY_PROTO = Key.of(); - public static final Key BOOL_PROTO = Key.of(); - public static final Key NUMBER_PROTO = Key.of(); - public static final Key STRING_PROTO = Key.of(); - public static final Key SYMBOL_PROTO = Key.of(); - public static final Key ERROR_PROTO = Key.of(); - public static final Key SYNTAX_ERR_PROTO = Key.of(); - public static final Key TYPE_ERR_PROTO = Key.of(); - public static final Key RANGE_ERR_PROTO = Key.of(); - public static final Key GLOBAL = Key.of(); - public static final Key> INTRINSICS = Key.of(); - - public static final VoidValue UNDEFINED = new VoidValue("undefined", new StringValue("undefined")); - public static final VoidValue NULL = new VoidValue("null", new StringValue("object")); - - public abstract StringValue type(); - public abstract boolean isPrimitive(); - - public final boolean isNaN() { - return this instanceof NumberValue && Double.isNaN(((NumberValue)this).value); - } - - public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) { - if (name == null || name.equals("")) name = "(intermediate value)"; - - if (isNew) throw EngineException.ofType(name + " is not a constructor"); - else throw EngineException.ofType(name + " is not a function"); - } - public final Value callNew(Environment env, String name, Value ...args) { - var res = new ObjectValue(); - var proto = getMember(env, new StringValue("prototype")); - - if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto); - else res.setPrototype(env, null); - - var ret = this.call(env, true, name, res, args); - - if (!ret.isPrimitive()) return ret; - return res; - } - - public final Value call(Environment env, Value self, Value ...args) { - return call(env, false, "", self, args); - } - public final Value callNew(Environment env, Value ...args) { - return callNew(env, "", args); - } - - public abstract Value toPrimitive(Environment env); - public abstract NumberValue toNumber(Environment env); - public abstract StringValue toString(Environment env); - public abstract boolean toBoolean(); - - public final int toInt(Environment env) { return (int)toNumber(env).value; } - public final long toLong(Environment env) { return (long)toNumber(env).value; } - - public final boolean isInstanceOf(Environment env, Value proto) { - for (var val = getPrototype(env); val != null; val = getPrototype(env)) { - if (val.equals(proto)) return true; - } - - return false; - } - - public abstract Member getOwnMember(Environment env, KeyCache key); - public abstract Map getOwnMembers(Environment env); - public abstract Map getOwnSymbolMembers(Environment env); - public abstract boolean defineOwnMember(Environment env, KeyCache key, Member member); - public abstract boolean deleteOwnMember(Environment env, KeyCache key); - - public abstract ObjectValue getPrototype(Environment env); - public abstract boolean setPrototype(Environment env, ObjectValue val); - - public final Member getOwnMember(Environment env, Value key) { - return getOwnMember(env, new KeyCache(key)); - } - public final Member getOwnMember(Environment env, String key) { - return getOwnMember(env, new KeyCache(key)); - } - public final Member getOwnMember(Environment env, int key) { - return getOwnMember(env, new KeyCache(key)); - } - public final Member getOwnMember(Environment env, double key) { - return getOwnMember(env, new KeyCache(key)); - } - - public final boolean defineOwnMember(Environment env, Value key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); - } - public final boolean defineOwnMember(Environment env, String key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); - } - public final boolean defineOwnMember(Environment env, int key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); - } - public final boolean defineOwnMember(Environment env, double key, Member member) { - return defineOwnMember(env, new KeyCache(key), member); - } - - public final boolean defineOwnMember(Environment env, KeyCache key, Value val) { - return defineOwnMember(env, key, FieldMember.of(val)); - } - public final boolean defineOwnMember(Environment env, Value key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); - } - public final boolean defineOwnMember(Environment env, String key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); - } - public final boolean defineOwnMember(Environment env, int key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); - } - public final boolean defineOwnMember(Environment env, double key, Value val) { - return defineOwnMember(env, new KeyCache(key), FieldMember.of(val)); - } - - public final boolean deleteOwnMember(Environment env, Value key) { - return deleteOwnMember(env, new KeyCache(key)); - } - public final boolean deleteOwnMember(Environment env, String key) { - return deleteOwnMember(env, new KeyCache(key)); - } - public final boolean deleteOwnMember(Environment env, int key) { - return deleteOwnMember(env, new KeyCache(key)); - } - public final boolean deleteOwnMember(Environment env, double key) { - return deleteOwnMember(env, new KeyCache(key)); - } - - public final Value getMemberOrNull(Environment env, KeyCache key) { - for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { - var member = obj.getOwnMember(env, key); - if (member != null) return member.get(env, obj); - } - - return null; - } - public final Value getMemberOrNull(Environment env, Value key) { - return getMemberOrNull(env, new KeyCache(key)); - } - public final Value getMemberOrNull(Environment env, String key) { - return getMemberOrNull(env, new KeyCache(key)); - } - public final Value getMemberOrNull(Environment env, int key) { - return getMemberOrNull(env, new KeyCache(key)); - } - public final Value getMemberOrNull(Environment env, double key) { - return getMemberOrNull(env, new KeyCache(key)); - } - - public final Value getMember(Environment env, KeyCache key) { - var res = getMemberOrNull(env, key); - if (res != null) return res; - else return Value.UNDEFINED; - } - public final Value getMember(Environment env, Value key) { - return getMember(env, new KeyCache(key)); - } - public final Value getMember(Environment env, String key) { - return getMember(env, new KeyCache(key)); - } - public final Value getMember(Environment env, int key) { - return getMember(env, new KeyCache(key)); - } - public final Value getMember(Environment env, double key) { - return getMember(env, new KeyCache(key)); - } - - public final boolean setMember(Environment env, KeyCache key, Value val) { - for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { - var member = obj.getOwnMember(env, key); - if (member != null) { - if (member.set(env, val, obj)) { - if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); - return true; - } - else return false; - } - } - - if (defineOwnMember(env, key, FieldMember.of(val))) { - if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); - return true; - } - else return false; - } - public final boolean setMember(Environment env, Value key, Value val) { - return setMember(env, new KeyCache(key), val); - } - public final boolean setMember(Environment env, String key, Value val) { - return setMember(env, new KeyCache(key), val); - } - public final boolean setMember(Environment env, int key, Value val) { - return setMember(env, new KeyCache(key), val); - } - public final boolean setMember(Environment env, double key, Value val) { - return setMember(env, new KeyCache(key), val); - } - - public final boolean setMemberIfExists(Environment env, KeyCache key, Value val) { - for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { - var member = obj.getOwnMember(env, key); - if (member != null) { - if (member.set(env, val, obj)) { - if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); - return true; - } - else return false; - } - } - - return false; - } - public final boolean setMemberIfExists(Environment env, Value key, Value val) { - return setMemberIfExists(env, new KeyCache(key), val); - } - public final boolean setMemberIfExists(Environment env, String key, Value val) { - return setMemberIfExists(env, new KeyCache(key), val); - } - public final boolean setMemberIfExists(Environment env, int key, Value val) { - return setMemberIfExists(env, new KeyCache(key), val); - } - public final boolean setMemberIfExists(Environment env, double key, Value val) { - return setMemberIfExists(env, new KeyCache(key), val); - } - - public final boolean hasMember(Environment env, KeyCache key, boolean own) { - for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { - if (obj.getOwnMember(env, key) != null) return true; - if (own) return false; - } - - return false; - } - public final boolean hasMember(Environment env, Value key, boolean own) { - return hasMember(env, new KeyCache(key), own); - } - public final boolean hasMember(Environment env, String key, boolean own) { - return hasMember(env, new KeyCache(key), own); - } - public final boolean hasMember(Environment env, int key, boolean own) { - return hasMember(env, new KeyCache(key), own); - } - public final boolean hasMember(Environment env, double key, boolean own) { - return hasMember(env, new KeyCache(key), own); - } - - public final boolean deleteMember(Environment env, KeyCache key) { - if (!hasMember(env, key, true)) return true; - return deleteOwnMember(env, key); - } - public final boolean deleteMember(Environment env, Value key) { - return deleteMember(env, new KeyCache(key)); - } - public final boolean deleteMember(Environment env, String key) { - return deleteMember(env, new KeyCache(key)); - } - public final boolean deleteMember(Environment env, int key) { - return deleteMember(env, new KeyCache(key)); - } - public final boolean deleteMember(Environment env, double key) { - return deleteMember(env, new KeyCache(key)); - } - - public final Map getMembers(Environment env, boolean own, boolean onlyEnumerable) { - var res = new LinkedHashMap(); - var protos = new ArrayList(); - - for (var proto = this; proto != null; proto = proto.getPrototype(env)) { - protos.add(proto); - if (own) break; - } - - Collections.reverse(protos); - - for (var proto : protos) { - if (onlyEnumerable) { - for (var el : proto.getOwnMembers(env).entrySet()) { - if (!el.getValue().enumerable()) continue; - res.put(el.getKey(), el.getValue()); - } - } - else res.putAll(proto.getOwnMembers(env)); - } - - return res; - } - public final Map getSymbolMembers(Environment env, boolean own, boolean onlyEnumerable) { - var res = new LinkedHashMap(); - var protos = new ArrayList(); - - for (var proto = this; proto != null; proto = proto.getPrototype(env)) { - protos.add(proto); - if (own) break; - } - - Collections.reverse(protos); - - for (var proto : protos) { - if (onlyEnumerable) { - for (var el : proto.getOwnSymbolMembers(env).entrySet()) { - if (!el.getValue().enumerable()) continue; - res.put(el.getKey(), el.getValue()); - } - } - else res.putAll(proto.getOwnSymbolMembers(env)); - } - - return res; - } - - public final Value getMemberPath(Environment env, Value ...path) { - var res = this; - for (var key : path) res = res.getMember(env, key); - return res; - } - public final ObjectValue getMemberDescriptor(Environment env, Value key) { - var member = getOwnMember(env, new KeyCache(key)); - - if (member != null) return member.descriptor(env, this); - else return null; - } - - public Iterable toIterable(Environment env) { - return () -> { - if (!(this instanceof FunctionValue)) return Collections.emptyIterator(); - var func = (FunctionValue)this; - - return new Iterator() { - private Object value = null; - public boolean consumed = true; - private FunctionValue supplier = func; - - private void loadNext() { - if (supplier == null) value = null; - else if (consumed) { - var curr = supplier.call(env, Value.UNDEFINED); - - if (curr == null) { supplier = null; value = null; } - if (curr.getMember(env, new StringValue("done")).toBoolean()) { supplier = null; value = null; } - else { - this.value = curr.getMember(env, new StringValue("value")); - consumed = false; - } - } - } - - @Override public boolean hasNext() { - loadNext(); - return supplier != null; - } - @Override public Object next() { - loadNext(); - var res = value; - value = null; - consumed = true; - return res; - } - }; - }; - } - - public void callWith(Environment env, Iterable it) { - for (var el : it) { - this.call(env, Value.UNDEFINED, el); - } - } - public void callWithAsync(Environment env, Iterable it, boolean async) { - for (var el : it) { - env.get(EventLoop.KEY).pushMsg(() -> this.call(env, Value.UNDEFINED, el), true); - } - } - - private final boolean isEmptyFunc(Environment env, ObjectValue val) { - if (!(val instanceof FunctionValue)) return false; - if (val.members.size() + val.symbolMembers.size() > 1) return false; - - var proto = ((FunctionValue)val).prototype; - if (!(proto instanceof ObjectValue)) return false; - var protoObj = (ObjectValue)proto; - - if (protoObj.getMember(env, new StringValue("constructor")) != val) return false; - if (protoObj.getOwnMembers(env).size() + protoObj.getOwnSymbolMembers(env).size() != 1) return false; - - return true; - } - private final String toReadable(Environment env, HashSet passed, int tab) { - if (passed.contains(this)) return "[circular]"; - - if (this instanceof ObjectValue) { - var res = new StringBuilder(); - var dbg = DebugContext.get(env); - var printed = true; - - if (this instanceof FunctionValue) { - res.append(this.toString()); - var loc = this instanceof CodeFunction ? dbg.getMapOrEmpty((CodeFunction)this).start() : null; - - if (loc != null) res.append(" @ " + loc); - } - else if (this instanceof ArrayValue) { - res.append("["); - var arr = (ArrayValue)this; - - for (int i = 0; i < arr.size(); i++) { - if (i != 0) res.append(", "); - else res.append(" "); - if (arr.has(i)) res.append(arr.get(i).toReadable(env, passed, tab)); - else res.append(""); - } - - res.append(" ] "); - } - else printed = false; - - if (tab > 3) return "{...}"; - - passed.add(this); - - var obj = (ObjectValue)this; - if (obj.getOwnSymbolMembers(env).size() + obj.getOwnMembers(env).size() == 0 || isEmptyFunc(env, obj)) { - if (!printed) res.append("{}\n"); - } - else { - res.append("{\n"); - - for (var entry : obj.getOwnSymbolMembers(env).entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append("[" + entry.getKey().value + "]" + ": "); - - var member = entry.getValue(); - if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); - else res.append("[property]"); - - res.append(",\n"); - } - for (var entry : obj.getOwnMembers(env).entrySet()) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(entry.getKey() + ": "); - - var member = entry.getValue(); - if (member instanceof FieldMember) res.append(((FieldMember)member).get(env, obj).toReadable(env, passed, tab + 1)); - else res.append("[property]"); - - res.append(",\n"); - } - - for (int i = 0; i < tab; i++) res.append(" "); - res.append("}"); - } - - passed.remove(this); - return res.toString(); - } - else if (this instanceof VoidValue) return ((VoidValue)this).name; - else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value)); - else if (this instanceof SymbolValue) return this.toString(); - else return this.toString(env).value; - } - - public final String toReadable(Environment ext) { - return toReadable(ext, new HashSet<>(), 0); - } - - public static final ObjectValue global(Environment env) { - return env.initFrom(GLOBAL, () -> new ObjectValue()); - } - public static final Map intrinsics(Environment env) { - return env.initFrom(INTRINSICS, () -> new HashMap<>()); - } - - public static FunctionValue fromIterator(Environment ext, Iterable iterable) { - var it = iterable.iterator(); - - return new NativeFunction("", args -> { - var obj = new ObjectValue(); - - if (!it.hasNext()) obj.defineOwnMember(args.env, "done", FieldMember.of(BoolValue.TRUE)); - else obj.defineOwnMember(args.env, "value", FieldMember.of(it.next())); - - return obj; - }); - } - - public static final boolean lessOrEqual(Environment env, Value a, Value b) { - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) <= 0; - } - else { - return a.toNumber(env).value <= b.toNumber(env).value; - } - } - public static final boolean greaterOrEqual(Environment env, Value a, Value b) { - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) >= 0; - } - else { - return a.toNumber(env).value >= b.toNumber(env).value; - } - } - public static final boolean less(Environment env, Value a, Value b) { - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) >= 0; - } - else { - return a.toNumber(env).value < b.toNumber(env).value; - } - } - public static final boolean greater(Environment env, Value a, Value b) { - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - if (a instanceof StringValue aStr && b instanceof StringValue bStr) { - return aStr.value.compareTo(bStr.value) >= 0; - } - else { - return a.toNumber(env).value > b.toNumber(env).value; - } - } - - public static final Value add(Environment env, Value a, Value b) { - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - if (a instanceof StringValue || b instanceof StringValue) { - return new StringValue(a.toString(env).value + b.toString(env).value); - } - else { - return new NumberValue(a.toNumber(env).value + b.toNumber(env).value); - } - } - - public static final NumberValue subtract(Environment env, Value a, Value b) { - return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); - } - public static final NumberValue multiply(Environment env, Value a, Value b) { - return new NumberValue(a.toNumber(env).value - b.toNumber(env).value); - } - public static final NumberValue divide(Environment env, Value a, Value b) { - return new NumberValue(a.toNumber(env).value / b.toNumber(env).value); - } - public static final NumberValue modulo(Environment env, Value a, Value b) { - return new NumberValue(a.toNumber(env).value % b.toNumber(env).value); - } - public static final NumberValue negative(Environment env, Value a) { - return new NumberValue(-a.toNumber(env).value); - } - - public static final NumberValue and(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) & b.toInt(env)); - } - public static final NumberValue or(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) | b.toInt(env)); - } - public static final NumberValue xor(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) ^ b.toInt(env)); - } - public static final NumberValue bitwiseNot(Environment env, Value a) { - return new NumberValue(~a.toInt(env)); - } - - public static final NumberValue shiftLeft(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) << b.toInt(env)); - } - public static final NumberValue shiftRight(Environment env, Value a, Value b) { - return new NumberValue(a.toInt(env) >> b.toInt(env)); - } - public static final NumberValue unsignedShiftRight(Environment env, Value a, Value b) { - long _a = a.toInt(env); - long _b = b.toInt(env); - - if (_a < 0) _a += 0x100000000l; - if (_b < 0) _b += 0x100000000l; - - return new NumberValue(_a >>> _b); - } - - public static final boolean looseEqual(Environment env, Value a, Value b) { - // In loose equality, null is equivalent to undefined - if (a instanceof VoidValue || b instanceof VoidValue) return a instanceof VoidValue && b instanceof VoidValue; - - // If both are objects, just compare their references - if (!a.isPrimitive() && !b.isPrimitive()) return a.equals(b); - - // Convert values to primitives - a = a.toPrimitive(env); - b = b.toPrimitive(env); - - // Compare symbols by reference - if (a instanceof SymbolValue || b instanceof SymbolValue) return a.equals(b); - // Compare booleans as numbers - if (a instanceof BoolValue || b instanceof BoolValue) return a.toNumber(env).equals(b.toNumber(env)); - // Comparse numbers as numbers - if (a instanceof NumberValue || b instanceof NumberValue) return a.toNumber(env).equals(b.toNumber(env)); - - // Default to strings - return a.toString(env).equals(b.toString(env)); - } - - // public static Value operation(Environment env, Operation op, Value ...args) { - // } - - public static final String errorToReadable(RuntimeException err, String prefix) { - prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; - if (err instanceof EngineException) { - var ee = ((EngineException)err); - try { - return prefix + " " + ee.toString(ee.env); - } - catch (EngineException ex) { - return prefix + " " + ee.value.toReadable(ee.env); - } - } - else if (err instanceof SyntaxException) { - return prefix + " SyntaxError " + ((SyntaxException)err).msg; - } - else if (err.getCause() instanceof InterruptedException) return ""; - else { - var str = new ByteArrayOutputStream(); - err.printStackTrace(new PrintStream(str)); - - return prefix + " internal error " + str.toString(); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java new file mode 100644 index 0000000..db93cd8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Values.java @@ -0,0 +1,761 @@ +package me.topchetoeu.jscript.runtime.values; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.lib.PromiseLib; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.ConvertException; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; + +public class Values { + public static enum CompareResult { + NOT_EQUAL, + EQUAL, + LESS, + GREATER; + + public boolean less() { return this == LESS; } + public boolean greater() { return this == GREATER; } + public boolean lessOrEqual() { return this == LESS || this == EQUAL; } + public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } + + public static CompareResult from(int cmp) { + if (cmp < 0) return LESS; + if (cmp > 0) return GREATER; + return EQUAL; + } + } + + public static final Object NULL = new Object(); + public static final Object NO_RETURN = new Object(); + + public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; } + public static boolean isWrapper(Object val, Class clazz) { + if (!isWrapper(val)) return false; + var res = (NativeWrapper)val; + return res != null && clazz.isInstance(res.wrapped); + } + public static boolean isNan(Object val) { return val instanceof Number && Double.isNaN(number(val)); } + + public static double number(Object val) { + if (val instanceof Number) return ((Number)val).doubleValue(); + else return Double.NaN; + } + + @SuppressWarnings("unchecked") + public static T wrapper(Object val, Class clazz) { + if (isWrapper(val)) val = ((NativeWrapper)val).wrapped; + if (val != null && clazz.isInstance(val)) return (T)val; + else return null; + } + + public static String type(Object val) { + if (val == null) return "undefined"; + if (val instanceof String) return "string"; + if (val instanceof Number) return "number"; + if (val instanceof Boolean) return "boolean"; + if (val instanceof Symbol) return "symbol"; + if (val instanceof FunctionValue) return "function"; + return "object"; + } + + private static Object tryCallConvertFunc(Extensions ext, Object obj, String name) { + var func = getMember(ext, obj, name); + + if (func instanceof FunctionValue) { + var res = Values.call(ext, func, obj); + if (isPrimitive(res)) return res; + } + + throw EngineException.ofType("Value couldn't be converted to a primitive."); + } + + public static boolean isPrimitive(Object obj) { + return + obj instanceof Number || + obj instanceof String || + obj instanceof Boolean || + obj instanceof Symbol || + obj == null || + obj == NULL; + } + + public static Object toPrimitive(Extensions ext, Object obj, ConvertHint hint) { + obj = normalize(ext, obj); + if (isPrimitive(obj)) return obj; + + var first = hint == ConvertHint.VALUEOF ? "valueOf" : "toString"; + var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; + + if (ext != null) { + try { return tryCallConvertFunc(ext, obj, first); } + catch (EngineException unused) { return tryCallConvertFunc(ext, obj, second); } + } + + throw EngineException.ofType("Value couldn't be converted to a primitive."); + } + public static boolean toBoolean(Object obj) { + if (obj == NULL || obj == null) return false; + if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false; + if (obj instanceof String && ((String)obj).equals("")) return false; + if (obj instanceof Boolean) return (Boolean)obj; + return true; + } + public static double toNumber(Extensions ext, Object obj) { + var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); + + if (val instanceof Number) return number(val); + if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; + if (val instanceof String) { + try { return Double.parseDouble((String)val); } + catch (NumberFormatException e) { return Double.NaN; } + } + return Double.NaN; + } + public static String toString(Extensions ext, Object obj) { + var val = toPrimitive(ext, obj, ConvertHint.VALUEOF); + + if (val == null) return "undefined"; + if (val == NULL) return "null"; + + if (val instanceof Number) { + var d = number(val); + if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; + if (d == Double.POSITIVE_INFINITY) return "Infinity"; + if (Double.isNaN(d)) return "NaN"; + return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); + } + if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; + if (val instanceof String) return (String)val; + if (val instanceof Symbol) return val.toString(); + + return "Unknown value"; + } + + public static Object add(Extensions ext, Object a, Object b) { + if (a instanceof String || b instanceof String) return toString(ext, a) + toString(ext, b); + else return toNumber(ext, a) + toNumber(ext, b); + } + public static double subtract(Extensions ext, Object a, Object b) { + return toNumber(ext, a) - toNumber(ext, b); + } + public static double multiply(Extensions ext, Object a, Object b) { + return toNumber(ext, a) * toNumber(ext, b); + } + public static double divide(Extensions ext, Object a, Object b) { + return toNumber(ext, a) / toNumber(ext, b); + } + public static double modulo(Extensions ext, Object a, Object b) { + return toNumber(ext, a) % toNumber(ext, b); + } + + public static double negative(Extensions ext, Object obj) { + return -toNumber(ext, obj); + } + + public static int and(Extensions ext, Object a, Object b) { + return (int)toNumber(ext, a) & (int)toNumber(ext, b); + } + public static int or(Extensions ext, Object a, Object b) { + return (int)toNumber(ext, a) | (int)toNumber(ext, b); + } + public static int xor(Extensions ext, Object a, Object b) { + return (int)toNumber(ext, a) ^ (int)toNumber(ext, b); + } + public static int bitwiseNot(Extensions ext, Object obj) { + return ~(int)toNumber(ext, obj); + } + + public static int shiftLeft(Extensions ext, Object a, Object b) { + return (int)toNumber(ext, a) << (int)toNumber(ext, b); + } + public static int shiftRight(Extensions ext, Object a, Object b) { + return (int)toNumber(ext, a) >> (int)toNumber(ext, b); + } + public static long unsignedShiftRight(Extensions ext, Object a, Object b) { + long _a = (long)toNumber(ext, a); + long _b = (long)toNumber(ext, b); + + if (_a < 0) _a += 0x100000000l; + if (_b < 0) _b += 0x100000000l; + return _a >>> _b; + } + + public static CompareResult compare(Extensions ext, Object a, Object b) { + a = toPrimitive(ext, a, ConvertHint.VALUEOF); + b = toPrimitive(ext, b, ConvertHint.VALUEOF); + + if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b)); + + var _a = toNumber(ext, a); + var _b = toNumber(ext, b); + + if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL; + + return CompareResult.from(Double.compare(_a, _b)); + } + + public static boolean not(Object obj) { + return !toBoolean(obj); + } + + public static boolean isInstanceOf(Extensions ext, Object obj, Object proto) { + if (obj == null || obj == NULL || proto == null || proto == NULL) return false; + var val = getPrototype(ext, obj); + + while (val != null) { + if (val.equals(proto)) return true; + val = val.getPrototype(ext); + } + + return false; + } + + public static Object operation(Extensions ext, Operation op, Object ...args) { + switch (op) { + case ADD: return add(ext, args[0], args[1]); + case SUBTRACT: return subtract(ext, args[0], args[1]); + case DIVIDE: return divide(ext, args[0], args[1]); + case MULTIPLY: return multiply(ext, args[0], args[1]); + case MODULO: return modulo(ext, args[0], args[1]); + + case AND: return and(ext, args[0], args[1]); + case OR: return or(ext, args[0], args[1]); + case XOR: return xor(ext, args[0], args[1]); + + case EQUALS: return strictEquals(ext, args[0], args[1]); + case NOT_EQUALS: return !strictEquals(ext, args[0], args[1]); + case LOOSE_EQUALS: return looseEqual(ext, args[0], args[1]); + case LOOSE_NOT_EQUALS: return !looseEqual(ext, args[0], args[1]); + + case GREATER: return compare(ext, args[0], args[1]).greater(); + case GREATER_EQUALS: return compare(ext, args[0], args[1]).greaterOrEqual(); + case LESS: return compare(ext, args[0], args[1]).less(); + case LESS_EQUALS: return compare(ext, args[0], args[1]).lessOrEqual(); + + case INVERSE: return bitwiseNot(ext, args[0]); + case NOT: return not(args[0]); + case POS: return toNumber(ext, args[0]); + case NEG: return negative(ext, args[0]); + + case SHIFT_LEFT: return shiftLeft(ext, args[0], args[1]); + case SHIFT_RIGHT: return shiftRight(ext, args[0], args[1]); + case USHIFT_RIGHT: return unsignedShiftRight(ext, args[0], args[1]); + + case IN: return hasMember(ext, args[1], args[0], false); + case INSTANCEOF: { + var proto = getMember(ext, args[1], "prototype"); + return isInstanceOf(ext, args[0], proto); + } + + default: return null; + } + } + + public static Object getMember(Extensions ctx, Object obj, Object key) { + obj = normalize(ctx, obj); key = normalize(ctx, key); + if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); + if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMember(ctx, key, obj); + + if (obj instanceof String && key instanceof Number) { + var i = number(key); + var s = (String)obj; + if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) { + return s.charAt((int)i) + ""; + } + } + + var proto = getPrototype(ctx, obj); + + if (proto == null) return "__proto__".equals(key) ? NULL : null; + else if (key != null && "__proto__".equals(key)) return proto; + else return proto.getMember(ctx, key, obj); + } + public static Object getMemberPath(Extensions ctx, Object obj, Object ...path) { + var res = obj; + for (var key : path) res = getMember(ctx, res, key); + return res; + } + public static boolean setMember(Extensions ctx, Object obj, Object key, Object val) { + obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); + if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); + if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); + if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).setMember(ctx, key, val, obj, false); + + var proto = getPrototype(ctx, obj); + return proto.setMember(ctx, key, val, obj, true); + } + public static boolean hasMember(Extensions ctx, Object obj, Object key, boolean own) { + if (obj == null || obj == NULL) return false; + obj = normalize(ctx, obj); key = normalize(ctx, key); + + if ("__proto__".equals(key)) return true; + if (obj instanceof ObjectValue) return ((ObjectValue)obj).hasMember(ctx, key, own); + + if (obj instanceof String && key instanceof Number) { + var i = number(key); + var s = (String)obj; + if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) return true; + } + + if (own) return false; + + var proto = getPrototype(ctx, obj); + return proto != null && proto.hasMember(ctx, key, own); + } + public static boolean deleteMember(Extensions ext, Object obj, Object key) { + if (obj == null || obj == NULL) return false; + obj = normalize(ext, obj); key = normalize(ext, key); + + if (obj instanceof ObjectValue) return ((ObjectValue)obj).deleteMember(ext, key); + else return false; + } + public static ObjectValue getPrototype(Extensions ext, Object obj) { + if (obj == null || obj == NULL) return null; + obj = normalize(ext, obj); + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getPrototype(ext); + if (ext == null) return null; + + if (obj instanceof String) return ext.get(Environment.STRING_PROTO); + else if (obj instanceof Number) return ext.get(Environment.NUMBER_PROTO); + else if (obj instanceof Boolean) return ext.get(Environment.BOOL_PROTO); + else if (obj instanceof Symbol) return ext.get(Environment.SYMBOL_PROTO); + + return null; + } + public static boolean setPrototype(Extensions ext, Object obj, Object proto) { + obj = normalize(ext, obj); + return obj instanceof ObjectValue && ((ObjectValue)obj).setPrototype(ext, proto); + } + public static void makePrototypeChain(Extensions ext, Object... chain) { + for(var i = 1; i < chain.length; i++) { + setPrototype(ext, chain[i], chain[i - 1]); + } + } + public static List getMembers(Extensions ext, Object obj, boolean own, boolean includeNonEnumerable) { + List res = new ArrayList<>(); + + if (obj instanceof ObjectValue) res = ((ObjectValue)obj).keys(includeNonEnumerable); + if (obj instanceof String) { + for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i); + } + + if (!own) { + var proto = getPrototype(ext, obj); + + while (proto != null) { + res.addAll(proto.keys(includeNonEnumerable)); + proto = getPrototype(ext, proto); + } + } + + + return res; + } + public static ObjectValue getMemberDescriptor(Extensions ext, Object obj, Object key) { + if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ext, key); + else if (obj instanceof String && key instanceof Number) { + var i = ((Number)key).intValue(); + var _i = ((Number)key).doubleValue(); + if (i - _i != 0) return null; + if (i < 0 || i >= ((String)obj).length()) return null; + + return new ObjectValue(ext, Map.of( + "value", ((String)obj).charAt(i) + "", + "writable", false, + "enumerable", true, + "configurable", false + )); + } + else return null; + } + + public static Object call(Extensions ext, Object func, Object thisArg, Object ...args) { + if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value."); + return ((FunctionValue)func).call(ext, thisArg, args); + } + public static Object callNew(Extensions ext, Object func, Object ...args) { + var res = new ObjectValue(); + try { + var proto = Values.getMember(ext, func, "prototype"); + setPrototype(ext, res, proto); + + var ret = call(ext, func, res, args); + + if (!isPrimitive(ret)) return ret; + return res; + } + catch (IllegalArgumentException e) { + throw EngineException.ofType("Tried to call new on an invalid constructor."); + } + } + + public static boolean strictEquals(Extensions ext, Object a, Object b) { + a = normalize(ext, a); + b = normalize(ext, b); + + if (a == null || b == null) return a == null && b == null; + if (isNan(a) || isNan(b)) return false; + if (a instanceof Number && number(a) == -0.) a = 0.; + if (b instanceof Number && number(b) == -0.) b = 0.; + + return a == b || a.equals(b); + } + public static boolean looseEqual(Extensions ext, Object a, Object b) { + a = normalize(ext, a); b = normalize(ext, b); + + // In loose equality, null is equivalent to undefined + if (a == NULL) a = null; + if (b == NULL) b = null; + + if (a == null || b == null) return a == null && b == null; + // If both are objects, just compare their references + if (!isPrimitive(a) && !isPrimitive(b)) return a == b; + + // Convert values to primitives + a = toPrimitive(ext, a, ConvertHint.VALUEOF); + b = toPrimitive(ext, b, ConvertHint.VALUEOF); + + // Compare symbols by reference + if (a instanceof Symbol || b instanceof Symbol) return a == b; + if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b); + if (a instanceof Number || b instanceof Number) return strictEquals(ext, toNumber(ext, a), toNumber(ext, b)); + + // Default to strings + return toString(ext, a).equals(toString(ext, b)); + } + + public static Object normalize(Extensions ext, Object val) { + if (val instanceof Number) return number(val); + if (isPrimitive(val) || val instanceof ObjectValue) return val; + if (val instanceof Character) return val + ""; + + if (val instanceof Map) { + var res = new ObjectValue(); + + for (var entry : ((Map)val).entrySet()) { + res.defineProperty(ext, entry.getKey(), entry.getValue()); + } + + return res; + } + + if (val instanceof Iterable) { + var res = new ArrayValue(); + + for (var entry : ((Iterable)val)) { + res.set(ext, res.size(), entry); + } + + return res; + } + + if (val instanceof Class) { + if (ext == null) return null; + else return NativeWrapperProvider.get(ext).getConstr((Class)val); + } + + return NativeWrapper.of(ext, val); + } + + @SuppressWarnings("unchecked") + public static T convert(Extensions ext, Object obj, Class clazz) { + if (clazz == Void.class) return null; + + if (obj instanceof NativeWrapper) { + var res = ((NativeWrapper)obj).wrapped; + if (clazz.isInstance(res)) return (T)res; + } + + if (clazz == null || clazz == Object.class) return (T)obj; + + if (obj instanceof ArrayValue) { + if (clazz.isAssignableFrom(ArrayList.class)) { + var raw = ((ArrayValue)obj).toArray(); + var res = new ArrayList<>(); + for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); + return (T)new ArrayList<>(res); + } + if (clazz.isAssignableFrom(HashSet.class)) { + var raw = ((ArrayValue)obj).toArray(); + var res = new HashSet<>(); + for (var i = 0; i < raw.length; i++) res.add(convert(ext, raw[i], Object.class)); + return (T)new HashSet<>(res); + } + if (clazz.isArray()) { + var raw = ((ArrayValue)obj).toArray(); + Object res = Array.newInstance(clazz.getComponentType(), raw.length); + for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ext, raw[i], Object.class)); + return (T)res; + } + } + + if (obj instanceof ObjectValue && clazz.isAssignableFrom(HashMap.class)) { + var res = new HashMap<>(); + for (var el : ((ObjectValue)obj).values.entrySet()) res.put( + convert(ext, el.getKey(), null), + convert(ext, el.getValue(), null) + ); + return (T)res; + } + + if (clazz == String.class) return (T)toString(ext, obj); + if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj); + if (clazz == Byte.class || clazz == byte.class) return (T)(Byte)(byte)toNumber(ext, obj); + if (clazz == Integer.class || clazz == int.class) return (T)(Integer)(int)toNumber(ext, obj); + if (clazz == Long.class || clazz == long.class) return (T)(Long)(long)toNumber(ext, obj); + if (clazz == Short.class || clazz == short.class) return (T)(Short)(short)toNumber(ext, obj); + if (clazz == Float.class || clazz == float.class) return (T)(Float)(float)toNumber(ext, obj); + if (clazz == Double.class || clazz == double.class) return (T)(Double)toNumber(ext, obj); + + if (clazz == Character.class || clazz == char.class) { + if (obj instanceof Number) return (T)(Character)(char)number(obj); + else { + var res = toString(ext, obj); + if (res.length() == 0) throw new ConvertException("\"\"", "Character"); + else return (T)(Character)res.charAt(0); + } + } + + if (obj == null) return null; + if (clazz.isInstance(obj)) return (T)obj; + if (clazz.isAssignableFrom(NativeWrapper.class)) { + return (T)NativeWrapper.of(ext, obj); + } + + throw new ConvertException(type(obj), clazz.getSimpleName()); + } + + public static Iterable fromJSIterator(Extensions ext, Object obj) { + return () -> { + try { + var symbol = Symbol.get("Symbol.iterator"); + + var iteratorFunc = getMember(ext, obj, symbol); + if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator(); + var iterator = iteratorFunc instanceof FunctionValue ? + ((FunctionValue)iteratorFunc).call(ext, obj, obj) : + iteratorFunc; + var nextFunc = getMember(ext, call(ext, iteratorFunc, obj), "next"); + + if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator(); + + return new Iterator() { + private Object value = null; + public boolean consumed = true; + private FunctionValue next = (FunctionValue)nextFunc; + + private void loadNext() { + if (next == null) value = null; + else if (consumed) { + var curr = next.call(ext, iterator); + if (curr == null) { next = null; value = null; } + if (toBoolean(Values.getMember(ext, curr, "done"))) { next = null; value = null; } + else { + this.value = Values.getMember(ext, curr, "value"); + consumed = false; + } + } + } + + @Override + public boolean hasNext() { + loadNext(); + return next != null; + } + @Override + public Object next() { + loadNext(); + var res = value; + value = null; + consumed = true; + return res; + } + }; + } + catch (IllegalArgumentException | NullPointerException e) { + return Collections.emptyIterator(); + } + }; + } + + public static ObjectValue toJSIterator(Extensions ext, Iterator it) { + var res = new ObjectValue(); + + try { + var key = getMember(ext, getMember(ext, ext.get(Environment.SYMBOL_PROTO), "constructor"), "iterator"); + res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); + } + catch (IllegalArgumentException | NullPointerException e) { } + + res.defineProperty(ext, "next", new NativeFunction("", args -> { + if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); + else { + var obj = new ObjectValue(); + obj.defineProperty(args.ctx, "value", it.next()); + return obj; + } + })); + + return res; + } + + public static ObjectValue toJSIterator(Extensions ext, Iterable it) { + return toJSIterator(ext, it.iterator()); + } + + public static ObjectValue toJSAsyncIterator(Extensions ext, Iterator it) { + var res = new ObjectValue(); + + try { + var key = getMemberPath(ext, ext.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator"); + res.defineProperty(ext, key, new NativeFunction("", args -> args.self)); + } + catch (IllegalArgumentException | NullPointerException e) { } + + res.defineProperty(ext, "next", new NativeFunction("", args -> { + return PromiseLib.await(args.ctx, () -> { + if (!it.hasNext()) return new ObjectValue(ext, Map.of("done", true)); + else { + var obj = new ObjectValue(); + obj.defineProperty(args.ctx, "value", it.next()); + return obj; + } + }); + })); + + return res; + } + + private static boolean isEmptyFunc(ObjectValue val) { + if (!(val instanceof FunctionValue)) return false; + if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false; + var proto = val.values.get("prototype"); + if (!(proto instanceof ObjectValue)) return false; + var protoObj = (ObjectValue)proto; + if (protoObj.values.get("constructor") != val) return false; + if (protoObj.values.size() + protoObj.properties.size() != 1) return false; + return true; + } + private static String toReadable(Extensions ext, Object val, HashSet passed, int tab) { + if (tab == 0 && val instanceof String) return (String)val; + + if (passed.contains(val)) return "[circular]"; + + var printed = true; + var res = new StringBuilder(); + var dbg = DebugContext.get(ext); + + if (val instanceof FunctionValue) { + res.append(val.toString()); + var loc = val instanceof CodeFunction ? dbg.getMapOrEmpty((CodeFunction)val).start() : null; + + if (loc != null) res.append(" @ " + loc); + } + else if (val instanceof ArrayValue) { + res.append("["); + var obj = ((ArrayValue)val); + for (int i = 0; i < obj.size(); i++) { + if (i != 0) res.append(", "); + else res.append(" "); + if (obj.has(i)) res.append(toReadable(ext, obj.get(i), passed, tab)); + else res.append(""); + } + res.append(" ] "); + } + else if (val instanceof NativeWrapper) { + var obj = ((NativeWrapper)val).wrapped; + res.append("Native " + obj.toString() + " "); + } + else printed = false; + + if (val instanceof ObjectValue) { + if (tab > 3) { + return "{...}"; + } + + passed.add(val); + + var obj = (ObjectValue)val; + if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) { + if (!printed) res.append("{}\n"); + } + else { + res.append("{\n"); + + for (var el : obj.values.entrySet()) { + for (int i = 0; i < tab + 1; i++) res.append(" "); + res.append(toReadable(ext, el.getKey(), passed, tab + 1)); + res.append(": "); + res.append(toReadable(ext, el.getValue(), passed, tab + 1)); + res.append(",\n"); + } + for (var el : obj.properties.entrySet()) { + for (int i = 0; i < tab + 1; i++) res.append(" "); + res.append(toReadable(ext, el.getKey(), passed, tab + 1)); + res.append(": [prop],\n"); + } + + for (int i = 0; i < tab; i++) res.append(" "); + res.append("}"); + } + + passed.remove(val); + } + else if (val == null) return "undefined"; + else if (val == Values.NULL) return "null"; + else if (val instanceof String) return "'" + val + "'"; + else return Values.toString(ext, val); + + return res.toString(); + } + + public static String toReadable(Extensions ext, Object val) { + return toReadable(ext, val, new HashSet<>(), 0); + } + public static String errorToReadable(RuntimeException err, String prefix) { + prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; + if (err instanceof EngineException) { + var ee = ((EngineException)err); + try { + return prefix + " " + ee.toString(ee.ext); + } + catch (EngineException ex) { + return prefix + " " + toReadable(ee.ext, ee.value); + } + } + else if (err instanceof SyntaxException) { + return prefix + " SyntaxError " + ((SyntaxException)err).msg; + } + else if (err.getCause() instanceof InterruptedException) return ""; + else { + var str = new ByteArrayOutputStream(); + err.printStackTrace(new PrintStream(str)); + + return prefix + " internal error " + str.toString(); + } + } + public static void printValue(Extensions ext, Object val) { + System.out.print(toReadable(ext, val)); + } + public static void printError(RuntimeException err, String prefix) { + System.out.println(errorToReadable(err, prefix)); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java deleted file mode 100644 index 1a7f276..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.functions; - - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; - -public class Arguments { - public final Value self; - public final Value[] args; - public final Environment env; - public final boolean isNew; - - public int n() { - return args.length; - } - - public boolean has(int i) { - return i == -1 || i >= 0 && i < args.length; - } - - public Value self() { - return get(-1); - } - public Value get(int i) { - if (i >= args.length || i < -1) return Value.UNDEFINED; - else if (i == -1) return self; - else return args[i]; - } - public Value getOrDefault(int i, Value def) { - if (i < -1 || i >= args.length) return def; - else return get(i); - } - - public Arguments(Environment env, boolean isNew, Value thisArg, Value... args) { - this.env = env; - this.args = args; - this.self = thisArg; - this.isNew = isNew; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java deleted file mode 100644 index 6eb5aa6..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.functions; - -import me.topchetoeu.jscript.common.FunctionBody; -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.Frame; -import me.topchetoeu.jscript.runtime.values.Value; - -public final class CodeFunction extends FunctionValue { - public final FunctionBody body; - public final Value[][] captures; - public Environment env; - - private Value onCall(Frame frame) { - frame.onPush(); - - try { - while (true) { - var res = frame.next(null, null, null); - if (res != null) return res; - } - } - finally { - frame.onPop(); - } - } - - @Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) { - var frame = new Frame(env, isNew, thisArg, args, this); - return onCall(frame); - } - - public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) { - super(name, body.length); - this.captures = captures; - this.env = env; - this.body = body; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java deleted file mode 100644 index 543a6d1..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ /dev/null @@ -1,94 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.functions; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; - -public abstract class FunctionValue extends ObjectValue { - public String name = ""; - public int length; - public Value prototype = new ObjectValue(); - - public boolean enableCall = true; - public boolean enableNew = true; - - private final FieldMember nameField = new FieldMember(true, false, false) { - @Override public Value get(Environment env, Value self) { - if (name == null) return new StringValue(""); - return new StringValue(name); - } - @Override public boolean set(Environment env, Value val, Value self) { - name = val.toString(env).value; - return true; - } - }; - private final FieldMember lengthField = new FieldMember(true, false, false) { - @Override public Value get(Environment env, Value self) { - return new NumberValue(length); - } - @Override public boolean set(Environment env, Value val, Value self) { - return false; - } - }; - private final FieldMember prototypeField = new FieldMember(false, false, true) { - @Override public Value get(Environment env, Value self) { - return prototype; - } - @Override public boolean set(Environment env, Value val, Value self) { - prototype = val; - return true; - } - }; - - protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args); - - @Override public String toString() { return String.format("function %s(...)", name); } - @Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) { - if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args); - if (!isNew && !enableCall) super.call(ext, isNew, name, thisArg, args); - - return onCall(ext, isNew, name, thisArg, args); - } - - @Override public Member getOwnMember(Environment env, KeyCache key) { - switch (key.toString(env)) { - case "length": return lengthField; - case "name": return nameField; - case "prototype": return prototypeField; - default: return super.getOwnMember(env, key); - } - } - @Override public boolean deleteOwnMember(Environment env, KeyCache key) { - switch (key.toString(env)) { - case "length": - length = 0; - return true; - case "name": - name = ""; - return true; - case "prototype": - return false; - default: return super.deleteOwnMember(env, key); - } - } - - public void setName(String val) { - if (this.name == null || this.name.equals("")) this.name = val; - } - - public FunctionValue(String name, int length) { - setPrototype(FUNCTION_PROTO); - - if (name == null) name = ""; - this.length = length; - this.name = name; - - prototype.defineOwnMember(null, "constructor", FieldMember.of(this)); - } -} - diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java deleted file mode 100644 index 0ad250a..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.functions; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; - -public final class NativeFunction extends FunctionValue { - public static interface NativeFunctionRunner { - Value run(Arguments args); - } - - public final NativeFunctionRunner action; - - @Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) { - return action.run(new Arguments(env, isNew, self, args)); - } - - public NativeFunction(String name, NativeFunctionRunner action) { - super(name, 0); - this.action = action; - } - public NativeFunction(NativeFunctionRunner action) { - super("", 0); - this.action = action; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java deleted file mode 100644 index 43a326b..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ /dev/null @@ -1,230 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.objects; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; - -// TODO: Make methods generic -public class ArrayValue extends ObjectValue implements Iterable { - private Value[] values; - private int size; - - private final FieldMember lengthField = new FieldMember(false, false, true) { - @Override public Value get(Environment env, Value self) { - return new NumberValue(size); - } - @Override public boolean set(Environment env, Value val, Value self) { - size = val.toInt(env); - return true; - } - }; - - private class IndexField extends FieldMember { - private int i; - private ArrayValue arr; - - @Override public Value get(Environment env, Value self) { - return arr.get(i); - } - @Override public boolean set(Environment env, Value val, Value self) { - arr.set(i, val); - return true; - } - public IndexField(int i, ArrayValue arr) { - super(true, true, true); - this.arr = arr; - this.i = i; - } - } - - private Value[] alloc(int index) { - index++; - if (index < values.length) return values; - if (index < values.length * 2) index = values.length * 2; - - var arr = new Value[index]; - System.arraycopy(values, 0, arr, 0, values.length); - return values = arr; - } - - public int size() { return size; } - public boolean setSize(int val) { - if (val < 0) return false; - if (size > val) shrink(size - val); - else { - values = alloc(val); - size = val; - } - return true; - } - - public Value get(int i) { - if (i < 0 || i >= size) return null; - var res = values[i]; - - if (res == null) return Value.UNDEFINED; - else return res; - } - public void set(int i, Value val) { - if (i < 0) return; - - alloc(i)[i] = val; - if (i >= size) size = i + 1; - } - public boolean has(int i) { - return i >= 0 && i < size && values[i] != null; - } - public void remove(int i) { - if (i < 0 || i >= values.length) return; - values[i] = null; - } - public void shrink(int n) { - if (n >= values.length) { - values = new Value[16]; - size = 0; - } - else { - for (int i = 0; i < n; i++) values[--size] = null; - } - } - - public Value[] toArray() { - var res = new Value[size]; - copyTo(res, 0, 0, size); - return res; - } - - public void copyTo(Value[] arr, int sourceStart, int destStart, int count) { - var nullFill = Math.max(0, arr.length - size - destStart); - count -= nullFill; - - System.arraycopy(values, sourceStart, arr, destStart, count); - Arrays.fill(arr, count, nullFill + count, null); - } - public void copyTo(ArrayValue arr, int sourceStart, int destStart, int count) { - if (arr == this) { - move(sourceStart, destStart, count); - return; - } - - arr.copyFrom(values, sourceStart, destStart, count); - } - public void copyFrom(Value[] arr, int sourceStart, int destStart, int count) { - alloc(destStart + count); - System.arraycopy(arr, sourceStart, values, destStart, count); - if (size < destStart + count) size = destStart + count; - } - - public void move(int srcI, int dstI, int n) { - values = alloc(dstI + n); - System.arraycopy(values, srcI, values, dstI, n); - if (dstI + n >= size) size = dstI + n; - } - - public void sort(Comparator comparator) { - Arrays.sort(values, 0, size, (a, b) -> { - var _a = 0; - var _b = 0; - - if (a == null) _a = 2; - if (a instanceof VoidValue) _a = 1; - - if (b == null) _b = 2; - if (b instanceof VoidValue) _b = 1; - - if (_a != 0 || _b != 0) return Integer.compare(_a, _b); - - return comparator.compare(a, b); - }); - } - - @Override public Member getOwnMember(Environment env, KeyCache key) { - var res = super.getOwnMember(env, key); - if (res != null) return res; - - var num = key.toNumber(env); - var i = key.toInt(env); - - if (i == num && i >= 0 && i < size && has(i)) return new IndexField(i, this); - else if (key.toString(env).equals("length")) return lengthField; - else return null; - } - @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { - if (!(member instanceof FieldMember) || hasMember(env, key, true)) return super.defineOwnMember(env, key, member); - if (!extensible) return false; - - var num = key.toNumber(env); - var i = key.toInt(env); - - if (i == num && i >= 0) { - set(i, ((FieldMember)member).get(env, this)); - return true; - } - else return super.defineOwnMember(env, key, member); - } - @Override public boolean deleteOwnMember(Environment env, KeyCache key) { - if (!super.deleteOwnMember(env, key)) return false; - - var num = key.toNumber(env); - var i = key.toInt(env); - - if (i == num && i >= 0 && i < size) return super.deleteOwnMember(env, key); - else return true; - } - - @Override public Map getOwnMembers(Environment env) { - var res = new LinkedHashMap(); - - for (var i = 0; i < size; i++) { - var member = getOwnMember(env, i); - if (member != null) res.put(i + "", member); - } - - res.put("length", lengthField); - - res.putAll(super.getOwnMembers(env)); - - return res; - } - @Override public Iterator iterator() { - return new Iterator<>() { - private int i = 0; - - @Override public boolean hasNext() { - return i < size(); - } - @Override public Value next() { - if (!hasNext()) return null; - return get(i++); - } - }; - } - - public ArrayValue() { - this(16); - } - public ArrayValue(int cap) { - setPrototype(env -> env.get(ARRAY_PROTO)); - values = new Value[Math.min(cap, 16)]; - size = 0; - } - public ArrayValue(Value ...values) { - this(); - copyFrom(values, 0, 0, values.length); - } - - public static ArrayValue of(Collection values) { - return new ArrayValue(values.toArray(Value[]::new)); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java deleted file mode 100644 index da5cba4..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ /dev/null @@ -1,128 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.objects; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; -import me.topchetoeu.jscript.runtime.values.primitives.NumberValue; -import me.topchetoeu.jscript.runtime.values.primitives.StringValue; -import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; - -public class ObjectValue extends Value { - public static interface PrototypeProvider { - public ObjectValue get(Environment env); - } - - public static enum State { - NORMAL, - NO_EXTENSIONS, - SEALED, - FROZEN, - } - - public static class Property { - public final FunctionValue getter; - public final FunctionValue setter; - - public Property(FunctionValue getter, FunctionValue setter) { - this.getter = getter; - this.setter = setter; - } - } - - private static final StringValue typeString = new StringValue("object"); - - protected PrototypeProvider prototype; - - public boolean extensible = true; - - public LinkedHashMap members = new LinkedHashMap<>(); - public LinkedHashMap symbolMembers = new LinkedHashMap<>(); - - @Override public boolean isPrimitive() { return false; } - @Override public Value toPrimitive(Environment env) { - if (env != null) { - var valueOf = getMember(env, new StringValue("valueOf")); - - if (valueOf instanceof FunctionValue) { - var res = valueOf.call(env, this); - if (res.isPrimitive()) return res; - } - - var toString = getMember(env, new StringValue("toString")); - if (toString instanceof FunctionValue) { - var res = toString.call(env, this); - if (res.isPrimitive()) return res; - } - } - - throw EngineException.ofType("Value couldn't be converted to a primitive."); - } - @Override public StringValue toString(Environment env) { return toPrimitive(env).toString(env); } - @Override public boolean toBoolean() { return true; } - @Override public NumberValue toNumber(Environment env) { return toPrimitive(env).toNumber(env); } - @Override public StringValue type() { return typeString; } - - public final void preventExtensions() { - extensible = false; - } - - @Override public Member getOwnMember(Environment env, KeyCache key) { - if (key.isSymbol()) return symbolMembers.get(key.toSymbol()); - else return members.get(key.toString(env)); - } - @Override public boolean defineOwnMember(Environment env, KeyCache key, Member member) { - var old = getOwnMember(env, key); - if (old != null && old.configure(env, member, this)) return true; - if (old != null && !old.configurable()) return false; - - if (key.isSymbol()) symbolMembers.put(key.toSymbol(), member); - else members.put(key.toString(env), member); - - return true; - } - @Override public boolean deleteOwnMember(Environment env, KeyCache key) { - if (!extensible) return false; - - var member = getOwnMember(env, key); - if (member == null) return true; - if (member.configurable()) return false; - - if (key.isSymbol()) symbolMembers.remove(key.toSymbol()); - else members.remove(key.toString(env)); - return true; - } - - @Override public Map getOwnMembers(Environment env) { - return members; - } - @Override public Map getOwnSymbolMembers(Environment env) { - return Collections.unmodifiableMap(symbolMembers); - } - - @Override public ObjectValue getPrototype(Environment env) { - if (prototype == null || env == null) return null; - else return prototype.get(env); - } - @Override public final boolean setPrototype(Environment env, ObjectValue val) { - return setPrototype(_env -> val); - } - - public final boolean setPrototype(PrototypeProvider val) { - if (!extensible) return false; - prototype = val; - return true; - } - public final boolean setPrototype(Key key) { - if (!extensible) return false; - prototype = env -> env.get(key); - return true; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java deleted file mode 100644 index a968637..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ScopeValue.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.objects; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.Member.FieldMember; - -public final class ScopeValue extends ObjectValue { - private class VariableField extends FieldMember { - private int i; - - public VariableField(int i) { - super(false, true, true); - this.i = i; - } - - @Override public Value get(Environment env, Value self) { - return variables[i][0]; - } - - @Override public boolean set(Environment env, Value val, Value self) { - variables[i][0] = val; - return true; - } - } - - public final Value[][] variables; - - public ScopeValue(Value[][] variables, String[] names) { - this.variables = variables; - for (var i = 0; i < names.length && i < variables.length; i++) { - defineOwnMember(Environment.empty(), i, new VariableField(i)); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java deleted file mode 100644 index 4b567db..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class BoolValue extends PrimitiveValue { - public static final BoolValue TRUE = new BoolValue(true); - public static final BoolValue FALSE = new BoolValue(false); - private static final StringValue typeString = new StringValue("boolean"); - - public final boolean value; - - @Override public StringValue type() { return typeString; } - - @Override public boolean toBoolean() { return value; } - @Override public NumberValue toNumber(Environment ext) { - return value ? new NumberValue(1) : new NumberValue(0); - } - @Override public StringValue toString(Environment ext) { return new StringValue(value ? "true" : "false"); } - - @Override public ObjectValue getPrototype(Environment env) { - return env.get(BOOL_PROTO); - } - - @Override public boolean equals(Object other) { - if (other instanceof BoolValue bool) return value == bool.value; - else return false; - } - - private BoolValue(boolean val) { - this.value = val; - } - - public static BoolValue of(boolean val) { - return val ? TRUE : FALSE; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java deleted file mode 100644 index 2fe9265..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/NumberValue.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class NumberValue extends PrimitiveValue { - public static final NumberValue NAN = new NumberValue(Double.NaN); - private static final StringValue typeString = new StringValue("number"); - - public final double value; - - @Override public StringValue type() { return typeString; } - - @Override public boolean toBoolean() { return value != 0; } - @Override public NumberValue toNumber(Environment ext) { return this; } - @Override public StringValue toString(Environment ext) { return new StringValue(toString()); } - @Override public String toString() { return JSON.stringify(JSONElement.number(value)); } - - @Override public ObjectValue getPrototype(Environment env) { - return env.get(NUMBER_PROTO); - } - - @Override public boolean equals(Object other) { - if (other instanceof NumberValue val) return value == val.value; - else return false; - } - - public NumberValue(double value) { - this.value = value; - } - - public static NumberValue parseInt(String str, int radix, boolean relaxed) { - if (radix < 2 || radix > 36) return new NumberValue(Double.NaN); - - str = str.trim(); - var res = Parsing.parseInt(new Source(str), 0, "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, radix), true); - if (res.isSuccess()) { - if (relaxed || res.n == str.length()) return new NumberValue(res.result); - } - return new NumberValue(Double.NaN); - } - public static NumberValue parseFloat(String str, boolean relaxed) { - str = str.trim(); - var res = Parsing.parseFloat(new Source(str), 0, true); - if (res.isSuccess()) { - if (relaxed || res.n == str.length()) return new NumberValue(res.result); - } - return new NumberValue(Double.NaN); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java deleted file mode 100644 index 4d18f7a..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/PrimitiveValue.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public abstract class PrimitiveValue extends Value { - @Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } - @Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } - @Override public final boolean isPrimitive() { return true; } - @Override public final Value toPrimitive(Environment env) { return this; } - - @Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; } - - @Override public Member getOwnMember(Environment env, KeyCache key) { return null; } - @Override public Map getOwnMembers(Environment env) { return Map.of(); } - @Override public Map getOwnSymbolMembers(Environment env) { return Map.of(); } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java deleted file mode 100644 index 8df3f52..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class StringValue extends PrimitiveValue { - public final String value; - private static final StringValue typeString = new StringValue("string"); - - @Override public StringValue type() { return typeString; } - - @Override public boolean toBoolean() { return !value.equals(""); } - @Override public NumberValue toNumber(Environment ext) { - var val = value.trim(); - if (val.equals("")) return new NumberValue(0); - var res = Parsing.parseNumber(new Source(val), 0, true); - - if (res.isSuccess() && res.n == val.length()) return new NumberValue(res.result); - else return new NumberValue(Double.NaN); - } - @Override public StringValue toString(Environment ext) { return this; } - - @Override public boolean equals(Object other) { - if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value); - else return false; - } - - @Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); } - - @Override public Map getOwnMembers(Environment env) { - // TODO Auto-generated method stub - return super.getOwnMembers(env); - } - - public StringValue(String value) { - this.value = value; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java deleted file mode 100644 index e5baeb4..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java +++ /dev/null @@ -1,49 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import java.util.HashMap; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.Value; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class SymbolValue extends PrimitiveValue { - private static final HashMap registry = new HashMap<>(); - private static final StringValue typeString = new StringValue("symbol"); - - public final String value; - - public Value key() { - return registry.containsKey(value) && registry.get(value) == this ? new StringValue(value) : Value.UNDEFINED; - } - - @Override public StringValue type() { return typeString; } - - @Override public boolean toBoolean() { return false; } - @Override public StringValue toString(Environment env) { - throw EngineException.ofType("Cannot convert a Symbol value to a string"); - } - @Override public NumberValue toNumber(Environment env) { - throw EngineException.ofType("Cannot convert a Symbol value to a number"); - } - - @Override public ObjectValue getPrototype(Environment env) { return env.get(SYMBOL_PROTO); } - - @Override public String toString() { - if (value == null) return "Symbol()"; - else return "Symbol(" + value + ")"; - } - - public SymbolValue(String value) { - this.value = value; - } - - public static SymbolValue get(String name) { - if (registry.containsKey(name)) return registry.get(name); - else { - var res = new SymbolValue(name); - registry.put(name, res); - return res; - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java deleted file mode 100644 index 9e0539d..0000000 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ /dev/null @@ -1,43 +0,0 @@ -package me.topchetoeu.jscript.runtime.values.primitives; - -import java.util.Map; - -import me.topchetoeu.jscript.common.environment.Environment; -import me.topchetoeu.jscript.runtime.exceptions.EngineException; -import me.topchetoeu.jscript.runtime.values.KeyCache; -import me.topchetoeu.jscript.runtime.values.Member; -import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; - -public final class VoidValue extends PrimitiveValue { - private final StringValue nameString; - - public final String name; - public final StringValue typeString; - - @Override public StringValue type() { return typeString; } - @Override public boolean toBoolean() { return false; } - @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } - @Override public StringValue toString(Environment ext) { return nameString; } - - @Override public ObjectValue getPrototype(Environment env) { return null; } - - @Override public Member getOwnMember(Environment env, KeyCache key) { - throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); - } - @Override public Map getOwnMembers(Environment env) { - throw EngineException.ofError(String.format("Cannot read properties of %s (listing all members)", name)); - } - @Override public Map getOwnSymbolMembers(Environment env) { - throw EngineException.ofError(String.format("Cannot read properties of %s (listing all symbol members)", name)); - } - - // @Override public Value call(Environment env, Value self, Value... args) { - // throw EngineException.ofType(String.format("Tried to call a value of %s", name)); - // } - - public VoidValue(String name, StringValue type) { - this.name = name; - this.typeString = type; - this.nameString = new StringValue(name); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java b/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java new file mode 100644 index 0000000..81e2c5a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/JSCompiler.java @@ -0,0 +1,36 @@ +package me.topchetoeu.jscript.utils; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.FunctionBody; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.runtime.Compiler; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.debug.DebugContext; + +public class JSCompiler implements Compiler { + public final Extensions ext; + + private void registerFunc(FunctionBody body, CompileResult res) { + var map = res.map(); + + DebugContext.get(ext).onFunctionLoad(body, map); + + for (var i = 0; i < body.children.length; i++) { + registerFunc(body.children[i], res.children.get(i)); + } + } + + @Override public FunctionBody compile(Filename filename, String source) { + var res = Parsing.compile(filename, source); + var func = res.body(); + DebugContext.get(ext).onSource(filename, source); + registerFunc(func, res); + + return func; + } + + public JSCompiler(Extensions ext) { + this.ext = ext; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java b/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java new file mode 100644 index 0000000..5f55ed5 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/JScriptRepl.java @@ -0,0 +1,152 @@ +package me.topchetoeu.jscript.utils; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.Metadata; +import me.topchetoeu.jscript.common.Reading; +import me.topchetoeu.jscript.lib.Internals; +import me.topchetoeu.jscript.runtime.Compiler; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Engine; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.InterruptException; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.Values; +import me.topchetoeu.jscript.utils.debug.DebugServer; +import me.topchetoeu.jscript.utils.debug.SimpleDebugger; +import me.topchetoeu.jscript.utils.filesystem.Filesystem; +import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem; +import me.topchetoeu.jscript.utils.filesystem.Mode; +import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem; +import me.topchetoeu.jscript.utils.filesystem.RootFilesystem; +import me.topchetoeu.jscript.utils.filesystem.STDFilesystem; +import me.topchetoeu.jscript.utils.interop.NativeWrapperProvider; +import me.topchetoeu.jscript.utils.modules.ModuleRepo; +import me.topchetoeu.jscript.utils.permissions.PermissionsManager; +import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; + +public class JScriptRepl { + static Thread engineTask, debugTask; + static Engine engine = new Engine(); + static DebugServer debugServer = new DebugServer(); + static Environment environment = new Environment(); + + static int j = 0; + static String[] args; + + private static void reader() { + try { + for (var arg : args) { + try { + var file = Path.of(arg); + var raw = Files.readString(file); + var res = engine.pushMsg( + false, environment, + Filename.fromFile(file.toFile()), + raw, null + ).await(); + Values.printValue(null, res); + System.out.println(); + } + catch (EngineException e) { Values.printError(e, null); } + } + for (var i = 0; ; i++) { + try { + var raw = Reading.readline(); + + if (raw == null) break; + var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw); + var res = engine.pushMsg(false, environment, func, null).await(); + Values.printValue(null, res); + System.out.println(); + } + catch (EngineException e) { Values.printError(e, null); } + catch (SyntaxException e) { Values.printError(e, null); } + } + } + catch (IOException e) { + System.out.println(e.toString()); + engine.thread().interrupt(); + } + catch (RuntimeException ex) { + if (ex instanceof InterruptException) return; + else { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } + } + } + + private static void initEnv() { + environment = Internals.apply(environment); + + var glob = GlobalScope.get(environment); + + glob.define(null, false, new NativeFunction("exit", args -> { + throw new InterruptException(); + })); + glob.define(null, false, new NativeFunction("go", args -> { + try { + var f = Path.of("do.js"); + var func = args.ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); + return func.call(args.ctx); + } + catch (IOException e) { + throw new EngineException("Couldn't open do.js"); + } + })); + glob.define(null, false, new NativeFunction("log", args -> { + for (var el : args.args) { + Values.printValue(args.ctx, el); + } + + return null; + })); + + var fs = new RootFilesystem(PermissionsProvider.get(environment)); + fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); + fs.protocols.put("file", new PhysicalFilesystem(".")); + fs.protocols.put("std", new STDFilesystem(System.in, System.out, System.err)); + + environment.add(PermissionsProvider.KEY, PermissionsManager.ALL_PERMS); + environment.add(Filesystem.KEY, fs); + environment.add(ModuleRepo.KEY, ModuleRepo.ofFilesystem(fs)); + environment.add(Compiler.KEY, new JSCompiler(new Context(environment))); + environment.add(EventLoop.KEY, engine); + } + private static void initEngine() { + var ctx = new DebugContext(); + environment.add(DebugContext.KEY, ctx); + + debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx)); + engineTask = engine.start(); + debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); + } + + public static void main(String args[]) throws InterruptedException { + System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); + + JScriptRepl.args = args; + var reader = new Thread(JScriptRepl::reader); + + initEnv(); + initEngine(); + + reader.setDaemon(true); + reader.setName("STD Reader"); + reader.start(); + + engine.thread().join(); + debugTask.interrupt(); + engineTask.interrupt(); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java b/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java new file mode 100644 index 0000000..db69e7f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/DebugServer.java @@ -0,0 +1,251 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.MessageDigest; +import java.util.Base64; +import java.util.HashMap; + +import me.topchetoeu.jscript.common.Metadata; +import me.topchetoeu.jscript.common.Reading; +import me.topchetoeu.jscript.common.events.Notifier; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONList; +import me.topchetoeu.jscript.common.json.JSONMap; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.utils.debug.WebSocketMessage.Type; + +public class DebugServer { + public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); + + public final HashMap targets = new HashMap<>(); + + private final byte[] favicon, index, protocol; + private final Notifier connNotifier = new Notifier(); + + private static void send(HttpRequest req, String val) throws IOException { + req.writeResponse(200, "OK", "application/json", val.getBytes()); + } + + // SILENCE JAVA + private MessageDigest getDigestInstance() { + try { + return MessageDigest.getInstance("sha1"); + } + catch (Throwable e) { throw new RuntimeException(e); } + } + + private static Thread runAsync(Runnable func, String name) { + var res = new Thread(func); + res.setName(name); + res.start(); + return res; + } + + private void handle(WebSocket ws, Debugger debugger) throws IOException { + WebSocketMessage raw; + + while ((raw = ws.receive()) != null) { + if (raw.type != Type.Text) { + ws.send(new V8Error("Expected a text message.")); + continue; + } + + V8Message msg; + + try { + msg = new V8Message(raw.textData()); + } + catch (SyntaxException e) { + ws.send(new V8Error(e.getMessage())); + return; + } + + switch (msg.name) { + case "Debugger.enable": + connNotifier.next(); + debugger.enable(msg); + continue; + case "Debugger.disable": debugger.close(); continue; + + case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; + case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; + case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue; + + case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue; + case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue; + + case "Debugger.resume": debugger.resume(msg); continue; + case "Debugger.pause": debugger.pause(msg); continue; + + case "Debugger.stepInto": debugger.stepInto(msg); continue; + case "Debugger.stepOut": debugger.stepOut(msg); continue; + case "Debugger.stepOver": debugger.stepOver(msg); continue; + + case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue; + case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue; + + case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue; + case "Runtime.releaseObject": debugger.releaseObject(msg); continue; + case "Runtime.getProperties": debugger.getProperties(msg); continue; + case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue; + case "Runtime.enable": debugger.runtimeEnable(msg); continue; + } + + if ( + msg.name.startsWith("DOM.") || + msg.name.startsWith("DOMDebugger.") || + msg.name.startsWith("Emulation.") || + msg.name.startsWith("Input.") || + msg.name.startsWith("Network.") || + msg.name.startsWith("Page.") + ) ws.send(new V8Error("This isn't a browser...")); + else ws.send(new V8Error("This API is not supported yet.")); + } + + debugger.close(); + } + private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { + var key = req.headers.get("sec-websocket-key"); + + if (key == null) { + req.writeResponse( + 426, "Upgrade Required", "text/txt", + "Expected a WS upgrade".getBytes() + ); + return; + } + + var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( + (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() + )); + + req.writeCode(101, "Switching Protocols"); + req.writeHeader("Connection", "Upgrade"); + req.writeHeader("Sec-WebSocket-Accept", resKey); + req.writeLastHeader("Upgrade", "WebSocket"); + + var ws = new WebSocket(socket); + var debugger = debuggerProvider.getDebugger(ws, req); + + if (debugger == null) { + ws.close(); + return; + } + + runAsync(() -> { + var handle = new Thread(() -> { + System.out.println("test"); + debugger.close(); + }); + + Runtime.getRuntime().addShutdownHook(handle); + + try { handle(ws, debugger); } + catch (RuntimeException | IOException e) { + try { + e.printStackTrace(); + ws.send(new V8Error(e.getMessage())); + } + catch (IOException e2) { /* Shit outta luck */ } + } + finally { + Runtime.getRuntime().removeShutdownHook(handle); + ws.close(); + debugger.close(); + } + }, "Debug Handler"); + } + + public void awaitConnection() { + connNotifier.await(); + } + + public void run(InetSocketAddress address) { + try { + ServerSocket server = new ServerSocket(); + server.bind(address); + + try { + while (true) { + var socket = server.accept(); + var req = HttpRequest.read(socket); + + if (req == null) continue; + switch (req.path) { + case "/json/version": + send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}"); + break; + case "/json/list": + case "/json": { + var res = new JSONList(); + + for (var el : targets.entrySet()) { + res.add(new JSONMap() + .set("description", "JScript debugger") + .set("favicon", "/favicon.ico") + .set("id", el.getKey()) + .set("type", "node") + .set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey()) + ); + } + send(req, JSON.stringify(res)); + break; + } + case "/json/protocol": + req.writeResponse(200, "OK", "application/json", protocol); + break; + case "/json/new": + case "/json/activate": + case "/json/close": + case "/devtools/inspector.html": + req.writeResponse( + 501, "Not Implemented", "text/txt", + "This feature isn't (and probably won't be) implemented.".getBytes() + ); + break; + case "/": + case "/index.html": + req.writeResponse(200, "OK", "text/html", index); + break; + case "/favicon.ico": + req.writeResponse(200, "OK", "image/png", favicon); + break; + default: + if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) { + onWsConnect(req, socket, targets.get(req.path.substring(1))); + } + break; + } + } + } + finally { server.close(); } + } + catch (IOException e) { throw new UncheckedIOException(e); } + } + + public Thread start(InetSocketAddress address, boolean daemon) { + var res = new Thread(() -> run(address), "Debug Server"); + res.setDaemon(daemon); + res.start(); + return res; + } + + public DebugServer() { + try { + this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes(); + this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes(); + this.index = Reading.resourceToString("debugger/index.html") + .replace("${NAME}", Metadata.name()) + .replace("${VERSION}", Metadata.version()) + .replace("${AUTHOR}", Metadata.author()) + .getBytes(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java b/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java new file mode 100644 index 0000000..bc64a2e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/Debugger.java @@ -0,0 +1,37 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.io.IOException; + +import me.topchetoeu.jscript.runtime.debug.DebugHandler; + +public interface Debugger extends DebugHandler { + void close(); + + void enable(V8Message msg) throws IOException; + void disable(V8Message msg) throws IOException; + + void setBreakpointByUrl(V8Message msg) throws IOException; + void removeBreakpoint(V8Message msg) throws IOException; + void continueToLocation(V8Message msg) throws IOException; + + void getScriptSource(V8Message msg) throws IOException; + void getPossibleBreakpoints(V8Message msg) throws IOException; + + void resume(V8Message msg) throws IOException; + void pause(V8Message msg) throws IOException; + + void stepInto(V8Message msg) throws IOException; + void stepOut(V8Message msg) throws IOException; + void stepOver(V8Message msg) throws IOException; + + void setPauseOnExceptions(V8Message msg) throws IOException; + + void evaluateOnCallFrame(V8Message msg) throws IOException; + + void getProperties(V8Message msg) throws IOException; + void releaseObjectGroup(V8Message msg) throws IOException; + void releaseObject(V8Message msg) throws IOException; + void callFunctionOn(V8Message msg) throws IOException; + + void runtimeEnable(V8Message msg) throws IOException; +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java b/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java new file mode 100644 index 0000000..fba409d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/DebuggerProvider.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.utils.debug; + +public interface DebuggerProvider { + Debugger getDebugger(WebSocket socket, HttpRequest req); +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java b/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java new file mode 100644 index 0000000..4fcc42f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/HttpRequest.java @@ -0,0 +1,102 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.util.HashMap; +import java.util.IllegalFormatException; +import java.util.Map; + +public class HttpRequest { + public final String method; + public final String path; + public final Map headers; + public final OutputStream out; + + + public void writeCode(int code, String name) { + try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); } + catch (IOException e) { } + } + public void writeHeader(String name, String value) { + try { out.write((name + ": " + value + "\r\n").getBytes()); } + catch (IOException e) { } + } + public void writeLastHeader(String name, String value) { + try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); } + catch (IOException e) { } + } + public void writeHeadersEnd() { + try { out.write("\n".getBytes()); } + catch (IOException e) { } + } + + public void writeResponse(int code, String name, String type, byte[] data) { + writeCode(code, name); + writeHeader("Content-Type", type); + writeLastHeader("Content-Length", data.length + ""); + try { + out.write(data); + out.close(); + } + catch (IOException e) { } + } + public void writeResponse(int code, String name, String type, InputStream data) { + try { + writeResponse(code, name, type, data.readAllBytes()); + } + catch (IOException e) { } + } + + public HttpRequest(String method, String path, Map headers, OutputStream out) { + this.method = method; + this.path = path; + this.headers = headers; + this.out = out; + } + + // We dont need no http library + public static HttpRequest read(Socket socket) { + try { + var str = socket.getInputStream(); + var lines = new BufferedReader(new InputStreamReader(str)); + var line = lines.readLine(); + var i1 = line.indexOf(" "); + var i2 = line.indexOf(" ", i1 + 1); + + if (i1 < 0 || i2 < 0) { + socket.close(); + return null; + } + + var method = line.substring(0, i1).trim().toUpperCase(); + var path = line.substring(i1 + 1, i2).trim(); + var headers = new HashMap(); + + while (!(line = lines.readLine()).isEmpty()) { + var i = line.indexOf(":"); + if (i < 0) continue; + var name = line.substring(0, i).trim().toLowerCase(); + var value = line.substring(i + 1).trim(); + + if (name.length() == 0) continue; + headers.put(name, value); + } + + if (headers.containsKey("content-length")) { + try { + var i = Integer.parseInt(headers.get("content-length")); + str.skip(i); + } + catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ } + } + + return new HttpRequest(method, path, headers, socket.getOutputStream()); + } + catch (IOException | NullPointerException e) { return null; } + } +} + diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java b/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java new file mode 100644 index 0000000..9b89386 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/SimpleDebugger.java @@ -0,0 +1,1091 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.FunctionBody; +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.common.Instruction.Type; +import me.topchetoeu.jscript.common.events.Notifier; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.common.json.JSONList; +import me.topchetoeu.jscript.common.json.JSONMap; +import me.topchetoeu.jscript.common.mapping.FunctionMap; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Engine; +import me.topchetoeu.jscript.runtime.Environment; +import me.topchetoeu.jscript.runtime.EventLoop; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Frame; +import me.topchetoeu.jscript.runtime.debug.DebugContext; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.runtime.values.ArrayValue; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; + +// very simple indeed +public class SimpleDebugger implements Debugger { + public static final Set VSCODE_EMPTY = Set.of( + "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}", + "function(...runtimeArgs){\n let r = 1024; let e = null;\n if(e)try{let t=\"<>\",n=e.call(this,t);if(n!==t)return String(n)}catch(t){return`<>${JSON.stringify([String(t),\"object\"])}`}if(typeof this==\"object\"&&this){let t;for(let n of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])if(typeof this[n]==\"function\")try{t=this[n]();break}catch{}if(!t&&!String(this.toString).includes(\"[native code]\")&&(t=String(this)),t&&!t.startsWith(\"[object\"))return t.length>=r?t.slice(0,r)+\"\\u2026\":t};}", + "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}", + "function(...runtimeArgs){\n let r = 1024; let e = null;\n let t={},n=\"<>\";if(typeof this!=\"object\"||!this)return t;for(let[i,o]of Object.entries(this)){if(e)try{let s=e.call(o,n);if(s!==n){t[i]=String(s);continue}}catch(s){t[i]=`<>${JSON.stringify([String(s),i])}`;continue}if(typeof o==\"object\"&&o){let s;for(let a of runtimeArgs[0])if(typeof o[a]==\"function\")try{s=o[a]();break}catch{}!s&&!String(o.toString).includes(\"[native code]\")&&(s=String(o)),s&&!s.startsWith(\"[object \")&&(t[i]=s.length>=r?s.slice(0,r)+\"\\u2026\":s)}}return t\n ;\n\n}", + "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}", + "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}" + ); + public static final Set VSCODE_SELF = Set.of( + "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s{if(p!==\"function\")return n;if(l===\"constructor\")return\"class\";let m=String(f);return m.startsWith(\"class \")||m.includes(\"[native code]\")&&/^[A-Z]/.test(l)?\"class\":r?\"function\":\"method\"\n},o=l=>{switch(typeof l){case\"number\":case\"boolean\":return`${l}`;case\"object\":return l===null?\"null\":l.constructor.name||\"object\";case\"function\":return`fn(${new Array(l.length).fill(\"?\").join(\", \")})`;default:return typeof l}},s=[],a=new Set,u=\"~\",c=t===void 0?this:t;for(;c!=null;c=c.__proto__){u+=\"~\";let l=Object.getOwnPropertyNames(c).filter(p=>p.startsWith(e)&&!p.match(/^\\d+$/));for(let p of l){if(a.has(p))continue;a.add(p);let f=Object.getOwnPropertyDescriptor(c,p),m=n,h;try{let H=c[p];m=i(p,typeof f?.value,H),h=o(H)}catch{}s.push({label:p,sortText:u+p.replace(/^_+/,H=>\"{\".repeat(H.length)),type:m,detail:h})}r=!1}return{result:s,isArray:this instanceof Array}}"; + + private static enum State { + RESUMED, + STEPPING_IN, + STEPPING_OUT, + STEPPING_OVER, + PAUSED_NORMAL, + PAUSED_EXCEPTION, + } + private static enum CatchType { + NONE, + UNCAUGHT, + ALL, + } + private static class DebugSource { + public final int id; + public final Filename filename; + public final String source; + + public DebugSource(int id, Filename filename, String source) { + this.id = id; + this.filename = filename; + this.source = source; + } + } + + private class Breakpoint { + public final int id; + public final String condition; + public final Pattern pattern; + public final int line, start; + public final long locNum; + public final HashMap resolvedLocations = new HashMap<>(); + public final HashMap resolvedDistances = new HashMap<>(); + + public Breakpoint(int id, Pattern pattern, int line, int start, String condition) { + this.id = id; + this.condition = condition; + this.pattern = pattern; + this.line = line; + this.start = start; + this.locNum = start | ((long)line << 32); + + if (condition != null && condition.trim().equals("")) condition = null; + } + + // TODO: Figure out how to unload a breakpoint + // TODO: Do location resolution with function boundaries + public void addFunc(FunctionBody body, FunctionMap map) { + try { + for (var loc : map.correctBreakpoint(pattern, line, start)) { + var currNum = loc.start() + ((long)loc.line() << 32); + long currDist = 0; + if (currNum > locNum) currDist = currNum - locNum; + else currDist = locNum - currNum; + + if ( currDist > resolvedDistances.getOrDefault(loc.filename(), Long.MAX_VALUE)) continue; + + resolvedLocations.put(loc.filename(), loc); + resolvedDistances.put(loc.filename(), currDist); + } + + for (var loc : resolvedLocations.values()) { + ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap() + .set("breakpointId", id) + .set("location", serializeLocation(loc)) + )); + } + + updateBreakpoints(); + } + catch (IOException e) { + ws.close(); + close(); + } + } + } + private class DebugFrame { + public Frame frame; + public int id; + public ObjectValue local, capture, global, valstack; + public JSONMap serialized; + public Location location; + + public void updateLoc(Location loc) { + if (loc == null) return; + this.location = loc; + } + + public DebugFrame(Frame frame, int id) { + this.frame = frame; + this.id = id; + + this.global = GlobalScope.get(frame.ctx).obj; + this.local = frame.getLocalScope(); + this.capture = frame.getCaptureScope(); + Values.makePrototypeChain(frame.ctx, global, capture, local); + this.valstack = frame.getValStackScope(); + + this.serialized = new JSONMap() + .set("callFrameId", id + "") + .set("functionName", frame.function.name) + .set("scopeChain", new JSONList() + .add(new JSONMap() + .set("type", "local") + .set("name", "Local Scope") + .set("object", serializeObj(frame.ctx, local)) + ) + .add(new JSONMap() + .set("type", "closure") + .set("name", "Closure") + .set("object", serializeObj(frame.ctx, capture)) + ) + .add(new JSONMap() + .set("type", "global") + .set("name", "Global Scope") + .set("object", serializeObj(frame.ctx.extensions, global)) + ) + .add(new JSONMap() + .set("type", "other") + .set("name", "Value Stack") + .set("object", serializeObj(frame.ctx.extensions, valstack)) + ) + ); + } + } + private class ObjRef { + public final ObjectValue obj; + public final Extensions ext; + public final HashSet heldGroups = new HashSet<>(); + public boolean held = true; + + public boolean shouldRelease() { + return !held && heldGroups.size() == 0; + } + + public ObjRef(Extensions ext, ObjectValue obj) { + this.ext = ext; + this.obj = obj; + } + } + + private static class RunResult { + public final Extensions ext; + public final Object result; + public final EngineException error; + + public RunResult(Extensions ext, Object result, EngineException error) { + this.ext = ext; + this.result = result; + this.error = error; + } + } + + public boolean enabled = true; + public CatchType execptionType = CatchType.NONE; + public State state = State.RESUMED; + + public final WebSocket ws; + + private ObjectValue emptyObject = new ObjectValue(); + + private WeakHashMap contexts = new WeakHashMap<>(); + private WeakHashMap mappings = new WeakHashMap<>(); + private HashMap> bpLocs = new HashMap<>(); + + private HashMap idToBreakpoint = new HashMap<>(); + + private HashMap filenameToId = new HashMap<>(); + private HashMap idToSource = new HashMap<>(); + private ArrayList pendingSources = new ArrayList<>(); + + private HashMap idToFrame = new HashMap<>(); + private HashMap codeFrameToFrame = new HashMap<>(); + + private HashMap idToObject = new HashMap<>(); + private HashMap objectToId = new HashMap<>(); + private HashMap> objectGroups = new HashMap<>(); + + private Notifier updateNotifier = new Notifier(); + private boolean pendingPause = false; + + private int nextId = 0; + private DebugFrame stepOutFrame = null, currFrame = null; + private int stepOutPtr = 0; + + private boolean compare(String src, String target) { + src = src.replaceAll("\\s", ""); + target = target.replaceAll("\\s", ""); + if (src.length() != target.length()) return false; + var diff = 0; + var all = 0; + + for (var i = 0; i < src.length(); i++) { + var a = src.charAt(i); + var b = target.charAt(i); + var letter = Parsing.isLetter(a) && Parsing.isLetter(b); + + if (a != b) { + if (letter) diff++; + else return false; + } + + if (letter) all++; + } + + return diff / (float)all < .5f; + } + private boolean compare(String src, Set target) { + for (var el : target) { + if (compare(src, el)) return true; + } + return false; + } + + private int nextId() { + return nextId++; + } + + private synchronized DebugFrame getFrame(Frame frame) { + if (!codeFrameToFrame.containsKey(frame)) { + var id = nextId(); + var fr = new DebugFrame(frame, id); + + idToFrame.put(id, fr); + codeFrameToFrame.put(frame, fr); + + return fr; + } + else return codeFrameToFrame.get(frame); + } + private synchronized void updateFrames(Context ctx) { + var frame = ctx.frame; + if (frame == null) return; + + currFrame = getFrame(frame); + } + private JSONList serializeFrames(Context ctx) { + var res = new JSONList(); + + for (var el : ctx.frames()) { + var frame = getFrame(el); + if (frame.location == null) continue; + frame.serialized.set("location", serializeLocation(frame.location)); + if (frame.location != null) res.add(frame.serialized); + } + + return res; + } + + private void updateBreakpoints() { + bpLocs.clear(); + + for (var bp : idToBreakpoint.values()) { + for (var loc : bp.resolvedLocations.values()) { + bpLocs.putIfAbsent(loc, new HashSet<>()); + var set = bpLocs.get(loc); + + set.add(bp); + } + } + } + + private Location deserializeLocation(JSONElement el) { + if (!el.isMap()) throw new RuntimeException("Expected location to be a map."); + var id = Integer.parseInt(el.map().string("scriptId")); + var line = (int)el.map().number("lineNumber") + 1; + var column = (int)el.map().number("columnNumber") + 1; + + if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id)); + + var res = new Location(line, column, idToSource.get(id).filename); + return res; + } + private JSONMap serializeLocation(Location loc) { + var source = filenameToId.get(loc.filename()); + return new JSONMap() + .set("scriptId", source + "") + .set("lineNumber", loc.line() - 1) + .set("columnNumber", loc.start() - 1); + } + + private JSONMap serializeObj(Extensions env, Object val, boolean byValue) { + val = Values.normalize(null, val); + env = sanitizeEnvironment(env); + var ctx = Context.of(env); + + if (val == Values.NULL) { + return new JSONMap() + .set("type", "object") + .set("subtype", "null") + .setNull("value") + .set("description", "null"); + } + + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + int id; + + if (objectToId.containsKey(obj)) id = objectToId.get(obj); + else { + id = nextId(); + var ref = new ObjRef(env, obj); + objectToId.put(obj, id); + idToObject.put(id, ref); + } + + var type = "object"; + String subtype = null; + String className = null; + + if (obj instanceof FunctionValue) type = "function"; + if (obj instanceof ArrayValue) subtype = "array"; + + try { className = Values.toString(ctx, Values.getMemberPath(ctx, obj, "constructor", "name")); } + catch (Exception e) { } + + var res = new JSONMap() + .set("type", type) + .set("objectId", id + ""); + + if (subtype != null) res.set("subtype", subtype); + if (className != null) { + res.set("className", className); + res.set("description", className); + } + + if (obj instanceof ArrayValue) res.set("description", "Array(" + ((ArrayValue)obj).size() + ")"); + else if (obj instanceof FunctionValue) res.set("description", obj.toString()); + else { + var defaultToString = false; + + try { + defaultToString = + Values.getMember(ctx, obj, "toString") == + Values.getMember(ctx, env.get(Environment.OBJECT_PROTO), "toString"); + } + catch (Exception e) { } + + try { res.set("description", className + (defaultToString ? "" : " { " + Values.toString(ctx, obj) + " }")); } + catch (Exception e) { } + } + + + if (byValue) try { res.put("value", JSON.fromJs(env, obj)); } + catch (Exception e) { } + + return res; + } + + if (val == null) return new JSONMap().set("type", "undefined"); + if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val); + if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val); + if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString()); + if (val instanceof Number) { + var num = (double)(Number)val; + var res = new JSONMap().set("type", "number"); + + if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity"); + else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0"); + else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0"); + else if (Double.isNaN(num)) res.set("unserializableValue", "NaN"); + else res.set("value", num); + + return res; + } + + throw new IllegalArgumentException("Unexpected JS object."); + } + private JSONMap serializeObj(Extensions ext, Object val) { + return serializeObj(ext, val, false); + } + private void addObjectGroup(String name, Object val) { + if (val instanceof ObjectValue) { + var obj = (ObjectValue)val; + var id = objectToId.getOrDefault(obj, -1); + if (id < 0) return; + + var ref = idToObject.get(id); + + if (objectGroups.containsKey(name)) objectGroups.get(name).add(ref); + else objectGroups.put(name, new ArrayList<>(List.of(ref))); + + ref.heldGroups.add(name); + } + } + private void releaseGroup(String name) { + var objs = objectGroups.remove(name); + + if (objs != null) for (var obj : objs) { + if (obj.heldGroups.remove(name) && obj.shouldRelease()) { + var id = objectToId.remove(obj.obj); + if (id != null) idToObject.remove(id); + } + } + } + private Object deserializeArgument(JSONMap val) { + if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId"))).obj; + else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) { + case "NaN": return Double.NaN; + case "-Infinity": return Double.NEGATIVE_INFINITY; + case "Infinity": return Double.POSITIVE_INFINITY; + case "-0": return -0.; + } + var res = val.get("value"); + if (res == null) return null; + else return JSON.toJs(res); + } + + private JSONMap serializeException(Extensions ext, EngineException err) { + String text = null; + + try { + text = Values.toString(Context.of(ext), err.value); + } + catch (EngineException e) { + text = "[error while stringifying]"; + } + + var res = new JSONMap() + .set("exceptionId", nextId()) + .set("exception", serializeObj(ext, err.value)) + .set("text", text); + + return res; + } + + private void resume(State state) { + try { + this.state = state; + ws.send(new V8Event("Debugger.resumed", new JSONMap())); + updateNotifier.next(); + } + catch (IOException e) { + ws.close(); + close(); + } + } + private void pauseDebug(Context ctx, Breakpoint bp) { + try { + state = State.PAUSED_NORMAL; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "debugCommand"); + + if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + "")); + ws.send(new V8Event("Debugger.paused", map)); + } + catch (IOException e) { + ws.close(); + close(); + } + } + private void pauseException(Context ctx) { + try { + state = State.PAUSED_EXCEPTION; + var map = new JSONMap() + .set("callFrames", serializeFrames(ctx)) + .set("reason", "exception"); + + ws.send(new V8Event("Debugger.paused", map)); + } + catch (IOException e) { + ws.close(); + close(); + } + } + + private void sendSource(DebugSource src){ + try { + ws.send(new V8Event("Debugger.scriptParsed", new JSONMap() + .set("scriptId", src.id + "") + .set("hash", src.source.hashCode()) + .set("url", src.filename + "") + )); + } + catch (IOException e) { + ws.close(); + close(); + } + } + + private Extensions sanitizeEnvironment(Extensions ext) { + var res = ext.child(); + + res.remove(EventLoop.KEY); + res.remove(DebugContext.KEY); + res.add(DebugContext.IGNORE); + + return res; + } + + private RunResult run(DebugFrame codeFrame, String code) { + if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!")); + var engine = new Engine(); + var env = codeFrame.frame.ctx.extensions.copy(); + + env.remove(DebugContext.KEY); + env.remove(EventLoop.KEY); + env.remove(GlobalScope.KEY); + env.add(EventLoop.KEY, engine); + env.add(GlobalScope.KEY, new GlobalScope(codeFrame.local)); + + var awaiter = engine.pushMsg(false, env, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + + try { + engine.run(true); + return new RunResult(env, awaiter.await(), null); + } + catch (EngineException e) { return new RunResult(env, null, e); } + catch (SyntaxException e) { return new RunResult(env, null, EngineException.ofSyntax(e.toString())); } + } + + private ObjectValue vscodeAutoSuggest(Extensions ext, Object target, String query, boolean variable) { + var res = new ArrayValue(); + var passed = new HashSet(); + var tildas = "~"; + var ctx = Context.of(ext); + if (target == null) target = GlobalScope.get(ext); + + for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { + for (var el : Values.getMembers(ctx, proto, true, true)) { + var strKey = Values.toString(ctx, el); + if (passed.contains(strKey)) continue; + passed.add(strKey); + + var val = Values.getMember(ctx, Values.getMemberDescriptor(ctx, proto, el), "value"); + var desc = new ObjectValue(); + var sortText = ""; + if (strKey.startsWith(query)) sortText += "0@"; + else if (strKey.toLowerCase().startsWith(query.toLowerCase())) sortText += "1@"; + else if (strKey.contains(query)) sortText += "2@"; + else if (strKey.toLowerCase().contains(query.toLowerCase())) sortText += "3@"; + else sortText += "4@"; + sortText += tildas + strKey; + + desc.defineProperty(ctx, "label", strKey); + desc.defineProperty(ctx, "sortText", sortText); + + if (val instanceof FunctionValue) { + if (strKey.equals("constructor")) desc.defineProperty(ctx, "type", "name"); + else desc.defineProperty(ctx, "type", variable ? "function" : "method"); + } + else desc.defineProperty(ctx, "type", variable ? "variable" : "property"); + + switch (Values.type(val)) { + case "number": + case "boolean": + desc.defineProperty(ctx, "detail", Values.toString(ctx, val)); + break; + case "object": + if (val == Values.NULL) desc.defineProperty(ctx, "detail", "null"); + else try { + desc.defineProperty(ctx, "detail", Values.getMemberPath(ctx, target, "constructor", "name")); + } + catch (IllegalArgumentException e) { + desc.defineProperty(ctx, "detail", "object"); + } + break; + case "function": { + var type = "fn("; + for (var i = 0; i < ((FunctionValue)val).length; i++) { + if (i != 0) type += ","; + type += "?"; + } + type += ")"; + desc.defineProperty(ctx, "detail", type); + break; + } + default: + desc.defineProperty(ctx, "type", Values.type(val)); + break; + } + + res.set(ctx, res.size(), desc); + } + + tildas += "~"; + variable = true; + } + + var resObj = new ObjectValue(); + resObj.defineProperty(ctx, "result", res); + resObj.defineProperty(ctx, "isArray", target instanceof ArrayValue); + return resObj; + } + + @Override public synchronized void enable(V8Message msg) throws IOException { + enabled = true; + ws.send(msg.respond()); + + for (var el : pendingSources) sendSource(el); + pendingSources.clear(); + + updateNotifier.next(); + } + @Override public synchronized void disable(V8Message msg) throws IOException { + close(); + ws.send(msg.respond()); + } + @Override public synchronized void close() { + if (state != State.RESUMED) { + resume(State.RESUMED); + } + + enabled = false; + execptionType = CatchType.NONE; + state = State.RESUMED; + + mappings.clear(); + bpLocs.clear(); + + idToBreakpoint.clear(); + + filenameToId.clear(); + idToSource.clear(); + pendingSources.clear(); + + idToFrame.clear(); + codeFrameToFrame.clear(); + + idToObject.clear(); + objectToId.clear(); + objectGroups.clear(); + + pendingPause = false; + + stepOutFrame = currFrame = null; + stepOutPtr = 0; + + for (var ctx : contexts.keySet()) ctx.detachDebugger(this); + contexts.clear(); + + updateNotifier.next(); + } + + @Override public synchronized void getScriptSource(V8Message msg) throws IOException { + int id = Integer.parseInt(msg.params.string("scriptId")); + ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source))); + } + @Override public synchronized void getPossibleBreakpoints(V8Message msg) throws IOException { + var start = deserializeLocation(msg.params.get("start")); + var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end")) : null; + var res = new JSONList(); + + for (var el : mappings.values()) { + for (var bp : el.breakpoints(start, end)) { + res.add(serializeLocation(bp)); + } + } + + ws.send(msg.respond(new JSONMap().set("locations", res))); + } + + @Override public synchronized void pause(V8Message msg) throws IOException { + pendingPause = true; + ws.send(msg.respond()); + } + @Override public synchronized void resume(V8Message msg) throws IOException { + resume(State.RESUMED); + ws.send(msg.respond(new JSONMap())); + } + + @Override public synchronized void setBreakpointByUrl(V8Message msg) throws IOException { + var line = (int)msg.params.number("lineNumber") + 1; + var col = (int)msg.params.number("columnNumber", 0) + 1; + var cond = msg.params.string("condition", "").trim(); + + if (cond.equals("")) cond = null; + if (cond != null) cond = "(" + cond + ")"; + + Pattern regex; + + if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url"))); + else if (msg.params.isString("urlRegex")) regex = Pattern.compile(msg.params.string("urlRegex")); + else { + ws.send(msg.respond(new JSONMap() + .set("breakpointId", "john-doe") + .set("locations", new JSONList()) + )); + return; + } + + var bpt = new Breakpoint(nextId(), regex, line, col, cond); + idToBreakpoint.put(bpt.id, bpt); + + + for (var el : mappings.entrySet()) { + bpt.addFunc(el.getKey(), el.getValue()); + } + + var locs = new JSONList(); + + for (var loc : bpt.resolvedLocations.values()) { + locs.add(serializeLocation(loc)); + } + + ws.send(msg.respond(new JSONMap() + .set("breakpointId", bpt.id + "") + .set("locations", locs) + )); + } + @Override public synchronized void removeBreakpoint(V8Message msg) throws IOException { + var id = Integer.parseInt(msg.params.string("breakpointId")); + + idToBreakpoint.remove(id); + updateBreakpoints(); + ws.send(msg.respond()); + } + @Override public synchronized void continueToLocation(V8Message msg) throws IOException { + // TODO: Figure out if we need this + + // var loc = correctLocation(deserializeLocation(msg.params.get("location"))); + + // tmpBreakpts.add(loc); + + // resume(State.RESUMED); + // ws.send(msg.respond()); + } + + @Override public synchronized void setPauseOnExceptions(V8Message msg) throws IOException { + switch (msg.params.string("state")) { + case "none": execptionType = CatchType.NONE; break; + case "all": execptionType = CatchType.ALL; break; + case "uncaught": execptionType = CatchType.UNCAUGHT; break; + default: + ws.send(new V8Error("Invalid exception pause type.")); + return; + } + + ws.send(msg.respond()); + } + + @Override public synchronized void stepInto(V8Message msg) throws IOException { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_IN); + ws.send(msg.respond()); + } + } + @Override public synchronized void stepOut(V8Message msg) throws IOException { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_OUT); + ws.send(msg.respond()); + } + } + @Override public synchronized void stepOver(V8Message msg) throws IOException { + if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed.")); + else { + stepOutFrame = currFrame; + stepOutPtr = currFrame.frame.codePtr; + resume(State.STEPPING_OVER); + ws.send(msg.respond()); + } + } + + @Override public synchronized void evaluateOnCallFrame(V8Message msg) throws IOException { + var cfId = Integer.parseInt(msg.params.string("callFrameId")); + var expr = msg.params.string("expression"); + var group = msg.params.string("objectGroup", null); + + var cf = idToFrame.get(cfId); + var res = run(cf, expr); + + if (group != null) addObjectGroup(group, res.result); + + if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(res.ext, res.error)))); + else ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ext, res.result)))); + } + + @Override public synchronized void releaseObjectGroup(V8Message msg) throws IOException { + var group = msg.params.string("objectGroup"); + releaseGroup(group); + ws.send(msg.respond()); + } + @Override public synchronized void releaseObject(V8Message msg) throws IOException { + var id = Integer.parseInt(msg.params.string("objectId")); + var ref = idToObject.get(id); + ref.held = false; + + if (ref.shouldRelease()) { + objectToId.remove(ref.obj); + idToObject.remove(id); + } + + ws.send(msg.respond()); + } + @Override public synchronized void getProperties(V8Message msg) throws IOException { + var ref = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var obj = ref.obj; + var ext = ref.ext; + var ctx = Context.of(ext); + var res = new JSONList(); + var own = true; + + if (obj != emptyObject && obj != null) { + while (obj != null) { + for (var key : obj.keys(true)) { + var propDesc = new JSONMap(); + + if (obj.properties.containsKey(key)) { + var prop = obj.properties.get(key); + + propDesc.set("name", Values.toString(ctx, key)); + if (prop.getter != null) propDesc.set("get", serializeObj(ext, prop.getter)); + if (prop.setter != null) propDesc.set("set", serializeObj(ext, prop.setter)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", true); + res.add(propDesc); + } + else { + propDesc.set("name", Values.toString(ctx, key)); + propDesc.set("value", serializeObj(ext, Values.getMember(ctx, obj, key))); + propDesc.set("writable", obj.memberWritable(key)); + propDesc.set("enumerable", obj.memberEnumerable(key)); + propDesc.set("configurable", obj.memberConfigurable(key)); + propDesc.set("isOwn", own); + res.add(propDesc); + } + } + + var proto = Values.getPrototype(ctx, obj); + + if (own) { + var protoDesc = new JSONMap(); + protoDesc.set("name", "__proto__"); + protoDesc.set("value", serializeObj(ext, proto == null ? Values.NULL : proto)); + protoDesc.set("writable", true); + protoDesc.set("enumerable", false); + protoDesc.set("configurable", false); + protoDesc.set("isOwn", own); + res.add(protoDesc); + } + + obj = proto; + own = false; + } + } + + ws.send(msg.respond(new JSONMap().set("result", res))); + } + @Override public synchronized void callFunctionOn(V8Message msg) throws IOException { + var src = msg.params.string("functionDeclaration"); + var args = msg.params + .list("arguments", new JSONList()) + .stream() + .map(v -> v.map()) + .map(this::deserializeArgument) + .collect(Collectors.toList()); + var byValue = msg.params.bool("returnByValue", false); + + var thisArgRef = idToObject.get(Integer.parseInt(msg.params.string("objectId"))); + var thisArg = thisArgRef.obj; + var ext = thisArgRef.ext; + var ctx = Context.of(ext); + + while (true) { + var start = src.lastIndexOf("//# sourceURL="); + if (start < 0) break; + var end = src.indexOf("\n", start); + if (end < 0) src = src.substring(0, start); + else src = src.substring(0, start) + src.substring(end + 1); + } + + try { + Object res = null; + if (compare(src, VSCODE_EMPTY)) res = emptyObject; + else if (compare(src, VSCODE_SELF)) res = thisArg; + else if (compare(src, CHROME_GET_PROP_FUNC)) { + res = thisArg; + for (var el : JSON.parse(null, (String)args.get(0)).list()) res = Values.getMember(ctx, res, JSON.toJs(el)); + } + else if (compare(src, CHROME_GET_PROP_FUNC_2)) { + res = Values.call(ctx, args.get(0), thisArg); + } + else if (compare(src, VSCODE_CALL)) { + var func = (FunctionValue)(args.size() < 1 ? null : args.get(0)); + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ext, func.call(ctx, thisArg))))); + } + else if (compare(src, VSCODE_AUTOCOMPLETE)) { + var target = args.get(0); + if (target == null) target = thisArg; + res = vscodeAutoSuggest(ext, target, Values.toString(ctx, args.get(1)), Values.toBoolean(args.get(2))); + } + else { + ws.send(new V8Error("Please use well-known functions with callFunctionOn")); + return; + } + ws.send(msg.respond(new JSONMap().set("result", serializeObj(ext, res, byValue)))); + } + catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ext, e)))); } + } + + @Override public synchronized void runtimeEnable(V8Message msg) throws IOException { + ws.send(msg.respond()); + } + + @Override public void onSourceLoad(Filename filename, String source) { + int id = nextId(); + var src = new DebugSource(id, filename, source); + + idToSource.put(id, src); + filenameToId.put(filename, id); + + if (!enabled) pendingSources.add(src); + else sendSource(src); + } + @Override public void onFunctionLoad(FunctionBody body, FunctionMap map) { + for (var bpt : idToBreakpoint.values()) { + bpt.addFunc(body, map); + } + mappings.put(body, map); + } + @Override public boolean onInstruction(Context ctx, Frame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (!enabled) return false; + + boolean isBreakpointable; + Location loc; + DebugFrame frame; + BreakpointType bptType; + + synchronized (this) { + frame = getFrame(cf); + + var map = DebugContext.get(ctx).getMap(frame.frame.function); + + frame.updateLoc(map.toLocation(frame.frame.codePtr)); + loc = frame.location; + bptType = map.getBreakpoint(frame.frame.codePtr); + isBreakpointable = loc != null && (bptType.shouldStepIn()); + + if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { + pauseException(ctx); + } + else if ( + loc != null && + (state == State.STEPPING_IN || state == State.STEPPING_OVER) && + returnVal != Values.NO_RETURN && stepOutFrame == frame + ) { + pauseDebug(ctx, null); + } + else if (isBreakpointable && bpLocs.containsKey(loc)) { + for (var bp : bpLocs.get(loc)) { + var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result); + if (ok) pauseDebug(ctx, bp); + } + } + // else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null); + else if (isBreakpointable && pendingPause) { + pauseDebug(ctx, null); + pendingPause = false; + } + else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null); + } + + + while (enabled) { + synchronized (this) { + switch (state) { + case PAUSED_EXCEPTION: + case PAUSED_NORMAL: break; + + case STEPPING_OUT: + case RESUMED: return false; + + case STEPPING_IN: + case STEPPING_OVER: + if (stepOutFrame.frame == frame.frame) { + if (returnVal != Values.NO_RETURN || error != null) { + state = State.STEPPING_OUT; + continue; + } + else if (stepOutPtr != frame.frame.codePtr) { + + if (state == State.STEPPING_IN && bptType.shouldStepIn()) { + pauseDebug(ctx, null); + break; + } + else if (state == State.STEPPING_OVER && bptType.shouldStepOver()) { + pauseDebug(ctx, null); + break; + } + } + } + return false; + } + } + updateNotifier.await(); + } + + return false; + } + @Override public void onFramePush(Context ctx, Frame frame) { + var prevFrame = currFrame; + updateFrames(ctx); + + if (stepOutFrame != null && stepOutFrame.frame == prevFrame.frame && state == State.STEPPING_IN) { + stepOutFrame = currFrame; + } + } + @Override public void onFramePop(Context ctx, Frame frame) { + updateFrames(ctx); + + try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } + catch (NullPointerException e) { } + + if (ctx.stackSize == 0) { + if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED); + } + else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) { + state = State.STEPPING_IN; + stepOutFrame = currFrame; + } + } + + public SimpleDebugger attach(DebugContext ctx) { + ctx.attachDebugger(this); + contexts.put(ctx, ctx); + return this; + } + + public SimpleDebugger(WebSocket ws) { + this.ws = ws; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java new file mode 100644 index 0000000..352dc70 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Error.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.utils.debug; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONMap; + +public class V8Error { + public final String message; + + public V8Error(String message) { + this.message = message; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap().set("error", new JSONMap() + .set("message", message) + )); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java new file mode 100644 index 0000000..4ce0a36 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Event.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.utils.debug; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONMap; + +public class V8Event { + public final String name; + public final JSONMap params; + + public V8Event(String name, JSONMap params) { + this.name = name; + this.params = params; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + ); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java new file mode 100644 index 0000000..6d31e0d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Message.java @@ -0,0 +1,50 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.util.Map; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; +import me.topchetoeu.jscript.common.json.JSONMap; + +public class V8Message { + public final String name; + public final int id; + public final JSONMap params; + + public V8Message(String name, int id, Map params) { + this.name = name; + this.params = new JSONMap(params); + this.id = id; + } + public V8Result respond(JSONMap result) { + return new V8Result(id, result); + } + public V8Result respond() { + return new V8Result(id, new JSONMap()); + } + + public V8Message(JSONMap raw) { + if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'."); + if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'."); + + this.name = raw.string("method"); + this.id = (int)raw.number("id"); + this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); + } + public V8Message(String raw) { + this(JSON.parse(null, raw).map()); + } + + public JSONMap toMap() { + var res = new JSONMap(); + return res; + } + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + .set("id", id) + ); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java new file mode 100644 index 0000000..d28d33a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/V8Result.java @@ -0,0 +1,22 @@ +package me.topchetoeu.jscript.utils.debug; + +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONMap; + +public class V8Result { + public final int id; + public final JSONMap result; + + public V8Result(int id, JSONMap result) { + this.id = id; + this.result = result; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("id", id) + .set("result", result) + ); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java new file mode 100644 index 0000000..bb80125 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocket.java @@ -0,0 +1,195 @@ +package me.topchetoeu.jscript.utils.debug; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import me.topchetoeu.jscript.utils.debug.WebSocketMessage.Type; + +public class WebSocket implements AutoCloseable { + public long maxLength = 1 << 20; + + private Socket socket; + private boolean closed = false; + + private OutputStream out() throws IOException { + return socket.getOutputStream(); + } + private InputStream in() throws IOException { + return socket.getInputStream(); + } + + private long readLen(int byteLen) throws IOException { + long res = 0; + + if (byteLen == 126) { + res |= in().read() << 8; + res |= in().read(); + return res; + } + else if (byteLen == 127) { + res |= in().read() << 56; + res |= in().read() << 48; + res |= in().read() << 40; + res |= in().read() << 32; + res |= in().read() << 24; + res |= in().read() << 16; + res |= in().read() << 8; + res |= in().read(); + return res; + } + else return byteLen; + } + private byte[] readMask(boolean has) throws IOException { + if (has) { + return new byte[] { + (byte)in().read(), + (byte)in().read(), + (byte)in().read(), + (byte)in().read() + }; + } + else return new byte[4]; + } + + private void writeLength(int len) throws IOException { + if (len < 126) { + out().write((int)len); + } + else if (len <= 0xFFFF) { + out().write(126); + out().write((int)(len >> 8) & 0xFF); + out().write((int)len & 0xFF); + } + else { + out().write(127); + out().write((len >> 56) & 0xFF); + out().write((len >> 48) & 0xFF); + out().write((len >> 40) & 0xFF); + out().write((len >> 32) & 0xFF); + out().write((len >> 24) & 0xFF); + out().write((len >> 16) & 0xFF); + out().write((len >> 8) & 0xFF); + out().write(len & 0xFF); + } + } + private synchronized void write(int type, byte[] data) throws IOException { + int i; + + for (i = 0; i < data.length / 0xFFFF; i++) { + out().write(type); + writeLength(0xFFFF); + out().write(data, i * 0xFFFF, 0xFFFF); + type = 0; + } + + out().write(type | 0x80); + writeLength(data.length % 0xFFFF); + out().write(data, i * 0xFFFF, data.length % 0xFFFF); + } + + public void send(String data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.getBytes()); + } + public void send(byte[] data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(2, data); + } + public void send(WebSocketMessage msg) throws IOException { + if (msg.type == Type.Binary) send(msg.binaryData()); + else send(msg.textData()); + } + public void send(Object data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.toString().getBytes()); + } + + public void close(String reason) { + if (socket != null) { + try { + write(8, reason.getBytes()); + socket.close(); + } + catch (Throwable e) { } + } + + socket = null; + closed = true; + } + public void close() { + close(""); + } + + private WebSocketMessage fail(String reason) { + System.out.println("WebSocket Error: " + reason); + close(reason); + return null; + } + + private byte[] readData() throws IOException { + var maskLen = in().read(); + var hasMask = (maskLen & 0x80) != 0; + var len = (int)readLen(maskLen & 0x7F); + var mask = readMask(hasMask); + + if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size"); + else { + var buff = new byte[len]; + + if (in().read(buff) < len) fail("WebSocket Error: payload too short"); + else { + for (int i = 0; i < len; i++) { + buff[i] ^= mask[(int)(i % 4)]; + } + return buff; + } + } + + return null; + } + + public WebSocketMessage receive() throws IOException { + var data = new ByteArrayOutputStream(); + var type = 0; + + while (socket != null && !closed) { + var finId = in().read(); + if (finId < 0) break; + var fin = (finId & 0x80) != 0; + int id = finId & 0x0F; + + if (id == 0x8) { close(); return null; } + if (id >= 0x8) { + if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented"); + if (id == 0x9) write(0xA, data.toByteArray()); + continue; + } + + if (type == 0) type = id; + if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment"); + + var buff = readData(); + if (buff == null) break; + + if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size"); + data.write(buff); + + if (!fin) continue; + var raw = data.toByteArray(); + + if (type == 1) { + return new WebSocketMessage(new String(raw)); + } + else return new WebSocketMessage(raw); + } + + return null; + } + + public WebSocket(Socket socket) { + this.socket = socket; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java new file mode 100644 index 0000000..10d3959 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/debug/WebSocketMessage.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.utils.debug; + +public class WebSocketMessage { + public static enum Type { + Text, + Binary, + } + + public final Type type; + private final Object data; + + public final String textData() { + if (type != Type.Text) throw new IllegalStateException("Message is not text."); + return (String)data; + } + public final byte[] binaryData() { + if (type != Type.Binary) throw new IllegalStateException("Message is not binary."); + return (byte[])data; + } + + public WebSocketMessage(String data) { + this.type = Type.Text; + this.data = data; + } + public WebSocketMessage(byte[] data) { + this.type = Type.Binary; + this.data = data; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java new file mode 100644 index 0000000..323fadb --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ActionType.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public enum ActionType { + UNKNOWN(0, "An operation performed upon", "An operation was performed upon"), + READ(1, "Reading from", "Read from"), + WRITE(2, "Writting to", "Wrote to"), + SEEK(3, "Seeking in", "Sought in"), + CLOSE(4, "Closing", "Closed"), + STAT(5, "Stat of", "Statted"), + OPEN(6, "Opening", "Opened"), + CREATE(7, "Creating", "Created"), + DELETE(8, "Deleting", "Deleted"), + CLOSE_FS(9, "Closing filesystem", "Closed filesystem"); + + public final int code; + public final String continuous, past; + + public String readable(boolean usePast) { + if (usePast) return past; + else return continuous; + } + + private ActionType(int code, String continuous, String past) { + this.code = code; + this.continuous = continuous; + this.past = past; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java new file mode 100644 index 0000000..f552bba --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/BaseFile.java @@ -0,0 +1,59 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public abstract class BaseFile implements File { + private T handle; + private Mode mode; + + protected final T handle() { + return handle; + } + + protected abstract int onRead(byte[] buff); + protected abstract void onWrite(byte[] buff); + protected abstract long onSeek(long offset, int pos); + protected abstract boolean onClose(); + + @Override public synchronized int read(byte[] buff) { + try { + if (handle == null) throw new FilesystemException(ErrorReason.CLOSED); + if (!mode.readable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for reading."); + return onRead(buff); + } + catch (FilesystemException e) { throw e.setAction(ActionType.READ); } + } + @Override public synchronized void write(byte[] buff) { + try { + if (handle == null) throw new FilesystemException(ErrorReason.CLOSED); + if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for writting."); + onWrite(buff); + } + catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); } + } + @Override public synchronized long seek(long offset, int pos) { + try { + if (handle == null) throw new FilesystemException(ErrorReason.CLOSED); + if (mode == Mode.NONE) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for seeking."); + return onSeek(offset, pos); + } + catch (FilesystemException e) { throw e.setAction(ActionType.SEEK); } + } + @Override public synchronized boolean close() { + if (handle != null) { + try { + var res = onClose(); + handle = null; + mode = Mode.NONE; + return res; + } + catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE); } + } + else return false; + } + + public BaseFile(T handle, Mode mode) { + this.mode = mode; + this.handle = handle; + + if (mode == Mode.NONE) this.handle = null; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java new file mode 100644 index 0000000..09bd373 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/EntryType.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public enum EntryType { + NONE("none"), + FILE("file"), + FOLDER("folder"); + + public final String name; + + private EntryType(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java new file mode 100644 index 0000000..91cdc2b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/ErrorReason.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public enum ErrorReason { + UNKNOWN(0, "failed", false), + NO_PERMISSION(1, "is not allowed", false), + CLOSED(1, "that was closed", true), + UNSUPPORTED(2, "is not supported", false), + ILLEGAL_ARGS(3, "with illegal arguments", true), + DOESNT_EXIST(4, "that doesn't exist", true), + ALREADY_EXISTS(5, "that already exists", true), + ILLEGAL_PATH(6, "with illegal path", true), + NO_PARENT(7, "with a missing parent folder", true); + + public final int code; + public final boolean usePast; + public final String readable; + + private ErrorReason(int code, String readable, boolean usePast) { + this.code = code; + this.readable = readable; + this.usePast = usePast; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java new file mode 100644 index 0000000..f98c566 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/File.java @@ -0,0 +1,169 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.LinkedList; + +import me.topchetoeu.jscript.common.Buffer; +public interface File { + default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); } + default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); } + default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); } + default boolean close() { return false; } + + default byte[] readAll() { + var parts = new LinkedList(); + var sizes = new LinkedList(); + var buff = new byte[1024]; + var size = 0; + + while (true) { + var n = read(buff); + if (n < 0) break; + else if (n == 0) continue; + + parts.add(buff); + sizes.add(n); + size += n; + buff = new byte[1024]; + } + + buff = new byte[size]; + + var i = 0; + var j = 0; + + for (var part : parts) { + var currSize = sizes.get(j++); + + System.arraycopy(part, 0, buff, i, currSize); + i += currSize; + } + + return buff; + } + default String readToString() { + return new String(readAll()); + } + default String readLine() { + var res = new Buffer(); + var buff = new byte[1]; + + while (true) { + if (read(buff) == 0) { + if (res.length() == 0) return null; + else break; + } + + if (buff[0] == '\n') break; + + res.write(res.length(), buff); + } + return new String(res.data()); + } + + public static File ofStream(InputStream str) { + return new File() { + @Override public synchronized int read(byte[] buff) { + try { + try { return str.read(buff); } + catch (NullPointerException e) { throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); } + catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); } + } + catch (FilesystemException e) { throw e.setAction(ActionType.READ); } + } + }; + } + public static File ofStream(OutputStream str) { + return new File() { + @Override public synchronized void write(byte[] buff) { + try { + try { str.write(buff); } + catch (NullPointerException e) {throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); } + catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); } + } + catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); } + } + }; + } + public static File ofLineWriter(LineWriter writer) { + var buff = new Buffer(); + return new File() { + @Override public synchronized void write(byte[] val) { + try { + if (val == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null."); + + for (var b : val) { + if (b == '\n') { + try { + writer.writeLine(new String(buff.data())); + buff.clear(); + } + catch (IOException e) { + throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); + } + } + else buff.append(b); + } + } + catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); } + } + }; + } + public static File ofLineReader(LineReader reader) { + return new File() { + private int offset = 0; + private byte[] prev = new byte[0]; + + @Override + public synchronized int read(byte[] buff) { + try { + if (buff == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null."); + var ptr = 0; + + while (true) { + if (prev == null) break; + if (offset >= prev.length) { + try { + var line = reader.readLine(); + + if (line == null) { + prev = null; + break; + } + else prev = (line + "\n").getBytes(); + + offset = 0; + } + catch (IOException e) { + throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); + } + } + + if (ptr + prev.length - offset > buff.length) { + var n = buff.length - ptr; + System.arraycopy(prev, offset, buff, ptr, buff.length - ptr); + offset += n; + ptr += n; + break; + } + else { + var n = prev.length - offset; + System.arraycopy(prev, offset, buff, ptr, n); + offset += n; + ptr += n; + } + } + + return ptr; + } + catch (FilesystemException e) { throw e.setAction(ActionType.READ); } + } + }; + } + public static File ofIterator(Iterator it) { + return ofLineReader(LineReader.ofIterator(it)); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java new file mode 100644 index 0000000..903bde1 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FileStat.java @@ -0,0 +1,14 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public class FileStat { + public final Mode mode; + public final EntryType type; + + public FileStat(Mode mode, EntryType type) { + if (mode == Mode.NONE) type = EntryType.NONE; + if (type == EntryType.NONE) mode = Mode.NONE; + + this.mode = mode; + this.type = type; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java new file mode 100644 index 0000000..6f477cb --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Filesystem.java @@ -0,0 +1,18 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; + +public interface Filesystem { + public static final Key KEY = new Key<>(); + + default String normalize(String... path) { return Paths.normalize(path); } + default boolean create(String path, EntryType type) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.CREATE); } + File open(String path, Mode mode); + FileStat stat(String path); + void close(); + + public static Filesystem get(Extensions exts) { + return exts.get(KEY); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java new file mode 100644 index 0000000..802179a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/FilesystemException.java @@ -0,0 +1,86 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.util.ArrayList; + +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.Values; + +public class FilesystemException extends RuntimeException { + public final ErrorReason reason; + public final String details; + private ActionType action; + private EntryType entry = EntryType.FILE; + private String path; + + public FilesystemException setPath(String path) { + this.path = path; + return this; + } + public FilesystemException setAction(ActionType action) { + if (action == null) action = ActionType.UNKNOWN; + + this.action = action; + return this; + } + public FilesystemException setEntry(EntryType entry) { + if (entry == null) entry = EntryType.NONE; + + this.entry = entry; + return this; + } + + public ActionType action() { + return action; + } + public String path() { + return path; + } + public EntryType entry() { + return entry; + } + + public EngineException toEngineException() { + var res = EngineException.ofError("IOError", getMessage()); + + Values.setMember(null, res.value, "action", action.code); + Values.setMember(null, res.value, "reason", reason.code); + Values.setMember(null, res.value, "path", path); + Values.setMember(null, res.value, "entry", entry.name); + if (details != null) Values.setMember(null, res.value, "details", details); + + return res; + } + + @Override public String getMessage() { + var parts = new ArrayList(10); + + parts.add(action == null ? "An action performed upon " : action.readable(reason.usePast)); + + if (entry == EntryType.FILE) parts.add("file"); + if (entry == EntryType.FOLDER) parts.add("folder"); + + if (path != null && !path.isBlank()) parts.add(path.trim()); + + parts.add(reason.readable); + + var msg = String.join(" ", parts); + if (details != null) msg += ": " + details; + + return msg; + } + + public FilesystemException(ErrorReason type, String details) { + super(); + if (type == null) type = ErrorReason.UNKNOWN; + + this.details = details; + this.reason = type; + } + public FilesystemException(ErrorReason type) { + this(type, null); + } + public FilesystemException() { + this(null); + } + +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java new file mode 100644 index 0000000..c6dbacb --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/HandleManager.java @@ -0,0 +1,32 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.util.HashSet; +import java.util.Set; + +public class HandleManager { + private Set files = new HashSet<>(); + + public File put(File val) { + var handle = new File() { + @Override public int read(byte[] buff) { + return val.read(buff); + } + @Override public void write(byte[] buff) { + val.write(buff); + } + @Override public long seek(long offset, int pos) { + return val.seek(offset, pos); + } + @Override public boolean close() { + return files.remove(this) && val.close(); + } + }; + files.add(handle); + return handle; + } + public void close() { + while (!files.isEmpty()) { + files.stream().findFirst().get().close(); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java new file mode 100644 index 0000000..6d84450 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineReader.java @@ -0,0 +1,16 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.IOException; +import java.util.Iterator; + + +public interface LineReader { + String readLine() throws IOException; + + public static LineReader ofIterator(Iterator it) { + return () -> { + if (it.hasNext()) return it.next(); + else return null; + }; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java new file mode 100644 index 0000000..501631a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/LineWriter.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.IOException; + +public interface LineWriter { + void writeLine(String value) throws IOException; +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java new file mode 100644 index 0000000..cb89d1d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFile.java @@ -0,0 +1,36 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import me.topchetoeu.jscript.common.Buffer; + +class MemoryFile extends BaseFile { + private int ptr; + + @Override protected int onRead(byte[] buff) { + if (ptr >= handle().length()) return -1; + var res = handle().read(ptr, buff); + ptr += res; + return res; + } + @Override protected void onWrite(byte[] buff) { + handle().write(ptr, buff); + ptr += buff.length; + } + @Override protected long onSeek(long offset, int pos) { + if (pos == 0) ptr = (int)offset; + else if (pos == 1) ptr += (int)offset; + else if (pos == 2) ptr = handle().length() - (int)offset; + + if (ptr < 0) ptr = 0; + if (ptr > handle().length()) ptr = handle().length(); + + return pos; + } + @Override protected boolean onClose() { + ptr = 0; + return true; + } + + public MemoryFile(Buffer buff, Mode mode) { + super(buff, mode); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java new file mode 100644 index 0000000..68b1e4c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/MemoryFilesystem.java @@ -0,0 +1,100 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; + +import me.topchetoeu.jscript.common.Buffer; +import me.topchetoeu.jscript.common.Filename; + +public class MemoryFilesystem implements Filesystem { + public final Mode mode; + private HashMap files = new HashMap<>(); + private HashSet folders = new HashSet<>(); + private HandleManager handles = new HandleManager(); + + private Path realPath(String path) { + return Filename.normalize(path); + } + + @Override public String normalize(String... path) { + return Paths.normalize(path); + } + @Override public synchronized File open(String _path, Mode perms) { + try { + var path = realPath(_path); + var pcount = path.getNameCount(); + + if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms)); + else if (folders.contains(path)) { + var res = new StringBuilder(); + + for (var folder : folders) { + if (pcount + 1 != folder.getNameCount()) continue; + if (!folder.startsWith(path)) continue; + res.append(folder.toFile().getName()).append('\n'); + } + + for (var file : files.keySet()) { + if (pcount + 1 != file.getNameCount()) continue; + if (!file.startsWith(path)) continue; + res.append(file.toFile().getName()).append('\n'); + } + + return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ))); + } + else throw new FilesystemException(ErrorReason.DOESNT_EXIST); + } + catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); } + } + @Override public synchronized boolean create(String _path, EntryType type) { + try { + var path = realPath(_path); + + switch (type) { + case FILE: + if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT); + if (folders.contains(path) || files.containsKey(path)) return false; + files.put(path, new Buffer()); + return true; + case FOLDER: + if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT); + if (folders.contains(path) || files.containsKey(path)) return false; + folders.add(path); + return true; + default: + case NONE: + return folders.remove(path) || files.remove(path) != null; + } + } + catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.CREATE); } + } + @Override public synchronized FileStat stat(String _path) { + var path = realPath(_path); + + if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE); + else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER); + else return new FileStat(Mode.NONE, EntryType.NONE); + } + @Override public synchronized void close() throws FilesystemException { + handles.close(); + } + + public MemoryFilesystem put(String path, byte[] data) { + var _path = realPath(path); + var _curr = "/"; + + for (var seg : _path) { + create(_curr, EntryType.FOLDER); + _curr += seg + "/"; + } + + files.put(_path, new Buffer(data)); + return this; + } + + public MemoryFilesystem(Mode mode) { + this.mode = mode; + folders.add(Path.of("/")); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java new file mode 100644 index 0000000..b64d288 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Mode.java @@ -0,0 +1,41 @@ +package me.topchetoeu.jscript.utils.filesystem; + +public enum Mode { + NONE("", false, false), + READ("r", true, false), + WRITE("rw", false, true), + READ_WRITE("rw", true, true); + + public final String name; + public final boolean readable; + public final boolean writable; + + public Mode intersect(Mode other) { + return of(readable && other.readable, writable && other.writable); + } + + private Mode(String mode, boolean r, boolean w) { + this.name = mode; + this.readable = r; + this.writable = w; + } + + public static Mode of(boolean read, boolean write) { + if (read && write) return READ_WRITE; + if (read) return READ; + if (write) return WRITE; + return NONE; + } + + public static Mode parse(String mode) { + switch (mode.toLowerCase()) { + case "r": return READ; + case "w": return WRITE; + case "r+": + case "w+": + case "wr": + case "rw": return READ_WRITE; + default: return NONE; + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java new file mode 100644 index 0000000..0bc3967 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/Paths.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.util.ArrayList; + +public class Paths { + public static String normalize(String... path) { + var parts = String.join("/", path).split("[\\\\/]"); + var res = new ArrayList(); + + for (var part : parts) { + if (part.equals("...")) res.clear(); + else if (part.equals("..")) { + if (res.size() > 0) res.remove(res.size() - 1); + } + else if (!part.equals(".") && !part.isEmpty()) res.add(part); + } + + var sb = new StringBuilder(); + + for (var el : res) sb.append("/").append(el); + + if (sb.length() == 0) return "/"; + else return sb.toString(); + } + + public static String chroot(String root, String path) { + return normalize(root) + normalize(path); + } + + public static String cwd(String cwd, String path) { + return normalize(cwd + "/" + path); + } + + public static String filename(String path) { + var i = path.lastIndexOf('/'); + if (i < 0) i = path.lastIndexOf('\\'); + + if (i < 0) return path; + else return path.substring(i + 1); + } + + public static String extension(String path) { + var i = path.lastIndexOf('.'); + + if (i < 0) return ""; + else return path.substring(i + 1); + } + + public static String dir(String path) { + return normalize(path + "/.."); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java new file mode 100644 index 0000000..67efb80 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFile.java @@ -0,0 +1,35 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Path; + +public class PhysicalFile extends BaseFile { + @Override protected int onRead(byte[] buff) { + try { return handle().read(buff); } + catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.READ); } + } + @Override protected void onWrite(byte[] buff) { + try { handle().write(buff); } + catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.WRITE); } + } + @Override protected long onSeek(long offset, int pos) { + try { + if (pos == 1) offset += handle().getFilePointer(); + else if (pos == 2) offset += handle().length(); + handle().seek(offset); + return offset; + } + catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.SEEK); } + } + @Override protected boolean onClose() { + try { handle().close(); } + catch (IOException e) {} // SHUT + return true; + } + + public PhysicalFile(Path path, Mode mode) throws FileNotFoundException { + super(new RandomAccessFile(path.toFile(), mode.name), mode); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java new file mode 100644 index 0000000..9ff0c0e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/PhysicalFilesystem.java @@ -0,0 +1,92 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; + +public class PhysicalFilesystem implements Filesystem { + public final String root; + private HandleManager handles = new HandleManager(); + + private void checkMode(Path path, Mode mode) { + if (!path.startsWith(root)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "Tried to jailbreak the sandbox."); + + if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions"); + if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions"); + } + + private Path realPath(String path) { + return Path.of(Paths.chroot(root, path)); + } + + @Override public String normalize(String... paths) { + return Paths.normalize(paths); + } + @Override public synchronized File open(String _path, Mode perms) { + try { + var path = realPath(normalize(_path)); + checkMode(path, perms); + + try { + if (Files.isDirectory(path)) return handles.put(File.ofIterator( + Files.list(path).map(v -> v.getFileName().toString()).iterator() + )); + else return handles.put(new PhysicalFile(path, perms)); + } + catch (FileNotFoundException e) { throw new FilesystemException(ErrorReason.DOESNT_EXIST); } + catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION); } + } + catch (FilesystemException e) { throw e.setAction(ActionType.OPEN).setPath(_path); } + } + @Override public synchronized boolean create(String _path, EntryType type) { + try { + var path = realPath(_path); + + try { + switch (type) { + case FILE: + Files.createFile(path); + break; + case FOLDER: + Files.createDirectories(path); + break; + case NONE: + default: + Files.delete(path); + } + } + catch (FileAlreadyExistsException | NoSuchFileException e) { return false; } + catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PARENT); } + } + catch (FilesystemException e) { throw e.setAction(ActionType.CREATE).setPath(_path); } + + return true; + } + @Override public synchronized FileStat stat(String _path) { + var path = realPath(_path); + + if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE); + + var perms = Mode.of(Files.isReadable(path), Files.isWritable(path)); + + if (perms == Mode.NONE) return new FileStat(Mode.NONE, EntryType.NONE); + + return new FileStat( + perms, + Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE + ); + } + @Override public synchronized void close() throws FilesystemException { + try { + handles.close(); + } + catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); } + } + + public PhysicalFilesystem(String root) { + this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString()); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java new file mode 100644 index 0000000..1db7355 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java @@ -0,0 +1,99 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.utils.permissions.Matcher; +import me.topchetoeu.jscript.utils.permissions.Permission; +import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; + +public class RootFilesystem implements Filesystem { + public final Map protocols = new HashMap<>(); + public final PermissionsProvider perms; + + public static final Permission PERM_READ = new Permission("jscript.file.read", Matcher.fileWildcard()); + public static final Permission PERM_WRITE = new Permission("jscript.file.read", Matcher.fileWildcard()); + + private boolean canRead(String _path) { + return perms.hasPermission(PERM_READ, _path); + } + private boolean canWrite(String _path) { + return perms.hasPermission(PERM_WRITE, _path); + } + + private void modeAllowed(String _path, Mode mode) throws FilesystemException { + if (mode.readable && perms != null && !canRead(_path)) { + throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions").setPath(_path); + } + if (mode.writable && perms != null && !canWrite(_path)) { + throw new FilesystemException(ErrorReason.NO_PERMISSION, "No wtrite permissions").setPath(_path); + } + } + + private Filesystem getProtocol(Filename filename) { + var protocol = protocols.get(filename.protocol); + + if (protocol == null) { + throw new FilesystemException(ErrorReason.DOESNT_EXIST, "The protocol '" + filename.protocol + "' doesn't exist."); + } + + return protocol; + } + + @Override public String normalize(String... paths) { + if (paths.length == 0) return "file://"; + else { + var filename = Filename.parse(paths[0]); + var protocol = protocols.get(filename.protocol); + paths[0] = filename.path; + + + if (protocol == null) return Paths.normalize(paths); + else return filename.protocol + "://" + protocol.normalize(paths); + } + } + @Override public synchronized File open(String path, Mode perms) throws FilesystemException { + try { + var filename = Filename.parse(path); + var protocol = getProtocol(filename); + + modeAllowed(filename.toString(), perms); + return protocol.open(filename.path, perms); + } + catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.OPEN); } + } + @Override public synchronized boolean create(String path, EntryType type) throws FilesystemException { + try { + var filename = Filename.parse(path); + var protocol = getProtocol(filename); + + modeAllowed(filename.toString(), Mode.WRITE); + return protocol.create(filename.path, type); + } + catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); } + } + @Override public synchronized FileStat stat(String path) throws FilesystemException { + try { + var filename = Filename.parse(path); + var protocol = getProtocol(filename); + + return protocol.stat(filename.path); + } + catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.STAT); } + } + @Override public synchronized void close() throws FilesystemException { + try { + for (var protocol : protocols.values()) { + protocol.close(); + } + + protocols.clear(); + } + catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); } + } + + public RootFilesystem(PermissionsProvider perms) { + this.perms = perms; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java b/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java new file mode 100644 index 0000000..5b82c0e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/filesystem/STDFilesystem.java @@ -0,0 +1,52 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import java.io.InputStream; +import java.io.OutputStream; + +public class STDFilesystem implements Filesystem { + private File in; + private File out; + private File err; + + @Override + public String normalize(String... path) { + var res = Paths.normalize(path); + while (res.startsWith("/")) res = res.substring(1); + while (res.endsWith("/")) res = res.substring(0, res.length() - 1); + return res; + } + + @Override public synchronized File open(String path, Mode mode) { + path = normalize(path); + if (in != null && path.equals("in")) return in; + else if (out != null && path.equals("out")) return out; + else if (err != null && path.equals("err")) return err; + else throw new FilesystemException(ErrorReason.DOESNT_EXIST).setAction(ActionType.OPEN).setPath(path); + } + @Override public synchronized FileStat stat(String path) { + path = normalize(path); + if (path.equals("in") || path.equals("out") || path.equals("err")) return new FileStat(Mode.READ_WRITE, EntryType.FILE); + else return new FileStat(Mode.NONE, EntryType.NONE); + } + @Override public synchronized void close() { + in = out = err = null; + } + + public STDFilesystem(File in, File out, File err) { + this.in = in; + this.out = out; + this.err = err; + } + public STDFilesystem(InputStream in, OutputStream out, OutputStream err) { + if (in != null) this.in = File.ofStream(in); + if (out != null) this.out = File.ofStream(out); + if (err != null) this.err = File.ofStream(err); + } + public STDFilesystem(LineReader in, LineWriter out) { + if (in != null) this.in = File.ofLineReader(in); + if (out != null) { + this.out = File.ofLineWriter(out); + this.err = File.ofLineWriter(out); + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java b/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java new file mode 100644 index 0000000..4e8c2ff --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/Arguments.java @@ -0,0 +1,139 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.reflect.Array; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.runtime.values.NativeWrapper; +import me.topchetoeu.jscript.runtime.values.Values; + +public class Arguments implements Extensions { + public final Object self; + public final Object[] args; + public final Context ctx; + + @Override public void add(Key key, T obj) { + ctx.add(key, obj); + } + @Override public T get(Key key) { + return ctx.get(key); + } + @Override public boolean has(Key key) { + return ctx.has(key); + } + @Override public boolean remove(Key key) { + return ctx.remove(key); + } + @Override public Iterable> keys() { + return ctx.keys(); + } + + public int n() { + return args.length; + } + + public boolean has(int i) { + return i == -1 || i >= 0 && i < args.length; + } + + public T self(Class type) { + return convert(-1, type); + } + public T convert(int i, Class type) { + return Values.convert(ctx, get(i), type); + } + public Object get(int i, boolean unwrap) { + Object res = null; + + if (i == -1) res = self; + if (i >= 0 && i < args.length) res = args[i]; + if (unwrap && res instanceof NativeWrapper) res = ((NativeWrapper)res).wrapped; + + return res; + } + public Object get(int i) { + return get(i, false); + } + public Object getOrDefault(int i, Object def) { + if (i < 0 || i >= args.length) return def; + else return get(i); + } + + public Arguments slice(int start) { + var res = new Object[Math.max(0, args.length - start)]; + for (int j = start; j < args.length; j++) res[j - start] = get(j); + return new Arguments(ctx, args, res); + } + + @SuppressWarnings("unchecked") + public T[] convert(Class type) { + var res = Array.newInstance(type, args.length); + for (int i = 0; i < args.length; i++) Array.set(res, i, convert(i, type)); + return (T[])res; + } + public int[] convertInt() { + var res = new int[args.length]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Integer.class); + return res; + } + public long[] convertLong() { + var res = new long[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Long.class); + return res; + } + public short[] sliceShort() { + var res = new short[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Short.class); + return res; + } + public float[] sliceFloat() { + var res = new float[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Float.class); + return res; + } + public double[] sliceDouble() { + var res = new double[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Double.class); + return res; + } + public byte[] sliceByte() { + var res = new byte[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Byte.class); + return res; + } + public char[] sliceChar() { + var res = new char[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Character.class); + return res; + } + public boolean[] sliceBool() { + var res = new boolean[Math.max(0, args.length)]; + for (int i = 0; i < args.length; i++) res[i] = convert(i, Boolean.class); + return res; + } + + public String getString(int i) { return Values.toString(ctx, get(i)); } + public boolean getBoolean(int i) { return Values.toBoolean(get(i)); } + public int getInt(int i) { return (int)Values.toNumber(ctx, get(i)); } + public long getLong(int i) { return (long)Values.toNumber(ctx, get(i)); } + public double getDouble(int i) { return Values.toNumber(ctx, get(i)); } + public float getFloat(int i) { return (float)Values.toNumber(ctx, get(i)); } + + public int getInt(int i, int def) { + var res = get(i); + if (res == null) return def; + else return (int)Values.toNumber(ctx, res); + } + public String getString(int i, String def) { + var res = get(i); + if (res == null) return def; + else return Values.toString(ctx, res); + } + + public Arguments(Context ctx, Object thisArg, Object... args) { + this.ctx = ctx; + this.args = args; + this.self = thisArg; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java b/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java new file mode 100644 index 0000000..5ef6815 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/Expose.java @@ -0,0 +1,14 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Expose { + public String value() default ""; + public ExposeType type() default ExposeType.METHOD; + public ExposeTarget target() default ExposeTarget.MEMBER; +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java new file mode 100644 index 0000000..f5b97e6 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeConstructor.java @@ -0,0 +1,10 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExposeConstructor { } diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java new file mode 100644 index 0000000..a66330e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeField.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExposeField { + public String value() default ""; + public ExposeTarget target() default ExposeTarget.MEMBER; +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java new file mode 100644 index 0000000..311a627 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeTarget.java @@ -0,0 +1,28 @@ +package me.topchetoeu.jscript.utils.interop; + +public enum ExposeTarget { + STATIC(true, true, false), + MEMBER(false, false, true), + NAMESPACE(false, true, false), + CONSTRUCTOR(true, false, false), + PROTOTYPE(false, false, true), + ALL(true, true, true); + + public final boolean constructor; + public final boolean namespace; + public final boolean prototype; + + public boolean shouldApply(ExposeTarget other) { + if (other.constructor && !constructor) return false; + if (other.namespace && !namespace) return false; + if (other.prototype && !prototype) return false; + + return true; + } + + private ExposeTarget(boolean constructor, boolean namespace, boolean prototype) { + this.constructor = constructor; + this.namespace = namespace; + this.prototype = prototype; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java new file mode 100644 index 0000000..15e91a9 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/ExposeType.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.utils.interop; + +public enum ExposeType { + METHOD, + GETTER, + SETTER, +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java b/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java new file mode 100644 index 0000000..3f7573a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/NativeWrapperProvider.java @@ -0,0 +1,456 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Copyable; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.exceptions.InterruptException; +import me.topchetoeu.jscript.runtime.values.FunctionValue; +import me.topchetoeu.jscript.runtime.values.NativeFunction; +import me.topchetoeu.jscript.runtime.values.ObjectValue; +import me.topchetoeu.jscript.runtime.values.Symbol; +import me.topchetoeu.jscript.runtime.values.Values; + +public class NativeWrapperProvider implements Copyable { + public static final Key KEY = new Key<>(); + + private final HashMap, FunctionValue> constructors = new HashMap<>(); + private final HashMap, ObjectValue> prototypes = new HashMap<>(); + private final HashMap, ObjectValue> namespaces = new HashMap<>(); + private final HashMap, Class> classToProxy = new HashMap<>(); + private final HashMap, Class> proxyToClass = new HashMap<>(); + private final HashMap, Class> interfaceToProxy = new HashMap<>(); + private final HashSet> ignore = new HashSet<>(); + + private static Object call(Context ctx, String name, Method method, Object thisArg, Object... args) { + try { + var realArgs = new Object[method.getParameterCount()]; + System.arraycopy(args, 0, realArgs, 0, realArgs.length); + if (Modifier.isStatic(method.getModifiers())) thisArg = null; + return Values.normalize(ctx, method.invoke(Values.convert(ctx, thisArg, method.getDeclaringClass()), realArgs)); + } + catch (InvocationTargetException e) { + if (e.getTargetException() instanceof EngineException) { + throw ((EngineException)e.getTargetException()).add(ctx, name, Location.INTERNAL); + } + else if (e.getTargetException() instanceof NullPointerException) { + // e.getTargetException().printStackTrace(); + throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, Location.INTERNAL); + } + else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) { + throw new InterruptException(); + } + else throw new EngineException(e.getTargetException()).add(ctx, name, Location.INTERNAL); + } + catch (ReflectiveOperationException e) { + throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL); + } + } + private static FunctionValue create(String name, Method method) { + return new NativeFunction(name, args -> call(args.ctx, name, method, args.self, args)); + } + private static void checkSignature(Method method, boolean forceStatic, Class ...params) { + if (forceStatic && !Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException(String.format( + "Method %s must be static.", + method.getDeclaringClass().getName() + "." + method.getName() + )); + + var actual = method.getParameterTypes(); + + boolean failed = actual.length > params.length; + + if (!failed) for (var i = 0; i < actual.length; i++) { + if (!actual[i].isAssignableFrom(params[i])) { + failed = true; + break; + } + } + + if (failed) throw new IllegalArgumentException(String.format( + "Method %s was expected to have a signature of '%s', found '%s' instead.", + method.getDeclaringClass().getName() + "." + method.getName(), + String.join(", ", Arrays.stream(params).map(v -> v.getName()).collect(Collectors.toList())), + String.join(", ", Arrays.stream(actual).map(v -> v.getName()).collect(Collectors.toList())) + )); + } + private static String getName(Class ...classes) { + String last = null; + + for (var clazz : classes) { + var classNat = clazz.getAnnotation(WrapperName.class); + if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim(); + else if (last != null) last = clazz.getSimpleName(); + } + + return last; + } + + private static void checkUnderscore(Member member) { + if (!member.getName().startsWith("__")) { + System.out.println(String.format("WARNING: The name of the exposed member '%s.%s' doesn't start with '__'.", + member.getDeclaringClass().getName(), + member.getName() + )); + } + } + private static String getName(Member member, String overrideName) { + if (overrideName == null) overrideName = ""; + if (overrideName.isBlank()) { + var res = member.getName(); + if (res.startsWith("__")) res = res.substring(2); + return res; + } + else return overrideName.trim(); + } + private static Object getKey(String name) { + if (name.startsWith("@@")) return Symbol.get(name.substring(2)); + else return name; + } + + private static boolean apply(ObjectValue obj, ExposeTarget target, Class clazz) { + var getters = new HashMap(); + var setters = new HashMap(); + var props = new HashSet(); + var nonProps = new HashSet(); + var any = false; + + for (var method : clazz.getDeclaredMethods()) { + for (var annotation : method.getAnnotationsByType(Expose.class)) { + any = true; + if (!annotation.target().shouldApply(target)) continue; + + checkUnderscore(method); + var name = getName(method, annotation.value()); + var key = getKey(name); + var repeat = false; + + switch (annotation.type()) { + case METHOD: + if (props.contains(key) || nonProps.contains(key)) repeat = true; + else { + checkSignature(method, false, Arguments.class); + obj.defineProperty(null, key, create(name, method), true, true, false); + nonProps.add(key); + } + break; + case GETTER: + if (nonProps.contains(key) || getters.containsKey(key)) repeat = true; + else { + checkSignature(method, false, Arguments.class); + getters.put(key, create(name, method)); + props.add(key); + } + break; + case SETTER: + if (nonProps.contains(key) || setters.containsKey(key)) repeat = true; + else { + checkSignature(method, false, Arguments.class); + setters.put(key, create(name, method)); + props.add(key); + } + break; + } + + if (repeat) + throw new IllegalArgumentException(String.format( + "A member '%s' in the wrapper for '%s' of type '%s' is already present.", + name, clazz.getName(), target.toString() + )); + } + for (var annotation : method.getAnnotationsByType(ExposeField.class)) { + any = true; + if (!annotation.target().shouldApply(target)) continue; + + checkUnderscore(method); + var name = getName(method, annotation.value()); + var key = getKey(name); + var repeat = false; + + if (props.contains(key) || nonProps.contains(key)) repeat = true; + else { + checkSignature(method, true); + try { + obj.defineProperty(null, key, method.invoke(null), true, true, false); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + nonProps.add(key); + } + + if (repeat) + throw new IllegalArgumentException(String.format( + "A member '%s' in the wrapper for '%s' of type '%s' is already present.", + name, clazz.getName(), target.toString() + )); + } + } + for (var field : clazz.getDeclaredFields()) { + for (var annotation : field.getAnnotationsByType(ExposeField.class)) { + any = true; + if (!annotation.target().shouldApply(target)) continue; + + checkUnderscore(field); + var name = getName(field, annotation.value()); + var key = getKey(name); + var repeat = false; + + if (props.contains(key) || nonProps.contains(key)) repeat = true; + else { + try { + if (Modifier.isStatic(field.getModifiers())) { + obj.defineProperty(null, key, Values.normalize(null, field.get(null)), true, true, false); + } + else { + obj.defineProperty( + null, key, + new NativeFunction("get " + key, args -> { + try { return field.get(args.self(clazz)); } + catch (IllegalAccessException e) { e.printStackTrace(); return null; } + }), + Modifier.isFinal(field.getModifiers()) ? null : new NativeFunction("get " + key, args -> { + try { field.set(args.self(clazz), args.convert(0, field.getType())); } + catch (IllegalAccessException e) { e.printStackTrace(); } + return null; + }), + true, false + ); + } + nonProps.add(key); + } + catch (IllegalArgumentException | IllegalAccessException e) { } + } + + if (repeat) + throw new IllegalArgumentException(String.format( + "A member '%s' in the wrapper for '%s' of type '%s' is already present.", + name, clazz.getName(), target.toString() + )); + } + } + + for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, false); + + return any; + } + private static boolean apply(ObjectValue obj, ExposeTarget target, Class ...appliers) { + var res = false; + + for (var i = appliers.length - 1; i >= 0; i--) { + res |= apply(obj, target, appliers[i]); + } + + return res; + } + + private static Method getConstructor(Class ...appliers) { + for (var clazz : appliers) { + for (var method : clazz.getDeclaredMethods()) { + if (!method.isAnnotationPresent(ExposeConstructor.class)) continue; + checkSignature(method, true, Arguments.class); + return method; + } + } + + return null; + } + + /** + * Generates a prototype for the given class. + * The returned object will have appropriate wrappers for all instance members. + * All accessors and methods will expect the this argument to be a native wrapper of the given class type. + * @param clazz The class for which a prototype should be generated + */ + public static ObjectValue makeProto(Class ...appliers) { + var res = new ObjectValue(); + res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(appliers)); + if (!apply(res, ExposeTarget.PROTOTYPE, appliers)) return null; + return res; + } + /** + * Generates a constructor for the given class. + * The returned function will have appropriate wrappers for all static members. + * When the function gets called, the underlying constructor will get called, unless the constructor is inaccessible. + * @param clazz The class for which a constructor should be generated + */ + public static FunctionValue makeConstructor(Class ...appliers) { + var constr = getConstructor(appliers); + + FunctionValue res = constr == null ? + new NativeFunction(getName(appliers), args -> { throw EngineException.ofError("This constructor is not invokable."); }) : + create(getName(appliers), constr); + + if (!apply(res, ExposeTarget.CONSTRUCTOR, appliers) && constr == null) return null; + + return res; + } + /** + * Generates a namespace for the given class. + * The returned function will have appropriate wrappers for all static members. + * This method behaves almost like {@link NativeWrapperProvider#makeConstructor}, but will return an object instead. + * @param clazz The class for which a constructor should be generated + */ + public static ObjectValue makeNamespace(Class ...appliers) { + var res = new ObjectValue(); + + if (!apply(res, ExposeTarget.NAMESPACE, appliers)) return null; + + return res; + } + + private Class[] getAppliers(Class clazz) { + var res = new ArrayList>(); + + res.add(clazz); + + if (classToProxy.containsKey(clazz)) res.add(classToProxy.get(clazz)); + for (var intf : interfaceToProxy.keySet()) { + if (intf.isAssignableFrom(clazz)) res.add(interfaceToProxy.get(intf)); + } + + return res.toArray(Class[]::new); + } + + private void updateProtoChain(Class clazz, ObjectValue proto, FunctionValue constr) { + var parent = clazz; + + while (true) { + parent = parent.getSuperclass(); + if (parent == null) break; + + var parentProto = getProto(parent); + var parentConstr = getConstr(parent); + + if (parentProto != null && parentConstr != null) { + Values.setPrototype(Extensions.EMPTY, proto, parentProto); + Values.setPrototype(Extensions.EMPTY, constr, parentConstr); + + return; + } + } + } + + private void initType(Class clazz, FunctionValue constr, ObjectValue proto) { + if (constr != null && proto != null || ignore.contains(clazz)) return; + // i vomit + if ( + clazz == Object.class || + clazz == Void.class || + clazz == Number.class || clazz == Double.class || clazz == Float.class || + clazz == Long.class || clazz == Integer.class || clazz == Short.class || + clazz == Character.class || clazz == Byte.class || clazz == Boolean.class || + clazz.isPrimitive() || + clazz.isArray() || + clazz.isAnonymousClass() || + clazz.isEnum() || + clazz.isInterface() || + clazz.isSynthetic() + ) return; + + var appliers = getAppliers(clazz); + + if (constr == null) constr = makeConstructor(appliers); + if (proto == null) proto = makeProto(appliers); + + if (constr == null || proto == null) return; + + proto.defineProperty(null, "constructor", constr, true, false, false); + constr.defineProperty(null, "prototype", proto, true, false, false); + + prototypes.put(clazz, proto); + constructors.put(clazz, constr); + + updateProtoChain(clazz, proto, constr); + } + + public ObjectValue getProto(Class clazz) { + if (proxyToClass.containsKey(clazz)) return getProto(proxyToClass.get(clazz)); + + initType(clazz, constructors.get(clazz), prototypes.get(clazz)); + while (clazz != null) { + var res = prototypes.get(clazz); + if (res != null) return res; + clazz = clazz.getSuperclass(); + } + return null; + } + public ObjectValue getNamespace(Class clazz) { + if (proxyToClass.containsKey(clazz)) return getNamespace(proxyToClass.get(clazz)); + + if (!namespaces.containsKey(clazz)) namespaces.put(clazz, makeNamespace(clazz)); + while (clazz != null) { + var res = namespaces.get(clazz); + if (res != null) return res; + clazz = clazz.getSuperclass(); + } + return null; + } + public FunctionValue getConstr(Class clazz) { + if (proxyToClass.containsKey(clazz)) return getConstr(proxyToClass.get(clazz)); + + initType(clazz, constructors.get(clazz), prototypes.get(clazz)); + while (clazz != null) { + var res = constructors.get(clazz); + if (res != null) return res; + clazz = clazz.getSuperclass(); + } + return null; + } + + public NativeWrapperProvider copy() { + var res = new NativeWrapperProvider(); + + for (var pair : classToProxy.entrySet()) { + res.set(pair.getKey(), pair.getValue()); + } + + return this; + } + + public void set(Class clazz, Class wrapper) { + if (clazz == null) return; + + if (clazz.isInterface()) { + if (wrapper == null || wrapper == clazz) interfaceToProxy.remove(clazz); + else interfaceToProxy.put(clazz, wrapper); + } + else { + if (wrapper == null || wrapper == clazz) classToProxy.remove(clazz); + else classToProxy.put(clazz, wrapper); + } + + var classes = Stream.concat( + Stream.of(clazz), + prototypes.keySet().stream().filter(clazz::isAssignableFrom) + ).toArray(Class[]::new); + + for (var el : classes) { + prototypes.remove(el); + constructors.remove(el); + namespaces.remove(el); + } + + for (var el : classes) { + initType(el, null, null); + } + } + + public NativeWrapperProvider() { } + + public static NativeWrapperProvider get(Extensions ext) { + return ext.get(KEY); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java b/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java new file mode 100644 index 0000000..7b0e67d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/OnWrapperInit.java @@ -0,0 +1,12 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnWrapperInit { + +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java b/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java new file mode 100644 index 0000000..daf078c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/interop/WrapperName.java @@ -0,0 +1,12 @@ +package me.topchetoeu.jscript.utils.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface WrapperName { + String value(); +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java b/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java new file mode 100644 index 0000000..a7523b3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/mapping/SourceMap.java @@ -0,0 +1,109 @@ +package me.topchetoeu.jscript.utils.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.json.JSON; + +public class SourceMap { + private final TreeMap origToComp = new TreeMap<>(); + private final TreeMap compToOrig = new TreeMap<>(); + + public Location toCompiled(Location loc) { return convert(loc, origToComp); } + public Location toOriginal(Location loc) { return convert(loc, compToOrig); } + + private void add(long orig, long comp) { + var a = origToComp.remove(orig); + var b = compToOrig.remove(comp); + + if (b != null) origToComp.remove(b); + if (a != null) compToOrig.remove(a); + + origToComp.put(orig, comp); + compToOrig.put(comp, orig); + } + + public SourceMap apply(SourceMap map) { + var res = new SourceMap(); + + for (var el : new ArrayList<>(origToComp.entrySet())) { + var mapped = convert(el.getValue(), map.origToComp); + res.origToComp.put(el.getKey(), mapped); + } + for (var el : new ArrayList<>(compToOrig.entrySet())) { + var mapped = convert(el.getKey(), map.compToOrig); + res.compToOrig.put(mapped, el.getValue()); + res.add(el.getValue(), mapped); + } + + return res; + } + + public SourceMap clone() { + var res = new SourceMap(); + res.origToComp.putAll(this.origToComp); + res.compToOrig.putAll(this.compToOrig); + return res; + } + + public static SourceMap parse(String raw) { + var mapping = VLQ.decodeMapping(raw); + var res = new SourceMap(); + + var compRow = 0l; + var compCol = 0l; + + for (var origRow = 0; origRow < mapping.length; origRow++) { + var origCol = 0; + + for (var rawSeg : mapping[origRow]) { + if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files."); + origCol += rawSeg.length > 0 ? rawSeg[0] : 0; + compRow += rawSeg.length > 2 ? rawSeg[2] : 0; + compCol += rawSeg.length > 3 ? rawSeg[3] : 0; + + var compPacked = ((long)compRow << 32) | compCol; + var origPacked = ((long)origRow << 32) | origCol; + + res.add(origPacked, compPacked); + } + } + + return res; + } + public static List getSources(String raw) { + var json = JSON.parse(null, raw).map(); + return json + .list("sourcesContent") + .stream() + .map(v -> v.string()) + .collect(Collectors.toList()); + } + + public static SourceMap chain(SourceMap ...maps) { + if (maps.length == 0) return null; + var res = maps[0]; + + for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]); + + return res; + } + + private static Long convert(long packed, TreeMap map) { + if (map.containsKey(packed)) return map.get(packed); + var key = map.floorKey(packed); + if (key == null) return null; + else return map.get(key); + } + + private static Location convert(Location loc, TreeMap map) { + var packed = ((loc.line() - 1l) << 32) | (loc.start() - 1); + var resPacked = convert(packed, map); + + if (resPacked == null) return null; + else return new Location((int)(resPacked >> 32) + 1, (int)(resPacked & 0xFFFF) + 1, loc.filename()); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java b/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java new file mode 100644 index 0000000..5f49aea --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/mapping/VLQ.java @@ -0,0 +1,95 @@ +package me.topchetoeu.jscript.utils.mapping; + +import java.util.ArrayList; +import java.util.List; + +public class VLQ { + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + private static long[] toArray(List list) { + var arr = new long[list.size()]; + for (var i = 0; i < list.size(); i++) arr[i] = list.get(i); + return arr; + } + + public static String encode(long... arr) { + var raw = new StringBuilder(); + + for (var data : arr) { + var b = data < 0 ? 1 : 0; + data = Math.abs(data); + b |= (int)(data & 0b1111) << 1; + data >>= 4; + b |= data > 0 ? 0x20 : 0;; + raw.append(ALPHABET.charAt(b)); + + while (data > 0) { + b = (int)(data & 0b11111); + data >>= 5; + b |= data > 0 ? 0x20 : 0; + raw.append(ALPHABET.charAt(b)); + } + } + + return raw.toString(); + } + public static long[] decode(String val) { + if (val.length() == 0) return new long[0]; + + var list = new ArrayList(); + + for (var i = 0; i < val.length();) { + var sign = 1; + var curr = ALPHABET.indexOf(val.charAt(i++)); + var cont = (curr & 0x20) == 0x20; + if ((curr & 1) == 1) sign = -1; + long res = (curr & 0b11110) >> 1; + var n = 4; + + for (; i < val.length() && cont;) { + curr = ALPHABET.indexOf(val.charAt(i++)); + cont = (curr & 0x20) == 0x20; + res |= (curr & 0b11111) << n; + n += 5; + if (!cont) break; + } + + list.add(res * sign); + } + + return toArray(list); + } + + public static String encodeMapping(long[][][] arr) { + var res = new StringBuilder(); + var semicolon = false; + + for (var line : arr) { + var comma = false; + + if (semicolon) res.append(";"); + semicolon = true; + + for (var el : line) { + if (comma) res.append(","); + comma = true; + res.append(encode(el)); + } + } + + return res.toString(); + } + public static long[][][] decodeMapping(String val) { + var lines = new ArrayList(); + + for (var line : val.split(";", -1)) { + var elements = new ArrayList(); + for (var el : line.split(",", -1)) { + elements.add(decode(el)); + } + lines.add(elements.toArray(long[][]::new)); + } + + return lines.toArray(long[][][]::new); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java b/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java new file mode 100644 index 0000000..ad84d6c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/modules/Module.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.utils.modules; + +import me.topchetoeu.jscript.runtime.Context; + +public abstract class Module { + private Object value; + private boolean loaded; + + public Object value() { return value; } + public boolean loaded() { return loaded; } + + protected abstract Object onLoad(Context ctx); + + public void load(Context ctx) { + if (loaded) return; + this.value = onLoad(ctx); + this.loaded = true; + } +} + diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java b/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java new file mode 100644 index 0000000..1138485 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/modules/ModuleRepo.java @@ -0,0 +1,47 @@ +package me.topchetoeu.jscript.utils.modules; + +import java.util.HashMap; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; +import me.topchetoeu.jscript.runtime.scope.GlobalScope; +import me.topchetoeu.jscript.utils.filesystem.Filesystem; +import me.topchetoeu.jscript.utils.filesystem.Mode; + +public interface ModuleRepo { + public static final Key KEY = new Key<>(); + public static final Key CWD = new Key<>(); + + public Module getModule(Context ctx, String cwd, String name); + + public static ModuleRepo ofFilesystem(Filesystem fs) { + var modules = new HashMap(); + + return (ctx, cwd, name) -> { + name = fs.normalize(cwd, name); + var filename = Filename.parse(name); + var src = fs.open(name, Mode.READ).readToString(); + + if (modules.containsKey(name)) return modules.get(name); + + var env = Context.clean(ctx.extensions).child(); + env.add(CWD, fs.normalize(name, "..")); + var glob = env.get(GlobalScope.KEY); + env.add(GlobalScope.KEY, glob.child()); + + var mod = new SourceModule(filename, src, env); + modules.put(name, mod); + + return mod; + }; + } + + public static String cwd(Extensions exts) { + return exts.init(CWD, "/"); + } + public static ModuleRepo get(Extensions exts) { + return exts.get(KEY); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java b/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java new file mode 100644 index 0000000..6e9ca40 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/modules/RootModuleRepo.java @@ -0,0 +1,30 @@ +package me.topchetoeu.jscript.utils.modules; + +import java.util.HashMap; + +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; + +public class RootModuleRepo implements ModuleRepo { + public final HashMap repos = new HashMap<>(); + + @Override + public Module getModule(Context ctx, String cwd, String name) { + var i = name.indexOf(":"); + String repoName, modName; + + if (i < 0) { + repoName = "file"; + modName = name; + } + else { + repoName = name.substring(0, i); + modName = name.substring(i + 1); + } + + var repo = repos.get(repoName); + if (repo == null) throw EngineException.ofError("ModuleError", "Couldn't find module repo '" + repoName + "'."); + + return repo.getModule(ctx, cwd, modName); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java b/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java new file mode 100644 index 0000000..4efd72a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/modules/SourceModule.java @@ -0,0 +1,23 @@ +package me.topchetoeu.jscript.utils.modules; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.runtime.Context; +import me.topchetoeu.jscript.runtime.Extensions; + +public class SourceModule extends Module { + public final Filename filename; + public final String source; + public final Extensions ext; + + @Override + protected Object onLoad(Context ctx) { + var res = new Context(ext).compile(filename, source); + return res.call(ctx); + } + + public SourceModule(Filename filename, String source, Extensions ext) { + this.filename = filename; + this.source = source; + this.ext = ext; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java new file mode 100644 index 0000000..fc1138c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/permissions/Matcher.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.utils.permissions; + +import java.util.LinkedList; + +public interface Matcher { + static class State { + public final int predI, trgI, wildcardI; + public final boolean wildcard; + + @Override + public String toString() { + return String.format("State [pr=%s;trg=%s;wildN=%s;wild=%s]", predI, trgI, wildcardI, wildcard); + } + + public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { + this.predI = predicateI; + this.trgI = targetI; + this.wildcardI = wildcardI; + this.wildcard = wildcard; + } + } + + boolean match(String predicate, String value); + + public static Matcher fileWildcard() { + return (predicate, value) -> execWildcard(predicate, value, '/'); + } + public static Matcher namespaceWildcard() { + return (predicate, value) -> execWildcard(predicate, value, '.'); + } + public static Matcher wildcard() { + return (predicate, value) -> execWildcard(predicate, value, '\0'); + } + + public static boolean execWildcard(String predicate, String target, char delim) { + if (predicate.equals("")) return target.equals(""); + + var queue = new LinkedList(); + queue.push(new State(0, 0, 0, false)); + + while (!queue.isEmpty()) { + var state = queue.poll(); + var predEnd = state.predI >= predicate.length(); + + if (state.trgI >= target.length()) return predEnd; + var predC = predEnd ? 0 : predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (state.wildcard) { + if (state.wildcardI == 2 || trgC != delim) { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + } + queue.add(new State(state.predI, state.trgI, 0, false)); + } + else if (predC == '*') { + queue.add(new State(state.predI + 1, state.trgI, state.wildcardI + 1, false)); + } + else if (state.wildcardI > 0) { + if (state.wildcardI > 2) throw new IllegalArgumentException("Too many sequential stars."); + queue.add(new State(state.predI, state.trgI, state.wildcardI, true)); + } + else if (!predEnd && (predC == '?' || predC == trgC)) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java new file mode 100644 index 0000000..f120500 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/permissions/Permission.java @@ -0,0 +1,19 @@ +package me.topchetoeu.jscript.utils.permissions; + + +public class Permission { + public final String namespace; + public final Matcher matcher; + + @Override public String toString() { + return namespace; + } + + public Permission(String namespace, Matcher matcher) { + this.namespace = namespace; + this.matcher = matcher; + } + public Permission(String raw) { + this(raw, null); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java new file mode 100644 index 0000000..5bedf8d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java @@ -0,0 +1,44 @@ +package me.topchetoeu.jscript.utils.permissions; + +public class PermissionPredicate { + public final String namespace; + public final String value; + public final boolean denies; + + public boolean match(Permission permission, String value) { + if (!match(permission)) return false; + if (this.value == null || value == null) return true; + if (permission.matcher == null) return true; + else return permission.matcher.match(this.value, value); + } + public boolean match(Permission permission) { + return Matcher.namespaceWildcard().match(namespace, permission.namespace); + } + + @Override + public String toString() { + if (value != null) return namespace + ":" + value; + else return namespace; + } + + public PermissionPredicate(String raw) { + raw = raw.trim(); + + if (raw.startsWith("!")) { + denies = true; + raw = raw.substring(1).trim(); + } + else denies = false; + + var i = raw.indexOf(':'); + + if (i > 0) { + value = raw.substring(i + 1); + namespace = raw.substring(0, i); + } + else { + value = null; + namespace = raw; + } + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java new file mode 100644 index 0000000..72468cc --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java @@ -0,0 +1,59 @@ +package me.topchetoeu.jscript.utils.permissions; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; + +public class PermissionsManager implements PermissionsProvider { + public final ArrayList predicates = new ArrayList<>(); + + public PermissionsProvider add(PermissionPredicate perm) { + predicates.add(perm); + return this; + } + public PermissionsProvider add(String perm) { + predicates.add(new PermissionPredicate(perm)); + return this; + } + + @Override public boolean hasPermission(Permission perm, String value) { + for (var el : predicates) { + if (el.match(perm, value)) { + if (el.denies) return false; + else return true; + } + } + + return false; + } + @Override public boolean hasPermission(Permission perm) { + for (var el : predicates) { + if (el.match(perm)) { + if (el.denies) return false; + else return true; + } + } + + return false; + } + + public PermissionsProvider addFromStream(InputStream stream) throws IOException { + var reader = new BufferedReader(new InputStreamReader(stream)); + String line; + + while ((line = reader.readLine()) != null) { + var i = line.indexOf('#'); + if (i >= 0) line = line.substring(0, i); + + line = line.trim(); + + if (line.isEmpty()) continue; + + add(line); + } + + return this; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java new file mode 100644 index 0000000..53112b3 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java @@ -0,0 +1,29 @@ +package me.topchetoeu.jscript.utils.permissions; + +import me.topchetoeu.jscript.runtime.Extensions; +import me.topchetoeu.jscript.runtime.Key; + +public interface PermissionsProvider { + public static final Key KEY = new Key<>(); + public static final PermissionsProvider ALL_PERMS = (perm, value) -> true; + + boolean hasPermission(Permission perm, String value); + + default boolean hasPermission(Permission perm) { + return hasPermission(perm, null); + } + + default boolean hasPermission(String perm, String value, Matcher matcher) { + return hasPermission(new Permission(perm, matcher), value); + } + default boolean hasPermission(String perm, Matcher matcher) { + return hasPermission(new Permission(perm, matcher)); + } + + public static PermissionsProvider get(Extensions exts) { + return (perm, value) -> { + if (exts.hasNotNull(KEY)) return exts.get(KEY).hasPermission(perm); + else return true; + }; + } +} \ No newline at end of file diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js deleted file mode 100644 index 96d0e60..0000000 --- a/src/main/resources/lib/index.js +++ /dev/null @@ -1,321 +0,0 @@ -#! my special comment lol - -(function(target, primordials) { - var makeSymbol = primordials.symbol.makeSymbol; - var getSymbol = primordials.symbol.getSymbol; - var getSymbolKey = primordials.symbol.getSymbolKey; - var getSymbolDescription = primordials.symbol.getSymbolDescription; - - var parseInt = primordials.number.parseInt; - var parseFloat = primordials.number.parseFloat; - var isNaN = primordials.number.isNaN; - var NaN = primordials.number.NaN; - var Infinity = primordials.number.Infinity; - - var fromCharCode = primordials.string.fromCharCode; - var fromCodePoint = primordials.string.fromCodePoint; - var stringBuild = primordials.string.stringBuild; - - var defineProperty = primordials.object.defineProperty; - var defineField = primordials.object.defineField; - var getOwnMember = primordials.object.getMember; - var getOwnSymbolMember = primordials.object.getOwnSymbolMember; - var getOwnMembers = primordials.object.getOwnMembers; - var getOwnSymbolMembers = primordials.object.getOwnSymbolMembers; - var getPrototype = primordials.object.getPrototype; - var setPrototype = primordials.object.setPrototype; - - var invokeType = primordials.function.invokeType; - var setConstructable = primordials.function.setConstructable; - var setCallable = primordials.function.setCallable; - var invoke = primordials.function.invoke; - - var setGlobalPrototype = primordials.setGlobalPrototype; - var compile = primordials.compile; - - var json = primordials.json; - - var valueKey = makeSymbol("Primitive.value"); - - function unwrapThis(self, type, constr, name, arg, defaultVal) { - if (arg == null) arg = "this"; - if (typeof self === type) return self; - if (self instanceof constr && valueKey in self) self = self[valueKey]; - if (typeof self === type) return self; - if (arguments.length > 5) return defaultVal; - - throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name); - } - - function wrapIndex(i, len) { - } - - var Symbol = function(name) { - if (arguments.length === 0) return makeSymbol(""); - else return makeSymbol(name + ""); - }; - setConstructable(Symbol, false); - - defineField(Symbol, "for", true, false, true, function(name) { - return getSymbol(name + ""); - }); - defineField(Symbol, "keyFor", true, false, true, function(symbol) { - return getSymbolKey(unwrapThis(symbol, "symbol", Symbol, "Symbol.keyFor")); - }); - - defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); - defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator")); - defineField(Symbol, "match", false, false, false, Symbol("Symbol.match")); - defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll")); - defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace")); - defineField(Symbol, "search", false, false, false, Symbol("Symbol.search")); - defineField(Symbol, "split", false, false, false, Symbol("Symbol.split")); - defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag")); - defineField(Symbol, "prototype", false, false, false, {}); - - defineProperty(Symbol.prototype, "description", false, true, function () { - return getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); - }, undefined); - defineField(Symbol.prototype, "toString", true, false, true, function() { - return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; - }); - defineField(Symbol.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); - }); - - target.Symbol = Symbol; - - var Number = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return 0; - else return +value; - } - - this[valueKey] = target.Number(value); - }; - - defineField(Number, "isFinite", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); - - if (value === undefined || isNaN(value)) return false; - if (value === Infinity || value === -Infinity) return false; - - return true; - }); - defineField(Number, "isInteger", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); - if (value === undefined) return false; - return parseInt(value) === value; - }); - defineField(Number, "isNaN", true, false, true, function(value) { - return isNaN(value); - }); - defineField(Number, "isSafeInteger", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); - if (value === undefined || parseInt(value) !== value) return false; - return value >= -9007199254740991 && value <= 9007199254740991; - }); - defineField(Number, "parseFloat", true, false, true, function(value) { - value = 0 + value; - return parseFloat(value); - }); - defineField(Number, "parseInt", true, false, true, function(value, radix) { - value = 0 + value; - radix = +radix; - if (isNaN(radix)) radix = 10; - - return parseInt(value, radix); - }); - - defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); - defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); - defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); - defineField(Number, "POSITIVE_INFINITY", false, false, false, +Infinity); - defineField(Number, "NEGATIVE_INFINITY", false, false, false, -Infinity); - defineField(Number, "NaN", false, false, false, NaN); - defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); - defineField(Number, "MIN_VALUE", false, false, false, 5e-324); - defineField(Number, "prototype", false, false, false, {}); - - defineField(Number.prototype, "toString", true, false, true); - defineField(Number.prototype, "toString", true, false, true, function() { - return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); - }); - defineField(Number.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "number", Number, "Number.prototype.toString"); - }); - - target.Number = Number; - - var String = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return ""; - else return value + ""; - } - - this[valueKey] = target.String(value); - }; - - defineField(String, "fromCharCode", true, false, true, function() { - var res = []; - res[arguments.length] = 0; - - for (var i = 0; i < arguments.length; i++) { - res[res.length] = fromCharCode(+arguments[i]); - } - - return stringBuild(res); - }); - defineField(String, "fromCodePoint", true, false, true, function(value) { - var res = []; - res[arguments.length] = 0; - - for (var i = 0; i < arguments.length; i++) { - res[res.length] = fromCodePoint(+arguments[i]); - } - - return stringBuild(res); - }); - - defineField(String, "prototype", false, false, false, {}); - - defineField(String.prototype, "at", true, false, true, function(index) { - return "" + unwrapThis(this, "string", String, "String.prototype.at"); - }); - defineField(String.prototype, "toString", true, false, true, function() { - return unwrapThis(this, "string", String, "String.prototype.toString"); - }); - defineField(String.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "string", String, "String.prototype.valueOf"); - }); - - target.String = String; - - var Boolean = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return false; - else return !!value; - } - - this[valueKey] = target.Boolean(value); - }; - - defineField(Boolean, "prototype", false, false, false, {}); - - defineField(Boolean.prototype, "toString", true, false, true, function() { - return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); - }); - defineField(Boolean.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); - }); - - target.Boolean = Boolean; - - var Object = function(value) { - if (typeof value === 'object' && value !== null) return value; - - if (typeof value === 'string') return new String(value); - if (typeof value === 'number') return new Number(value); - if (typeof value === 'boolean') return new Boolean(value); - if (typeof value === 'symbol') { - var res = {}; - setPrototype(res, Symbol.prototype); - res[valueKey] = value; - return res; - } - - var target = this; - if (target === undefined || target === null || typeof target !== 'object') target = {}; - - this[valueKey] = target.Object(value); - }; - - defineField(Object, "prototype", false, false, false, setPrototype({}, null)); - - defineField(Object.prototype, "toString", true, false, true, function() { - if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; - else if (typeof this === "number" || this instanceof Number) return "[object Number]"; - else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; - else if (typeof this === "string" || this instanceof String) return "[object String]"; - else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; - else if (typeof this === "function") return "[object Function]"; - else return "[object Object]"; - }); - defineField(Object.prototype, "valueOf", true, false, true, function() { - return this; - }); - - target.Boolean = Boolean; - - var Function = function() { - if (invokeType(arguments) === "new") return Function(value); - - var res = ["return function ("]; - - for (var i = 0; i < arguments.length - 1; i++) { - if (i > 0) res[res.length] = ","; - res[res.length] = arguments[i]; - } - res[res.length] = "){"; - res[res.length] = String(arguments[arguments.length - 1]); - res[res.length] = "}"; - - log(res); - - return compile(stringBuild(res))(); - }; - - defineField(Function, "compile", true, false, true, function(src, options) { - if (options == null) options = {}; - if (src == null) src = ""; - - if (options.globals == null) options.globals = []; - if (options.wrap == null) options.wrap = true; - - var res = []; - - if (options.wrap) res[res.length] = "return (function() {\n"; - if (options.globals.length > 0) { - res[res.length] = "var "; - - for (var i = 0; i < options.globals.length; i++) { - if (i > 0) res[res.length] = ","; - res[res.length] = options.globals[i]; - } - - res[res.length] = ";(function(g){"; - - for (var i = 0; i < options.globals.length; i++) { - var name = options.globals[i]; - res[res.length] = name; - res[res.length] = "=g["; - res[res.length] = json.stringify(name); - res[res.length] = "];"; - } - - res[res.length] = "})(arguments[0] || {});\n"; - } - - res[res.length] = src; - if (options.wrap) res[res.length] = "\n})(arguments[0])"; - - return compile(stringBuild(res)); - }); - defineField(Function, "prototype", false, false, false, setPrototype({}, null)); - - defineField(Function.prototype, "toString", true, false, true, function() { - if (this.name !== "") return "function " + this.name + "(...) { ... }"; - else return "function (...) { ... }"; - }); - defineField(Function.prototype, "valueOf", true, false, true, function() { - return this; - }); - - target.Function = Function; - - setGlobalPrototype("string", String.prototype); - setGlobalPrototype("number", Number.prototype); - setGlobalPrototype("boolean", Boolean.prototype); - setGlobalPrototype("symbol", Symbol.prototype); - setGlobalPrototype("object", Object.prototype); -})(arguments[0], arguments[1]); \ No newline at end of file