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