using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using DynamicExpresso.Exceptions;
using DynamicExpresso.Parsing;
using DynamicExpresso.Reflection;
using DynamicExpresso.Resources;
using DynamicExpresso.Visitors;
namespace DynamicExpresso
{
///
/// Class used to parse and compile a text expression into an Expression or a Delegate that can be invoked. Expression are written using a subset of C# syntax.
/// Only get properties, Parse and Eval methods are thread safe.
///
public class Interpreter
{
private readonly ParserSettings _settings;
private readonly ISet _visitors = new HashSet();
#region Constructors
///
/// Creates a new Interpreter using InterpreterOptions.Default.
///
public Interpreter()
: this(InterpreterOptions.Default)
{
}
///
/// Creates a new Interpreter using the specified options.
///
///
public Interpreter(InterpreterOptions options)
{
var caseInsensitive = options.HasFlag(InterpreterOptions.CaseInsensitive);
var lateBindObject = options.HasFlag(InterpreterOptions.LateBindObject);
_settings = new ParserSettings(caseInsensitive, lateBindObject);
if ((options & InterpreterOptions.SystemKeywords) == InterpreterOptions.SystemKeywords)
{
SetIdentifiers(LanguageConstants.Literals);
}
if ((options & InterpreterOptions.PrimitiveTypes) == InterpreterOptions.PrimitiveTypes)
{
Reference(LanguageConstants.PrimitiveTypes);
Reference(LanguageConstants.CSharpPrimitiveTypes);
}
if ((options & InterpreterOptions.CommonTypes) == InterpreterOptions.CommonTypes)
{
Reference(LanguageConstants.CommonTypes);
}
if ((options & InterpreterOptions.LambdaExpressions) == InterpreterOptions.LambdaExpressions)
{
_settings.LambdaExpressions = true;
}
_visitors.Add(new DisableReflectionVisitor());
}
///
/// Create a new interpreter with the settings copied from another interpreter
///
internal Interpreter(ParserSettings settings)
{
_settings = settings;
}
#endregion
#region Properties
public bool CaseInsensitive
{
get
{
return _settings.CaseInsensitive;
}
}
///
/// Gets a list of registeres types. Add types by using the Reference method.
///
public IEnumerable ReferencedTypes
{
get
{
return _settings.KnownTypes
.Select(p => p.Value)
.ToList();
}
}
///
/// Gets a list of known identifiers. Add identifiers using SetVariable, SetFunction or SetExpression methods.
///
public IEnumerable Identifiers
{
get
{
return _settings.Identifiers
.Select(p => p.Value)
.ToList();
}
}
///
/// Gets the available assignment operators.
///
public AssignmentOperators AssignmentOperators
{
get { return _settings.AssignmentOperators; }
}
#endregion
#region Options
///
/// Allow to set de default numeric type when no suffix is specified (Int by default, Double if real number)
///
///
///
public Interpreter SetDefaultNumberType(DefaultNumberType defaultNumberType)
{
_settings.DefaultNumberType = defaultNumberType;
return this;
}
///
/// Allows to enable/disable assignment operators.
/// For security when expression are generated by the users is more safe to disable assignment operators.
///
///
///
public Interpreter EnableAssignment(AssignmentOperators assignmentOperators)
{
_settings.AssignmentOperators = assignmentOperators;
return this;
}
#endregion
#region Visitors
public ISet Visitors
{
get { return _visitors; }
}
///
/// Enable reflection expression (like x.GetType().GetMethod() or typeof(double).Assembly) by removing the DisableReflectionVisitor.
///
///
public Interpreter EnableReflection()
{
var visitor = Visitors.FirstOrDefault(p => p is DisableReflectionVisitor);
if (visitor != null)
Visitors.Remove(visitor);
return this;
}
#endregion
#region Register identifiers
///
/// Allow the specified function delegate to be called from a parsed expression.
/// Overloads can be added (ie. multiple delegates can be registered with the same name).
/// A delegate will replace any delegate with the exact same signature that is already registered.
///
///
///
///
public Interpreter SetFunction(string name, Delegate value)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
if (_settings.Identifiers.TryGetValue(name, out var identifier) &&
identifier is FunctionIdentifier fIdentifier)
{
fIdentifier.AddOverload(value);
}
else
{
SetIdentifier(new FunctionIdentifier(name, value));
}
return this;
}
///
/// Allow the specified variable to be used in a parsed expression.
///
///
///
///
public Interpreter SetVariable(string name, object value)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
return SetExpression(name, Expression.Constant(value));
}
///
/// Allow the specified variable to be used in a parsed expression.
///
///
///
///
public Interpreter SetVariable(string name, T value)
{
return SetVariable(name, value, typeof(T));
}
///
/// Allow the specified variable to be used in a parsed expression.
///
///
///
///
///
public Interpreter SetVariable(string name, object value, Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
return SetExpression(name, Expression.Constant(value, type));
}
///
/// Allow the specified Expression to be used in a parsed expression.
/// Basically add the specified expression as a known identifier.
///
///
///
///
public Interpreter SetExpression(string name, Expression expression)
{
return SetIdentifier(new Identifier(name, expression));
}
///
/// Allow the specified list of identifiers to be used in a parsed expression.
/// Basically add the specified expressions as a known identifier.
///
///
///
public Interpreter SetIdentifiers(IEnumerable identifiers)
{
foreach (var i in identifiers)
SetIdentifier(i);
return this;
}
///
/// Allow the specified identifier to be used in a parsed expression.
/// Basically add the specified expression as a known identifier.
///
///
///
public Interpreter SetIdentifier(Identifier identifier)
{
if (identifier == null)
throw new ArgumentNullException(nameof(identifier));
if (LanguageConstants.ReservedKeywords.Contains(identifier.Name))
throw new InvalidOperationException(string.Format(ErrorMessages.ReservedWord, identifier.Name));
_settings.Identifiers[identifier.Name] = identifier;
return this;
}
///
/// Remove from the list of known identifiers.
///
/// >
///
public Interpreter UnsetFunction(string name)
{
return UnsetIdentifier(name);
}
///
/// Remove from the list of known identifiers.
///
/// >
///
public Interpreter UnsetVariable(string name)
{
return UnsetIdentifier(name);
}
///
/// Remove from the list of known identifiers.
///
/// >
///
public Interpreter UnsetExpression(string name)
{
return UnsetIdentifier(name);
}
///
/// Remove from the list of known identifiers.
///
///
///
public Interpreter UnsetIdentifier(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
_settings.Identifiers.Remove(name);
return this;
}
#endregion
#region Register referenced types
///
/// Allow the specified type to be used inside an expression. The type will be available using its name.
/// If the type contains method extensions methods they will be available inside expressions.
///
///
///
public Interpreter Reference(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
return Reference(type, type.Name);
}
///
/// Allow the specified type to be used inside an expression.
/// See Reference(Type, string) method.
///
///
///
public Interpreter Reference(IEnumerable types)
{
if (types == null)
throw new ArgumentNullException(nameof(types));
foreach (var t in types)
Reference(t);
return this;
}
///
/// Allow the specified type to be used inside an expression by using a custom alias.
/// If the type contains extensions methods they will be available inside expressions.
///
///
/// Public name that must be used in the expression.
///
public Interpreter Reference(Type type, string typeName)
{
return Reference(new ReferenceType(typeName, type));
}
///
/// Allow the specified type to be used inside an expression by using a custom alias.
/// If the type contains extensions methods they will be available inside expressions.
///
///
///
public Interpreter Reference(ReferenceType type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
_settings.KnownTypes[type.Name] = type;
_settings.ExtensionMethods.UnionWith(type.ExtensionMethods);
return this;
}
#endregion
#region Parse
///
/// Parse a text expression and returns a Lambda class that can be used to invoke it.
///
/// Expression statement
///
///
///
public Lambda Parse(string expressionText, params Parameter[] parameters)
{
return Parse(expressionText, typeof(void), parameters);
}
///
/// Parse a text expression and returns a Lambda class that can be used to invoke it.
/// If the expression cannot be converted to the type specified in the expressionType parameter
/// an exception is throw.
///
/// Expression statement
/// The expected return type. Use void or object type if there isn't an expected return type.
///
///
///
public Lambda Parse(string expressionText, Type expressionType, params Parameter[] parameters)
{
return ParseAsLambda(expressionText, expressionType, parameters);
}
[Obsolete("Use ParseAsDelegate(string, params string[])")]
public TDelegate Parse(string expressionText, params string[] parametersNames)
{
return ParseAsDelegate(expressionText, parametersNames);
}
///
/// Parse a text expression and convert it into a delegate.
///
/// Delegate to use
/// Expression statement
/// Names of the parameters. If not specified the parameters names defined inside the delegate are used.
///
///
public TDelegate ParseAsDelegate(string expressionText, params string[] parametersNames)
{
var lambda = ParseAs(expressionText, parametersNames);
return lambda.Compile();
}
///
/// Parse a text expression and convert it into a lambda expression.
///
/// Delegate to use
/// Expression statement
/// Names of the parameters. If not specified the parameters names defined inside the delegate are used.
///
///
public Expression ParseAsExpression(string expressionText,
params string[] parametersNames)
{
var lambda = ParseAs(expressionText, parametersNames);
return lambda.LambdaExpression();
}
internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText,
params string[] parametersNames)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);
// return type is object means that we have no information beforehand
// => we force it to typeof(void) so that no conversion expression is emitted by the parser
// and the actual expression type is preserved
var returnType = delegateInfo.ReturnType;
if (returnType == typeof(object))
returnType = typeof(void);
var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters);
return lambda.LambdaExpression(delegateType);
}
public Lambda ParseAs(string expressionText, params string[] parametersNames)
{
return ParseAs(typeof(TDelegate), expressionText, parametersNames);
}
internal Lambda ParseAs(Type delegateType, string expressionText, params string[] parametersNames)
{
var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames);
return ParseAsLambda(expressionText, delegateInfo.ReturnType, delegateInfo.Parameters);
}
#endregion
#region Eval
///
/// Parse and invoke the specified expression.
///
///
///
///
public object Eval(string expressionText, params Parameter[] parameters)
{
return Eval(expressionText, typeof(void), parameters);
}
///
/// Parse and invoke the specified expression.
///
///
///
///
public T Eval(string expressionText, params Parameter[] parameters)
{
return (T)Eval(expressionText, typeof(T), parameters);
}
///
/// Parse and invoke the specified expression.
///
///
/// The return type of the expression. Use void or object if you don't know the expected return type.
///
///
public object Eval(string expressionText, Type expressionType, params Parameter[] parameters)
{
var s = Parse(expressionText, expressionType, parameters);
return s.Invoke(parameters);
}
#endregion
#region Detection
public IdentifiersInfo DetectIdentifiers(string expression)
{
var detector = new Detector(_settings);
return detector.DetectIdentifiers(expression, DetectorOptions.None);
}
public IdentifiersInfo DetectIdentifiers(string expression, DetectorOptions options)
{
var detector = new Detector(_settings);
return detector.DetectIdentifiers(expression, options);
}
#endregion
#region Private methods
private Lambda ParseAsLambda(string expressionText, Type expressionType, Parameter[] parameters)
{
var arguments = new ParserArguments(
expressionText,
_settings,
expressionType,
parameters);
var expression = Parser.Parse(arguments);
foreach (var visitor in Visitors)
expression = visitor.Visit(expression);
var lambda = new Lambda(expression, arguments);
#if TEST_DetectIdentifiers
AssertDetectIdentifiers(lambda);
#endif
return lambda;
}
#if TEST_DetectIdentifiers
private void AssertDetectIdentifiers(Lambda lambda)
{
var info = DetectIdentifiers(lambda.ExpressionText);
if (info.Identifiers.Count() != lambda.Identifiers.Count())
throw new Exception("Detected identifiers doesn't match actual identifiers");
if (info.Types.Count() != lambda.Types.Count())
throw new Exception("Detected types doesn't match actual types");
if (info.UnknownIdentifiers.Count() != lambda.UsedParameters.Count())
throw new Exception("Detected unknown identifiers doesn't match actual parameters");
}
#endif
#endregion
}
}