/*
* 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
}
}