diff --git a/src/assets/protocol.json b/src/assets/protocol.json index 246e736..4e5dcfe 100644 --- a/src/assets/protocol.json +++ b/src/assets/protocol.json @@ -56,7 +56,7 @@ "properties": [ { "name": "scriptId", "description": "Script identifier as reported in the `Debugger.scriptParsed`.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "lineNumber", "description": "Line number in the script (0-based).", @@ -108,57 +108,21 @@ "description": "Disables debugging. This will have some performance benefit." }, - { "name": "continueToLocation", - "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.", + { "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { "name": "resume", + "description": "Resumes JavaScript execution.", "parameters": [ - { "name": "location", - "description": "Location to continue to.", - "$ref": "Location" - } - ] - }, - { "name": "evaluateOnCallFrame", - "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.", - "parameters": [ - { "name": "callFrameId", - "description": "Call frame to evaluate on.", - "$ref": "CallFrameId" - }, - { "name": "expression", - "description": "Expression to evaluate.", - "type": "string" - }, - { "name": "silent", - "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "deprecated": true, - "optional": true, - "type": "boolean" - }, - { "name": "timeout", - "description": "Terminate execution after timing out (number of milliseconds).", - "optional": true, - "type": "number" - } - ], - "returns": [ - { - "name": "result", - "description": "Object wrapper for the evaluation result.", - "$ref": "Runtime.RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "Runtime.ExceptionDetails" + { + "name": "terminateOnResume", + "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.", + "optional": true, + "type": "boolean" } ] }, + { "name": "getPossibleBreakpoints", "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same.", "parameters": [ @@ -197,7 +161,7 @@ { "name": "scriptId", "description": "Id of the script to get source for.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" } ], "returns": [ @@ -214,24 +178,6 @@ } ] }, - { "name": "getWasmBytecode", - "description": "This command is deprecated. Use getScriptSource instead.", - "deprecated": true, - "parameters": [ - { - "name": "scriptId", - "description": "Id of the Wasm script to get source for.", - "$ref": "Runtime.ScriptId" - } - ], - "returns": [ - { - "name": "bytecode", - "description": "Script source.", - "type": "binary" - } - ] - }, { "name": "getStackTrace", "description": "Returns stack trace with given `stackTraceId`.", "experimental": true, @@ -248,202 +194,7 @@ } ] }, - { "name": "pause", - "description": "Stops on the next JavaScript statement." - }, - { "name": "pauseOnAsyncCall", - "experimental": true, - "deprecated": true, - "parameters": [ - { - "name": "parentStackTraceId", - "description": "Debugger will pause when async call with given stack trace is started.", - "$ref": "Runtime.StackTraceId" - } - ] - }, - { "name": "removeBreakpoint", - "description": "Removes JavaScript breakpoint.", - "parameters": [ - { - "name": "breakpointId", - "$ref": "BreakpointId" - } - ] - }, - { "name": "restartFrame", - "description": "Restarts particular call frame from the beginning.", - "deprecated": true, - "parameters": [ - { - "name": "callFrameId", - "description": "Call frame identifier to evaluate on.", - "$ref": "CallFrameId" - } - ], - "returns": [ - { - "name": "callFrames", - "description": "New stack trace.", - "type": "array", - "items": { - "$ref": "CallFrame" - } - }, - { - "name": "asyncStackTrace", - "description": "Async stack trace, if any.", - "optional": true, - "$ref": "Runtime.StackTrace" - }, - { - "name": "asyncStackTraceId", - "description": "Async stack trace, if any.", - "experimental": true, - "optional": true, - "$ref": "Runtime.StackTraceId" - } - ] - }, - { "name": "resume", - "description": "Resumes JavaScript execution.", - "parameters": [ - { - "name": "terminateOnResume", - "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.", - "optional": true, - "type": "boolean" - } - ] - }, - { "name": "searchInContent", - "description": "Searches for given string in script content.", - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script to search in.", - "$ref": "Runtime.ScriptId" - }, - { - "name": "query", - "description": "String to search for.", - "type": "string" - }, - { - "name": "caseSensitive", - "description": "If true, search is case sensitive.", - "optional": true, - "type": "boolean" - }, - { - "name": "isRegex", - "description": "If true, treats string parameter as regex.", - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "List of search matches.", - "type": "array", - "items": { - "$ref": "SearchMatch" - } - } - ] - }, - { "name": "setAsyncCallStackDepth", - "description": "Enables or disables async call stacks tracking.", - "parameters": [ - { - "name": "maxDepth", - "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", - "type": "integer" - } - ] - }, - { "name": "setBlackboxPatterns", - "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful.", - "experimental": true, - "parameters": [ - { - "name": "patterns", - "description": "Array of regexps that will be used to check script url for blackbox state.", - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - { "name": "setBlackboxedRanges", - "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted.", - "experimental": true, - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script.", - "$ref": "Runtime.ScriptId" - }, - { - "name": "positions", - "type": "array", - "items": { - "$ref": "ScriptPosition" - } - } - ] - }, - { "name": "setBreakpoint", - "description": "Sets JavaScript breakpoint at a given location.", - "parameters": [ - { - "name": "location", - "description": "Location to set breakpoint in.", - "$ref": "Location" - }, - { - "name": "condition", - "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", - "optional": true, - "type": "string" - } - ], - "returns": [ - { - "name": "breakpointId", - "description": "Id of the created breakpoint for further reference.", - "$ref": "BreakpointId" - }, - { - "name": "actualLocation", - "description": "Location this breakpoint resolved into.", - "$ref": "Location" - } - ] - }, - { "name": "setInstrumentationBreakpoint", - "description": "Sets instrumentation breakpoint.", - "parameters": [ - { - "name": "instrumentation", - "description": "Instrumentation name.", - "type": "string", - "enum": [ - "beforeScriptExecution", - "beforeScriptWithSourceMapExecution" - ] - } - ], - "returns": [ - { - "name": "breakpointId", - "description": "Id of the created breakpoint for further reference.", - "$ref": "BreakpointId" - } - ] - }, + { "name": "setBreakpointByUrl", "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads.", "parameters": [ @@ -499,18 +250,17 @@ } ] }, - { "name": "setBreakpointOnFunctionCall", - "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint.", - "experimental": true, + { "name": "setBreakpoint", + "description": "Sets JavaScript breakpoint at a given location.", "parameters": [ { - "name": "objectId", - "description": "Function object id.", - "$ref": "Runtime.RemoteObjectId" + "name": "location", + "description": "Location to set breakpoint in.", + "$ref": "Location" }, { "name": "condition", - "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true.", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", "optional": true, "type": "string" } @@ -520,9 +270,36 @@ "name": "breakpointId", "description": "Id of the created breakpoint for further reference.", "$ref": "BreakpointId" + }, + { + "name": "actualLocation", + "description": "Location this breakpoint resolved into.", + "$ref": "Location" } ] }, + { "name": "setInstrumentationBreakpoint", + "description": "Sets instrumentation breakpoint.", + "parameters": [ + { + "name": "instrumentation", + "description": "Instrumentation name.", + "type": "string", + "enum": [ + "beforeScriptExecution", + "beforeScriptWithSourceMapExecution" + ] + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ], + "todo": "// TODO: implement!!" + }, { "name": "setBreakpointsActive", "description": "Activates / deactivates all breakpoints on the page.", "parameters": [ @@ -543,114 +320,74 @@ "enum": [ "none", "uncaught", - "all" + "all" ] } ] }, - { "name": "setReturnValue", - "description": "Changes return value in top frame. Available only at return break position.", - "experimental": true, + + { "name": "continueToLocation", + "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.", "parameters": [ - { - "name": "newValue", - "description": "New return value.", - "$ref": "Runtime.CallArgument" + { "name": "location", + "description": "Location to continue to.", + "$ref": "Location" } ] }, - { "name": "setScriptSource", - "description": "Edits JavaScript source live.", + { "name": "removeBreakpoint", + "description": "Removes JavaScript breakpoint.", "parameters": [ { - "name": "scriptId", - "description": "Id of the script to edit.", - "$ref": "Runtime.ScriptId" + "name": "breakpointId", + "$ref": "BreakpointId" + } + ] + }, + + { "name": "evaluateOnCallFrame", + "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.", + "parameters": [ + { "name": "callFrameId", + "description": "Call frame to evaluate on.", + "$ref": "CallFrameId" }, - { - "name": "scriptSource", - "description": "New content of the script.", + { "name": "expression", + "description": "Expression to evaluate.", "type": "string" }, - { - "name": "dryRun", - "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code.", + { "name": "silent", + "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.", "optional": true, "type": "boolean" + }, + { "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "deprecated": true, + "optional": true, + "type": "boolean" + }, + { "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "optional": true, + "type": "number" } ], "returns": [ { - "name": "callFrames", - "description": "New stack trace in case editing has happened while VM was stopped.", - "optional": true, - "type": "array", - "items": { - "$ref": "CallFrame" - } - }, - { - "name": "stackChanged", - "description": "Whether current call stack was modified after applying the changes.", - "optional": true, - "type": "boolean" - }, - { - "name": "asyncStackTrace", - "description": "Async stack trace, if any.", - "optional": true, - "$ref": "Runtime.StackTrace" - }, - { - "name": "asyncStackTraceId", - "description": "Async stack trace, if any.", - "experimental": true, - "optional": true, - "$ref": "Runtime.StackTraceId" + "name": "result", + "description": "Object wrapper for the evaluation result.", + "$ref": "Runtime.RemoteObject" }, { "name": "exceptionDetails", - "description": "Exception details if any.", + "description": "Exception details.", "optional": true, "$ref": "Runtime.ExceptionDetails" } ] }, - { "name": "setSkipAllPauses", - "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).", - "parameters": [ - { - "name": "skip", - "description": "New value for skip pauses state.", - "type": "boolean" - } - ] - }, - { "name": "setVariableValue", - "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually.", - "parameters": [ - { - "name": "scopeNumber", - "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually.", - "type": "integer" - }, - { - "name": "variableName", - "description": "Variable name.", - "type": "string" - }, - { - "name": "newValue", - "description": "New variable value.", - "$ref": "Runtime.CallArgument" - }, - { - "name": "callFrameId", - "description": "Id of callframe that holds variable.", - "$ref": "CallFrameId" - } - ] - }, + { "name": "stepInto", "description": "Steps into the function call." }, @@ -754,7 +491,7 @@ { "name": "scriptId", "description": "Identifier of the script parsed.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "url", @@ -852,7 +589,7 @@ { "name": "scriptId", "description": "Identifier of the script parsed.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "url", @@ -963,32 +700,29 @@ { "domain": "Runtime", "types": [ - { "id": "ScriptId", - "description": "Unique script identifier.", + { "id": "ID", + "description": "The string representation of an integer ID. All identifiable constructs share the same ID repository.", "type": "string" }, - { "id": "RemoteObjectId", - "description": "Unique object identifier.", - "type": "string" - }, - { "id": "Timestamp", "description": "Number of milliseconds since epoch.", "type": "number" }, - { "id": "TimeDelta", - "description": "Number of milliseconds.", - "type": "number" - }, + { "id": "UnserializableValue", - "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.", + "description": "The string representation of a value that can't be JSON serialized.", "type": "string" }, { "id": "RemoteObject", "description": "Mirror object referencing original JavaScript object.", "type": "object", "properties": [ + { "name": "objectId", + "description": "Unique object identifier (for non-primitive values).", + "optional": true, + "$ref": "ID" + }, { "name": "type", "description": "Object type.", "type": "string", @@ -1008,13 +742,7 @@ "type": "string", "enum": [ "array", - "null", - "regexp", - "date", - "map", - "set", - "generator", - "promise" + "null" ] }, { "name": "className", @@ -1023,12 +751,12 @@ "type": "string" }, { "name": "value", - "description": "Remote object value in case of primitive values or JSON values (if it was requested).", + "description": "Remote object value in case of primitive values.", "optional": true, "type": "any" }, { "name": "unserializableValue", - "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty.", + "description": "In case of a non JSON-serializable value, its string representation is provided here.", "optional": true, "$ref": "UnserializableValue" }, @@ -1036,85 +764,6 @@ "description": "String representation of the object.", "optional": true, "type": "string" - }, - { "name": "objectId", - "description": "Unique object identifier (for non-primitive values).", - "optional": true, - "$ref": "RemoteObjectId" - } - ] - }, - { "id": "MemberDescriptor", - "description": "Object member descriptor, mirroring the object descriptor in JavaScript (https://developer.mozilla.org/en-US/docs/Glossary/Property/JavaScript).", - "type": "object", - "properties": [ - { "name": "name", - "description": "String representation of the member name.", - "type": "string" - }, - { "name": "value", - "description": "The value of the member, if it's a field.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "writable", - "description": "Whether or not the member is writable. Present only if the member is a field.", - "optional": true, - "type": "boolean" - }, - { "name": "configurable", - "description": "Whether or not the member is configurable.", - "type": "boolean" - }, - { "name": "enumerable", - "description": "Whether or not the member is enumerable..", - "type": "boolean" - }, - { "name": "get", - "description": "The getter of the member. Present only if the member is a property and the property has a getter.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "set", - "description": "The setter of the member. Present only if the member is a property and the property has a setter.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "wasThrown", - "description": "True if the member's value was thrown during the evaluation of the getter.", - "optional": true, - "type": "boolean" - }, - { "name": "isOwn", - "description": "True if the property is owned for the object.", - "optional": true, - "type": "boolean" - }, - { "name": "symbol", - "description": "If the member name is a symbol, this will be the reference of the symbol.", - "optional": true, - "$ref": "RemoteObject" - } - ] - }, - { "id": "CallArgument", - "description": "Represents function call argument. Only one of the three optional fields should be specified, or none for undefined.", - "type": "object", - "properties": [ - { "name": "value", - "description": "Primitive value or serializable javascript object.", - "optional": true, - "type": "any" - }, - { "name": "unserializableValue", - "description": "Primitive value which can not be JSON-stringified.", - "optional": true, - "$ref": "UnserializableValue" - }, - { "name": "objectId", - "description": "Remote object handle.", - "optional": true, - "$ref": "RemoteObjectId" } ] }, @@ -1142,7 +791,7 @@ }, { "name": "scriptId", "description": "Script ID of the exception location.", - "$ref": "ScriptId" + "$ref": "ID" }, { "name": "lineNumber", "description": "Line number of the exception location (0-based).", @@ -1164,7 +813,7 @@ }, { "name": "scriptId", "description": "JavaScript script id.", - "$ref": "ScriptId" + "$ref": "ID" }, { "name": "lineNumber", "description": "JavaScript script line number (0-based).", @@ -1191,637 +840,106 @@ "items": { "$ref": "CallFrame" } } ] + }, + + { "id": "MemberDescriptor", + "description": "An object, representing a property of another object.", + "type": "object", + "properties": [ + { "name": "name", + "description": "The name of the property. Although the property's key might not be a string,\nthe key is always converted to a string.", + "type": "string" + }, + + { "name": "writable", + "description": "The writable flag of the property. Present only if the member is a field.", + "type": "boolean", + "optional": true + }, + { "name": "enumerable", + "description": "The enumerable flag of the property.", + "type": "boolean", + "optional": true + }, + { "name": "configurable", + "description": "The configurable flag of the property.", + "type": "boolean", + "optional": true + }, + { "name": "isOwn", + "description": "Always true.", + "type": "boolean" + }, + + { "name": "get", + "description": "The remote function that is the getter of the property.\nNot present if the member is not a property or it doesn't have a getter.", + "$ref": "RemoteObject", + "optional": true + }, + { "name": "set", + "description": "The remote function that is the setter of the property.\nNot present if the member is not a property or it doesn't have a setter.", + "$ref": "RemoteObject", + "optional": true + }, + { "name": "value", + "description": "The remote value of the field. Present only if the member is a field.", + "$ref": "RemoteObject", + "optional": true + } + ] } ], "commands": [ - { "name": "awaitPromise", - "description": "Add handler to promise with given promise object id.", + { "name": "enable", + "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." + }, + { "name": "getProperties", + "description": "Returns a list of all owned members of the specified object.", "parameters": [ - { "name": "promiseObjectId", - "description": "Identifier of the promise.", - "$ref": "RemoteObjectId" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "optional": true, - "type": "boolean" + { "name": "objectId", + "description": "Identifier of the object to return properties for.", + "$ref": "ID" } ], "returns": [ { "name": "result", - "description": "Promise result. Will contain rejected value if promise was rejected.", - "$ref": "RemoteObject" - }, - { "name": "exceptionDetails", - "description": "Exception details if stack strace is available.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { "name": "callFunctionOn", - "description": "Calls function with given declaration on the given object.", - "parameters": [ - { "name": "functionDeclaration", - "description": "Declaration of the function to call.", - "type": "string" - }, - { "name": "objectId", - "description": "Identifier of the object to call function on. Either objectId or executionContextId should be specified.", - "optional": true, - "$ref": "RemoteObjectId" - }, - { "name": "arguments", - "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject.", - "optional": true, - "type": "array", - "items": { - "$ref": "CallArgument" - } - }, - { "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object which should be sent by value.", - "optional": true, - "type": "boolean" - }, - { "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object.", - "optional": true, - "type": "string" - }, - { - "name": "throwOnSideEffect", - "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "generateWebDriverValue", - "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Call result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "compileScript", - "description": "Compiles expression.", - "parameters": [ - { - "name": "expression", - "description": "Expression to compile.", - "type": "string" - }, - { - "name": "sourceURL", - "description": "Source url to be set for the script.", - "type": "string" - }, - { - "name": "persistScript", - "description": "Specifies whether the compiled script should be persisted.", - "type": "boolean" - }, - { - "name": "executionContextId", - "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", - "optional": true, - "$ref": "ExecutionContextId" - } - ], - "returns": [ - { - "name": "scriptId", - "description": "Id of the script.", - "optional": true, - "$ref": "ScriptId" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "disable", - "description": "Disables reporting of execution contexts creation." - }, - { - "name": "discardConsoleEntries", - "description": "Discards collected exceptions and console API calls." - }, - { - "name": "enable", - "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." - }, - { - "name": "evaluate", - "description": "Evaluates expression on global object.", - "parameters": [ - { - "name": "expression", - "description": "Expression to evaluate.", - "type": "string" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects.", - "optional": true, - "type": "string" - }, - { - "name": "includeCommandLineAPI", - "description": "Determines whether Command Line API should be available during the evaluation.", - "optional": true, - "type": "boolean" - }, - { - "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { - "name": "contextId", - "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.\nThis is mutually exclusive with `uniqueContextId`, which offers an\nalternative way to identify the execution context that is more reliable\nin a multi-process environment.", - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the result.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "userGesture", - "description": "Whether execution should be treated as initiated by user in the UI.", - "optional": true, - "type": "boolean" - }, - { - "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - }, - { - "name": "throwOnSideEffect", - "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.\nThis implies `disableBreaks` below.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "timeout", - "description": "Terminate execution after timing out (number of milliseconds).", - "experimental": true, - "optional": true, - "$ref": "TimeDelta" - }, - { - "name": "disableBreaks", - "description": "Disable breakpoints during execution.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "replMode", - "description": "Setting this flag to true enables `let` re-declaration and top-level `await`.\nNote that `let` variables can only be re-declared if they originate from\n`replMode` themselves.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "allowUnsafeEvalBlockedByCSP", - "description": "The Content Security Policy (CSP) for the target might block 'unsafe-eval'\nwhich includes eval(), Function(), setTimeout() and setInterval()\nwhen called with non-callable arguments. This flag bypasses CSP for this\nevaluation and allows unsafe-eval. Defaults to true.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "uniqueContextId", - "description": "An alternative way to specify the execution context to evaluate in.\nCompared to contextId that may be reused across processes, this is guaranteed to be\nsystem-unique, so it can be used to prevent accidental evaluation of the expression\nin context different than intended (e.g. as a result of navigation across process\nboundaries).\nThis is mutually exclusive with `contextId`.", - "experimental": true, - "optional": true, - "type": "string" - }, - { - "name": "generateWebDriverValue", - "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Evaluation result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "getIsolateId", - "description": "Returns the isolate id.", - "experimental": true, - "returns": [ - { - "name": "id", - "description": "The isolate id.", - "type": "string" - } - ] - }, - { - "name": "getHeapUsage", - "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime.", - "experimental": true, - "returns": [ - { - "name": "usedSize", - "description": "Used heap size in bytes.", - "type": "number" - }, - { - "name": "totalSize", - "description": "Allocated heap size in bytes.", - "type": "number" - } - ] - }, - { - "name": "getProperties", - "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject.", - "parameters": [ - { - "name": "objectId", - "description": "Identifier of the object to return properties for.", - "$ref": "RemoteObjectId" - }, - { - "name": "ownProperties", - "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain.", - "optional": true, - "type": "boolean" - }, - { - "name": "accessorPropertiesOnly", - "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the results.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "nonIndexedPropertiesOnly", - "description": "If true, returns non-indexed properties only.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", "description": "Object properties.", "type": "array", "items": { - "$ref": "PropertyDescriptor" - } - }, - { - "name": "internalProperties", - "description": "Internal object properties (only of the element itself).", - "optional": true, - "type": "array", - "items": { - "$ref": "InternalPropertyDescriptor" - } - }, - { - "name": "privateProperties", - "description": "Object private properties.", - "experimental": true, - "optional": true, - "type": "array", - "items": { - "$ref": "PrivatePropertyDescriptor" - } - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "globalLexicalScopeNames", - "description": "Returns all let, const and class variables from global scope.", - "parameters": [ - { - "name": "executionContextId", - "description": "Specifies in which execution context to lookup global scope variables.", - "optional": true, - "$ref": "ExecutionContextId" - } - ], - "returns": [ - { - "name": "names", - "type": "array", - "items": { - "type": "string" + "$ref": "MemberDescriptor" } } ] }, - { - "name": "queryObjects", - "parameters": [ - { - "name": "prototypeObjectId", - "description": "Identifier of the prototype to return objects for.", - "$ref": "RemoteObjectId" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release the results.", - "optional": true, - "type": "string" - } - ], - "returns": [ - { - "name": "objects", - "description": "Array with objects.", - "$ref": "RemoteObject" - } - ] - }, - { - "name": "releaseObject", + { "name": "releaseObject", "description": "Releases remote object with given id.", "parameters": [ - { - "name": "objectId", + { "name": "objectId", "description": "Identifier of the object to release.", - "$ref": "RemoteObjectId" + "$ref": "ID" } ] }, - { - "name": "releaseObjectGroup", + { "name": "releaseObjectGroup", "description": "Releases all remote objects that belong to a given group.", "parameters": [ - { - "name": "objectGroup", + { "name": "objectGroup", "description": "Symbolic object group name.", "type": "string" } ] }, - { - "name": "runIfWaitingForDebugger", - "description": "Tells inspected instance to run if it was waiting for debugger to attach." - }, - { - "name": "runScript", - "description": "Runs script with given id in a given context.", - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script to run.", - "$ref": "ScriptId" - }, - { - "name": "executionContextId", - "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects.", - "optional": true, - "type": "string" - }, - { - "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { - "name": "includeCommandLineAPI", - "description": "Determines whether Command Line API should be available during the evaluation.", - "optional": true, - "type": "boolean" - }, - { - "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object which should be sent by value.", - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the result.", - "optional": true, - "type": "boolean" - }, - { - "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Run result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "setAsyncCallStackDepth", - "description": "Enables or disables async call stacks tracking.", - "redirect": "Debugger", - "parameters": [ - { - "name": "maxDepth", - "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", - "type": "integer" - } - ] - }, - { - "name": "setCustomObjectFormatterEnabled", - "experimental": true, - "parameters": [ - { - "name": "enabled", - "type": "boolean" - } - ] - }, - { - "name": "setMaxCallStackSizeToCapture", - "experimental": true, - "parameters": [ - { - "name": "size", - "type": "integer" - } - ] - }, - { - "name": "terminateExecution", - "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.", - "experimental": true - }, - { - "name": "addBinding", - "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - }, - { - "name": "executionContextId", - "description": "If specified, the binding would only be exposed to the specified\nexecution context. If omitted and `executionContextName` is not set,\nthe binding is exposed to all execution contexts of the target.\nThis parameter is mutually exclusive with `executionContextName`.\nDeprecated in favor of `executionContextName` due to an unclear use case\nand bugs in implementation (crbug.com/1169639). `executionContextId` will be\nremoved in the future.", - "deprecated": true, - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "executionContextName", - "description": "If specified, the binding is exposed to the executionContext with\nmatching name, even for contexts created after the binding is added.\nSee also `ExecutionContext.name` and `worldName` parameter to\n`Page.addScriptToEvaluateOnNewDocument`.\nThis parameter is mutually exclusive with `executionContextId`.", - "experimental": true, - "optional": true, - "type": "string" - } - ] - }, - { - "name": "removeBinding", - "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - } - ] - }, - { - "name": "getExceptionDetails", - "description": "This method tries to lookup and populate exception details for a\nJavaScript Error object.\nNote that the stackTrace portion of the resulting exceptionDetails will\nonly be populated if the Runtime domain was enabled at the time when the\nError was thrown.", - "experimental": true, - "parameters": [ - { - "name": "errorObjectId", - "description": "The error object for which to resolve the exception details.", - "$ref": "RemoteObjectId" - } - ], - "returns": [ - { - "name": "exceptionDetails", - "optional": true, - "$ref": "ExceptionDetails" - } - ] + { "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach.", + "todo": "// TODO: implement!!" } ], "events": [ - { - "name": "bindingCalled", - "description": "Notification is issued every time when binding is called.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - }, - { - "name": "payload", - "type": "string" - }, - { - "name": "executionContextId", - "description": "Identifier of the context where the call was made.", - "$ref": "ExecutionContextId" - } - ] - }, - { - "name": "consoleAPICalled", + { "name": "consoleAPICalled", + "todo":"// TODO: implement!!", "description": "Issued when console API was called.", "parameters": [ - { - "name": "type", + { "name": "type", "description": "Type of the call.", "type": "string", "enum": [ @@ -1845,57 +963,28 @@ "timeEnd" ] }, - { - "name": "args", + { "name": "args", "description": "Call arguments.", "type": "array", - "items": { - "$ref": "RemoteObject" - } + "items": { "$ref": "RemoteObject" } }, - { - "name": "executionContextId", + { "name": "executionContextId", "description": "Identifier of the context where the call was made.", "$ref": "ExecutionContextId" }, - { - "name": "timestamp", + { "name": "timestamp", "description": "Call timestamp.", "$ref": "Timestamp" }, - { - "name": "stackTrace", + { "name": "stackTrace", "description": "Stack trace captured when the call was made. The async stack chain is automatically reported for\nthe following call types: `assert`, `error`, `trace`, `warning`. For other types the async call\nchain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field.", "optional": true, "$ref": "StackTrace" - }, - { - "name": "context", - "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context.", - "experimental": true, - "optional": true, - "type": "string" } ] }, - { - "name": "exceptionRevoked", - "description": "Issued when unhandled exception was revoked.", - "parameters": [ - { - "name": "reason", - "description": "Reason describing why exception was revoked.", - "type": "string" - }, - { - "name": "exceptionId", - "description": "The id of revoked exception, as reported in `exceptionThrown`.", - "type": "integer" - } - ] - }, - { - "name": "exceptionThrown", + { "name": "exceptionThrown", + "todo":"// TODO: implement!!", "description": "Issued when exception was thrown and unhandled.", "parameters": [ { @@ -1908,98 +997,6 @@ "$ref": "ExceptionDetails" } ] - }, - { - "name": "executionContextCreated", - "description": "Issued when new execution context is created.", - "parameters": [ - { - "name": "context", - "description": "A newly created execution context.", - "$ref": "ExecutionContextDescription" - } - ] - }, - { - "name": "executionContextDestroyed", - "description": "Issued when execution context is destroyed.", - "parameters": [ - { - "name": "executionContextId", - "description": "Id of the destroyed context", - "$ref": "ExecutionContextId" - } - ] - }, - { - "name": "executionContextsCleared", - "description": "Issued when all executionContexts were cleared in browser" - }, - { - "name": "inspectRequested", - "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall).", - "parameters": [ - { - "name": "object", - "$ref": "RemoteObject" - }, - { - "name": "hints", - "type": "object" - }, - { - "name": "executionContextId", - "description": "Identifier of the context where the call was made.", - "experimental": true, - "optional": true, - "$ref": "ExecutionContextId" - } - ] - } - ] - }, - { - "domain": "NodeWorker", - "description": "Implemented partly just because of pesky vscode.", - "deprecated": true, - "commands": [ - { - "name": "enable", - "description": "Used to get the attachedToWorker event", - "parameters": [ ] - } - ], - "events": [ - { - "name": "attachedToWorker", - "description": "Issued when attached to a worker.", - "parameters": [ - { "name": "sessionId", - "description": "Identifier assigned to the session used to send/receive messages.", - "$ref": "string" - }, - { - "name": "workerInfo", - "type": "object", - "properties": [ - { "name": "workerId", - "$ref": "string" - }, - { "name": "type", - "type": "string" - }, - { "name": "title", - "type": "string" - }, - { "name": "url", - "type": "string" - } - ] - }, - { "name": "waitingForDebugger", - "type": "boolean" - } - ] } ] } diff --git a/src/me/topchetoeu/jscript/Filename.java b/src/me/topchetoeu/jscript/Filename.java index 8e87ef6..3138887 100644 --- a/src/me/topchetoeu/jscript/Filename.java +++ b/src/me/topchetoeu/jscript/Filename.java @@ -51,4 +51,10 @@ public class Filename { this.protocol = protocol; this.path = path; } + + public static Filename parse(String val) { + var i = val.indexOf("://"); + if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim()); + else return new Filename("file", val.trim()); + } } diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index e0535c8..452660a 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -16,15 +16,13 @@ import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.filesystem.MemoryFilesystem; +import me.topchetoeu.jscript.filesystem.Mode; +import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.lib.Internals; -public class Main { - static Thread engineTask, debugTask; - static Engine engine; - static Environment env; - static int j = 0; - - private static Observer valuePrinter = new Observer() { +public class Main { + public static class Printer implements Observer { public void next(Object data) { Values.printValue(null, data); System.out.println(); @@ -34,27 +32,80 @@ public class Main { Values.printError(err, null); } - @Override public void finish() { engineTask.interrupt(); } - }; + } - public static void main(String args[]) { - System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); - engine = new Engine(true); + static Thread engineTask, debugTask; + static Engine engine = new Engine(true); + static DebugServer debugServer = new DebugServer(); + static Environment environment = new Environment(null, null, null); - var exited = new boolean[1]; - var server = new DebugServer(); - server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); - - env = Internals.apply(new Environment(null, null, null)); + static int j = 0; + static boolean exited = false; + static String[] args; - env.global.define("exit", _ctx -> { - exited[0] = true; + private static void reader() { + try { + for (var arg : args) { + try { + if (arg.equals("--ts")) initTypescript(); + else { + var file = Path.of(arg); + var raw = Files.readString(file); + var res = engine.pushMsg( + false, new Context(engine, 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.read(); + + if (raw == null) break; + var res = engine.pushMsg( + false, new Context(engine, environment), + new Filename("jscript", "repl/" + i + ".js"), + raw, 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()); + exited = true; + } + catch (RuntimeException ex) { + if (!exited) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } + } + if (exited) { + debugTask.interrupt(); + engineTask.interrupt(); + } + } + + private static void initEnv() { + environment = Internals.apply(environment); + + environment.global.define("exit", _ctx -> { + exited = true; throw new InterruptException(); }); - env.global.define("go", _ctx -> { + environment.global.define("go", _ctx -> { try { var f = Path.of("do.js"); var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); @@ -65,9 +116,15 @@ public class Main { } }); + environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); + environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); + } + private static void initEngine() { + debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); engineTask = engine.start(); - debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); - + debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); + } + private static void initTypescript() { try { var tsEnv = Internals.apply(new Environment(null, null, null)); var bsEnv = Internals.apply(new Environment(null, null, null)); @@ -84,46 +141,23 @@ public class Main { engine.pushMsg( false, ctx, new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, - tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) + tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) ).await(); } catch (EngineException e) { Values.printError(e, "(while initializing TS)"); } + } - var reader = new Thread(() -> { - try { - for (var arg : args) { - try { - var file = Path.of(arg); - var raw = Files.readString(file); - valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await()); - } - catch (EngineException e) { Values.printError(e, ""); } - } - for (var i = 0; ; i++) { - try { - var raw = Reading.read(); + public static void main(String args[]) { + System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); + + Main.args = args; + var reader = new Thread(Main::reader); + + initEnv(); + initEngine(); - if (raw == null) break; - valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await()); - } - catch (EngineException e) { Values.printError(e, ""); } - } - } - catch (IOException e) { exited[0] = true; } - catch (SyntaxException ex) { - if (exited[0]) return; - System.out.println("Syntax error:" + ex.msg); - } - catch (RuntimeException ex) { - if (!exited[0]) { - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); - } - } - if (exited[0]) debugTask.interrupt(); - }); reader.setDaemon(true); reader.setName("STD Reader"); reader.start(); diff --git a/src/me/topchetoeu/jscript/Metadata.java b/src/me/topchetoeu/jscript/Metadata.java index 5a0bf0b..05eefaf 100644 --- a/src/me/topchetoeu/jscript/Metadata.java +++ b/src/me/topchetoeu/jscript/Metadata.java @@ -1,7 +1,20 @@ package me.topchetoeu.jscript; public class Metadata { - public static final String VERSION = "${VERSION}"; - public static final String AUTHOR = "${AUTHOR}"; - public static final String NAME = "${NAME}"; + private static final String VERSION = "${VERSION}"; + private static final String AUTHOR = "${AUTHOR}"; + private static final String NAME = "${NAME}"; + + public static String version() { + if (VERSION.equals("$" + "{VERSION}")) return "1337-devel"; + else return VERSION; + } + public static String author() { + if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous"; + else return AUTHOR; + } + public static String name() { + if (NAME.equals("$" + "{NAME}")) return "some-product"; + else return NAME; + } } diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 0d07093..9efc9e7 100644 --- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -33,8 +33,8 @@ public class SwitchStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - var caseMap = new HashMap(); - var stmIndexMap = new HashMap(); + var caseToStatement = new HashMap(); + var statementToIndex = new HashMap(); value.compile(target, scope, true); @@ -42,7 +42,7 @@ public class SwitchStatement extends Statement { target.add(Instruction.dup().locate(loc())); ccase.value.compile(target, scope, true); target.add(Instruction.operation(Operation.EQUALS).locate(loc())); - caseMap.put(target.size(), ccase.statementI); + caseToStatement.put(target.size(), ccase.statementI); target.add(Instruction.nop().locate(ccase.value.loc())); } @@ -51,28 +51,31 @@ public class SwitchStatement extends Statement { target.add(Instruction.nop()); for (var stm : body) { - stmIndexMap.put(stmIndexMap.size(), target.size()); + statementToIndex.put(statementToIndex.size(), target.size()); stm.compileWithDebug(target, scope, false); } - if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc())); - else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc()); + int end = target.size(); + target.add(Instruction.discard().locate(loc())); + if (pollute) target.add(Instruction.loadValue(null)); - for (int i = start; i < target.size(); i++) { + if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start).locate(loc())); + else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)).locate(loc()); + + 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(target.size() - i).locate(instr.location)); + target.set(i, Instruction.jmp(end - i).locate(instr.location)); } } - for (var el : caseMap.entrySet()) { + for (var el : caseToStatement.entrySet()) { var loc = target.get(el.getKey()).location; - var i = stmIndexMap.get(el.getValue()); - if (i == null) i = target.size(); + var i = statementToIndex.get(el.getValue()); + if (i == null) i = end; target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc)); target.setDebug(el.getKey()); } - target.add(Instruction.discard().locate(loc())); } public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { diff --git a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java b/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java index f92f41c..6bbf9c6 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java @@ -13,7 +13,7 @@ public class CommaStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { for (var i = 0; i < values.length; i++) { - values[i].compile(target, scope, i == values.length - 1 && pollute); + values[i].compileWithDebug(target, scope, i == values.length - 1 && pollute); } } diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 0261737..c8246a5 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -1,18 +1,23 @@ package me.topchetoeu.jscript.engine; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Stack; import java.util.TreeSet; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.parsing.Parsing; public class Context { private final Stack env = new Stack<>(); - public final Data data; + private final ArrayList frames = new ArrayList<>(); public final Engine engine; public Environment environment() { @@ -28,7 +33,8 @@ public class Context { } public FunctionValue compile(Filename filename, String raw) { - var transpiled = environment().compile.call(this, null, raw, filename.toString()); + var env = environment(); + var transpiled = env.compile.call(this, null, raw, filename.toString(), env); String source = null; FunctionValue runner = null; @@ -40,7 +46,7 @@ public class Context { else source = Values.toString(this, transpiled); var breakpoints = new TreeSet(); - FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source); + FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source); engine.onSource(filename, source, breakpoints); if (runner != null) res = (FunctionValue)runner.call(this, null, res); @@ -48,16 +54,57 @@ public class Context { return res; } - public Context(Engine engine, Data data) { - this.data = new Data(engine.data); - if (data != null) this.data.addAll(data); + + public void pushFrame(CodeFrame frame) { + frames.add(frame); + if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + pushEnv(frame.function.environment); + } + public boolean popFrame(CodeFrame frame) { + if (frames.size() == 0) return false; + if (frames.get(frames.size() - 1) != frame) return false; + frames.remove(frames.size() - 1); + popEnv(); + engine.onFramePop(this, frame); + return true; + } + public CodeFrame peekFrame() { + if (frames.size() == 0) return null; + return frames.get(frames.size() - 1); + } + + public List frames() { + return Collections.unmodifiableList(frames); + } + public List stackTrace() { + var res = new ArrayList(); + + for (var i = frames.size() - 1; i >= 0; i--) { + var el = frames.get(i); + var name = el.function.name; + Location loc = null; + + for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; + if (loc == null) loc = el.function.loc(); + + var trace = ""; + + if (loc != null) trace += "at " + loc.toString() + " "; + if (name != null && !name.equals("")) trace += "in " + name + " "; + + trace = trace.trim(); + + if (!trace.equals("")) res.add(trace); + } + + return res; + } + + public Context(Engine engine) { this.engine = engine; } - public Context(Engine engine) { - this(engine, (Data)null); - } public Context(Engine engine, Environment env) { - this(engine, (Data)null); + this(engine); this.pushEnv(env); } diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java index 6c2d58f..54b78a2 100644 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -5,7 +5,6 @@ import java.util.Map; @SuppressWarnings("unchecked") public class Data { - public final Data parent; private HashMap, Object> data = new HashMap<>(); public Data copy() { @@ -33,19 +32,12 @@ public class Data { return this; } public T get(DataKey key, T val) { - for (var it = this; it != null; it = it.parent) { - if (it.data.containsKey(key)) { - return (T)it.data.get((DataKey)key); - } - } - + if (data.containsKey(key)) return (T)data.get((DataKey)key); set(key, val); return val; } public T get(DataKey key) { - for (var it = this; it != null; it = it.parent) { - if (it.data.containsKey(key)) return (T)it.data.get((DataKey)key); - } + if (data.containsKey(key)) return (T)data.get((DataKey)key); return null; } public boolean has(DataKey key) { return data.containsKey(key); } @@ -61,11 +53,4 @@ public class Data { public int increase(DataKey key) { return increase(key, 1, 0); } - - public Data() { - this.parent = null; - } - public Data(Data parent) { - this.parent = parent; - } } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 88a56ed..5dde3da 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -2,7 +2,7 @@ package me.topchetoeu.jscript.engine; import java.util.HashMap; import java.util.TreeSet; -import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.PriorityBlockingQueue; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; @@ -35,34 +35,41 @@ public class Engine implements DebugController { } } - private static class Task { + private static class Task implements Comparable { public final FunctionValue func; public final Object thisArg; public final Object[] args; public final DataNotifier notifier = new DataNotifier<>(); public final Context ctx; + public final boolean micro; - public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) { + public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) { this.ctx = ctx; this.func = func; this.thisArg = thisArg; this.args = args; + this.micro = micro; + } + + @Override + public int compareTo(Task other) { + return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1); } } private static int nextId = 0; public static final HashMap functions = new HashMap<>(); - private Thread thread; - private LinkedBlockingDeque macroTasks = new LinkedBlockingDeque<>(); - private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); - public final int id = ++nextId; - public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); public final boolean debugging; + public int maxStackFrames = 10000; + private final HashMap sources = new HashMap<>(); private final HashMap> bpts = new HashMap<>(); + private DebugController debugger; + private Thread thread; + private PriorityBlockingQueue tasks = new PriorityBlockingQueue<>(); public boolean attachDebugger(DebugController debugger) { if (!debugging || this.debugger != null) return false; @@ -80,20 +87,6 @@ public class Engine implements DebugController { return true; } - @Override public void onFramePop(Context ctx, CodeFrame frame) { - if (debugging && debugger != null) debugger.onFramePop(ctx, frame); - } - @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { - if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); - else return false; - } - @Override public void onSource(Filename filename, String source, TreeSet breakpoints) { - if (!debugging) return; - if (debugger != null) debugger.onSource(filename, source, breakpoints); - sources.put(filename, source); - bpts.put(filename, breakpoints); - } - private void runTask(Task task) { try { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); @@ -104,18 +97,12 @@ public class Engine implements DebugController { } } public void run(boolean untilEmpty) { - while (!untilEmpty || !macroTasks.isEmpty()) { + while (!untilEmpty || !tasks.isEmpty()) { try { - runTask(macroTasks.take()); - - while (!microTasks.isEmpty()) { - runTask(microTasks.take()); - } + runTask(tasks.take()); } catch (InterruptedException | InterruptException e) { - for (var msg : macroTasks) { - msg.notifier.error(new InterruptException(e)); - } + for (var msg : tasks) msg.notifier.error(new InterruptException(e)); break; } } @@ -140,15 +127,28 @@ public class Engine implements DebugController { } public Awaitable pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { - var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args); - if (micro) microTasks.addLast(msg); - else macroTasks.addLast(msg); + var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro); + tasks.add(msg); return msg.notifier; } public Awaitable pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); } + @Override public void onFramePop(Context ctx, CodeFrame frame) { + if (debugging && debugger != null) debugger.onFramePop(ctx, frame); + } + @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); + else return false; + } + @Override public void onSource(Filename filename, String source, TreeSet breakpoints) { + if (!debugging) return; + if (debugger != null) debugger.onSource(filename, source, breakpoints); + sources.put(filename, source); + bpts.put(filename, breakpoints); + } + public Engine(boolean debugging) { this.debugging = debugging; } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5c2c4e2..49d67c5 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -8,12 +8,15 @@ import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.filesystem.RootFilesystem; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeWrapperProvider; +import me.topchetoeu.jscript.permissions.Permission; +import me.topchetoeu.jscript.permissions.PermissionsProvider; -public class Environment { +public class Environment implements PermissionsProvider { private HashMap prototypes = new HashMap<>(); public final Data data = new Data(); @@ -21,6 +24,12 @@ public class Environment { public GlobalScope global; public WrappersProvider wrappers; + public PermissionsProvider permissions = null; + public final RootFilesystem filesystem = new RootFilesystem(this); + + private static int nextId = 0; + + @Native public int id = ++nextId; @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @@ -68,9 +77,13 @@ public class Environment { return res; } - public Context context(Engine engine, Data data) { - return new Context(engine, data).pushEnv(this); + @Override public boolean hasPermission(Permission perm, char delim) { + return permissions == null || permissions.hasPermission(perm, delim); } + @Override public boolean hasPermission(Permission perm) { + return permissions == null || permissions.hasPermission(perm); + } + public Context context(Engine engine) { return new Context(engine).pushEnv(this); } diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java deleted file mode 100644 index 9e42eca..0000000 --- a/src/me/topchetoeu/jscript/engine/StackData.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.topchetoeu.jscript.engine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.engine.debug.Debugger; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.exceptions.EngineException; - -public class StackData { - public static final DataKey> FRAMES = new DataKey<>(); - public static final DataKey MAX_FRAMES = new DataKey<>(); - public static final DataKey DEBUGGER = new DataKey<>(); - - public static void pushFrame(Context ctx, CodeFrame frame) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - frames.add(frame); - if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) - throw EngineException.ofRange("Stack overflow!"); - ctx.pushEnv(frame.function.environment); - } - public static boolean popFrame(Context ctx, CodeFrame frame) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - if (frames.size() == 0) return false; - if (frames.get(frames.size() - 1) != frame) return false; - frames.remove(frames.size() - 1); - ctx.popEnv(); - ctx.engine.onFramePop(ctx, frame); - return true; - } - public static CodeFrame peekFrame(Context ctx) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - if (frames.size() == 0) return null; - return frames.get(frames.size() - 1); - } - - public static List frames(Context ctx) { - return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>())); - } - public static List stackTrace(Context ctx) { - var res = new ArrayList(); - var frames = frames(ctx); - - for (var i = frames.size() - 1; i >= 0; i--) { - var el = frames.get(i); - var name = el.function.name; - Location loc = null; - - for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; - if (loc == null) loc = el.function.loc(); - - var trace = ""; - - if (loc != null) trace += "at " + loc.toString() + " "; - if (name != null && !name.equals("")) trace += "in " + name + " "; - - trace = trace.trim(); - - if (!trace.equals("")) res.add(trace); - } - - return res; - } -} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 5d7248a..3154ab6 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -19,7 +19,7 @@ import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; public class DebugServer { - public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION; + public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); public final HashMap targets = new HashMap<>(); @@ -236,9 +236,9 @@ public class DebugServer { this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); this.index = index - .replace("${NAME}", Metadata.NAME) - .replace("${VERSION}", Metadata.VERSION) - .replace("${AUTHOR}", Metadata.AUTHOR) + .replace("${NAME}", Metadata.name()) + .replace("${VERSION}", Metadata.version()) + .replace("${AUTHOR}", Metadata.author()) .getBytes(); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index c2d7c4a..eaae038 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -15,7 +15,6 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.GlobalScope; @@ -32,6 +31,7 @@ import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; +// very simple indeed public class SimpleDebugger implements Debugger { public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e>\",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}"; @@ -137,7 +137,7 @@ public class SimpleDebugger implements Debugger { } } - private class RunResult { + private static class RunResult { public final Context ctx; public final Object result; public final EngineException error; @@ -187,7 +187,7 @@ public class SimpleDebugger implements Debugger { } private void updateFrames(Context ctx) { - var frame = StackData.peekFrame(ctx); + var frame = ctx.peekFrame(); if (frame == null) return; if (!codeFrameToFrame.containsKey(frame)) { @@ -202,7 +202,7 @@ public class SimpleDebugger implements Debugger { } private JSONList serializeFrames(Context ctx) { var res = new JSONList(); - var frames = StackData.frames(ctx); + var frames = ctx.frames(); for (var i = frames.size() - 1; i >= 0; i--) { res.add(codeFrameToFrame.get(frames.get(i)).serialized); @@ -474,8 +474,9 @@ public class SimpleDebugger implements Debugger { @Override public void setBreakpointByUrl(V8Message msg) { var line = (int)msg.params.number("lineNumber") + 1; var col = (int)msg.params.number("columnNumber", 0) + 1; - var cond = msg.params.string("condition", null); + var cond = msg.params.string("condition", "").trim(); + if (cond.equals("")) cond = null; if (cond != null) cond = "(" + cond + ")"; Pattern regex; @@ -599,10 +600,10 @@ public class SimpleDebugger implements Debugger { if (obj != emptyObject) { 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(ctx, prop.getter)); if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); @@ -782,7 +783,7 @@ public class SimpleDebugger implements Debugger { try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } - if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); + if (ctx.frames().size() == 0) resume(State.RESUMED); else if (stepOutFrame != null && stepOutFrame.frame == frame && (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) ) { diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java index 253bf38..ea1eaef 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -118,8 +118,6 @@ public class WebSocket implements AutoCloseable { else send(msg.textData()); } public void send(Object data) { - // TODO: Remove - // System.out.println("SEND: " + data); if (closed) throw new IllegalStateException("Object is closed."); write(1, data.toString().getBytes()); } @@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable { if (!fin) continue; var raw = data.toByteArray(); - // TODO: Remove - // System.out.println("RECEIVED: " + new String(raw)); - - if (type == 1) return new WebSocketMessage(new String(raw)); else return new WebSocketMessage(raw); } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 57a4e05..7ea3a4d 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Stack; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -151,15 +152,17 @@ public class CodeFrame { public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); + Instruction instr = null; + if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr]; + if (returnValue == Runners.NO_RETURN && error == null) { try { if (Thread.currentThread().isInterrupted()) throw new InterruptException(); - var instr = function.body[codePtr]; - ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); - - if (codePtr < 0 || codePtr >= function.body.length) returnValue = null; + if (instr == null) returnValue = null; else { + ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + if (instr.location != null) prevLoc = instr.location; try { @@ -278,11 +281,11 @@ public class CodeFrame { } if (error != null) { - ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false); + ctx.engine.onInstruction(ctx, this, instr, null, error, false); throw error; } if (returnValue != Runners.NO_RETURN) { - ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); + ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false); return returnValue; } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index e2dc2bf..adc14d4 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -5,7 +5,6 @@ import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -35,7 +34,7 @@ public class CodeFunction extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); try { - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); while (true) { var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); @@ -43,7 +42,7 @@ public class CodeFunction extends FunctionValue { } } finally { - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); } } diff --git a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java index 865a381..01eb281 100644 --- a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java +++ b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java @@ -11,7 +11,7 @@ public abstract class FunctionValue extends ObjectValue { @Override public String toString() { - return "function(...) { ...}"; + return "function %s(...)".formatted(name); } public abstract Object call(Context ctx, Object thisArg, Object ...args); @@ -21,21 +21,21 @@ public abstract class FunctionValue extends ObjectValue { @Override protected Object getField(Context ctx, Object key) { - if (key.equals("name")) return name; - if (key.equals("length")) return length; + if ("name".equals(key)) return name; + if ("length".equals(key)) return length; return super.getField(ctx, key); } @Override protected boolean setField(Context ctx, Object key, Object val) { - if (key.equals("name")) name = Values.toString(ctx, val); - else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); + if ("name".equals(key)) name = Values.toString(ctx, val); + else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val); else return super.setField(ctx, key, val); return true; } @Override protected boolean hasField(Context ctx, Object key) { - if (key.equals("name")) return true; - if (key.equals("length")) return true; + if ("name".equals(key)) return true; + if ("length".equals(key)) return true; return super.hasField(ctx, key); } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 49d785e..cda9881 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue { else return super.getPrototype(ctx); } + @Override + public String toString() { + return wrapped.toString(); + } + @Override + public boolean equals(Object obj) { + return wrapped.equals(obj); + } + @Override + public int hashCode() { + return wrapped.hashCode(); + } + public NativeWrapper(Object wrapped) { this.wrapped = wrapped; prototype = NATIVE_PROTO; diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index f6b4552..9bbc3ef 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -262,7 +262,7 @@ public class ObjectValue { values.put(key, val); return true; } - else if (key.equals("__proto__")) return setPrototype(ctx, val); + else if ("__proto__".equals(key)) return setPrototype(ctx, val); else if (nonWritableSet.contains(key)) return false; else return setField(ctx, key, val); } @@ -273,7 +273,7 @@ public class ObjectValue { public final boolean hasMember(Context ctx, Object key, boolean own) { key = Values.normalize(ctx, key); - if (key != null && key.equals("__proto__")) return true; + if (key != null && "__proto__".equals(key)) return true; if (hasField(ctx, key)) return true; if (properties.containsKey(key)) return true; if (own) return false; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 7ece5de..b4cc5a4 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -17,8 +17,27 @@ import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.lib.PromiseLib; 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 boolean isObject(Object val) { return val instanceof ObjectValue; } @@ -105,7 +124,7 @@ public class Values { } public static boolean toBoolean(Object obj) { if (obj == NULL || obj == null) return false; - if (obj instanceof Number && number(obj) == 0) 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; @@ -137,7 +156,7 @@ public class Values { } if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof String) return (String)val; - if (val instanceof Symbol) return ((Symbol)val).toString(); + if (val instanceof Symbol) return val.toString(); return "Unknown value"; } @@ -191,12 +210,18 @@ public class Values { return _a >>> _b; } - public static int compare(Context ctx, Object a, Object b) { + public static CompareResult compare(Context ctx, Object a, Object b) { a = toPrimitive(ctx, a, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF); - if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b); - else return Double.compare(toNumber(ctx, a), toNumber(ctx, b)); + if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b)); + + var _a = toNumber(ctx, a); + var _b = toNumber(ctx, 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) { @@ -232,10 +257,10 @@ public class Values { case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]); - case GREATER: return compare(ctx, args[0], args[1]) > 0; - case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; - case LESS: return compare(ctx, args[0], args[1]) < 0; - case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; + case GREATER: return compare(ctx, args[0], args[1]).greater(); + case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual(); + case LESS: return compare(ctx, args[0], args[1]).less(); + case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual(); case INVERSE: return bitwiseNot(ctx, args[0]); case NOT: return not(args[0]); @@ -272,15 +297,20 @@ public class Values { var proto = getPrototype(ctx, obj); - if (proto == null) return key.equals("__proto__") ? NULL : null; - else if (key != null && key.equals("__proto__")) return proto; + 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(Context ctx, Object obj, Object ...path) { + var res = obj; + for (var key : path) res = getMember(ctx, res, key); + return res; + } public static boolean setMember(Context 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.equals("__proto__")) return setPrototype(ctx, obj, val); + if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); var proto = getPrototype(ctx, obj); @@ -290,7 +320,7 @@ public class Values { if (obj == null || obj == NULL) return false; obj = normalize(ctx, obj); key = normalize(ctx, key); - if (key.equals("__proto__")) return true; + if ("__proto__".equals(key)) return true; if (isObject(obj)) return object(obj).hasMember(ctx, key, own); if (obj instanceof String && key instanceof Number) { @@ -518,7 +548,7 @@ public class Values { throw new ConvertException(type(obj), clazz.getSimpleName()); } - public static Iterable toJavaIterable(Context ctx, Object obj) { + public static Iterable fromJSIterator(Context ctx, Object obj) { return () -> { try { var symbol = ctx.environment().symbol("Symbol.iterator"); @@ -571,7 +601,7 @@ public class Values { }; } - public static ObjectValue fromJavaIterator(Context ctx, Iterator it) { + public static ObjectValue toJSIterator(Context ctx, Iterator it) { var res = new ObjectValue(); try { @@ -592,8 +622,31 @@ public class Values { return res; } - public static ObjectValue fromJavaIterable(Context ctx, Iterable it) { - return fromJavaIterator(ctx, it.iterator()); + public static ObjectValue toJSIterator(Context ctx, Iterable it) { + return toJSIterator(ctx, it.iterator()); + } + + public static ObjectValue toJSAsyncIterator(Context ctx, Iterator it) { + var res = new ObjectValue(); + + try { + var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator"); + res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); + } + catch (IllegalArgumentException | NullPointerException e) { } + + res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { + return PromiseLib.await(ctx, () -> { + if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); + else { + var obj = new ObjectValue(); + obj.defineProperty(_ctx, "value", it.next()); + return obj; + } + }); + })); + + return res; } private static boolean isEmptyFunc(ObjectValue val) { @@ -620,10 +673,7 @@ public class Values { var printed = true; if (val instanceof FunctionValue) { - System.out.print("function "); - var name = Values.getMember(ctx, val, "name"); - if (name != null) System.out.print(Values.toString(ctx, name)); - System.out.print("(...)"); + System.out.print(val.toString()); var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null; if (loc != null) System.out.print(" @ " + loc); diff --git a/src/me/topchetoeu/jscript/filesystem/Buffer.java b/src/me/topchetoeu/jscript/filesystem/Buffer.java new file mode 100644 index 0000000..3a77157 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Buffer.java @@ -0,0 +1,41 @@ +package me.topchetoeu.jscript.filesystem; + +public class Buffer { + private byte[] data; + private int length; + + public void write(int i, byte[] val) { + if (i + val.length > data.length) { + var newCap = i + val.length + 1; + if (newCap < data.length * 2) newCap = data.length * 2; + + var tmp = new byte[newCap]; + System.arraycopy(this.data, 0, tmp, 0, length); + this.data = tmp; + } + + System.arraycopy(val, 0, data, i, val.length); + if (i + val.length > length) length = i + val.length; + } + public int read(int i, byte[] buff) { + int n = buff.length; + if (i + n > length) n = length - i; + System.arraycopy(data, i, buff, 0, n); + return n; + } + + public byte[] data() { + var res = new byte[length]; + System.arraycopy(this.data, 0, res, 0, length); + return res; + } + public int length() { + return length; + } + + public Buffer(byte[] data) { + this.data = new byte[data.length]; + this.length = data.length; + System.arraycopy(data, 0, this.data, 0, data.length); + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/EntryType.java b/src/me/topchetoeu/jscript/filesystem/EntryType.java index 26e255a..c22a8df 100644 --- a/src/me/topchetoeu/jscript/filesystem/EntryType.java +++ b/src/me/topchetoeu/jscript/filesystem/EntryType.java @@ -1,7 +1,13 @@ package me.topchetoeu.jscript.filesystem; public enum EntryType { - NONE, - FILE, - FOLDER, + 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/me/topchetoeu/jscript/filesystem/File.java b/src/me/topchetoeu/jscript/filesystem/File.java index b7a14a8..487b5dc 100644 --- a/src/me/topchetoeu/jscript/filesystem/File.java +++ b/src/me/topchetoeu/jscript/filesystem/File.java @@ -1,27 +1,39 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; - public interface File { - int read() throws IOException, InterruptedException; - boolean write(byte val) throws IOException, InterruptedException; - long tell() throws IOException, InterruptedException; - void seek(long offset, int pos) throws IOException, InterruptedException; - void close() throws IOException, InterruptedException; - Permissions perms(); + int read(byte[] buff); + void write(byte[] buff); + long getPtr(); + void setPtr(long offset, int pos); + void close(); + Mode mode(); - default String readToString() throws IOException, InterruptedException { - seek(0, 2); - long len = tell(); + default String readToString() { + setPtr(0, 2); + long len = getPtr(); if (len < 0) return null; - seek(0, 0); - byte[] res = new byte[(int)len]; + setPtr(0, 0); - for (var i = 0; i < len; i++) { - res[i] = (byte)read(); - } + byte[] res = new byte[(int)len]; + read(res); return new String(res); } + default String readLine() { + var res = new Buffer(new byte[0]); + 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()); + } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/FileStat.java b/src/me/topchetoeu/jscript/filesystem/FileStat.java new file mode 100644 index 0000000..8140d4a --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/FileStat.java @@ -0,0 +1,11 @@ +package me.topchetoeu.jscript.filesystem; + +public class FileStat { + public final Mode mode; + public final EntryType type; + + public FileStat(Mode mode, EntryType type) { + this.mode = mode; + this.type = type; + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/Filesystem.java b/src/me/topchetoeu/jscript/filesystem/Filesystem.java index 02ae9d5..0cfea46 100644 --- a/src/me/topchetoeu/jscript/filesystem/Filesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/Filesystem.java @@ -1,10 +1,7 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; - public interface Filesystem { - File open(String path) throws IOException, InterruptedException; - boolean mkdir(String path) throws IOException, InterruptedException; - EntryType type(String path) throws IOException, InterruptedException; - boolean rm(String path) throws IOException, InterruptedException; -} + File open(String path, Mode mode) throws FilesystemException; + void create(String path, EntryType type) throws FilesystemException; + FileStat stat(String path) throws FilesystemException; +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/FilesystemException.java b/src/me/topchetoeu/jscript/filesystem/FilesystemException.java new file mode 100644 index 0000000..63745e3 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/FilesystemException.java @@ -0,0 +1,55 @@ +package me.topchetoeu.jscript.filesystem; + +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class FilesystemException extends RuntimeException { + public static enum FSCode { + DOESNT_EXIST(0x1), + NOT_FILE(0x2), + NOT_FOLDER(0x3), + NO_PERMISSIONS_R(0x4), + NO_PERMISSIONS_RW(0x5), + FOLDER_NOT_EMPTY(0x6), + ALREADY_EXISTS(0x7), + FOLDER_EXISTS(0x8); + + public final int code; + + private FSCode(int code) { this.code = code; } + } + + public static final String[] MESSAGES = { + "How did we get here?", + "The file or folder '%s' doesn't exist or is inaccessible.", + "'%s' is not a file", + "'%s' is not a folder", + "No permissions to read '%s'", + "No permissions to write '%s'", + "Can't delete '%s', since it is a full folder.", + "'%s' already exists." + }; + + public final String message, filename; + public final FSCode code; + + public FilesystemException(String message, String filename, FSCode code) { + super(code + ": " + message.formatted(filename)); + this.message = message; + this.code = code; + this.filename = filename; + } + public FilesystemException(String filename, FSCode code) { + super(code + ": " + MESSAGES[code.code].formatted(filename)); + this.message = MESSAGES[code.code]; + this.code = code; + this.filename = filename; + } + + public EngineException toEngineException() { + var res = EngineException.ofError("IOError", getMessage()); + Values.setMember(null, res.value, "code", code); + Values.setMember(null, res.value, "filename", filename.toString()); + return res; + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java b/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java deleted file mode 100644 index 2cc59b4..0000000 --- a/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.topchetoeu.jscript.filesystem; - -import java.io.IOException; - -public class InaccessibleFile implements File { - public static final InaccessibleFile INSTANCE = new InaccessibleFile(); - - @Override - public int read() throws IOException, InterruptedException { - return -1; - } - - @Override - public boolean write(byte val) throws IOException, InterruptedException { - return false; - } - - @Override - public long tell() throws IOException, InterruptedException { - return 0; - } - - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { } - - @Override - public void close() throws IOException, InterruptedException { } - - @Override - public Permissions perms() { - return Permissions.NONE; - } - - private InaccessibleFile() { } -} diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java index c414a7e..0ca8425 100644 --- a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java @@ -1,53 +1,66 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; public class MemoryFile implements File { private int ptr; - private Permissions mode; - public final byte[] data; + private Mode mode; + private Buffer data; + private String filename; + + public Buffer data() { return data; } @Override - public int read() throws IOException, InterruptedException { - if (data == null || !mode.readable || ptr >= data.length) return -1; - return data[ptr++]; + public int read(byte[] buff) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + var res = data.read(ptr, buff); + ptr += res; + return res; + } + @Override + public void write(byte[] buff) { + if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); + + data.write(ptr, buff); + ptr += buff.length; } @Override - public boolean write(byte val) throws IOException, InterruptedException { - if (data == null || !mode.writable || ptr >= data.length) return false; - data[ptr++] = val; - return true; - } - - @Override - public long tell() throws IOException, InterruptedException { + public long getPtr() { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); return ptr; } - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { - if (data == null) return; + public void setPtr(long offset, int pos) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (pos == 0) ptr = (int)offset; else if (pos == 1) ptr += (int)offset; - else if (pos == 2) ptr = data.length - (int)offset; + else if (pos == 2) ptr = data.length() - (int)offset; } @Override - public void close() throws IOException, InterruptedException { - mode = null; + public void close() { + mode = Mode.NONE; ptr = 0; } - @Override - public Permissions perms() { - if (data == null) return Permissions.NONE; + public Mode mode() { + if (data == null) return Mode.NONE; return mode; } - public MemoryFile(byte[] buff, Permissions mode) { + public MemoryFile(String filename, Buffer buff, Mode mode) { + this.filename = filename; this.data = buff; this.mode = mode; } + + public static MemoryFile fromFileList(String filename, java.io.File[] list) { + var res = new StringBuilder(); + + for (var el : list) res.append(el.getName()).append('\n'); + + return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ); + } } diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java new file mode 100644 index 0000000..7db85db --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.filesystem; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; + +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + +public class MemoryFilesystem implements Filesystem { + public final Mode mode; + private HashMap files = new HashMap<>(); + private HashSet folders = new HashSet<>(); + + private Path getPath(String name) { + return Path.of("/" + name.replace("\\", "/")).normalize(); + } + + @Override + public void create(String path, EntryType type) { + var _path = getPath(path); + + switch (type) { + case FILE: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + files.put(_path, new Buffer(new byte[0])); + break; + case FOLDER: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + folders.add(_path); + break; + default: + case NONE: + if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + } + + @Override + public File open(String path, Mode perms) { + var _path = getPath(path); + var pcount = _path.getNameCount(); + + if (files.containsKey(_path)) return new MemoryFile(path, 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 new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); + } + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + @Override + public FileStat stat(String path) { + var _path = getPath(path); + + if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); + else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + public MemoryFilesystem put(String path, byte[] data) { + var _path = getPath(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/me/topchetoeu/jscript/filesystem/Mode.java b/src/me/topchetoeu/jscript/filesystem/Mode.java new file mode 100644 index 0000000..617e77b --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Mode.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.filesystem; + +public enum Mode { + NONE("", false, false), + READ("r", true, false), + READ_WRITE("rw", true, true); + + public final String name; + public final boolean readable; + public final boolean writable; + + public Mode intersect(Mode other) { + if (this == NONE || other == NONE) return NONE; + if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE; + return READ; + } + + private Mode(String mode, boolean r, boolean w) { + this.name = mode; + this.readable = r; + this.writable = w; + } + + public static Mode parse(String mode) { + switch (mode) { + case "r": return READ; + case "rw": return READ_WRITE; + default: return NONE; + } + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/Permissions.java b/src/me/topchetoeu/jscript/filesystem/Permissions.java deleted file mode 100644 index 5a5ff34..0000000 --- a/src/me/topchetoeu/jscript/filesystem/Permissions.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.topchetoeu.jscript.filesystem; - -public enum Permissions { - NONE("", false, false), - READ("r", true, false), - READ_WRITE("rw", true, true); - - public final String readMode; - public final boolean readable; - public final boolean writable; - - private Permissions(String mode, boolean r, boolean w) { - this.readMode = mode; - this.readable = r; - this.writable = w; - } -} diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java index cb5d067..5fba05e 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java @@ -1,50 +1,62 @@ package me.topchetoeu.jscript.filesystem; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + public class PhysicalFile implements File { + private String filename; private RandomAccessFile file; - private Permissions perms; + private Mode perms; @Override - public int read() throws IOException, InterruptedException { - if (file == null || !perms.readable) return -1; - else return file.read(); + public int read(byte[] buff) { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + else try { return file.read(buff); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } + } + @Override + public void write(byte[] buff) { + if (file == null || !perms.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); + else try { file.write(buff); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); } } @Override - public boolean write(byte val) throws IOException, InterruptedException { - if (file == null || !perms.writable) return false; - file.write(val); - return true; + public long getPtr() { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + else try { return file.getFilePointer(); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } + } + @Override + public void setPtr(long offset, int pos) { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + + try { + if (pos == 1) pos += file.getFilePointer(); + else if (pos == 2) pos += file.length(); + file.seek(pos); + } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } } @Override - public long tell() throws IOException, InterruptedException { - if (file == null) return 0; - return file.getFilePointer(); - } - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { + public void close() { if (file == null) return; - if (pos == 0) file.seek(pos); - else if (pos == 1) file.seek(file.getFilePointer() + pos); - else if (pos == 2) file.seek(file.length() + pos); + try { file.close(); } + catch (IOException e) {} // SHUT + file = null; + perms = Mode.NONE; } - @Override - public void close() throws IOException, InterruptedException { - if (file == null) return; - file.close(); - } + public Mode mode() { return perms; } - @Override - public Permissions perms() { return perms; } - - public PhysicalFile(String path, Permissions mode) throws IOException { - if (mode == Permissions.NONE) file = null; - else file = new RandomAccessFile(path, mode.readMode); + public PhysicalFile(String path, Mode mode) throws FileNotFoundException { + if (mode == Mode.NONE) file = null; + else try { file = new RandomAccessFile(path, mode.name); } + catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); } perms = mode; } diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java index 0241676..80bcf2f 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java @@ -1,74 +1,74 @@ package me.topchetoeu.jscript.filesystem; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + public class PhysicalFilesystem implements Filesystem { public final Path root; - private Permissions getPerms(Path path) { - var file = path.toFile(); - if (!path.startsWith(root)) return Permissions.NONE; - if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE; - if (file.canRead()) return Permissions.READ; - - return Permissions.NONE; - } private Path getPath(String name) { - return root.resolve(name); + return root.resolve(name.replace("\\", "/")).normalize(); + } + + private void checkMode(Path path, Mode mode) { + if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R); + if (mode.readable && !path.toFile().canRead()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R); + if (mode.writable && !path.toFile().canWrite()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); } @Override - public File open(String path) throws IOException, InterruptedException { - var _path = root.resolve(path); - - var perms = getPerms(_path); - if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE; - + public File open(String path, Mode perms) { + var _path = getPath(path); var f = _path.toFile(); - if (f.isDirectory()) { - var res = new StringBuilder(); + checkMode(_path, perms); - for (var child : f.listFiles()) res.append(child.toString()).append('\n'); - return new MemoryFile(res.toString().getBytes(), Permissions.READ); + if (f.isDirectory()) return MemoryFile.fromFileList(path, f.listFiles()); + else try { return new PhysicalFile(path, perms); } + catch (FileNotFoundException e) { throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); } + } + + @Override + public void create(String path, EntryType type) { + var _path = getPath(path); + var f = _path.toFile(); + + switch (type) { + case FILE: + try { + if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); + else break; + } + catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); } + case FOLDER: + if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); + else break; + case NONE: + default: + if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); + else break; } - else return new PhysicalFile(path, perms); } @Override - public boolean mkdir(String path) throws IOException, InterruptedException { + public FileStat stat(String path) { var _path = getPath(path); - var perms = getPerms(_path); var f = _path.toFile(); - if (!perms.writable) return false; - else return f.mkdir(); - } + if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); + checkMode(_path, Mode.READ); - @Override - public EntryType type(String path) throws IOException, InterruptedException { - var _path = getPath(path); - var perms = getPerms(_path); - var f = _path.toFile(); - - if (perms == Permissions.NONE) return EntryType.NONE; - else if (f.isFile()) return EntryType.FILE; - else return EntryType.FOLDER; - } - - @Override - public boolean rm(String path) throws IOException, InterruptedException { - var _path = getPath(path); - var perms = getPerms(_path); - var f = _path.toFile(); - - if (!perms.writable) return false; - else return f.delete(); + return new FileStat( + f.canWrite() ? Mode.READ_WRITE : Mode.READ, + f.isFile() ? EntryType.FILE : EntryType.FOLDER + ); } public PhysicalFilesystem(Path root) { - this.root = root; + this.root = root.toAbsolutePath().normalize(); } } diff --git a/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java new file mode 100644 index 0000000..9afd813 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java @@ -0,0 +1,57 @@ +package me.topchetoeu.jscript.filesystem; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.permissions.PermissionsProvider; + +public class RootFilesystem implements Filesystem { + public final Map protocols = new HashMap<>(); + public final PermissionsProvider perms; + + private boolean canRead(String _path) { + return perms.hasPermission("jscript.file.read:" + _path, '/'); + } + private boolean canWrite(String _path) { + return perms.hasPermission("jscript.file.write:" + _path, '/'); + } + + private void modeAllowed(String _path, Mode mode) throws FilesystemException { + if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R); + if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); + } + + @Override public File open(String path, Mode perms) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), perms); + + try { return protocol.open(filename.path, perms); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + @Override public void create(String path, EntryType type) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), Mode.READ_WRITE); + + try { protocol.create(filename.path, type); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + @Override public FileStat stat(String path) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), Mode.READ); + + try { return protocol.stat(path); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + + public RootFilesystem(PermissionsProvider perms) { + this.perms = perms; + } +} diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index a120171..6edc93f 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -108,6 +108,12 @@ public class NativeWrapperProvider implements WrappersProvider { } } + public static String getName(Class clazz) { + var classNat = clazz.getAnnotation(Native.class); + if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim(); + else return clazz.getSimpleName(); + } + /** * Generates a prototype for the given class. * The returned object will have appropriate wrappers for all instance members. @@ -117,6 +123,8 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeProto(Environment ctx, Class clazz) { var res = new ObjectValue(); + res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz)); + for (var overload : clazz.getDeclaredMethods()) { var init = overload.getAnnotation(NativeInit.class); if (init == null || init.value() != InitType.PROTOTYPE) continue; @@ -137,11 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider { * @param clazz The class for which a constructor should be generated */ public static FunctionValue makeConstructor(Environment ctx, Class clazz) { - var name = clazz.getName(); - var classNat = clazz.getAnnotation(Native.class); - if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim(); - - FunctionValue func = new OverloadFunction(name); + FunctionValue func = new OverloadFunction(getName(clazz)); for (var overload : clazz.getDeclaredConstructors()) { var nat = overload.getAnnotation(Native.class); @@ -256,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider { constructors.put(clazz, value); } + private void initError() { + var proto = new ObjectValue(); + proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> { + if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage(); + else return null; + })); + proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass()))); + + var constr = makeConstructor(null, Throwable.class); + proto.defineProperty(null, "constructor", constr, true, false, false); + constr.defineProperty(null, "prototype", proto, true, false, false); + + proto.setPrototype(null, getProto(Object.class)); + constr.setPrototype(null, getConstr(Object.class)); + + setProto(Throwable.class, proto); + setConstr(Throwable.class, constr); + } + public NativeWrapperProvider(Environment env) { this.env = env; + initError(); } } diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index ee0470a..9edd2a5 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -8,6 +8,7 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeWrapper; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; @@ -92,7 +93,14 @@ public class OverloadFunction extends FunctionValue { throw new InterruptException(); } else { - throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); + var target = e.getTargetException(); + var targetClass = target.getClass(); + var err = new NativeWrapper(e.getTargetException()); + + err.defineProperty(ctx, "message", target.getMessage()); + err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass)); + + throw new EngineException(err).add(name, loc); } } catch (ReflectiveOperationException e) { diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/me/topchetoeu/jscript/js/bootstrap.js index 74477af..6736a28 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/me/topchetoeu/jscript/js/bootstrap.js @@ -1,7 +1,13 @@ (function (_arguments) { var ts = _arguments[0]; - var src = '', lib = _arguments[2].concat([ 'declare const exit: never; declare const go: any;' ]).join(''), decls = '', version = 0; + var src = '', version = 0; + var lib = _arguments[2].concat([ + 'declare const exit: never; declare const go: any;', + 'declare function getTsDeclarations(): string[];' + ]).join(''); var libSnapshot = ts.ScriptSnapshot.fromString(lib); + var environments = {}; + var declSnapshots = []; var settings = { outDir: "/out", @@ -21,18 +27,27 @@ var service = ts.createLanguageService({ getCurrentDirectory: function() { return "/"; }, getDefaultLibFileName: function() { return "/lib.d.ts"; }, - getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; }, + getScriptFileNames: function() { + var res = [ "/src.ts", "/lib.d.ts" ]; + for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts"); + return res; + }, getCompilationSettings: function () { return settings; }, fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; }, getScriptSnapshot: function(filename) { if (filename === "/lib.d.ts") return libSnapshot; if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src); - if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls); + + var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename); + if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) { + return declSnapshots[index - 1]; + } + throw new Error("File '" + filename + "' doesn't exist."); }, getScriptVersion: function (filename) { - if (filename === "/lib.d.ts") return 0; + if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0; else return version; }, }, reg); @@ -40,10 +55,12 @@ service.getEmitOutput("/lib.d.ts"); log("Loaded libraries!"); - function compile(code, filename) { + function compile(code, filename, env) { src = code; version++; + if (!environments[env.id]) environments[env.id] = [] + declSnapshots = environments[env.id]; var emit = service.getEmitOutput("/src.ts"); var diagnostics = [] @@ -58,31 +75,37 @@ if (file === "src.ts") file = filename; return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message; } - else return "Error: " + message; + else return message; }); if (diagnostics.length > 0) { throw new SyntaxError(diagnostics.join("\n")); } - return { - result: emit.outputFiles[0].text, - declaration: emit.outputFiles[1].text - }; - } - - _arguments[1].compile = function (filename, code) { - var res = compile(filename, code); + var result = emit.outputFiles[0].text; + var declaration = emit.outputFiles[1].text; + return { - source: res.result, + source: result, runner: function(func) { return function() { var val = func.apply(this, arguments); - decls += res.declaration; + if (declaration !== '') { + declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); + } return val; } } + }; + } + + function apply(env) { + env.compile = compile; + env.global.getTsDeclarations = function() { + return environments[env.id]; } } + + apply(_arguments[1]); })(arguments); diff --git a/src/me/topchetoeu/jscript/js/lib.d.ts b/src/me/topchetoeu/jscript/js/lib.d.ts index df95570..a128ca4 100644 --- a/src/me/topchetoeu/jscript/js/lib.d.ts +++ b/src/me/topchetoeu/jscript/js/lib.d.ts @@ -482,6 +482,35 @@ interface PromiseConstructor { allSettled(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult>}]>; } +interface FileStat { + type: 'file' | 'folder'; + mode: 'r' | 'rw'; +} +interface File { + readonly pointer: Promise; + readonly length: Promise; + readonly mode: Promise<'' | 'r' | 'rw'>; + + read(n: number): Promise; + write(buff: number[]): Promise; + close(): Promise; + setPointer(val: number): Promise; +} +interface Filesystem { + open(path: string, mode: 'r' | 'rw'): Promise; + ls(path: string): AsyncIterableIterator; + mkdir(path: string): Promise; + mkfile(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + stat(path: string): Promise; + exists(path: string): Promise; +} + +interface Encoding { + encode(val: string): number[]; + decode(val: number[]): string; +} + declare var String: StringConstructor; //@ts-ignore declare const arguments: IArguments; @@ -508,6 +537,8 @@ declare var Object: ObjectConstructor; declare var Symbol: SymbolConstructor; declare var Promise: PromiseConstructor; declare var Math: MathObject; +declare var Encoding: Encoding; +declare var Filesystem: Filesystem; declare var Error: ErrorConstructor; declare var RangeError: RangeErrorConstructor; diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index 413e0c7..b9b1065 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -4,16 +4,14 @@ import java.util.Iterator; import java.util.Stack; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeSetter; @Native("Array") public class ArrayLib { @@ -25,10 +23,10 @@ import me.topchetoeu.jscript.interop.NativeSetter; } @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, thisArg); + return Values.toJSIterator(ctx, thisArg); } @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, () -> new Iterator() { + return Values.toJSIterator(ctx, () -> new Iterator() { private int i = 0; @Override @@ -43,7 +41,7 @@ import me.topchetoeu.jscript.interop.NativeSetter; }); } @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, () -> new Iterator() { + return Values.toJSIterator(ctx, () -> new Iterator() { private int i = 0; @Override @@ -94,8 +92,11 @@ import me.topchetoeu.jscript.interop.NativeSetter; } @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) { + var defaultCmp = new NativeFunction("", (_ctx, thisArg, args) -> { + return Values.toString(ctx, args[0]).compareTo(Values.toString(ctx, args[1])); + }); arr.sort((a, b) -> { - var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); + var res = Values.toNumber(ctx, (cmp == null ? defaultCmp : cmp).call(ctx, null, a, b)); if (res < 0) return -1; if (res > 0) return 1; return 0; @@ -369,8 +370,4 @@ import me.topchetoeu.jscript.interop.NativeSetter; return res; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Array"); - } } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index ff248e4..c352a20 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -21,7 +20,7 @@ import me.topchetoeu.jscript.interop.Native; private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); ctx.pushEnv(frame.function.environment); awaiting = false; @@ -40,7 +39,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); if (awaiting) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 7f2e78c..0a2dd47 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.lib; import java.util.Map; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -29,7 +28,7 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); state = 0; while (state == 0) { @@ -50,7 +49,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); if (state == 1) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/BooleanLib.java b/src/me/topchetoeu/jscript/lib/BooleanLib.java index e9c2a3f..2ac5e0d 100644 --- a/src/me/topchetoeu/jscript/lib/BooleanLib.java +++ b/src/me/topchetoeu/jscript/lib/BooleanLib.java @@ -1,13 +1,10 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Boolean") public class BooleanLib { public static final BooleanLib TRUE = new BooleanLib(true); @@ -30,7 +27,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public BooleanLib(boolean val) { this.value = val; } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Boolean"); - } } diff --git a/src/me/topchetoeu/jscript/lib/EncodingLib.java b/src/me/topchetoeu/jscript/lib/EncodingLib.java new file mode 100644 index 0000000..3f7a495 --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/EncodingLib.java @@ -0,0 +1,20 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.interop.Native; + +@Native("Encoding") +public class EncodingLib { + @Native public static ArrayValue encode(String value) { + var res = new ArrayValue(); + for (var el : value.getBytes()) res.set(null, res.size(), (int)el); + return res; + } + @Native public static String decode(Context ctx, ArrayValue raw) { + var res = new byte[raw.size()]; + for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(ctx, raw.get(i)); + return new String(res); + } +} diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index e33cf1a..c681ed4 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -2,10 +2,10 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; @@ -51,8 +51,8 @@ import me.topchetoeu.jscript.interop.NativeInit; var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; - target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx))); - target.defineProperty(ctx, "name", "Error"); + target.setPrototype(PlaceholderProto.ERROR); + target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace())); if (message == null) target.defineProperty(ctx, "message", ""); else target.defineProperty(ctx, "message", Values.toString(ctx, message)); @@ -60,7 +60,6 @@ import me.topchetoeu.jscript.interop.NativeInit; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Error"); target.defineProperty(null, "name", "Error"); } } diff --git a/src/me/topchetoeu/jscript/lib/FileLib.java b/src/me/topchetoeu/jscript/lib/FileLib.java new file mode 100644 index 0000000..8918f10 --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/FileLib.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ArrayValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.filesystem.File; +import me.topchetoeu.jscript.filesystem.FilesystemException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; + +@Native("File") +public class FileLib { + public final File file; + + @NativeGetter public PromiseLib pointer(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.getPtr(); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter public PromiseLib length(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + long curr = file.getPtr(); + file.setPtr(0, 2); + long res = file.getPtr(); + file.setPtr(curr, 0); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter public PromiseLib getMode(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.mode().name; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + + @Native public PromiseLib read(Context ctx, int n) { + return PromiseLib.await(ctx, () -> { + try { + var buff = new byte[n]; + var res = new ArrayValue(); + int resI = file.read(buff); + + for (var i = resI - 1; i >= 0; i--) res.set(ctx, i, (int)buff[i]); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib write(Context ctx, ArrayValue val) { + return PromiseLib.await(ctx, () -> { + try { + var res = new byte[val.size()]; + + for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(ctx, val.get(i)); + file.write(res); + + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib close(Context ctx) { + return PromiseLib.await(ctx, () -> { + file.close(); + return null; + }); + } + @Native public PromiseLib setPointer(Context ctx, long ptr) { + return PromiseLib.await(ctx, () -> { + try { + file.setPtr(ptr, 0); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + + public FileLib(File file) { + this.file = file; + } +} diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java new file mode 100644 index 0000000..98a14d3 --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -0,0 +1,165 @@ +package me.topchetoeu.jscript.lib; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Stack; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.filesystem.EntryType; +import me.topchetoeu.jscript.filesystem.File; +import me.topchetoeu.jscript.filesystem.FileStat; +import me.topchetoeu.jscript.filesystem.Filesystem; +import me.topchetoeu.jscript.filesystem.FilesystemException; +import me.topchetoeu.jscript.filesystem.Mode; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.interop.Native; + +@Native("Filesystem") +public class FilesystemLib { + private static Filesystem fs(Context ctx) { + var env = ctx.environment(); + if (env != null) { + var fs = ctx.environment().filesystem; + if (fs != null) return fs; + } + throw EngineException.ofError("Current environment doesn't have a file system."); + } + + @Native public static PromiseLib open(Context ctx, String _path, String mode) { + var filename = Filename.parse(_path); + var _mode = Mode.parse(mode); + + return PromiseLib.await(ctx, () -> { + try { + if (fs(ctx).stat(filename.path).type != EntryType.FILE) { + throw new FilesystemException(filename.toString(), FSCode.NOT_FILE); + } + + var file = fs(ctx).open(filename.path, _mode); + return new FileLib(file); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public static ObjectValue ls(Context ctx, String _path) throws IOException { + var filename = Filename.parse(_path); + + return Values.toJSAsyncIterator(ctx, new Iterator<>() { + private boolean failed, done; + private File file; + private String nextLine; + + private void update() { + if (done) return; + if (!failed) { + if (file == null) { + if (fs(ctx).stat(filename.path).type != EntryType.FOLDER) { + throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER); + } + + file = fs(ctx).open(filename.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(); } + } + }); + } + @Native public static PromiseLib mkdir(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + fs(ctx).create(Filename.parse(_path).toString(), EntryType.FOLDER); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + + } + @Native public static PromiseLib mkfile(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + fs(ctx).create(Filename.parse(_path).toString(), EntryType.FILE); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public static PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + if (!recursive) fs(ctx).create(Filename.parse(_path).toString(), EntryType.NONE); + else { + var stack = new Stack(); + stack.push(_path); + + while (!stack.empty()) { + var path = Filename.parse(stack.pop()).toString(); + FileStat stat; + + try { stat = fs(ctx).stat(path); } + catch (FilesystemException e) { continue; } + + if (stat.type == EntryType.FOLDER) { + for (var el : fs(ctx).open(path, Mode.READ).readToString().split("\n")) stack.push(el); + } + else fs(ctx).create(path, EntryType.NONE); + } + } + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public static PromiseLib stat(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + var stat = fs(ctx).stat(_path); + var res = new ObjectValue(); + + res.defineProperty(ctx, "type", stat.type.name); + res.defineProperty(ctx, "mode", stat.mode.name); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public static PromiseLib exists(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { fs(ctx).stat(_path); return true; } + catch (FilesystemException e) { return false; } + }); + } +} diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index 51581a0..77f5068 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -2,16 +2,12 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Function") public class FunctionLib { @Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) { @@ -43,7 +39,7 @@ import me.topchetoeu.jscript.interop.NativeInit; }); } @Native(thisArg = true) public static String toString(Context ctx, Object func) { - return "function (...) { ... }"; + return func.toString(); } @Native public static FunctionValue async(FunctionValue func) { @@ -55,8 +51,4 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native public static FunctionValue generator(FunctionValue func) { return new GeneratorFunctionLib(func); } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Function"); - } } diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 6f055a7..9df2175 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.ObjectValue; @@ -25,7 +24,7 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); yielding = false; while (!yielding) { @@ -43,7 +42,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); if (done) frame = null; else res = frame.pop(); diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 9247779..d9473a5 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -119,6 +119,8 @@ public class Internals { 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, "Date", false, wp.getConstr(DateLib.class)); glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index 53acc2a..f7762c5 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -35,15 +35,15 @@ import me.topchetoeu.jscript.interop.NativeGetter; var res = map.entrySet().stream().map(v -> { return new ArrayValue(ctx, v.getKey(), v.getValue()); }).collect(Collectors.toList()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(map.keySet()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(map.values()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public Object get(Object key) { @@ -68,7 +68,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; } @Native public MapLib(Context ctx, Object iterable) { - for (var el : Values.toJavaIterable(ctx, iterable)) { + for (var el : Values.fromJSIterator(ctx, iterable)) { try { set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); } diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 29e280a..53998e5 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -1,13 +1,10 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Number") public class NumberLib { @Native public static final double EPSILON = java.lang.Math.ulp(1.0); @@ -52,8 +49,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public NumberLib(double val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Number"); - } } diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 165bee5..83457fa 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -1,17 +1,14 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Object") public class ObjectLib { @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { @@ -143,7 +140,7 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native public static ObjectValue fromEntries(Context ctx, Object iterable) { var res = new ObjectValue(); - for (var el : Values.toJavaIterable(ctx, iterable)) { + for (var el : Values.fromJSIterator(ctx, iterable)) { if (el instanceof ArrayValue) { res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); } @@ -212,8 +209,4 @@ import me.topchetoeu.jscript.interop.NativeInit; // else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg); else return arg; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Object"); - } } diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 769fa80..4489041 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -13,12 +12,12 @@ import me.topchetoeu.jscript.engine.values.NativeWrapper; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.exceptions.InterruptException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Promise") public class PromiseLib { + public static interface PromiseRunner { + Object run(); + } private static class Handle { public final Context ctx; public final FunctionValue fulfilled; @@ -171,9 +170,7 @@ import me.topchetoeu.jscript.interop.NativeInit; } var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { - try { - res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); - } + try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); } catch (EngineException err) { res.reject(ctx, err.value); } return null; }); @@ -234,7 +231,7 @@ import me.topchetoeu.jscript.interop.NativeInit; private boolean handled = false; private Object val; - public void fulfill(Context ctx, Object val) { + public synchronized void fulfill(Context ctx, Object val) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, @@ -242,12 +239,12 @@ import me.topchetoeu.jscript.interop.NativeInit; new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) ); else { - Object next; - try { next = Values.getMember(ctx, val, "next"); } - catch (IllegalArgumentException e) { next = null; } + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } try { - if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) ); @@ -255,9 +252,13 @@ import me.topchetoeu.jscript.interop.NativeInit; this.val = val; this.state = STATE_FULFILLED; - for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); - - handles = null; + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + for (var handle : handles) { + handle.fulfilled.call(handle.ctx, null, val); + } + handles = null; + return null; + }), null); } } catch (EngineException err) { @@ -265,7 +266,7 @@ import me.topchetoeu.jscript.interop.NativeInit; } } } - public void reject(Context ctx, Object val) { + public synchronized void reject(Context ctx, Object val) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, @@ -273,12 +274,12 @@ import me.topchetoeu.jscript.interop.NativeInit; new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) ); else { - Object next; - try { next = Values.getMember(ctx, val, "next"); } - catch (IllegalArgumentException e) { next = null; } + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } try { - if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) ); @@ -286,19 +287,14 @@ import me.topchetoeu.jscript.interop.NativeInit; this.val = val; this.state = STATE_REJECTED; - for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (handles.size() == 0) { - ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { - if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); - throw new InterruptException(); - } - - return null; - }), null); - } - - handles = null; + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + for (var handle : handles) handle.rejected.call(handle.ctx, null, val); + if (!handled) { + Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); + } + handles = null; + return null; + }), null); } } catch (EngineException err) { @@ -353,7 +349,18 @@ import me.topchetoeu.jscript.interop.NativeInit; this(STATE_PENDING, null); } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Promise"); + public static PromiseLib await(Context ctx, PromiseRunner runner) { + var res = new PromiseLib(); + + new Thread(() -> { + try { + res.fulfill(ctx, runner.run()); + } + catch (EngineException e) { + res.reject(ctx, e.value); + } + }, "Promisifier").start(); + + return res; } } diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index 9e24482..3c48d42 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; @@ -11,11 +12,11 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native("RangeError") public class RangeErrorLib extends ErrorLib { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); + target.setPrototype(PlaceholderProto.SYNTAX_ERROR); target.defineProperty(ctx, "name", "RangeError"); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "RangeError"); target.defineProperty(null, "name", "RangeError"); } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index f8efa93..cea6970 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -153,7 +153,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; @Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) { var pattern = new RegExpLib(this.source, this.flags() + "g"); - return Values.fromJavaIterator(ctx, new Iterator() { + return Values.toJSIterator(ctx, new Iterator() { private Object val = null; private boolean updated = false; diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index 0afc096..90ab717 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -22,15 +22,15 @@ import me.topchetoeu.jscript.interop.NativeGetter; @Native public ObjectValue entries(Context ctx) { var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(set); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(set); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public Object add(Object key) { @@ -58,6 +58,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; } @Native public SetLib(Context ctx, Object iterable) { - for (var el : Values.toJavaIterable(ctx, iterable)) add(el); + for (var el : Values.fromJSIterator(ctx, iterable)) add(el); } } diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 5e5df1f..eaee54b 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -3,17 +3,14 @@ package me.topchetoeu.jscript.lib; import java.util.regex.Pattern; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; // TODO: implement index wrapping properly @Native("String") public class StringLib { @@ -263,8 +260,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public StringLib(String val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "String"); - } } diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 03fed13..f8bb132 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -4,16 +4,13 @@ import java.util.HashMap; import java.util.Map; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Symbol") public class SymbolLib { private static final Map symbols = new HashMap<>(); @@ -63,8 +60,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public SymbolLib(Symbol val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Symbol"); - } } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index 7c2a1ef..d47feb3 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; @@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); - target.defineProperty(ctx, "name", "SyntaxError"); + target.setPrototype(PlaceholderProto.SYNTAX_ERROR); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "SyntaxError"); target.defineProperty(null, "name", "SyntaxError"); } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java index 27298cd..7e4179c 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; @@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native("TypeError") public class TypeErrorLib extends ErrorLib { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { var target = ErrorLib.constructor(ctx, thisArg, message); - target.defineProperty(ctx, "name", "TypeError"); + target.setPrototype(PlaceholderProto.SYNTAX_ERROR); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "TypeError"); target.defineProperty(null, "name", "TypeError"); } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index d96aa34..f57ab7e 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -11,7 +11,6 @@ import java.util.TreeSet; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.*; -import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; @@ -1896,10 +1895,7 @@ public class Parsing { res.add(Instruction.throwSyntax(e)); } - if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) { - res.set(res.size() - 1, Instruction.ret()); - } - else res.add(Instruction.ret()); + res.add(Instruction.ret()); return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); } diff --git a/src/me/topchetoeu/jscript/permissions/Permission.java b/src/me/topchetoeu/jscript/permissions/Permission.java new file mode 100644 index 0000000..7c8b591 --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/Permission.java @@ -0,0 +1,124 @@ +package me.topchetoeu.jscript.permissions; + +import java.util.LinkedList; + +public class Permission { + private static class State { + public final int predI, trgI, wildcardI; + public final boolean wildcard; + + @Override + public String toString() { + return "State [pr=%s;trg=%s;wildN=%s;wild=%s]".formatted(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; + } + } + + public final String namespace; + public final String value; + + public boolean match(Permission perm) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value); + } + public boolean match(Permission perm, char delim) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value, delim); + } + + public boolean match(String perm) { + return match(new Permission(perm)); + } + public boolean match(String perm, char delim) { + return match(new Permission(perm), delim); + } + + @Override + public String toString() { + if (value != null) return namespace + ":" + value; + else return namespace; + } + + public Permission(String raw) { + var i = raw.indexOf(':'); + + if (i > 0) { + value = raw.substring(i + 1); + namespace = raw.substring(0, i); + } + else { + value = null; + namespace = raw; + } + } + + public static boolean match(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; + } + public static boolean match(String predicate, String target) { + 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(); + + if (state.predI >= predicate.length() || state.trgI >= target.length()) { + return state.predI >= predicate.length() && state.trgI >= target.length(); + } + + var predC = predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (predC == '*') { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + queue.add(new State(state.predI + 1, state.trgI, 0, false)); + } + else if (predC == '?' || predC == trgC) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; + } +} diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java new file mode 100644 index 0000000..bea8355 --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java @@ -0,0 +1,34 @@ +package me.topchetoeu.jscript.permissions; + +import java.util.ArrayList; + +public class PermissionsManager implements PermissionsProvider { + public static final PermissionsProvider ALL_PERMS = new PermissionsManager().add(new Permission("**")); + + public final ArrayList allowed = new ArrayList<>(); + public final ArrayList denied = new ArrayList<>(); + + public PermissionsProvider add(Permission perm) { + allowed.add(perm); + return this; + } + public PermissionsProvider add(String perm) { + allowed.add(new Permission(perm)); + return this; + } + + @Override + public boolean hasPermission(Permission perm, char delim) { + for (var el : denied) if (el.match(perm, delim)) return false; + for (var el : allowed) if (el.match(perm, delim)) return true; + + return false; + } + @Override + public boolean hasPermission(Permission perm) { + for (var el : denied) if (el.match(perm)) return false; + for (var el : allowed) if (el.match(perm)) return true; + + return false; + } +} diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java new file mode 100644 index 0000000..73c5ee6 --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.permissions; + +public interface PermissionsProvider { + boolean hasPermission(Permission perm, char delim); + boolean hasPermission(Permission perm); + + default boolean hasPermission(String perm, char delim) { + return hasPermission(new Permission(perm), delim); + } + default boolean hasPermission(String perm) { + return hasPermission(new Permission(perm)); + } +} \ No newline at end of file