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 _assemblies; private ExpandoObject _globals = new ExpandoObject(); private Scope _dlrGlobals; public VBScript(IList 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 ParseFileToLambda(string filename, TextReader reader) { var scanner = new VB.Scanner(reader); var errorTable = new List(); var block = new VB.Parser().ParseScriptFile(scanner, errorTable); if (errorTable.Count > 0) { List errors = new List(); 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 body = new List(); //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>( Expression.Block( scope.Names.Values, body), scope.RuntimeExpr, scope.ModuleExpr); //if (!Debug) //{ return moduleFun.Compile(); //} //else //{ // Expression> lambda = (Expression>)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 ParseExprToLambda(TextReader reader) { var scanner = new VB.Scanner(reader); var errorTable = new List(); 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 body = new List(); body.Add(Expression.Convert(VBScriptGenerator.GenerateExpr(ast, scope), typeof(object))); if (scope.Errors.Count > 0) { throw new VBScriptCompilerException(scope.Errors); } var moduleFun = Expression.Lambda>( 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 _getMemberBinders = new Dictionary(); 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 _setMemberBinders = new Dictionary(); 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 _invokeBinders = new Dictionary(); 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 _invokeMemberBinders = new Dictionary(); 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 _createInstanceBinders = new Dictionary(); 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 _getIndexBinders = new Dictionary(); 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 _setIndexBinders = new Dictionary(); 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 _binaryOperationBinders = new Dictionary(); 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 _unaryOperationBinders = new Dictionary(); 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; // } //} } }