diff --git a/JavaScriptEngineSwitcher.NoSamples.sln b/JavaScriptEngineSwitcher.NoSamples.sln
index f0274683..7e619876 100644
--- a/JavaScriptEngineSwitcher.NoSamples.sln
+++ b/JavaScriptEngineSwitcher.NoSamples.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29613.14
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33424.131
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{19575E10-6B8E-4CF0-B7D2-898FFF47E157}"
ProjectSection(SolutionItems) = preProject
@@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaScriptEngineSwitcher.Ni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaScriptEngineSwitcher.Node", "src\JavaScriptEngineSwitcher.Node\JavaScriptEngineSwitcher.Node.csproj", "{89F9DDDD-5236-4D9A-99E4-3C1358B81149}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaScriptEngineSwitcher.Topaz", "src\JavaScriptEngineSwitcher.Topaz\JavaScriptEngineSwitcher.Topaz.csproj", "{C5FB0DE1-496F-4A78-A6DC-448649E77172}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaScriptEngineSwitcher.Benchmarks", "test\JavaScriptEngineSwitcher.Benchmarks\JavaScriptEngineSwitcher.Benchmarks.csproj", "{24A8F6A6-EA4E-43A6-A2D7-E1916F8CB4EE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaScriptEngineSwitcher.Tests", "test\JavaScriptEngineSwitcher.Tests\JavaScriptEngineSwitcher.Tests.csproj", "{E95FDEF6-18A0-4E26-8FDF-B4B590E6EDAF}"
@@ -185,6 +187,10 @@ Global
{E95FDEF6-18A0-4E26-8FDF-B4B590E6EDAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E95FDEF6-18A0-4E26-8FDF-B4B590E6EDAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E95FDEF6-18A0-4E26-8FDF-B4B590E6EDAF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5FB0DE1-496F-4A78-A6DC-448649E77172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5FB0DE1-496F-4A78-A6DC-448649E77172}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5FB0DE1-496F-4A78-A6DC-448649E77172}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5FB0DE1-496F-4A78-A6DC-448649E77172}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -212,6 +218,7 @@ Global
{89F9DDDD-5236-4D9A-99E4-3C1358B81149} = {0C281F46-F1D2-4A1C-8560-375EDA65D680}
{24A8F6A6-EA4E-43A6-A2D7-E1916F8CB4EE} = {53B43213-2E66-42C2-8476-600A2FD2DA75}
{E95FDEF6-18A0-4E26-8FDF-B4B590E6EDAF} = {53B43213-2E66-42C2-8476-600A2FD2DA75}
+ {C5FB0DE1-496F-4A78-A6DC-448649E77172} = {0C281F46-F1D2-4A1C-8560-375EDA65D680}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8184BE59-ACBC-4CD1-9419-D59A0FAC6131}
diff --git a/global.json b/global.json
index b1980314..8344741a 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "7.0.201"
+ "version": "7.0.202"
}
}
diff --git a/src/JavaScriptEngineSwitcher.Topaz/DefaultBuiltinObjectsInitializer.cs b/src/JavaScriptEngineSwitcher.Topaz/DefaultBuiltinObjectsInitializer.cs
new file mode 100644
index 00000000..158871eb
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/DefaultBuiltinObjectsInitializer.cs
@@ -0,0 +1,37 @@
+using IOriginalEngine = Tenray.Topaz.ITopazEngine;
+using OriginalArray = Tenray.Topaz.API.JsArray;
+using OriginalGlobalThis = Tenray.Topaz.API.GlobalThis;
+using OriginalJSONObject = Tenray.Topaz.API.JSONObject;
+using OriginalConcurrentObject = Tenray.Topaz.API.ConcurrentJsObject;
+using OriginalObject = Tenray.Topaz.API.JsObject;
+using OriginalUndefined = Tenray.Topaz.Undefined;
+using OriginalVariableKind = Tenray.Topaz.VariableKind;
+
+namespace JavaScriptEngineSwitcher.Topaz
+{
+ ///
+ /// Default built-in objects initializer
+ ///
+ public static class DefaultBuiltinObjectsInitializer
+ {
+ ///
+ /// Initializes a built-in objects in the global scope
+ ///
+ /// Original JS engine
+ public static void Initialize(IOriginalEngine engine)
+ {
+ // Constants
+ engine.SetValueAndKind("Infinity", double.PositiveInfinity, OriginalVariableKind.Const);
+ engine.SetValueAndKind("NaN", double.NaN, OriginalVariableKind.Const);
+ engine.SetValueAndKind("undefined", OriginalUndefined.Value, OriginalVariableKind.Const);
+
+ // Constructors
+ engine.AddType(engine.IsThreadSafe ? typeof(OriginalConcurrentObject) :typeof(OriginalObject), "Object");
+ engine.AddType(typeof(OriginalArray), "Array");
+
+ // Objects
+ engine.SetValue("globalThis", new OriginalGlobalThis(engine.GlobalScope));
+ engine.SetValue("JSON", new OriginalJSONObject());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/JavaScriptEngineSwitcher.Topaz/JavaScriptEngineSwitcher.Topaz.csproj b/src/JavaScriptEngineSwitcher.Topaz/JavaScriptEngineSwitcher.Topaz.csproj
new file mode 100644
index 00000000..9cbb92a4
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/JavaScriptEngineSwitcher.Topaz.csproj
@@ -0,0 +1,26 @@
+
+
+
+ JS Engine Switcher: Topaz
+ 3.20.10
+ net6.0;net7.0
+ Library
+ true
+ $(NoWarn);CS1591
+ true
+
+
+
+
+
+ JavaScriptEngineSwitcher.Topaz contains adapter `TopazJsEngine` (wrapper for the Topaz JavaScript Engine (https://github.com/koculu/Topaz) version 1.3.0).
+ $(PackageCommonTags);Topaz
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/JavaScriptEngineSwitcher.Topaz/JsEngineFactoryCollectionExtensions.cs b/src/JavaScriptEngineSwitcher.Topaz/JsEngineFactoryCollectionExtensions.cs
new file mode 100644
index 00000000..2c911f0e
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/JsEngineFactoryCollectionExtensions.cs
@@ -0,0 +1,79 @@
+using System;
+
+using JavaScriptEngineSwitcher.Core;
+
+namespace JavaScriptEngineSwitcher.Topaz
+{
+ ///
+ /// JS engine factory collection extensions
+ ///
+ public static class JsEngineFactoryCollectionExtensions
+ {
+ ///
+ /// Adds a instance of to
+ /// the specified
+ ///
+ /// Instance of
+ /// Instance of
+ public static JsEngineFactoryCollection AddTopaz(this JsEngineFactoryCollection source)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ return source.AddTopaz(new TopazSettings());
+ }
+
+ ///
+ /// Adds a instance of to
+ /// the specified
+ ///
+ /// Instance of
+ /// The delegate to configure the provided
+ /// Instance of
+ public static JsEngineFactoryCollection AddTopaz(this JsEngineFactoryCollection source,
+ Action configure)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ var settings = new TopazSettings();
+ configure(settings);
+
+ return source.AddTopaz(settings);
+ }
+
+ ///
+ /// Adds a instance of to
+ /// the specified
+ ///
+ /// Instance of
+ /// Settings of the Topaz JS engine
+ /// Instance of
+ public static JsEngineFactoryCollection AddTopaz(this JsEngineFactoryCollection source,
+ TopazSettings settings)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (settings == null)
+ {
+ throw new ArgumentNullException(nameof(settings));
+ }
+
+ source.Add(new TopazJsEngineFactory(settings));
+
+ return source;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngine.cs b/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngine.cs
new file mode 100644
index 00000000..86ffe610
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngine.cs
@@ -0,0 +1,539 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+using OriginalAssignmentWithoutDefinitionBehavior = Tenray.Topaz.Options.AssignmentWithoutDefinitionBehavior;
+using OriginalEngine = Tenray.Topaz.TopazEngine;
+using OriginalEngineOptions = Tenray.Topaz.Options.TopazEngineOptions;
+using OriginalEngineSetup = Tenray.Topaz.TopazEngineSetup;
+using OriginalException = Tenray.Topaz.TopazException;
+using OriginalParserException = Esprima.ParserException;
+using OriginalUndefined = Tenray.Topaz.Undefined;
+using OriginalVarScopeBehavior = Tenray.Topaz.Options.VarScopeBehavior;
+
+using JavaScriptEngineSwitcher.Core;
+using JavaScriptEngineSwitcher.Core.Constants;
+using JavaScriptEngineSwitcher.Core.Helpers;
+using JavaScriptEngineSwitcher.Core.Utilities;
+
+using CoreStrings = JavaScriptEngineSwitcher.Core.Resources.Strings;
+using WrapperCompilationException = JavaScriptEngineSwitcher.Core.JsCompilationException;
+using WrapperException = JavaScriptEngineSwitcher.Core.JsException;
+using WrapperInterruptedException = JavaScriptEngineSwitcher.Core.JsInterruptedException;
+using WrapperRuntimeException = JavaScriptEngineSwitcher.Core.JsRuntimeException;
+using WrapperScriptException = JavaScriptEngineSwitcher.Core.JsScriptException;
+
+namespace JavaScriptEngineSwitcher.Topaz
+{
+ ///
+ /// Adapter for the Topaz JS engine
+ ///
+ public sealed class TopazJsEngine : JsEngineBase
+ {
+ ///
+ /// Name of JS engine
+ ///
+ public const string EngineName = "TopazJsEngine";
+
+ ///
+ /// Version of original JS engine
+ ///
+ private const string EngineVersion = "1.3.0";
+
+ ///
+ /// Regular expression for working with the error message with type
+ ///
+ private static readonly Regex _errorMessageWithTypeRegex =
+ new Regex(@"^(?" + CommonRegExps.JsFullNamePattern + @"):\s+(?[\s\S]+?)$");
+
+ ///
+ /// Topaz JS engine
+ ///
+ private OriginalEngine _jsEngine;
+
+ ///
+ /// Token source for canceling of script execution
+ ///
+ private CancellationTokenSource _cancellationTokenSource;
+
+
+ ///
+ /// Constructs an instance of adapter for the Topaz JS engine
+ ///
+ public TopazJsEngine()
+ : this(new TopazSettings())
+ { }
+
+ ///
+ /// Constructs an instance of adapter for the Topaz JS engine
+ ///
+ /// Settings of the Topaz JS engine
+ public TopazJsEngine(TopazSettings settings)
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+
+ TopazSettings topazSettings = settings ?? new TopazSettings();
+
+ try
+ {
+ _jsEngine = new OriginalEngine(new OriginalEngineSetup
+ {
+ Options = new OriginalEngineOptions
+ {
+ AllowNullReferenceMemberAccess = false,
+ AllowUndefinedReferenceAccess = true,
+ AllowUndefinedReferenceMemberAccess = false,
+ AssignmentWithoutDefinitionBehavior = OriginalAssignmentWithoutDefinitionBehavior.DefineAsVarInGlobalScope,
+ DefineSpecialArgumentsObjectOnEachFunctionCall = true,
+ NoUndefined = false,
+ VarScopeBehavior = OriginalVarScopeBehavior.FunctionScope,
+ UseThreadSafeJsObjects = true
+ }
+ });
+ topazSettings.BuiltinObjectsInitializer?.Invoke(_jsEngine);
+ }
+ catch (Exception e)
+ {
+ throw JsErrorHelpers.WrapEngineLoadException(e, EngineName, EngineVersion, true);
+ }
+ }
+
+
+ #region Mapping
+
+ ///
+ /// Makes a mapping of value from the host type to a script type
+ ///
+ /// The source value
+ /// The mapped value
+ private static object MapToScriptType(object value)
+ {
+ if (value is Undefined)
+ {
+ return OriginalUndefined.Value;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Makes a mapping of array items from the host type to a script type
+ ///
+ /// The source array
+ /// The mapped array
+ private static object[] MapToScriptType(object[] args)
+ {
+ return args.Select(MapToScriptType).ToArray();
+ }
+
+ ///
+ /// Makes a mapping of value from the script type to a host type
+ ///
+ /// The source value
+ /// The mapped value
+ private static object MapToHostType(object value)
+ {
+ if (value is OriginalUndefined)
+ {
+ return Undefined.Value;
+ }
+
+ return value;
+ }
+
+ private static WrapperCompilationException WrapParserException(OriginalParserException originalParserException)
+ {
+ string description = originalParserException.Description;
+ string type = JsErrorType.Syntax;
+ int lineNumber = originalParserException.LineNumber;
+ int columnNumber = originalParserException.Column;
+ string message = JsErrorHelpers.GenerateScriptErrorMessage(type, description, string.Empty, lineNumber,
+ columnNumber);
+
+ var wrapperCompilationException = new WrapperCompilationException(message, EngineName, EngineVersion,
+ originalParserException)
+ {
+ Description = description,
+ Type = type,
+ LineNumber = lineNumber,
+ ColumnNumber = columnNumber
+ };
+
+ return wrapperCompilationException;
+ }
+
+ private static WrapperException WrapTopazException(OriginalException originalException)
+ {
+ WrapperScriptException wrapperScriptException;
+ string message = originalException.Message;
+ string description = message;
+ string type = string.Empty;
+
+ Match messageWithTypeMatch = _errorMessageWithTypeRegex.Match(message);
+ if (messageWithTypeMatch.Success)
+ {
+ GroupCollection messageWithTypeGroups = messageWithTypeMatch.Groups;
+ type = messageWithTypeGroups["type"].Value;
+ description = messageWithTypeGroups["description"].Value;
+ }
+ else
+ {
+ type = JsErrorType.Common;
+ }
+
+ message = JsErrorHelpers.GenerateScriptErrorMessage(type, description, string.Empty);
+
+ if (type == JsErrorType.Syntax)
+ {
+ wrapperScriptException = new WrapperCompilationException(message, EngineName, EngineVersion,
+ originalException);
+ }
+ else
+ {
+ wrapperScriptException = new WrapperRuntimeException(message, EngineName, EngineVersion,
+ originalException);
+ }
+
+ wrapperScriptException.Description = description;
+ wrapperScriptException.Type = type;
+
+ return wrapperScriptException;
+ }
+
+ private static WrapperInterruptedException WrapOperationCanceledException(
+ OperationCanceledException originalOperationCanceledException)
+ {
+ string message = CoreStrings.Runtime_ScriptInterrupted;
+ string description = message;
+
+ var wrapperInterruptedException = new WrapperInterruptedException(message, EngineName, EngineVersion,
+ originalOperationCanceledException)
+ {
+ Description = description
+ };
+
+ return wrapperInterruptedException;
+ }
+
+ private static WrapperException WrapTargetInvocationException(TargetInvocationException originalTargetInvocationException)
+ {
+ Exception innerException = originalTargetInvocationException.InnerException;
+ if (innerException == null)
+ {
+ return null;
+ }
+
+ var wrapperException = innerException as WrapperException;
+ if (wrapperException == null)
+ {
+ wrapperException = new WrapperException(innerException.Message, EngineName, EngineVersion,
+ innerException)
+ {
+ Description = innerException.Message
+ };
+ }
+
+ return wrapperException;
+ }
+
+ #endregion
+
+ #region JsEngineBase overrides
+
+ protected override IPrecompiledScript InnerPrecompile(string code)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected override IPrecompiledScript InnerPrecompile(string code, string documentName)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected override object InnerEvaluate(string expression)
+ {
+ return InnerEvaluate(expression, null);
+ }
+
+ protected override object InnerEvaluate(string expression, string documentName)
+ {
+ object result;
+
+ try
+ {
+ result = _jsEngine.ExecuteExpression(expression, _cancellationTokenSource.Token);
+ }
+ catch (OriginalParserException e)
+ {
+ throw WrapParserException(e);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ catch (OperationCanceledException e)
+ {
+ throw WrapOperationCanceledException(e);
+ }
+ catch (TargetInvocationException e)
+ {
+ var wrapperException = WrapTargetInvocationException(e);
+ if (wrapperException != null)
+ {
+ throw wrapperException;
+ }
+
+ throw;
+ }
+
+ result = MapToHostType(result);
+
+ return result;
+ }
+
+ protected override T InnerEvaluate(string expression)
+ {
+ return InnerEvaluate(expression, null);
+ }
+
+ protected override T InnerEvaluate(string expression, string documentName)
+ {
+ object result = InnerEvaluate(expression, documentName);
+
+ return TypeConverter.ConvertToType(result);
+ }
+
+ protected override void InnerExecute(string code)
+ {
+ InnerExecute(code, null);
+ }
+
+ protected override void InnerExecute(string code, string documentName)
+ {
+ try
+ {
+ _jsEngine.ExecuteScript(code, _cancellationTokenSource.Token);
+ }
+ catch (OriginalParserException e)
+ {
+ throw WrapParserException(e);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ catch (OperationCanceledException e)
+ {
+ throw WrapOperationCanceledException(e);
+ }
+ catch (TargetInvocationException e)
+ {
+ var wrapperException = WrapTargetInvocationException(e);
+ if (wrapperException != null)
+ {
+ throw wrapperException;
+ }
+
+ throw;
+ }
+ }
+
+ protected override void InnerExecute(IPrecompiledScript precompiledScript)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected override object InnerCallFunction(string functionName, params object[] args)
+ {
+ object result;
+ object[] processedArgs = MapToScriptType(args);
+
+ try
+ {
+ result = _jsEngine.InvokeFunction(functionName, _cancellationTokenSource.Token, processedArgs);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ catch (OperationCanceledException e)
+ {
+ throw WrapOperationCanceledException(e);
+ }
+ catch (TargetInvocationException e)
+ {
+ var wrapperException = WrapTargetInvocationException(e);
+ if (wrapperException != null)
+ {
+ throw wrapperException;
+ }
+
+ throw;
+ }
+
+ result = MapToHostType(result);
+
+ return result;
+ }
+
+ protected override T InnerCallFunction(string functionName, params object[] args)
+ {
+ object result = InnerCallFunction(functionName, args);
+
+ return TypeConverter.ConvertToType(result);
+ }
+
+ protected override bool InnerHasVariable(string variableName)
+ {
+ bool result;
+
+ try
+ {
+ object variableValue = _jsEngine.GetValue(variableName);
+ result = variableValue != OriginalUndefined.Value;
+ }
+ catch (OriginalException)
+ {
+ result = false;
+ }
+
+ return result;
+ }
+
+ protected override object InnerGetVariableValue(string variableName)
+ {
+ object variableValue;
+
+ try
+ {
+ variableValue = _jsEngine.GetValue(variableName);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+
+ object result = MapToHostType(variableValue);
+
+ return result;
+ }
+
+ protected override T InnerGetVariableValue(string variableName)
+ {
+ object result = InnerGetVariableValue(variableName);
+
+ return TypeConverter.ConvertToType(result);
+ }
+
+ protected override void InnerSetVariableValue(string variableName, object value)
+ {
+ object processedValue = MapToScriptType(value);
+
+ try
+ {
+ _jsEngine.SetValue(variableName, processedValue);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ }
+
+ protected override void InnerRemoveVariable(string variableName)
+ {
+ try
+ {
+ _jsEngine.SetValue(variableName, OriginalUndefined.Value);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ }
+
+ protected override void InnerEmbedHostObject(string itemName, object value)
+ {
+ try
+ {
+ _jsEngine.SetValue(itemName, value);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ }
+
+ protected override void InnerEmbedHostType(string itemName, Type type)
+ {
+ try
+ {
+ _jsEngine.AddType(type, itemName);
+ }
+ catch (OriginalException e)
+ {
+ throw WrapTopazException(e);
+ }
+ }
+
+ protected override void InnerInterrupt()
+ {
+ _cancellationTokenSource.Cancel();
+ }
+
+ protected override void InnerCollectGarbage()
+ {
+ throw new NotSupportedException();
+ }
+
+ #region IJsEngine implementation
+
+ public override string Name
+ {
+ get { return EngineName; }
+ }
+
+ public override string Version
+ {
+ get { return EngineVersion; }
+ }
+
+ public override bool SupportsScriptPrecompilation
+ {
+ get { return false; }
+ }
+
+ public override bool SupportsScriptInterruption
+ {
+ get { return true; }
+ }
+
+ public override bool SupportsGarbageCollection
+ {
+ get { return false; }
+ }
+
+ #endregion
+
+ #region IDisposable implementation
+
+ public override void Dispose()
+ {
+ if (_disposedFlag.Set())
+ {
+ _jsEngine = null;
+
+ if (_cancellationTokenSource != null)
+ {
+ _cancellationTokenSource.Dispose();
+ _cancellationTokenSource = null;
+ }
+ }
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngineFactory.cs b/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngineFactory.cs
new file mode 100644
index 00000000..bdb942b8
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/TopazJsEngineFactory.cs
@@ -0,0 +1,53 @@
+using JavaScriptEngineSwitcher.Core;
+
+namespace JavaScriptEngineSwitcher.Topaz
+{
+ ///
+ /// Topaz JS engine factory
+ ///
+ public sealed class TopazJsEngineFactory : IJsEngineFactory
+ {
+ ///
+ /// Settings of the Topaz JS engine
+ ///
+ private readonly TopazSettings _settings;
+
+
+ ///
+ /// Constructs an instance of the Topaz JS engine factory
+ ///
+ public TopazJsEngineFactory()
+ : this(new TopazSettings())
+ { }
+
+ ///
+ /// Constructs an instance of the Topaz JS engine factory
+ ///
+ /// Settings of the Topaz JS engine
+ public TopazJsEngineFactory(TopazSettings settings)
+ {
+ _settings = settings;
+ }
+
+
+ #region IJsEngineFactory implementation
+
+ ///
+ public string EngineName
+ {
+ get { return TopazJsEngine.EngineName; }
+ }
+
+
+ ///
+ /// Creates a instance of the Topaz JS engine
+ ///
+ /// Instance of the Topaz JS engine
+ public IJsEngine CreateEngine()
+ {
+ return new TopazJsEngine(_settings);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/JavaScriptEngineSwitcher.Topaz/TopazSettings.cs b/src/JavaScriptEngineSwitcher.Topaz/TopazSettings.cs
new file mode 100644
index 00000000..a81b10f0
--- /dev/null
+++ b/src/JavaScriptEngineSwitcher.Topaz/TopazSettings.cs
@@ -0,0 +1,29 @@
+using System;
+
+using IOriginalEngine = Tenray.Topaz.ITopazEngine;
+
+namespace JavaScriptEngineSwitcher.Topaz
+{
+ ///
+ /// Settings of the Topaz JS engine
+ ///
+ public sealed class TopazSettings
+ {
+ ///
+ /// Gets or sets a delegate invoked to initialize a built-in Javascript objects
+ ///
+ ///
+ /// When this property is set to null, the global scope remains empty.
+ ///
+ public Action BuiltinObjectsInitializer;
+
+
+ ///
+ /// Constructs an instance of the Topaz settings
+ ///
+ public TopazSettings()
+ {
+ BuiltinObjectsInitializer = DefaultBuiltinObjectsInitializer.Initialize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Benchmarks/HostObjectsEmbeddingBenchmark.cs b/test/JavaScriptEngineSwitcher.Benchmarks/HostObjectsEmbeddingBenchmark.cs
index 0c0bf796..0b792e69 100644
--- a/test/JavaScriptEngineSwitcher.Benchmarks/HostObjectsEmbeddingBenchmark.cs
+++ b/test/JavaScriptEngineSwitcher.Benchmarks/HostObjectsEmbeddingBenchmark.cs
@@ -12,6 +12,11 @@
using JavaScriptEngineSwitcher.Msie;
#if NET461 || NETCOREAPP3_1_OR_GREATER
using JavaScriptEngineSwitcher.NiL;
+#endif
+#if NET6_0_OR_GREATER
+using JavaScriptEngineSwitcher.Topaz;
+#endif
+#if NET461 || NETCOREAPP3_1_OR_GREATER
using JavaScriptEngineSwitcher.V8;
#endif
@@ -169,6 +174,17 @@ public void NiL()
Func createJsEngine = () => new NiLJsEngine();
EmbedAndUseHostObjects(createJsEngine);
}
+#endif
+#if NET6_0_OR_GREATER
+
+ [Benchmark]
+ public void Topaz()
+ {
+ Func createJsEngine = () => new TopazJsEngine();
+ EmbedAndUseHostObjects(createJsEngine);
+ }
+#endif
+#if NET461 || NETCOREAPP3_1_OR_GREATER
[Benchmark]
[Arguments(false)]
diff --git a/test/JavaScriptEngineSwitcher.Benchmarks/JavaScriptEngineSwitcher.Benchmarks.csproj b/test/JavaScriptEngineSwitcher.Benchmarks/JavaScriptEngineSwitcher.Benchmarks.csproj
index e3d71d94..5dc91dd7 100644
--- a/test/JavaScriptEngineSwitcher.Benchmarks/JavaScriptEngineSwitcher.Benchmarks.csproj
+++ b/test/JavaScriptEngineSwitcher.Benchmarks/JavaScriptEngineSwitcher.Benchmarks.csproj
@@ -45,6 +45,10 @@
+
+
+
+
diff --git a/test/JavaScriptEngineSwitcher.Tests/Interop/BundleTable.cs b/test/JavaScriptEngineSwitcher.Tests/Interop/BundleTable.cs
index 36673361..b9e477dd 100644
--- a/test/JavaScriptEngineSwitcher.Tests/Interop/BundleTable.cs
+++ b/test/JavaScriptEngineSwitcher.Tests/Interop/BundleTable.cs
@@ -4,6 +4,8 @@ public static class BundleTable
{
private static bool _enableOptimizations = true;
+ public static object SyncRoot = new object();
+
public static bool EnableOptimizations
{
get
diff --git a/test/JavaScriptEngineSwitcher.Tests/Interop/Logging/DefaultLogger.cs b/test/JavaScriptEngineSwitcher.Tests/Interop/Logging/DefaultLogger.cs
index 18728c43..b4c8fdeb 100644
--- a/test/JavaScriptEngineSwitcher.Tests/Interop/Logging/DefaultLogger.cs
+++ b/test/JavaScriptEngineSwitcher.Tests/Interop/Logging/DefaultLogger.cs
@@ -2,6 +2,7 @@
{
public class DefaultLogger
{
+ public static object SyncRoot = new object();
public static ILogger Current = new NullLogger();
}
}
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Tests/InteropTestsBase.cs b/test/JavaScriptEngineSwitcher.Tests/InteropTestsBase.cs
index 0527daf2..4c7332a2 100644
--- a/test/JavaScriptEngineSwitcher.Tests/InteropTestsBase.cs
+++ b/test/JavaScriptEngineSwitcher.Tests/InteropTestsBase.cs
@@ -1123,7 +1123,9 @@ public virtual void EmbeddingOfCustomReferenceTypeWithField()
// Arrange
Type defaultLoggerType = typeof(DefaultLogger);
Type throwExceptionLoggerType = typeof(ThrowExceptionLogger);
- const string updateCode = "DefaultLogger.Current = new ThrowExceptionLogger();";
+ const string updateCode = @"var oldLogger = DefaultLogger.Current;
+DefaultLogger.Current = new ThrowExceptionLogger();";
+ const string rollbackCode = "DefaultLogger.Current = oldLogger;";
const string input = "DefaultLogger.Current.ToString()";
const string targetOutput = "[throw exception logger]";
@@ -1135,9 +1137,13 @@ public virtual void EmbeddingOfCustomReferenceTypeWithField()
{
jsEngine.EmbedHostType("DefaultLogger", defaultLoggerType);
jsEngine.EmbedHostType("ThrowExceptionLogger", throwExceptionLoggerType);
- jsEngine.Execute(updateCode);
- output = jsEngine.Evaluate(input);
+ lock (DefaultLogger.SyncRoot)
+ {
+ jsEngine.Execute(updateCode);
+ output = jsEngine.Evaluate(input);
+ jsEngine.Execute(rollbackCode);
+ }
}
// Assert
@@ -1230,7 +1236,9 @@ public virtual void EmbeddingOfCustomReferenceTypeWithProperty()
{
// Arrange
Type bundleTableType = typeof(BundleTable);
- const string updateCode = "BundleTable.EnableOptimizations = false;";
+ const string updateCode = @"var oldEnableOptimizationsValue = BundleTable.EnableOptimizations;
+BundleTable.EnableOptimizations = false;";
+ const string rollbackCode = "BundleTable.EnableOptimizations = oldEnableOptimizationsValue;";
const string input = "BundleTable.EnableOptimizations";
const bool targetOutput = false;
@@ -1241,9 +1249,13 @@ public virtual void EmbeddingOfCustomReferenceTypeWithProperty()
using (var jsEngine = CreateJsEngine())
{
jsEngine.EmbedHostType("BundleTable", bundleTableType);
- jsEngine.Execute(updateCode);
- output = jsEngine.Evaluate(input);
+ lock (BundleTable.SyncRoot)
+ {
+ jsEngine.Execute(updateCode);
+ output = jsEngine.Evaluate(input);
+ jsEngine.Execute(rollbackCode);
+ }
}
// Assert
diff --git a/test/JavaScriptEngineSwitcher.Tests/JavaScriptEngineSwitcher.Tests.csproj b/test/JavaScriptEngineSwitcher.Tests/JavaScriptEngineSwitcher.Tests.csproj
index d623ee74..da95b050 100644
--- a/test/JavaScriptEngineSwitcher.Tests/JavaScriptEngineSwitcher.Tests.csproj
+++ b/test/JavaScriptEngineSwitcher.Tests/JavaScriptEngineSwitcher.Tests.csproj
@@ -92,6 +92,10 @@
+
+
+
+
diff --git a/test/JavaScriptEngineSwitcher.Tests/JsEngineSwitcherInitializer.cs b/test/JavaScriptEngineSwitcher.Tests/JsEngineSwitcherInitializer.cs
index f10dcb87..2ef72a4c 100644
--- a/test/JavaScriptEngineSwitcher.Tests/JsEngineSwitcherInitializer.cs
+++ b/test/JavaScriptEngineSwitcher.Tests/JsEngineSwitcherInitializer.cs
@@ -16,6 +16,9 @@
#if !NET452
using JavaScriptEngineSwitcher.Node;
#endif
+#if NET6_0_OR_GREATER
+using JavaScriptEngineSwitcher.Topaz;
+#endif
#if NETFRAMEWORK || NETCOREAPP3_1_OR_GREATER
using JavaScriptEngineSwitcher.V8;
#endif
@@ -52,6 +55,9 @@ public static void Initialize()
#if !NET452
.AddNode()
#endif
+#if NET6_0_OR_GREATER
+ .AddTopaz()
+#endif
#if NETFRAMEWORK || NETCOREAPP3_1_OR_GREATER
.AddV8()
#endif
diff --git a/test/JavaScriptEngineSwitcher.Tests/Topaz/CommonTests.cs b/test/JavaScriptEngineSwitcher.Tests/Topaz/CommonTests.cs
new file mode 100644
index 00000000..2cd3d03c
--- /dev/null
+++ b/test/JavaScriptEngineSwitcher.Tests/Topaz/CommonTests.cs
@@ -0,0 +1,269 @@
+#if NET6_0_OR_GREATER
+using System;
+
+using Xunit;
+
+using JavaScriptEngineSwitcher.Core;
+
+namespace JavaScriptEngineSwitcher.Tests.Topaz
+{
+ public class CommonTests : CommonTestsBase
+ {
+ protected override string EngineName
+ {
+ get { return "TopazJsEngine"; }
+ }
+
+ #region Evaluation of scripts
+
+ [Fact]
+ public override void EvaluationOfExpressionWithBooleanResult()
+ { }
+
+ [Fact]
+ public override void EvaluationOfExpressionWithDoubleResult()
+ {
+ // Math object not implemented
+ }
+
+ #endregion
+
+ #region Error handling
+
+ #region Mapping of errors
+
+ [Fact]
+ public void MappingCompilationErrorDuringEvaluationOfExpression()
+ {
+ // Arrange
+ const string inputCode = @"var $variable1 = 611;
+var _variable2 = 711;
+var variable3 = 678;";
+ const string inputExpression = "$variable1 + _variable2 - @variable3;";
+
+ JsCompilationException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(inputCode);
+ int result = jsEngine.Evaluate(inputExpression, "variables.js");
+ }
+ catch (JsCompilationException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Compilation error", exception.Category);
+ Assert.Equal("Unexpected token ILLEGAL", exception.Description);
+ Assert.Equal("SyntaxError", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(1, exception.LineNumber);
+ Assert.Equal(27, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ }
+
+ [Fact]
+ public void MappingRuntimeErrorDuringEvaluationOfExpression()
+ {
+ // Arrange
+ const string inputCode = @"var $variable1 = 611;
+var _variable2 = 711;
+var variable3 = 678;";
+ const string inputExpression = @"$variable1 + _variable2() - variable3;";
+
+ JsRuntimeException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(inputCode);
+ int result = jsEngine.Evaluate(inputExpression, "variables.js");
+ }
+ catch (JsRuntimeException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Runtime error", exception.Category);
+ Assert.Equal("Can not call 711 as a function.", exception.Description);
+ Assert.Equal("Error", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(0, exception.LineNumber);
+ Assert.Equal(0, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ Assert.Empty(exception.CallStack);
+ }
+
+ [Fact]
+ public void MappingCompilationErrorDuringExecutionOfCode()
+ {
+ // Arrange
+ const string input = @"function factorial(value) {
+ if (value <= 0) {
+ throw new Error(""The value must be greater than or equal to zero."");
+ }
+
+ return value !== 1 ? value * factorial(value - 1) : 1;
+}
+
+factorial(5);
+factorial(2%);
+factorial(0);";
+
+ JsCompilationException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(input, "factorial.js");
+ }
+ catch (JsCompilationException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Compilation error", exception.Category);
+ Assert.Equal("Unexpected token )", exception.Description);
+ Assert.Equal("SyntaxError", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(10, exception.LineNumber);
+ Assert.Equal(13, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ }
+
+ [Fact]
+ public void MappingRuntimeErrorDuringExecutionOfCode()
+ {
+ // Arrange
+ const string input = @"function factorial(value) {
+ if (value <= 0) {
+ throw new Error(""The value must be greater than or equal to zero."");
+ }
+
+ return value !== 1 ? value * factorial(value - 1) : 1;
+}
+
+factorial(5);
+factorial(-1);
+factorial(0);";
+
+ JsRuntimeException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(input, "factorial.js");
+ }
+ catch (JsRuntimeException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Runtime error", exception.Category);
+ Assert.Equal("Constructor call on undefined is not defined.", exception.Description);
+ Assert.Equal("Error", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(0, exception.LineNumber);
+ Assert.Equal(0, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ Assert.Empty(exception.CallStack);
+ }
+
+ #endregion
+
+ #region Generation of error messages
+
+ [Fact]
+ public void GenerationOfCompilationErrorMessage()
+ {
+ // Arrange
+ const string input = @"var arr = [];
+var obj = {};
+var foo = 'Browser's bar';";
+ string targetOutput = "SyntaxError: Unexpected identifier" + Environment.NewLine +
+ " at 3:20";
+
+ JsCompilationException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(input, "variables.js");
+ }
+ catch (JsCompilationException e)
+ {
+ exception = e;
+ }
+ }
+
+ Assert.NotNull(exception);
+ Assert.Equal(targetOutput, exception.Message);
+ }
+
+ [Fact]
+ public void GenerationOfRuntimeErrorMessage()
+ {
+ // Arrange
+ const string input = @"function foo(x, y) {
+ var z = x + y;
+ if (z > 20) {
+ bar();
+ }
+}
+
+(function (foo) {
+ var a = 8;
+ var b = 15;
+
+ foo(a, b);
+})(foo);";
+ string targetOutput = "Error: Can not call undefined as a function.";
+
+ JsRuntimeException exception = null;
+
+ // Act
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.Execute(input, "functions.js");
+ }
+ catch (JsRuntimeException e)
+ {
+ exception = e;
+ }
+ }
+
+ Assert.NotNull(exception);
+ Assert.Equal(targetOutput, exception.Message);
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Tests/Topaz/Es5Tests.cs b/test/JavaScriptEngineSwitcher.Tests/Topaz/Es5Tests.cs
new file mode 100644
index 00000000..1e4cec2f
--- /dev/null
+++ b/test/JavaScriptEngineSwitcher.Tests/Topaz/Es5Tests.cs
@@ -0,0 +1,91 @@
+#if NET6_0_OR_GREATER
+using System;
+
+using Xunit;
+
+using JavaScriptEngineSwitcher.Core;
+
+namespace JavaScriptEngineSwitcher.Tests.Topaz
+{
+ public class Es5Tests : Es5TestsBase
+ {
+ protected override string EngineName
+ {
+ get { return "TopazJsEngine"; }
+ }
+
+
+ #region Array methods
+
+ [Fact]
+ public override void SupportsArrayEveryMethod()
+ { }
+
+ [Fact]
+ public override void SupportsArrayFilterMethod()
+ { }
+
+ [Fact]
+ public override void SupportsArrayIndexOfMethod()
+ { }
+
+ [Fact]
+ public override void SupportsArrayIsArrayMethod()
+ { }
+
+ [Fact]
+ public override void SupportsArrayLastIndexOfMethod()
+ { }
+
+ [Fact]
+ public override void SupportsArraySomeMethod()
+ { }
+
+ #endregion
+
+ #region Date methods
+
+ [Fact]
+ public override void SupportsDateNowMethod()
+ { }
+
+ [Fact]
+ public override void SupportsDateToIsoStringMethod()
+ { }
+
+ #endregion
+
+ #region Function methods
+
+ [Fact]
+ public override void SupportsFunctionBindMethod()
+ { }
+
+ #endregion
+
+ #region Object methods
+
+ [Fact]
+ public override void SupportsObjectCreateMethod()
+ { }
+
+ [Fact]
+ public override void SupportsObjectKeysMethod()
+ { }
+
+ #endregion
+
+ #region String methods
+
+ [Fact]
+ public override void SupportsStringSplitMethod()
+ { }
+
+ [Fact]
+ public override void SupportsStringTrimMethod()
+ { }
+
+ #endregion
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Tests/Topaz/InteropTests.cs b/test/JavaScriptEngineSwitcher.Tests/Topaz/InteropTests.cs
new file mode 100644
index 00000000..3545480d
--- /dev/null
+++ b/test/JavaScriptEngineSwitcher.Tests/Topaz/InteropTests.cs
@@ -0,0 +1,482 @@
+#if NET6_0_OR_GREATER
+using System;
+using System.IO;
+
+using Xunit;
+
+using JavaScriptEngineSwitcher.Core;
+using JavaScriptEngineSwitcher.Tests.Interop;
+using JavaScriptEngineSwitcher.Tests.Interop.Animals;
+using JavaScriptEngineSwitcher.Tests.Interop.Logging;
+
+namespace JavaScriptEngineSwitcher.Tests.Topaz
+{
+ public class InteropTests : InteropTestsBase
+ {
+ protected override string EngineName
+ {
+ get { return "TopazJsEngine"; }
+ }
+
+
+ #region Embedding of objects
+
+ #region Objects with properties
+
+ [Fact]
+ public override void EmbeddingOfInstanceOfCustomReferenceTypeWithProperties()
+ {
+ // Arrange
+ var person = new Person("Vanya", "Ivanov");
+ const string updateCode = @"person.LastName = 'Ivanoff';
+person.Patronymic = null;";
+
+ const string input1 = "person.FirstName";
+ const string targetOutput1 = "Vanya";
+
+ const string input2 = "person.LastName";
+ const string targetOutput2 = "Ivanoff";
+
+ // Act
+ string output1;
+ string output2;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostObject("person", person);
+ jsEngine.Execute(updateCode);
+
+ output1 = jsEngine.Evaluate(input1);
+ output2 = jsEngine.Evaluate(input2);
+ }
+
+ // Assert
+ Assert.Equal(targetOutput1, output1);
+ Assert.Equal(targetOutput2, output2);
+ }
+
+ #endregion
+
+ #region Objects with methods
+
+ [Fact]
+ public override void EmbeddingOfInstanceOfCustomValueTypeWithMethods()
+ {
+ // Arrange
+ var programmerDayDate = new Date(2015, 9, 13);
+
+ const string input1 = "programmerDay.GetDayOfYear()";
+ const int targetOutput1 = 256;
+
+ const string input2 = @"programmerDay.AddDays(6).GetDayOfYear();";
+ const int targetOutput2 = 262;
+
+ // Act
+ int output1;
+ int output2;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostObject("programmerDay", programmerDayDate);
+ output1 = jsEngine.Evaluate(input1);
+ output2 = jsEngine.Evaluate(input2);
+ }
+
+ // Assert
+ Assert.Equal(targetOutput1, output1);
+ Assert.Equal(targetOutput2, output2);
+ }
+
+ #endregion
+
+ #region Delegates
+
+ [Fact]
+ public override void EmbeddingOfInstanceOfDelegateAndCheckingItsPrototype()
+ { }
+
+ [Fact]
+ public override void EmbeddingOfInstanceOfDelegateAndCallingItWithExtraParameter()
+ {
+ // Arrange
+ var sumFunc = new Func((a, b) => a + b);
+
+ const string input = "sum(678, 711, 611)";
+
+ // Act
+ JsRuntimeException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ jsEngine.EmbedHostObject("sum", sumFunc);
+ jsEngine.Evaluate(input);
+ }
+ catch (JsRuntimeException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Runtime error", exception.Category);
+ Assert.Equal(
+ "Type Error: Delegate method call argument mismatch. delegate(678, 711, 611)",
+ exception.Description
+ );
+ }
+
+ [Fact]
+ public override void EmbeddingOfInstanceOfDelegateAndGettingItsMethodProperty()
+ {
+ // Arrange
+ var cat = new Cat();
+ var cryFunc = new Func(cat.Cry);
+
+ const string input = "cry.Method;";
+ string targetOutput = "System.String Cry()";
+
+ // Act
+ string output;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostObject("cry", cryFunc);
+ output = jsEngine.Evaluate(input);
+ }
+
+ // Assert
+ Assert.Equal(targetOutput, output);
+ }
+
+ #endregion
+
+ #region Recursive calls
+
+ #region Mapping of errors
+
+ [Fact]
+ public void MappingCompilationErrorDuringRecursiveEvaluationOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-evaluation/compilation-error";
+ const string input = "evaluateFile('index').calculateResult();";
+
+ // Act
+ JsCompilationException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ Func evaluateFile = path => {
+ string absolutePath = Path.Combine(directoryPath, $"{path}.js");
+ string code = File.ReadAllText(absolutePath);
+ object result = jsEngine.Evaluate(code, absolutePath);
+
+ return result;
+ };
+
+ jsEngine.EmbedHostObject("evaluateFile", evaluateFile);
+ double output = jsEngine.Evaluate(input);
+ }
+ catch (JsCompilationException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Compilation error", exception.Category);
+ Assert.Equal("Unexpected token ,", exception.Description);
+ Assert.Equal("SyntaxError", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(25, exception.LineNumber);
+ Assert.Equal(11, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ }
+
+ [Fact]
+ public void MappingRuntimeErrorDuringRecursiveEvaluationOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-evaluation/runtime-error";
+ const string input = "evaluateFile('index').calculateResult();";
+
+ // Act
+ JsRuntimeException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ Func evaluateFile = path => {
+ string absolutePath = Path.Combine(directoryPath, $"{path}.js");
+ string code = File.ReadAllText(absolutePath);
+ object result = jsEngine.Evaluate(code, absolutePath);
+
+ return result;
+ };
+
+ jsEngine.EmbedHostObject("evaluateFile", evaluateFile);
+ double output = jsEngine.Evaluate(input);
+ }
+ catch (JsRuntimeException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Runtime error", exception.Category);
+ Assert.Equal("argumens is not defined", exception.Description);
+ Assert.Equal("ReferenceError", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(0, exception.LineNumber);
+ Assert.Equal(0, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ Assert.Empty(exception.CallStack);
+ }
+
+ [Fact]
+ public void MappingHostErrorDuringRecursiveEvaluationOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-evaluation/host-error";
+ const string input = "evaluateFile('index').calculateResult();";
+
+ // Act
+ JsException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ Func evaluateFile = path => {
+ string absolutePath = Path.Combine(directoryPath, $"{path}.js");
+ string code = File.ReadAllText(absolutePath);
+ object result = jsEngine.Evaluate(code, absolutePath);
+
+ return result;
+ };
+
+ jsEngine.EmbedHostObject("evaluateFile", evaluateFile);
+ double output = jsEngine.Evaluate(input);
+ }
+ catch (JsException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Unknown error", exception.Category);
+ Assert.StartsWith("Could not find file ", exception.Description);
+ }
+
+ [Fact]
+ public void MappingCompilationErrorDuringRecursiveExecutionOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-execution/compilation-error";
+ const string variableName = "num";
+
+ // Act
+ JsCompilationException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ Action executeFile = path => jsEngine.ExecuteFile(path);
+
+ jsEngine.SetVariableValue("directoryPath", directoryPath);
+ jsEngine.EmbedHostObject("executeFile", executeFile);
+ jsEngine.ExecuteFile(Path.Combine(directoryPath, "main-file.js"));
+
+ int output = jsEngine.GetVariableValue(variableName);
+ }
+ catch (JsCompilationException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Compilation error", exception.Category);
+ Assert.Equal("Unexpected token ILLEGAL", exception.Description);
+ Assert.Equal("SyntaxError", exception.Type);
+ Assert.Empty(exception.DocumentName);
+ Assert.Equal(1, exception.LineNumber);
+ Assert.Equal(6, exception.ColumnNumber);
+ Assert.Empty(exception.SourceFragment);
+ }
+
+ [Fact]
+ public void MappingRuntimeErrorDuringRecursiveExecutionOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-execution/runtime-error";
+ const string variableName = "num";
+
+ // Act
+ int output;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ Action executeFile = path => jsEngine.ExecuteFile(path);
+
+ jsEngine.SetVariableValue("directoryPath", directoryPath);
+ jsEngine.EmbedHostObject("executeFile", executeFile);
+ jsEngine.ExecuteFile(Path.Combine(directoryPath, "main-file.js"));
+
+ output = jsEngine.GetVariableValue(variableName);
+ }
+
+ // Assert
+ Assert.Equal(15, output);
+ }
+
+ [Fact]
+ public void MappingHostErrorDuringRecursiveExecutionOfFiles()
+ {
+ // Arrange
+ const string directoryPath = "Files/recursive-execution/host-error";
+ const string variableName = "num";
+
+ // Act
+ JsException exception = null;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ try
+ {
+ Action executeFile = path => jsEngine.ExecuteFile(path);
+
+ jsEngine.SetVariableValue("directoryPath", directoryPath);
+ jsEngine.EmbedHostObject("executeFile", executeFile);
+ jsEngine.ExecuteFile(Path.Combine(directoryPath, "main-file.js"));
+
+ int output = jsEngine.GetVariableValue(variableName);
+ }
+ catch (JsException e)
+ {
+ exception = e;
+ }
+ }
+
+ // Assert
+ Assert.NotNull(exception);
+ Assert.Equal("Unknown error", exception.Category);
+ Assert.StartsWith("File ", exception.Description);
+ }
+
+ #endregion
+
+ #endregion
+
+ #endregion
+
+
+ #region Embedding of types
+
+ #region Creating of instances
+
+ [Fact]
+ public override void CreatingAnInstanceOfEmbeddedBuiltinReferenceType()
+ {
+ // Arrange
+ Type uriType = typeof(Uri);
+
+ const string inputCode = @"var baseUri = new Uri('https://github.com'),
+ relativeUri = 'Taritsyn/MsieJavaScriptEngine'
+ ;";
+ const string inputExpression = @"(new Uri(baseUri, relativeUri)).ToString()";
+ const string targetOutput = "https://github.com/Taritsyn/MsieJavaScriptEngine";
+
+ // Act
+ string output;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostType("Uri", uriType);
+ jsEngine.Execute(inputCode);
+ output = jsEngine.Evaluate(inputExpression);
+ }
+
+ // Assert
+ Assert.Equal(targetOutput, output);
+ }
+
+ #endregion
+
+ #region Types with fields
+
+ [Fact]
+ public override void EmbeddingOfCustomReferenceTypeWithField()
+ {
+ // Arrange
+ Type defaultLoggerType = typeof(DefaultLogger);
+
+ const string input = "DefaultLogger.Current.ToString()";
+ const string targetOutput = "[null logger]";
+
+ // Act
+ string output;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostType("DefaultLogger", defaultLoggerType);
+
+ lock (DefaultLogger.SyncRoot)
+ {
+ output = jsEngine.Evaluate(input);
+ }
+ }
+
+ // Assert
+ Assert.Equal(targetOutput, output);
+ }
+
+ #endregion
+
+ #region Types with properties
+
+ [Fact]
+ public override void EmbeddingOfCustomReferenceTypeWithProperty()
+ {
+ // Arrange
+ Type bundleTableType = typeof(BundleTable);
+
+ const string input = "BundleTable.EnableOptimizations";
+ const bool targetOutput = true;
+
+ // Act
+ bool output;
+
+ using (var jsEngine = CreateJsEngine())
+ {
+ jsEngine.EmbedHostType("BundleTable", bundleTableType);
+
+ lock (BundleTable.SyncRoot)
+ {
+ output = jsEngine.Evaluate(input);
+ }
+ }
+
+ // Assert
+ Assert.Equal(targetOutput, output);
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Tests/Topaz/MultithreadingTests.cs b/test/JavaScriptEngineSwitcher.Tests/Topaz/MultithreadingTests.cs
new file mode 100644
index 00000000..0da06425
--- /dev/null
+++ b/test/JavaScriptEngineSwitcher.Tests/Topaz/MultithreadingTests.cs
@@ -0,0 +1,12 @@
+#if NET6_0_OR_GREATER
+namespace JavaScriptEngineSwitcher.Tests.Topaz
+{
+ public class MultithreadingTests : MultithreadingTestsBase
+ {
+ protected override string EngineName
+ {
+ get { return "TopazJsEngine"; }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/JavaScriptEngineSwitcher.Tests/Topaz/PrecompilationTests.cs b/test/JavaScriptEngineSwitcher.Tests/Topaz/PrecompilationTests.cs
new file mode 100644
index 00000000..6b11d628
--- /dev/null
+++ b/test/JavaScriptEngineSwitcher.Tests/Topaz/PrecompilationTests.cs
@@ -0,0 +1,12 @@
+#if NET6_0_OR_GREATER
+namespace JavaScriptEngineSwitcher.Tests.Topaz
+{
+ public class PrecompilationTests : PrecompilationTestsBase
+ {
+ protected override string EngineName
+ {
+ get { return "TopazJsEngine"; }
+ }
+ }
+}
+#endif
\ No newline at end of file