using Expressive; using Expressive.Expressions; using Expressive.Functions; using InABox.Clients; using System.Diagnostics.CodeAnalysis; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using Expressive.Exceptions; using Expressive.Operators; namespace InABox.Core { public interface IExpressionModel { } public interface IExpressionModel : IExpressionModel { } public abstract class CoreExpressionFunction : FunctionBase { public abstract string Group { get; } public abstract string Description { get; } public abstract string[] Parameters { get; } } public interface IExpressionConstant { string Name { get; set; } object? GetValue(); } public class CoreExpressionFunctor : CoreExpressionFunction { public delegate object? EvaluateDelegate(IExpression[] parameters, IDictionary variables, Context context); public EvaluateDelegate Function { get; set; } private string _name; public override string Name => _name; private string _description; public override string Description => _description; private string _group; public override string Group => _group; private string[] _parameters; public override string[] Parameters => _parameters; private int minParameters; public CoreExpressionFunctor(EvaluateDelegate function, string name, string description, string group, string[] parameters, int? minParameters = null) { Function = function; _name = name; _description = description; _group = group; _parameters = parameters; minParameters = minParameters ?? parameters.Length; } public override object? Evaluate(IExpression[] parameters, Context context) { ValidateParameterCount(parameters, _parameters.Length, minParameters); return Function(parameters, Variables, context); } } internal class FormatFunction : CoreExpressionFunction { #region IFunction Members public override object Evaluate(IExpression[] parameters, Context context) { ValidateParameterCount(parameters, -1, 2); string fmt = parameters.First()?.Evaluate(Variables).ToString() ?? string.Empty; object[] objects = parameters.Skip(1).Select(x => x.Evaluate(Variables)).ToArray(); return String.Format(fmt, objects); } public override string Group => "String"; public override string Name => "Format"; public override string Description => "Formats a list of objects using the specified format string"; public override string[] Parameters => new[] { "string format", "object[] parameters" }; #endregion } internal class Client_LoadDocumentFunction : CoreExpressionFunction { public override object? Evaluate(IExpression[] parameters, Context context) { ValidateParameterCount(parameters, 1, 1); var id = parameters[0].Evaluate(Variables); if (id is null) return null; if (!(id is Guid docID)) return null; return new Client() .Query( new Filter(x => x.ID).IsEqualTo(docID), Columns.None().Add(x => x.Data)) .Rows.FirstOrDefault() ?.Get(x => x.Data); } public override string Group => "Other"; public override string Name => "Client_LoadDocument"; public override string Description => "Retrieves a database document with the specified id"; public override string[] Parameters => new[] { "Guid id" }; } public class CoreExpression { private Expression Expression; public bool IsValid { get { try { return Expression.ReferencedVariables != null; } catch { return false; } } } public string ExpressionString { get; set; } public IReadOnlyCollection ReferencedVariables => Expression.ReferencedVariables; protected virtual Type? ReturnType { get; } public CoreExpression(string expressionString, Type? returnType = null) { Expression = new Expression(expressionString, _context); ExpressionString = expressionString; ReturnType = returnType; if (!IsValid) { var expr = "\"" + expressionString + "\""; var tags = new Regex(@"\[(.*?)\]").Matches(expressionString); foreach (var tag in tags) expr = expr.Replace($"{tag}", $"\"+{tag}+\""); expr = expr.Replace("+\"\"", ""); Expression = new Expression(expr); } } public object? Evaluate(Dictionary? variables) { var result = Expression.Evaluate(variables); if(ReturnType != null) { return CoreUtils.ChangeType(result, ReturnType); } return result; } public object? Evaluate(IVariableProvider provider) { var result = Expression.Evaluate(provider); if(ReturnType != null) { return CoreUtils.ChangeType(result, ReturnType); } return result; } [return: MaybeNull] public T Evaluate(IVariableProvider provider) { var result = Evaluate(provider); if(result is T ret) { return ret; } return default; } public Result TryEvaluate(IVariableProvider provider) { try { var result = Evaluate(provider); if(result is T ret) { return Result.Ok(ret); } return Result.Ok(default); } catch (Exception e) { return Result.Error(e); } } public static List GetModelVariables(Type modelType) { var props = DatabaseSchema.Properties(modelType).Select(x => x.Name).ToList(); props.Sort(); return props; } public static List GetModelVariables() where TModel : IExpressionModel => GetModelVariables(typeof(TModel)); #region Static public static List Functions = new List(); private static Context _context = new Context(ExpressiveOptions.None); public static void RegisterFunction() where T : CoreExpressionFunction, new () { var function = new T(); Functions.Add(function); _context.RegisterFunction(function); } public static void RegisterFunction(string name, string description, string group, string[] parameters, CoreExpressionFunctor.EvaluateDelegate function) { var f = new CoreExpressionFunctor(function, name, description, group, parameters); Functions.Add(f); _context.RegisterFunction(f); } static CoreExpression() { RegisterFunction(); RegisterFunction(); DateFunctions.Register(); ArrayFunctions.Register(); } #endregion } public class CoreExpression : CoreExpression where TModel : IExpressionModel { protected override Type? ReturnType => typeof(TReturn); public CoreExpression(string expressionString): base(expressionString) { } [return: MaybeNull] public new TReturn Evaluate(Dictionary? variables) { var result = base.Evaluate(variables); if(result is TReturn ret) { return ret; } return default; } [return: MaybeNull] public new TReturn Evaluate(IVariableProvider provider) { var result = base.Evaluate(provider); if(result is TReturn ret) { return ret; } return default; } public Result Evaluate(TModel model) { var values = new Dictionary(); foreach(var variable in ReferencedVariables) { values[variable] = CoreUtils.GetPropertyValue(model, variable); } try { var result = base.Evaluate(values); if(result is TReturn ret) { return Result.Ok(ret); } return Result.Ok(default); } catch (Exception e) { return Result.Error(e); } } } public static class CoreExpressionExtensions { public static T Evaluate(this IExpression expression, IDictionary? variables) { var result = expression.Evaluate(variables); return CoreUtils.ChangeType(result); } } }