diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 81f4d340a7..e110befc69 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -5,6 +5,7 @@ // Created by Brad Hefta-Gaub on 12/14/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -44,48 +45,126 @@ template inline T scriptvalue_cast(const ScriptValue& value); -/// [ScriptInterface] Provides an engine-independent interface for QScriptEngine +/** + * @brief Provides an engine-independent interface for a scripting engine + * + * Each script engine is strictly single threaded. + * + * This class only provides an interface to the underlying scripting engine, and doesn't + * provide the full environment needed to execute scripts. + * + * To execute scripts that have access to the API, use ScriptManager. + */ class ScriptEngine { public: typedef ScriptValue (*FunctionSignature)(ScriptContext*, ScriptEngine*); typedef ScriptValue (*MarshalFunction)(ScriptEngine*, const void*); typedef bool (*DemarshalFunction)(const ScriptValue&, QVariant &dest); + /** + * @brief Who owns a given object + * + */ enum ValueOwnership { - QtOwnership = 0, - ScriptOwnership = 1, - AutoOwnership = 2, + QtOwnership = 0, /** Object is managed by Qt */ + ScriptOwnership = 1, /** Object is managed by the script */ + AutoOwnership = 2, /** Ownership is determined automatically */ }; + /** + * @brief Which part of an object is exposed to the script + * + */ enum QObjectWrapOption { - //ExcludeChildObjects = 0x0001, // The script object will not expose child objects as properties. - ExcludeSuperClassMethods = 0x0002, // The script object will not expose signals and slots inherited from the superclass. - ExcludeSuperClassProperties = 0x0004, // The script object will not expose properties inherited from the superclass. + //ExcludeChildObjects = 0x0001, /** The script object will not expose child objects as properties. */ + ExcludeSuperClassMethods = 0x0002, /** The script object will not expose signals and slots inherited from the superclass. */ + ExcludeSuperClassProperties = 0x0004, /** The script object will not expose properties inherited from the superclass. */ ExcludeSuperClassContents = ExcludeSuperClassMethods | ExcludeSuperClassProperties, - //ExcludeDeleteLater = 0x0010, // The script object will not expose the QObject::deleteLater() slot. - ExcludeSlots = 0x0020, // The script object will not expose the QObject's slots. - AutoCreateDynamicProperties = 0x0100, // Properties that don't already exist in the QObject will be created as dynamic properties of that object, rather than as properties of the script object. - PreferExistingWrapperObject = 0x0200, // If a wrapper object with the requested configuration already exists, return that object. - SkipMethodsInEnumeration = 0x0008, // Don't include methods (signals and slots) when enumerating the object's properties. + //ExcludeDeleteLater = 0x0010, /** The script object will not expose the QObject::deleteLater() slot. */ + ExcludeSlots = 0x0020, /** The script object will not expose the QObject's slots. */ + AutoCreateDynamicProperties = 0x0100, /** Properties that don't already exist in the QObject will be created as dynamic properties of that object, rather than as properties of the script object. */ + PreferExistingWrapperObject = 0x0200, /** If a wrapper object with the requested configuration already exists, return that object. */ + SkipMethodsInEnumeration = 0x0008, /** Don't include methods (signals and slots) when enumerating the object's properties. */ }; Q_DECLARE_FLAGS(QObjectWrapOptions, QObjectWrapOption); public: + /** + * @brief Stops the currently running script + * + */ virtual void abortEvaluation() = 0; + + /** + * @brief Clears uncaughtException and related + * + */ virtual void clearExceptions() = 0; + virtual ScriptValue cloneUncaughtException(const QString& detail = QString()) = 0; + + /** + * @brief Context of the currently running script + * + * This allows getting a backtrace, the local variables of the currently running function, etc. + * + * @return ScriptContext* + */ virtual ScriptContext* currentContext() const = 0; + + /** + * @brief Runs a script + * + * This may be called several times during the lifetime of a scripting engine, with the + * side effects accumulating. + * + * @param program Code to run + * @param fileName Name of the script, for informational purposes + * @return ScriptValue Return value of the script when it finishes running. + */ virtual ScriptValue evaluate(const QString& program, const QString& fileName = QString()) = 0; + + /** + * @brief Evaluates a pre-compiled program + * + * @param program Program to evaluaate + * @return ScriptValue + */ virtual ScriptValue evaluate(const ScriptProgramPointer &program) = 0; + + + /** + * @brief Evaluate a script in a separate environment + * + * Used for evaluating included scripts + * + * @param locals Local variables available to the script + * @param program Code to run + * @return ScriptValue + */ virtual ScriptValue evaluateInClosure(const ScriptValue& locals, const ScriptProgramPointer& program) = 0; + + /** + * @brief Global namespace, containing all the public APIs + * + * @return ScriptValue + */ virtual ScriptValue globalObject() { Q_ASSERT(false); return ScriptValue(); } virtual bool hasUncaughtException() const = 0; + + /** + * @brief Whether a script is currently being evaluated + * + * @return true A script is currently being evaluated + * @return false No script is being evaluated + */ virtual bool isEvaluating() const = 0; //virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) = 0; - virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0; + + virtual ScriptValue checkScriptSyntax(ScriptProgramPointer program) = 0; virtual ScriptValue makeError(const ScriptValue& other = ScriptValue(), const QString& type = "Error") = 0; virtual ScriptManager* manager() const = 0; virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()) = 0; @@ -107,6 +186,15 @@ public: virtual ScriptValue newValue(const char* value) = 0; virtual ScriptValue newVariant(const QVariant& value) = 0; virtual ScriptValue nullValue() = 0; + + + /** + * @brief Causes an exception to be raised in the currently executing script + * + * @param exception + * @return true + * @return false + */ virtual bool raiseException(const ScriptValue& exception) = 0; virtual void registerEnum(const QString& enumName, QMetaEnum newEnum) = 0; virtual void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1) = 0; diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h index 08b3842877..a3326c8e1d 100644 --- a/libraries/script-engine/src/ScriptManager.h +++ b/libraries/script-engine/src/ScriptManager.h @@ -112,6 +112,8 @@ public: #define STATIC_SCRIPT_INITIALIZER(init) \ static ScriptManager::StaticInitializerNode static_script_initializer_(init); + + /**jsdoc * The Script API provides facilities for working with scripts. * @@ -144,6 +146,21 @@ public: * Read-only. * @property {Script.ResourceBuckets} ExternalPaths - External resource buckets. */ + +/** + * @brief Manages a single scripting engine + * + * This class manages and sets up a single scripting engine to make it execute scripts. + * + * It passes the objects needed to expose the public API, provides console access and error + * reporting and event management. + * + * This manipulates a single underlying instance of ScriptEngine. + * + * TODO: Split this class apart, create a ScriptManagerScriptingInterface to handle the JS API + * side separately. + * + */ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, public std::enable_shared_from_this { Q_OBJECT Q_PROPERTY(QString context READ getContext) @@ -153,6 +170,10 @@ public: static const QString SCRIPT_EXCEPTION_FORMAT; static const QString SCRIPT_BACKTRACE_SEP; + /** + * @brief Context of the script + * + */ enum Context { CLIENT_SCRIPT, ENTITY_CLIENT_SCRIPT, @@ -190,16 +211,34 @@ public: inline StaticTypesInitializerNode(ScriptManagerInitializer&& pInit) : init(std::move(pInit)),prev(nullptr) { registerNewStaticTypesInitializer(this); } }; static void registerNewStaticTypesInitializer(StaticTypesInitializerNode* dest); - /// run the script in a dedicated thread. This will have the side effect of evalulating - /// the current script contents and calling run(). Callers will likely want to register the script with external - /// services before calling this. + + /** + * @brief Run the script in a dedicated thread + * + * This will have the side effect of evaluating the current script contents and calling run(). + * Callers will likely want to register the script with external services before calling this. + */ void runInThread(); - /// run the script in the callers thread, exit when stop() is called. + /** + * @brief Run the script in the caller's thread, exit when stop() is called. + * + */ void run(); + + /** + * @brief Get the filename of the running script + * + * @return QString Filename + */ QString getFilename() const; + /** + * @brief Underlying scripting engine + * + * @return ScriptEnginePointer Scripting engine + */ inline ScriptEnginePointer engine() { return _engine; } QList getListOfEntityScriptIDs(); @@ -225,16 +264,29 @@ public: // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(bool marshal = false); - // Stop any evaluating scripts and wait for the scripting thread to finish. + /** + * @brief Stop any evaluating scripts and wait for the scripting thread to finish. + * + * @param shutdown True if we are currently shutting down + */ void waitTillDoneRunning(bool shutdown = false); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread - /// if the script engine is not already running, this will download the URL and start the process of seting it up - /// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed - /// to scripts. we may not need this to be invokable + + /** + * @brief Load a script from a given URL + * + * If the script engine is not already running, this will download the URL and start the process of seting it up + * to run. + * @note This is used by Application currently to load the url. We don't really want it to be exposed + * to scripts. we may not need this to be invokable + * + * @param scriptURL URL where to load the script from + * @param reload + */ void loadURL(const QUrl& scriptURL, bool reload); bool hasValidScriptSuffix(const QString& scriptFileName); @@ -648,9 +700,26 @@ public: void disconnectNonEssentialSignals(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // These are currently used by Application to track if a script is user loaded or not. Consider finding a solution - // inside of Application so that the ScriptManager class is not polluted by this notion + + /** + * @brief Set whether this script was user-loaded + * + * This is used by Application to track if a script is user loaded or not. + * @note Consider finding a solution inside of Application so that the ScriptManager class is not polluted by this notion + * + * @param isUserLoaded Script is user-loaded. + */ void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } + + /** + * @brief Whether the script was user-loaded. + * + * This is used by Application to track if a script is user loaded or not. + * @note Consider finding a solution inside of Application so that the ScriptManager class is not polluted by this notion + * + * @return true + * @return false + */ bool isUserLoaded() const { return _isUserLoaded; } void setQuitWhenFinished(const bool quitWhenFinished) { _quitWhenFinished = quitWhenFinished; } @@ -669,10 +738,21 @@ public: void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } - // call all the registered event handlers on an entity for the specified name. + + /** + * @brief Call all the registered event handlers on an entity for the specified name. + * + * @param entityID + * @param eventName + * @param eventHanderArgs + */ void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, const ScriptValueList& eventHanderArgs); - // remove all event handlers for the specified entityID (i.e. the entity is being removed) + /** + * @brief Remove all event handlers for the specified entityID (i.e. the entity is being removed) + * + * @param entityID + */ void removeAllEventHandlers(const EntityItemID& entityID); diff --git a/libraries/script-engine/src/ScriptProgram.h b/libraries/script-engine/src/ScriptProgram.h index 3392595432..2de3f96d6a 100644 --- a/libraries/script-engine/src/ScriptProgram.h +++ b/libraries/script-engine/src/ScriptProgram.h @@ -4,6 +4,7 @@ // // Created by Heather Anderson on 5/2/21. // Copyright 2021 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -22,32 +23,93 @@ class ScriptSyntaxCheckResult; using ScriptProgramPointer = std::shared_ptr; using ScriptSyntaxCheckResultPointer = std::shared_ptr; -/// [ScriptInterface] Provides an engine-independent interface for QScriptProgram +/** + * @brief Engine-independent representation of a script program + * + * This is an analog of QScriptProgram from Qt5. + * + * It's used to pre-compile scripts, and to check their syntax. + * + */ class ScriptProgram { public: virtual ScriptSyntaxCheckResultPointer checkSyntax() = 0; //It cannot be const anymore because V8 doesn't have separate syntax checking function + + /** + * @brief Returns the filename associated with this program. + * + * @return QString + */ virtual QString fileName() const = 0; + + /** + * @brief Returns the source code of this program. + * + * @return QString + */ virtual QString sourceCode() const = 0; protected: ~ScriptProgram() {} // prevent explicit deletion of base class }; -/// [ScriptInterface] Provides an engine-independent interface for QScriptSyntaxCheckResult + +/** + * @brief Engine-independent representation of a script syntax check + * + * This is an analog of QScriptSyntaxCheckResult from Qt5. + * + * + */ class ScriptSyntaxCheckResult { public: + + /** + * @brief State of the syntax check + * + */ enum State { - Error = 0, - Intermediate = 1, - Valid = 2 + Error = 0, /** The program contains a syntax error. */ + Intermediate = 1, /** The program is incomplete. */ + Valid = 2 /** The program is a syntactically correct program. */ }; public: + + /** + * @brief Returns the error column number of this ScriptSyntaxCheckResult, or -1 if there is no error. + * + * @return int + */ virtual int errorColumnNumber() const = 0; + + /** + * @brief Returns the error line number of this ScriptSyntaxCheckResult, or -1 if there is no error. + * + * @return int + */ virtual int errorLineNumber() const = 0; + + /** + * @brief Returns the error message of this ScriptSyntaxCheckResult, or an empty string if there is no error. + * + * @return QString + */ virtual QString errorMessage() const = 0; + + /** + * @brief + * + * @return QString + */ virtual QString errorBacktrace() const = 0; + + /** + * @brief Returns the state of this ScriptSyntaxCheckResult. + * + * @return State + */ virtual State state() const = 0; protected: diff --git a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h index c61b83f231..324655bbb4 100644 --- a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h +++ b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h @@ -23,6 +23,9 @@ #include "ScriptEngineV8.h" #include "V8Types.h" + +// V8TODO: This class is likely unnecessary, and it'd be enough +// to just use a non-abstract version of ScriptSyntaxCheckResult instead. class ScriptSyntaxCheckResultV8Wrapper final : public ScriptSyntaxCheckResult { public: // construction inline ScriptSyntaxCheckResultV8Wrapper() : _errorColumnNumber(0), _errorLineNumber(0), _errorMessage("Not compiled"), _state(ScriptSyntaxCheckResult::Error) {} diff --git a/tests/script-engine/src/ScriptEngineTests.cpp b/tests/script-engine/src/ScriptEngineTests.cpp index a06a67be99..c0836499d1 100644 --- a/tests/script-engine/src/ScriptEngineTests.cpp +++ b/tests/script-engine/src/ScriptEngineTests.cpp @@ -102,4 +102,6 @@ void ScriptEngineTests::scriptTest() { //ac->shutdownScripting(); + + //TODO: Add a test for Script.require(JSON) }