// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.ClearScript.Util;
namespace Microsoft.ClearScript
{
///
/// Provides the base implementation for all script engines.
///
public abstract class ScriptEngine : IScriptEngine, IHostContext
{
#region data
private ScriptObject engineInternal;
private Type accessContext;
private ScriptAccess defaultAccess;
private bool enforceAnonymousTypeAccess;
private bool exposeHostObjectStaticMembers;
private bool marshalEnumAsUnderlyingType;
private bool acceptEnumAsUnderlyingType;
private CustomAttributeLoader customAttributeLoader;
private DocumentSettings documentSettings;
private readonly DocumentSettings defaultDocumentSettings = new();
private static readonly IUniqueNameManager nameManager = new UniqueNameManager();
private static readonly object nullHostObjectProxy = new();
[ThreadStatic] private static ScriptEngine currentEngine;
#endregion
#region constructors
///
/// Initializes a new script engine instance.
///
/// A name to associate with the instance. Currently, this name is used only as a label in presentation contexts such as debugger user interfaces.
[Obsolete("Use ScriptEngine(string name, string fileNameExtensions) instead.")]
protected ScriptEngine(string name)
: this(name, null)
{
}
///
/// Initializes a new script engine instance with the specified list of supported file name extensions.
///
/// A name to associate with the instance. Currently, this name is used only as a label in presentation contexts such as debugger user interfaces.
/// A semicolon-delimited list of supported file name extensions.
protected ScriptEngine(string name, string fileNameExtensions)
{
Name = nameManager.GetUniqueName(name, GetType().GetRootName());
defaultDocumentSettings.FileNameExtensions = fileNameExtensions;
extensionMethodTable = realExtensionMethodTable = new ExtensionMethodTable();
}
#endregion
#region public members
///
/// Gets the script engine that is invoking a host member on the current thread.
///
///
/// If multiple script engines are invoking host members on the current thread, this
/// property gets the one responsible for the most deeply nested invocation. If no script
/// engines are invoking host members on the current thread, this property returns
/// null.
///
public static ScriptEngine Current => currentEngine;
#endregion
#region IScriptEngine implementation
///
public string Name { get; }
///
public abstract string FileNameExtension { get; }
///
public Type AccessContext
{
get => accessContext;
set
{
accessContext = value;
OnAccessSettingsChanged();
}
}
///
public ScriptAccess DefaultAccess
{
get => defaultAccess;
set
{
defaultAccess = value;
OnAccessSettingsChanged();
}
}
///
public bool EnforceAnonymousTypeAccess
{
get => enforceAnonymousTypeAccess;
set
{
enforceAnonymousTypeAccess = value;
OnAccessSettingsChanged();
}
}
///
public bool ExposeHostObjectStaticMembers
{
get => exposeHostObjectStaticMembers;
set
{
exposeHostObjectStaticMembers = value;
OnAccessSettingsChanged();
}
}
///
public bool DisableExtensionMethods
{
get => extensionMethodTable == emptyExtensionMethodTable;
set
{
var newExtensionMethodTable = value ? emptyExtensionMethodTable : realExtensionMethodTable;
if (newExtensionMethodTable != extensionMethodTable)
{
ScriptInvoke(
static ctx =>
{
if (ctx.newExtensionMethodTable != ctx.self.extensionMethodTable)
{
ctx.self.extensionMethodTable = ctx.newExtensionMethodTable;
ctx.self.ClearMethodBindCache();
ctx.self.OnAccessSettingsChanged();
}
},
(self: this, newExtensionMethodTable)
);
}
}
}
///
public bool FormatCode { get; set; }
///
public bool AllowReflection { get; set; }
///
public bool DisableTypeRestriction { get; set; }
///
public bool DisableListIndexTypeRestriction { get; set; }
///
public bool EnableNullResultWrapping { get; set; }
///
public bool DisableFloatNarrowing { get; set; }
///
public bool DisableDynamicBinding { get; set; }
///
public bool UseReflectionBindFallback { get; set; }
///
public bool MarshalEnumAsUnderlyingType {
get => marshalEnumAsUnderlyingType;
set
{
marshalEnumAsUnderlyingType = value;
OnAccessSettingsChanged();
}
}
///
public bool AcceptEnumAsUnderlyingType
{
get => acceptEnumAsUnderlyingType;
set
{
if (value != acceptEnumAsUnderlyingType)
{
ScriptInvoke(
static ctx =>
{
if (ctx.value != ctx.self.acceptEnumAsUnderlyingType)
{
ctx.self.acceptEnumAsUnderlyingType = ctx.value;
ctx.self.ClearMethodBindCache();
ctx.self.OnAccessSettingsChanged();
}
},
(self: this, value)
);
}
}
}
///
public bool EnableAutoHostVariables { get; set; }
///
public object UndefinedImportValue { get; set; } = Undefined.Value;
///
public object NullImportValue { get; set; }
///
public object NullExportValue { get; set; }
///
public object VoidResultValue { get; set; } = VoidResult.Value;
///
public ContinuationCallback ContinuationCallback { get; set; }
///
public abstract dynamic Script { get; }
///
public abstract ScriptObject Global { get; }
///
public DocumentSettings DocumentSettings
{
get => documentSettings ?? defaultDocumentSettings;
set => documentSettings = value;
}
///
public CustomAttributeLoader CustomAttributeLoader
{
get => customAttributeLoader;
set
{
customAttributeLoader = value;
OnAccessSettingsChanged();
}
}
///
public object HostData { get; set; }
///
public void AddHostObject(string itemName, object target)
{
AddHostObject(itemName, HostItemFlags.None, target);
}
///
public void AddHostObject(string itemName, HostItemFlags flags, object target)
{
MiscHelpers.VerifyNonNullArgument(target, nameof(target));
AddHostItem(itemName, flags, target);
}
///
public void AddRestrictedHostObject(string itemName, T target)
{
AddRestrictedHostObject(itemName, HostItemFlags.None, target);
}
///
public void AddRestrictedHostObject(string itemName, HostItemFlags flags, T target)
{
AddHostItem(itemName, flags, HostItem.Wrap(this, target, typeof(T)));
}
///
public void AddCOMObject(string itemName, string progID)
{
AddCOMObject(itemName, HostItemFlags.None, progID);
}
///
public void AddCOMObject(string itemName, string progID, string serverName)
{
AddCOMObject(itemName, HostItemFlags.None, progID, serverName);
}
///
public void AddCOMObject(string itemName, HostItemFlags flags, string progID)
{
AddCOMObject(itemName, flags, progID, null);
}
///
public void AddCOMObject(string itemName, HostItemFlags flags, string progID, string serverName)
{
AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(progID, serverName));
}
///
public void AddCOMObject(string itemName, Guid clsid)
{
AddCOMObject(itemName, HostItemFlags.None, clsid);
}
///
public void AddCOMObject(string itemName, Guid clsid, string serverName)
{
AddCOMObject(itemName, HostItemFlags.None, clsid, serverName);
}
///
public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid)
{
AddCOMObject(itemName, flags, clsid, null);
}
///
public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid, string serverName)
{
AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(clsid, serverName));
}
///
public void AddHostType(Type type)
{
AddHostType(HostItemFlags.None, type);
}
///
public void AddHostType(HostItemFlags flags, Type type)
{
AddHostType(type.GetRootName(), flags, type);
}
///
public void AddHostType(string itemName, Type type)
{
AddHostType(itemName, HostItemFlags.None, type);
}
///
public void AddHostType(string itemName, HostItemFlags flags, Type type)
{
MiscHelpers.VerifyNonNullArgument(type, nameof(type));
AddHostItem(itemName, flags, HostType.Wrap(type));
}
///
public void AddHostType(string itemName, string typeName, params Type[] typeArgs)
{
AddHostType(itemName, HostItemFlags.None, typeName, typeArgs);
}
///
public void AddHostType(string itemName, HostItemFlags flags, string typeName, params Type[] typeArgs)
{
AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, null, false, typeArgs));
}
///
public void AddHostType(string itemName, string typeName, string assemblyName, params Type[] typeArgs)
{
AddHostType(itemName, HostItemFlags.None, typeName, assemblyName, typeArgs);
}
///
public void AddHostType(string itemName, HostItemFlags flags, string typeName, string assemblyName, params Type[] typeArgs)
{
AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs));
}
///
public void AddHostTypes(params Type[] types)
{
if (types is not null)
{
foreach (var type in types)
{
if (type is not null)
{
AddHostType(type);
}
}
}
}
///
public void AddCOMType(string itemName, string progID)
{
AddCOMType(itemName, HostItemFlags.None, progID);
}
///
public void AddCOMType(string itemName, string progID, string serverName)
{
AddCOMType(itemName, HostItemFlags.None, progID, serverName);
}
///
public void AddCOMType(string itemName, HostItemFlags flags, string progID)
{
AddCOMType(itemName, flags, progID, null);
}
///
public void AddCOMType(string itemName, HostItemFlags flags, string progID, string serverName)
{
AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(progID, serverName)));
}
///
public void AddCOMType(string itemName, Guid clsid)
{
AddCOMType(itemName, HostItemFlags.None, clsid);
}
///
public void AddCOMType(string itemName, Guid clsid, string serverName)
{
AddCOMType(itemName, HostItemFlags.None, clsid, serverName);
}
///
public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid)
{
AddCOMType(itemName, flags, clsid, null);
}
///
public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid, string serverName)
{
AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(clsid, serverName)));
}
///
public void Execute(string code)
{
Execute(null, code);
}
///
public void Execute(string documentName, string code)
{
Execute(documentName, false, code);
}
///
public void Execute(string documentName, bool discard, string code)
{
Execute(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code);
}
///
public void Execute(DocumentInfo documentInfo, string code)
{
Execute(documentInfo.MakeUnique(this), code, false);
}
///
public void ExecuteDocument(string specifier)
{
ExecuteDocument(specifier, null);
}
///
public void ExecuteDocument(string specifier, DocumentCategory category)
{
ExecuteDocument(specifier, category, null);
}
///
public void ExecuteDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
Execute(document.Info, document.GetTextContents());
}
///
public virtual string ExecuteCommand(string command)
{
var documentInfo = new DocumentInfo("Command") { Flags = DocumentFlags.IsTransient };
return GetCommandResultString(Evaluate(documentInfo.MakeUnique(this), command, false));
}
///
public object Evaluate(string code)
{
return Evaluate(null, code);
}
///
public object Evaluate(string documentName, string code)
{
return Evaluate(documentName, true, code);
}
///
public object Evaluate(string documentName, bool discard, string code)
{
return Evaluate(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code);
}
///
public object Evaluate(DocumentInfo documentInfo, string code)
{
return Evaluate(documentInfo.MakeUnique(this, DocumentFlags.IsTransient), code, true);
}
///
public object EvaluateDocument(string specifier)
{
return EvaluateDocument(specifier, null);
}
///
public object EvaluateDocument(string specifier, DocumentCategory category)
{
return EvaluateDocument(specifier, category, null);
}
///
public object EvaluateDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
return Evaluate(document.Info, document.GetTextContents());
}
///
public object Invoke(string funcName, params object[] args)
{
MiscHelpers.VerifyNonBlankArgument(funcName, nameof(funcName), "Invalid function name");
return Global.InvokeMethod(funcName, args ?? ArrayHelpers.GetEmptyArray