aspclassic-core/AspClassic.VBScript/VBScript.cs
Jelle Luteijn 484dbfc9d9 progress
2022-05-15 11:19:49 +02:00

465 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using VB = AspClassic.Parser;
using AspClassic.Scripting;
using AspClassic.Scripting.Runtime;
//using AspClassic.Scripting.Debugging.CompilerServices;
//using AspClassic.Scripting.Debugging;
using System.Dynamic;
#if USE35
using AspClassic.Scripting.Ast;
using AspClassic.Scripting.Utils;
#else
using System.Linq;
using System.Linq.Expressions;
#endif
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.IO;
using Dlrsoft.VBScript.Binders;
using Dlrsoft.VBScript.Compiler;
using Dlrsoft.VBScript.Runtime;
namespace Dlrsoft.VBScript
{
public class VBScript
{
public const string ERR_PARAMETER = "err";
public const string TRACE_PARAMETER = "__trace";
private IList<Assembly> _assemblies;
private ExpandoObject _globals = new ExpandoObject();
private Scope _dlrGlobals;
public VBScript(IList<Assembly> assms, Scope dlrGlobals)
{
_assemblies = assms;
_dlrGlobals = dlrGlobals;
AddAssemblyNamesAndTypes();
}
// _addNamespacesAndTypes builds a tree of ExpandoObjects representing
// .NET namespaces, with TypeModel objects at the leaves. Though Sympl is
// case-insensitive, we store the names as they appear in .NET reflection
// in case our globals object or a namespace object gets passed as an IDO
// to another language or library, where they may be looking for names
// case-sensitively using EO's default lookup.
//
public void AddAssemblyNamesAndTypes() {
foreach (var assm in _assemblies) {
foreach (var typ in assm.GetExportedTypes()) {
string[] names = typ.FullName.Split('.');
var table = _globals;
for (int i = 0; i < names.Length - 1; i++) {
string name = names[i].ToLower();
if (DynamicObjectHelpers.HasMember(
(IDynamicMetaObjectProvider)table, name)) {
// Must be Expando since only we have put objs in
// the tables so far.
table = (ExpandoObject)(DynamicObjectHelpers
.GetMember(table, name));
} else {
var tmp = new ExpandoObject();
DynamicObjectHelpers.SetMember(table, name, tmp);
table = tmp;
}
}
DynamicObjectHelpers.SetMember(table, names[names.Length - 1],
new TypeModel(typ));
}
}
}
// ExecuteFile executes the file in a new module scope and stores the
// scope on Globals, using either the provided name, globalVar, or the
// file's base name. This function returns the module scope.
//
public IDynamicMetaObjectProvider ExecuteFile(string filename) {
return ExecuteFile(filename, null);
}
public IDynamicMetaObjectProvider ExecuteFile(string filename,
string globalVar) {
var moduleEO = CreateScope();
ExecuteFileInScope(filename, moduleEO);
globalVar = globalVar ?? Path.GetFileNameWithoutExtension(filename);
DynamicObjectHelpers.SetMember(this._globals, globalVar, moduleEO);
return moduleEO;
}
// ExecuteFileInScope executes the file in the given module scope. This
// does NOT store the module scope on Globals. This function returns
// nothing.
//
public void ExecuteFileInScope(string filename,
IDynamicMetaObjectProvider moduleEO) {
var f = new StreamReader(filename);
// Simple way to convey script rundir for RuntimeHelpes.SymplImport
// to load .sympl files.
DynamicObjectHelpers.SetMember(moduleEO, "__file__",
Path.GetFullPath(filename));
try {
var moduleFun = ParseFileToLambda(filename, f);
var d = moduleFun;
d(this, moduleEO);
} finally {
f.Close();
}
}
internal AspClassic.Scripting.Utils.Action<VBScript, IDynamicMetaObjectProvider>
ParseFileToLambda(string filename, TextReader reader) {
var scanner = new VB.Scanner(reader);
var errorTable = new List<VB.SyntaxError>();
var block = new VB.Parser().ParseScriptFile(scanner, errorTable);
if (errorTable.Count > 0)
{
List<VBScriptSyntaxError> errors = new List<VBScriptSyntaxError>();
foreach (VB.SyntaxError error in errorTable)
{
errors.Add(new VBScriptSyntaxError(
filename,
SourceUtil.ConvertSpan(error.Span),
(int)error.Type,
error.Type.ToString())
);
}
throw new VBScriptCompilerException(errors);
}
VBScriptSourceCodeReader sourceReader = reader as VBScriptSourceCodeReader;
ISourceMapper mapper = null;
if (sourceReader != null)
{
mapper = sourceReader.SourceMapper;
}
var scope = new AnalysisScope(
null,
filename,
this,
Expression.Parameter(typeof(VBScript), "vbscriptRuntime"),
Expression.Parameter(typeof(IDynamicMetaObjectProvider), "fileModule"),
mapper);
//Generate function table
List<Expression> body = new List<Expression>();
//Add the built in globals
ParameterExpression err = Expression.Parameter(typeof(ErrObject), ERR_PARAMETER);
scope.Names.Add(ERR_PARAMETER, err);
body.Add(
Expression.Assign(
err,
Expression.New(typeof(ErrObject))
)
);
if (Trace)
{
ParameterExpression trace = Expression.Parameter(typeof(ITrace), TRACE_PARAMETER);
scope.Names.Add(TRACE_PARAMETER, trace);
body.Add(
Expression.Assign(
trace,
Expression.Convert(
Expression.Dynamic(
scope.GetRuntime().GetGetMemberBinder(TRACE_PARAMETER),
typeof(object),
scope.GetModuleExpr()
),
typeof(ITrace)
)
)
);
}
//Put module variables and functions into the scope
VBScriptAnalyzer.AnalyzeFile(block, scope);
//Generate the module level code other than the methods:
if (block.Statements != null)
{
foreach (var s in block.Statements)
{
if (s is VB.MethodDeclaration)
{
//Make sure methods are created first before being executed
body.Insert(0, VBScriptGenerator.GenerateExpr(s, scope));
}
else
{
Expression stmt = VBScriptGenerator.GenerateExpr(s, scope);
if (scope.VariableScope.IsOnErrorResumeNextOn)
{
stmt = VBScriptGenerator.WrapTryCatchExpression(stmt, scope);
}
Expression debugInfo = null;
Expression clearDebugInfo = null;
if (Trace && s is VB.Statement && !(s is VB.BlockStatement))
{
debugInfo = VBScriptGenerator.GenerateDebugInfo(s, scope, out clearDebugInfo);
body.Add(debugInfo);
}
body.Add(stmt);
if (clearDebugInfo != null)
{
body.Add(clearDebugInfo);
}
}
}
}
body.Add(Expression.Constant(null)); //Stop anything from returning
if (scope.Errors.Count > 0)
{
throw new VBScriptCompilerException(scope.Errors);
}
//if (Debug)
//{
// Expression registerRuntimeVariables = VBScriptGenerator.GenerateRuntimeVariablesExpression(scope);
// body.Insert(0, registerRuntimeVariables);
//}
var moduleFun = Expression.Lambda<AspClassic.Scripting.Utils.Action<VBScript, IDynamicMetaObjectProvider>>(
Expression.Block(
scope.Names.Values,
body),
scope.RuntimeExpr,
scope.ModuleExpr);
//if (!Debug)
//{
return moduleFun.Compile();
//}
//else
//{
// Expression<Action<VBScript, IDynamicMetaObjectProvider>> lambda = (Expression<Action<VBScript, IDynamicMetaObjectProvider>>)DebugContext.TransformLambda(moduleFun);
// return lambda.Compile();
//}
}
// Execute a single expression parsed from string in the provided module
// scope and returns the resulting value.
//
public void ExecuteExpr(string expr_str,
IDynamicMetaObjectProvider moduleEO) {
var moduleFun = ParseExprToLambda(new StringReader(expr_str));
var d = moduleFun;
d(this, moduleEO);
}
internal AspClassic.Scripting.Utils.Action<VBScript, IDynamicMetaObjectProvider>
ParseExprToLambda(TextReader reader) {
var scanner = new VB.Scanner(reader);
var errorTable = new List<VB.SyntaxError>();
var ast = new VB.Parser().ParseScriptFile(scanner, errorTable);
VBScriptSourceCodeReader sourceReader = reader as VBScriptSourceCodeReader;
ISourceMapper mapper = null;
if (sourceReader != null)
{
mapper = sourceReader.SourceMapper;
}
var scope = new AnalysisScope(
null,
"__snippet__",
this,
Expression.Parameter(typeof(VBScript), "vbscriptRuntime"),
Expression.Parameter(typeof(IDynamicMetaObjectProvider), "fileModule"),
mapper
);
List<Expression> body = new List<Expression>();
body.Add(Expression.Convert(VBScriptGenerator.GenerateExpr(ast, scope), typeof(object)));
if (scope.Errors.Count > 0)
{
throw new VBScriptCompilerException(scope.Errors);
}
var moduleFun = Expression.Lambda<AspClassic.Scripting.Utils.Action<VBScript, IDynamicMetaObjectProvider>>(
Expression.Block(body),
scope.RuntimeExpr,
scope.ModuleExpr
);
return moduleFun.Compile();
}
public IDynamicMetaObjectProvider Globals { get { return _globals; } }
public IDynamicMetaObjectProvider DlrGlobals { get { return _dlrGlobals; } }
public bool Trace { get; set; }
public static ExpandoObject CreateScope() {
return new ExpandoObject();
}
/////////////////////////
// Canonicalizing Binders
/////////////////////////
// We need to canonicalize binders so that we can share L2 dynamic
// dispatch caching across common call sites. Every call site with the
// same operation and same metadata on their binders should return the
// same rules whenever presented with the same kinds of inputs. The
// DLR saves the L2 cache on the binder instance. If one site somewhere
// produces a rule, another call site performing the same operation with
// the same metadata could get the L2 cached rule rather than computing
// it again. For this to work, we need to place the same binder instance
// on those functionally equivalent call sites.
private Dictionary<string, VBScriptGetMemberBinder> _getMemberBinders =
new Dictionary<string, VBScriptGetMemberBinder>();
public VBScriptGetMemberBinder GetGetMemberBinder (string name) {
lock (_getMemberBinders) {
// Don't lower the name. Sympl is case-preserving in the metadata
// in case some DynamicMetaObject ignores ignoreCase. This makes
// some interop cases work, but the cost is that if a Sympl program
// spells ".foo" and ".Foo" at different sites, they won't share rules.
if (_getMemberBinders.ContainsKey(name))
return _getMemberBinders[name];
var b = new VBScriptGetMemberBinder(name);
_getMemberBinders[name] = b;
return b;
}
}
private Dictionary<string, VBScriptSetMemberBinder> _setMemberBinders =
new Dictionary<string, VBScriptSetMemberBinder>();
public VBScriptSetMemberBinder GetSetMemberBinder (string name) {
lock (_setMemberBinders) {
// Don't lower the name. Sympl is case-preserving in the metadata
// in case some DynamicMetaObject ignores ignoreCase. This makes
// some interop cases work, but the cost is that if a Sympl program
// spells ".foo" and ".Foo" at different sites, they won't share rules.
if (_setMemberBinders.ContainsKey(name))
return _setMemberBinders[name];
var b = new VBScriptSetMemberBinder(name);
_setMemberBinders[name] = b;
return b;
}
}
private Dictionary<CallInfo, VBScriptInvokeBinder> _invokeBinders =
new Dictionary<CallInfo, VBScriptInvokeBinder>();
public VBScriptInvokeBinder GetInvokeBinder (CallInfo info) {
lock (_invokeBinders) {
if (_invokeBinders.ContainsKey(info))
return _invokeBinders[info];
var b = new VBScriptInvokeBinder(info);
_invokeBinders[info] = b;
return b;
}
}
private Dictionary<InvokeMemberBinderKey, VBScriptInvokeMemberBinder>
_invokeMemberBinders =
new Dictionary<InvokeMemberBinderKey, VBScriptInvokeMemberBinder>();
public VBScriptInvokeMemberBinder GetInvokeMemberBinder
(InvokeMemberBinderKey info) {
lock (_invokeMemberBinders) {
if (_invokeMemberBinders.ContainsKey(info))
return _invokeMemberBinders[info];
var b = new VBScriptInvokeMemberBinder(info.Name, info.Info);
_invokeMemberBinders[info] = b;
return b;
}
}
private Dictionary<CallInfo, VBScriptCreateInstanceBinder>
_createInstanceBinders =
new Dictionary<CallInfo, VBScriptCreateInstanceBinder>();
public VBScriptCreateInstanceBinder GetCreateInstanceBinder(CallInfo info) {
lock (_createInstanceBinders) {
if (_createInstanceBinders.ContainsKey(info))
return _createInstanceBinders[info];
var b = new VBScriptCreateInstanceBinder(info);
_createInstanceBinders[info] = b;
return b;
}
}
private Dictionary<CallInfo, VBScriptGetIndexBinder> _getIndexBinders =
new Dictionary<CallInfo, VBScriptGetIndexBinder>();
public VBScriptGetIndexBinder GetGetIndexBinder(CallInfo info) {
lock (_getIndexBinders) {
if (_getIndexBinders.ContainsKey(info))
return _getIndexBinders[info];
var b = new VBScriptGetIndexBinder(info);
_getIndexBinders[info] = b;
return b;
}
}
private Dictionary<CallInfo, VBScriptSetIndexBinder> _setIndexBinders =
new Dictionary<CallInfo, VBScriptSetIndexBinder>();
public VBScriptSetIndexBinder GetSetIndexBinder(CallInfo info) {
lock (_setIndexBinders) {
if (_setIndexBinders.ContainsKey(info))
return _setIndexBinders[info];
var b = new VBScriptSetIndexBinder(info);
_setIndexBinders[info] = b;
return b;
}
}
private Dictionary<ExpressionType, VBScriptBinaryOperationBinder>
_binaryOperationBinders =
new Dictionary<ExpressionType, VBScriptBinaryOperationBinder>();
public VBScriptBinaryOperationBinder GetBinaryOperationBinder
(ExpressionType op) {
lock (_binaryOperationBinders) {
if (_binaryOperationBinders.ContainsKey(op))
return _binaryOperationBinders[op];
var b = new VBScriptBinaryOperationBinder(op);
_binaryOperationBinders[op] = b;
return b;
}
}
private Dictionary<ExpressionType, VBScriptUnaryOperationBinder>
_unaryOperationBinders =
new Dictionary<ExpressionType, VBScriptUnaryOperationBinder>();
public VBScriptUnaryOperationBinder GetUnaryOperationBinder
(ExpressionType op) {
lock (_unaryOperationBinders) {
if (_unaryOperationBinders.ContainsKey(op))
return _unaryOperationBinders[op];
var b = new VBScriptUnaryOperationBinder(op);
_unaryOperationBinders[op] = b;
return b;
}
}
//private DebugContext _debugContext;
//public DebugContext DebugContext
//{
// get
// {
// if (_debugContext == null)
// {
// _debugContext = DebugContext.CreateInstance();
// ITracePipeline pipeLine = TracePipeline.CreateInstance(_debugContext);
// pipeLine.TraceCallback = new VBScriptTraceCallbackListener();
// }
// return _debugContext;
// }
//}
}
}