/* * Copyright © 2011, Petro Protsyk, Denys Vuika * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Text; using Scripting.SSharp.Processing; using Scripting.SSharp.Runtime; using Scripting.SSharp.Parser.Ast; namespace Scripting.SSharp { /// /// Compiled script /// public class Script : IDisposable, IInvokable { #region Properties /// /// Ast of the given Source Code /// internal ScriptAst Ast { get; set; } /// /// Code of the script /// public string SourceCode { get; set; } /// /// Execution context /// private IScriptContext _context; public IScriptContext Context { get { return _context; } set { if (_context == value) return; SwitchContext(_context as ScriptContext, false); _context = value; SwitchContext(_context as ScriptContext, true); } } private readonly List _referenceCache = new List(64); #endregion #region Constructors /// /// Default constructor /// protected Script() { var ctxt = new ScriptContext(); RuntimeHost.InitializeScript(ctxt); Context = ctxt; } /// /// Script Constructor /// /// Context in which script will execute protected Script(IScriptContext context) { if (context == null) throw new ArgumentNullException("context"); Context = context; } #endregion #region Methods /// /// Executes current script returning result /// /// result of execution public object Execute() { return Ast.Execute(Context); } /// /// Returns source code for given node /// /// Node /// source code internal string Code(ScriptAst node) { return SourceCode.Substring(node.Span.Start.Position, node.Span.Length); } /// /// String representing syntax tree /// public string SyntaxTree { get { return Ast.ConcreteSyntaxTree(); } } #endregion #region Service Methods /// /// Compiles code and performs post processing /// /// /// instance of vistor /// /// internal static Script Compile(string code, IEnumerable postProcessings, bool isExpression) { var script = new Script { SourceCode = code }; RuntimeHost.Lock(); try { script.Ast = (ScriptAst)Parse(code, isExpression); if (postProcessings != null) { foreach (var postProcessing in postProcessings) { postProcessing.BeginProcessing(script); script.Ast.AcceptVisitor(postProcessing); postProcessing.EndProcessing(script); } } } finally { RuntimeHost.UnLock(); } return script; } /// /// Compiles Script.NET code into AST representation /// /// Code string /// Compiled Script. Throws Script Exception on Syntax Errors public static Script Compile(string code) { return Compile(code, new IPostProcessing[] { new FunctionDeclarationVisitor() }, false); } public static Script CompileExpression(string code) { return Compile(code, null, true); } /// /// Executes script code /// /// /// public static object RunCode(string code) { return RunCode(code, null, false); } public static object RunCode(string code, bool isExpression) { return RunCode(code, null, isExpression); } public static object RunCode(string code, IScriptContext context) { return RunCode(code, context, false); } public static object RunCode(string code, IScriptContext context, bool isExpression) { Script script = isExpression ? CompileExpression(code) : Compile(code); if (context != null) script.Context = context; object result = script.Execute(); script.Context = null; script.Dispose(); return result; } /// /// Parses code /// /// Script code /// /// AstNode or throws:ArgumentException, ScriptSyntaxErrorException internal static AstNode Parse(string code, bool isExpression) { if (string.IsNullOrEmpty(code)) throw new ArgumentNullException("code"); var result = isExpression ? RuntimeHost.Parser.Parse("return " + code + ";") : RuntimeHost.Parser.Parse(code); if (result == null) { var errorMessage = new StringBuilder("Parsing error:"); foreach (var error in RuntimeHost.Parser.Context.Errors) { errorMessage.AppendLine(error.Message); errorMessage.AppendLine(string.Format("at line:{0} position:{1} in code:", error.Location.Line, error.Location.Position)); errorMessage.AppendLine(code.Substring(error.Location.Position, Math.Min(50, code.Length - error.Location.Position))); } throw new ScriptSyntaxErrorException(errorMessage.ToString()); } return result; } private void SwitchContext(ScriptContext ctxt, bool isAssigning) { if (ctxt == null) return; if (isAssigning) { ctxt.SetOwner(this); } else { var refs = _referenceCache.ToArray(); foreach (var r in refs) r.Remove(); //Verify all references were cleared Diagnostics.Assumes.IsTrue(_referenceCache.Count == 0); ctxt.SetOwner(null); } } internal void NotifyReferenceCreated(IValueReference reference) { if (_referenceCache.Contains(reference)) return; _referenceCache.Add(reference); reference.Removed += ReferenceRemovedHandler; } private void ReferenceRemovedHandler(object sender, EventArgs e) { var reference = (IValueReference)sender; reference.Removed -= ReferenceRemovedHandler; _referenceCache.Remove(reference); } #endregion #region IDisposable Members private bool _disposed; protected bool Disposed { get { lock (this) { return _disposed; } } } public void Dispose() { lock (this) { if (_disposed) return; Cleanup(); _disposed = true; GC.SuppressFinalize(this); } } protected virtual void Cleanup() { EventBroker.ClearMapping(this); if (_context != null) { //Clear context owner and everything related SwitchContext(_context as ScriptContext, false); _context.Dispose(); _context = null; } Ast = null; SourceCode = null; } ~Script() { Cleanup(); } #endregion #region IInvokable Members public bool CanInvoke() { return Ast != null && !Disposed; } public object Invoke(IScriptContext context, object[] args) { if (!CanInvoke()) throw new InvalidOperationException("Script is empty"); //Switch context var oldContext = Context; Context = context; var result = RuntimeHost.NullValue; try { result = Execute(); } finally { Context = oldContext; } return result; } #endregion } }