Newer
Older
using System.Collections.Generic;
using System.Linq.Expressions;
using System;
using System.Linq;
using UnityEngine;
MaZiFAU
committed
using UnityEditor;
using System.Collections.ObjectModel;
using UnityEngine.Assertions;
namespace REST_JSON_API
{
abstract public partial class SOMDoc
{
protected static class SOMDocToLambdaExpression<T>
{
// TODO: Populate Dictionaries
#region ExpressionDictionaries
delegate LambdaExpression CustomFunction(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params);
private static readonly Dictionary<string, CustomFunction> MMTtoLambdaMaker = new()
{
{ MMTConstants.Sin,
MakeSin },
{ MMTConstants.Cos,
MakeCos },
{ MMTConstants.SquareRoot,
MakeCos },
{ MMTConstants.Tuple,
MakeTupel },
MaZiFAU
committed
{ MMTConstants.Product, // TODO:Automate
MakeTupel },
{ MMTConstants.MakeObjectArray,
MakeObjArray },
MaZiFAU
committed
{ MMTConstants.ListApplicant,
MakeInstantList },
{ MMTConstants.ListEnd,
MakeListEnd },
{ MMTConstants.ListLiteral,
InsertFrontListLiteral },
{ MMTConstants.ListType,
Identity0 },
MaZiFAU
committed
ProjLVecTupel },
MaZiFAU
committed
ProjRVecTupel },
{ MMTConstants.Map,
ChainMakes(new[]{
CallAnyFunction(false, "Select", typeof(Enumerable)),
CallAnyFunction(false, "ToArray", typeof(Enumerable))})},
{ MMTConstants.FeedForwardWhile,
CallAnyFunction(false, "FeedForwardWhile", typeof(IEnumerableExtensions)) },
{ MMTConstants.PartialAggregate,
CallAnyFunction(false, "PartialAggregate", typeof(IEnumerableExtensions)) },
{ MMTConstants.ToArray,
CallAnyFunction(false, "ToArray", typeof(Enumerable)) },
{ MMTConstants.PropertyX,
GetPropertyOrField("x") },
{ MMTConstants.PropertyY,
GetPropertyOrField("y") },
{ MMTConstants.PropertyZ,
GetPropertyOrField("z") },
};
private static readonly Dictionary<string, ExpressionType> MMTtoBinaryExpressionType = new()
{
ExpressionType.Add},
{ "AddAssign",
ExpressionType.AddAssign},
{ "AddAssignChecked",
ExpressionType.AddAssignChecked},
{ "AddChecked",
ExpressionType.AddChecked},
{ "And",
ExpressionType.And},
{ "AndAlso",
ExpressionType.AndAlso},
{ "AndAssign",
ExpressionType.AndAssign},
{ "Assign",
ExpressionType.Assign},
{ MMTConstants.Divide,
ExpressionType.Divide},
{ "DivideAssign",
ExpressionType.DivideAssign},
{ "Equal",
ExpressionType.Equal},
{ "ExclusiveOr",
ExpressionType.ExclusiveOr},
{ "ExclusiveOrAssign",
ExpressionType.ExclusiveOrAssign},
{ "GreaterThan",
ExpressionType.GreaterThan},
{ "GreaterThanOrEqual",
ExpressionType.GreaterThanOrEqual},
{ "LeftShift",
ExpressionType.LeftShift},
{ "LeftShiftAssign",
ExpressionType.LeftShiftAssign},
ExpressionType.LessThan},
{ "LessThanOrEqual",
ExpressionType.LessThanOrEqual},
{ "Modulo",
ExpressionType.Modulo},
{ "ModuloAssign",
ExpressionType.ModuloAssign},
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
ExpressionType.Multiply},
{ "MultiplyAssign",
ExpressionType.MultiplyAssign},
{ "MultiplyAssignChecked",
ExpressionType.MultiplyAssignChecked},
{ "MultiplyChecked",
ExpressionType.MultiplyChecked},
{ "NotEqual",
ExpressionType.NotEqual},
{ "Or",
ExpressionType.Or},
{ "OrAssign",
ExpressionType.OrAssign},
{ "OrElse",
ExpressionType.OrElse},
{ "Power",
ExpressionType.Power},
{ "PowerAssign",
ExpressionType.PowerAssign},
{ "RightShift",
ExpressionType.RightShift},
{ "RightShiftAssign",
ExpressionType.RightShiftAssign},
{ MMTConstants.Subtract,
ExpressionType.Subtract},
{ "SubtractAssign",
ExpressionType.SubtractAssign},
{ "SubtractAssignChecked",
ExpressionType.SubtractAssignChecked},
{ "SubtractChecked",
ExpressionType.SubtractChecked},
};
private static readonly Dictionary<string, ExpressionType> MMTtoUnaryExpressionType = new()
{
//{ "Constant", // Not Unary
// ExpressionType.Constant},
{ "Convert",
ExpressionType.Convert},
{ "ConvertChecked",
ExpressionType.ConvertChecked},
{ "Decrement",
ExpressionType.Decrement},
{ "Increment",
ExpressionType.Increment},
{ "Negate",
ExpressionType.Negate},
{ "NegateChecked",
ExpressionType.NegateChecked},
{ "Not",
ExpressionType.Not},
{ "OnesComplement",
ExpressionType.OnesComplement},
{ "PostDecrementAssign",
ExpressionType.PostDecrementAssign},
{ "PostIncrementAssign",
ExpressionType.PostIncrementAssign},
{ "PreDecrementAssign",
ExpressionType.PreDecrementAssign},
{ "PreIncrementAssign",
ExpressionType.PreIncrementAssign},
{ "UnaryPlus",
ExpressionType.UnaryPlus},
};
#endregion ExpressionDictionaries
//TODO: case ((f->x)->y) instead of assumed (f->(x->y))
public static LambdaExpression MakeLambdaExpression(string URI, LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
{
void ThrowArgumentException(ExpressionType expression_cast, int expected)
{
throw new ArgumentException(string.Format(
"\"Wrong number of Arguments. Required: {2}. Supplied: {3}.\\n\\tFor URI:\\\"{0}\\\"\\n\\tmapped to:\\\"{1}\\\"\"",
URI, expression_cast, expected, lambda_applicant.Count()
));
}
ParameterExpression[] lambda_params =
lambda_applicant
.SelectMany(l => l.Parameters)
.ToArray(); //PERF: .ToList().Sort() => .BinarySearch; //Too much overhead?
ParameterExpression[] found_bound_params =
bound_params
.Where(p => lambda_params.Contains(p))
.ToArray();
MaZiFAU
committed
if (FactRecorder.AllFacts.TryGetValue(URI, out Fact fact))
{
MMTFact decl = fact.MakeMMTDeclaration();
SOMDoc df = decl is MMTGeneralFact gf
? gf.defines
: (decl as MMTValueFact).lhs;
return df.GetLambdaExpression();
}
else
if (MMTtoUnaryExpressionType.TryGetValue(URI, out var unnary_type))
{
if (found_bound_params.Count() < 1)
ThrowArgumentException(unnary_type, 1);
Type UnarySecondArgument = found_bound_params.Count() < 2 ? null : found_bound_params[1].Type;
return Expression.Lambda(Expression.MakeUnary(unnary_type, lambda_applicant[0].Body, UnarySecondArgument), found_bound_params);
}
else
if (MMTtoBinaryExpressionType.TryGetValue(URI, out var binary_type))
{
if (lambda_applicant.Count() != 2)
ThrowArgumentException(binary_type, 2);
return Expression.Lambda(Expression.MakeBinary(binary_type, lambda_applicant[0].Body, lambda_applicant[1].Body), found_bound_params);
}
else
if (MMTtoLambdaMaker.TryGetValue(URI, out CustomFunction lamda_maker))
{
return lamda_maker(lambda_applicant, lambda_arguments, found_bound_params);
}
else
if (MMTConstants.OMS_TO_TYPE.TryGetValue(URI, out Type type))
{
return Expression.Lambda(Expression.Default(type), null);
}
throw new NotImplementedException("Could not map URI: \"" + URI + "\"");
}
MaZiFAU
committed
private static CustomFunction ChainMakes(CustomFunction[] makes)
=> (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) =>
{
foreach (var make in makes)
lambda_applicant = new[] { make(lambda_applicant, lambda_arguments, bound_params) };
return lambda_applicant[0];
};
private static LambdaExpression ExpresionFuncToLambda(LambdaExpression func, string name, LambdaExpression[] args_lamda, ParameterExpression[] bound_params, uint nTargs_fallback)
=> Expression.Lambda(Expression.Invoke(func, args_lamda.Select(l => l.Body)), name, bound_params);
private static LambdaExpression ParseFuncUUToExpression<U>(Func<U, U> func)
=> (Expression<Func<U, U>>)((U x) => func(x));
private static LambdaExpression MakeSin(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> ExpresionFuncToLambda(
lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Sin)
: lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Sin)
: throw new NotImplementedException("Sinus for " + lambda_applicant[0].ReturnType),
"Sin", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params, 1
);
private static LambdaExpression MakeCos(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> ExpresionFuncToLambda(
lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Cos)
: lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Cos)
: throw new NotImplementedException("Cosinus for " + lambda_applicant[0].ReturnType),
"Cos", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params, 1
);
MaZiFAU
committed
private static LambdaExpression ProjLVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> (
lambda_applicant[0].ReturnType == typeof(Vector3)
? GetPropertyOrField("x")
: GetPropertyOrField("Item1")
)
(lambda_applicant, lambda_arguments, bound_params);
private static LambdaExpression ProjRVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
MaZiFAU
committed
LambdaExpression[] Items_applicant =
lambda_applicant[0].ReturnType == typeof(Vector3)
? new string[] { "y", "z" }
.Select(prop => GetPropertyOrField(prop)(lambda_applicant, lambda_arguments, bound_params))
.ToArray()
: lambda_applicant[0].ReturnType.GetProperties()
.OrderBy(fi => fi.Name)
.Skip(1) // Item1
.Select(fi =>
Expression.Lambda(
Expression.Property(lambda_applicant[0].Body, fi),
bound_params))
.ToArray();
MaZiFAU
committed
return Items_applicant.Length == 1
? Items_applicant[0]
: MakeTupel(Items_applicant, lambda_arguments, bound_params);
private static LambdaExpression MakeTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
MaZiFAU
committed
if (lambda_applicant.Length == 3
&& lambda_applicant.All(l => l.ReturnType == typeof(float)))
return ExpresionFuncToLambda(
(Expression<Func<float, float, float, Vector3>>)((x, y, z) => new Vector3(x, y, z)),
"UnityEngineVector3", lambda_applicant, bound_params, 3
);
Type[] genericTypes = new Type[lambda_applicant.Length];
for (int i = 0; i < lambda_applicant.Length; i++)
genericTypes[i] = Type.MakeGenericMethodParameter(i);
MethodInfo create = typeof(Tuple)
.GetMethod("Create", genericTypes)
.MakeGenericMethod(lambda_applicant.Select(l => l.ReturnType).ToArray());
return Expression.Lambda(
Expression.Call(create, lambda_applicant.Select(l => l.Body)),
bound_params
);
}
private static LambdaExpression MakeObjArray(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
MaZiFAU
committed
{
if (lambda_applicant.Length == 1
&& lambda_applicant[0].ReturnType.IsArray
&& !typeof(object[]).IsAssignableFrom(lambda_applicant[0].ReturnType))
{
ParameterExpression lambda_param =
Expression.Parameter(lambda_applicant[0].ReturnType.GetElementType());
return CallAnyFunction(false, "ToArray", typeof(Enumerable))(
new[] {
CallAnyFunction(false, "Select", typeof(Enumerable),
new[] { (1u, Expression.Lambda(Expression.Lambda(Expression.Convert(lambda_param, typeof(object)), lambda_param))) }
) (lambda_applicant, lambda_arguments, bound_params)
},
lambda_arguments,
MaZiFAU
committed
}
return Expression.Lambda(
Expression.NewArrayInit(
typeof(object),
lambda_applicant.Select(l => Expression.Convert(l.Body, typeof(object)))
),
bound_params
);
}
private static LambdaExpression MakeInstantList(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> Expression.Lambda(
Expression.ListInit(
Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType)),
MaZiFAU
committed
lambda_applicant.Select(l => l.Body) // Expression.Convert(l.Body, lambda_applicant[0].ReturnType))
),
bound_params
);
private static LambdaExpression InsertFrontListLiteral(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> Expression.Lambda(
Expression.Call(
MaZiFAU
committed
lambda_applicant[0].ReturnType.GetMethod("Insert"),
Expression.Constant(0, typeof(int)),
lambda_applicant[1].Body
),
bound_params
);
private static LambdaExpression MakeListEnd(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> Expression.Lambda(
Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType))
);
private static LambdaExpression Identity0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> lambda_applicant[0];
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
private static LambdaExpression Index0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> Expression.Lambda(
Expression.ArrayIndex(lambda_applicant[0].Body, Expression.Constant(0))
);
private static LambdaExpression Tail(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
{
LambdaExpression enumerated =
CallAnyFunction(false, "Skip", typeof(Enumerable),
new[] { (1u, Expression.Lambda(Expression.Constant(1, typeof(int)))) }
)(lambda_applicant, lambda_arguments, bound_params);
return CallAnyFunction(false, "ToArray", typeof(Enumerable))(new[] { enumerated }, null, bound_params);
}
private static CustomFunction CallAnyFunction(bool self, string method_name, Type type = null, (uint, LambdaExpression)[] lambda_manual = null)
=> (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) =>
{
type ??= lambda_applicant[0].ReturnType;
LambdaExpression[] lambda_args_new = lambda_applicant
.Skip(self ? 1 : 0)
.AppendRangeAt(lambda_manual)
.ToArray();
Expression[] call_args = lambda_args_new
.Select(l => l.Body)
.ToArray();
Type[] lambda_args_type = lambda_args_new
.Select(l => l.ReturnType)
.ToArray();
MethodInfo call_method = // type.method_name
type.GetMethod(method_name, lambda_args_type);
if (call_method == null)
MaZiFAU
committed
{ // call_method is overloaded or generic
(MethodInfo m, bool Success, Dictionary<string, (Type To, int Dirty)> method_types)[] recipe =
type.GetMethods()
.Where(m =>
m.Name.Equals(method_name) &&
m.GetParameters().Length == call_args.Length
).Select(m =>
{
Dictionary<string, (Type To, int Dirty)> method_types = new();
(bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)[] test =
m.GetParameters()
.Zip(lambda_args_new, (par, lamb) =>
IsCompatibleType(par.ParameterType, lamb.ReturnType, lamb.Body, 0))
.ToArray();
if (!test.All(t => t.Success)
|| !test.All(t => method_types.TryAddAllFrom(t.GenericParameters)))
return (m, false, new());
else
return (m, Success: true, method_types);
}).Where(t => t.Success)
.ToArray();
if (recipe.Length == 0)
throw new Exception($"Could not find method \"{method_name}\" in Type \"{type}\" and Type Signature [{lambda_args_type}]");
MaZiFAU
committed
if (recipe.Length > 1) // AmbiguousMatchException
Debug.LogWarning($"Found methods not unique for \"{method_name}\" in Type \"{type}\" and Type Signature [{lambda_args_type}]");
MaZiFAU
committed
call_method = recipe[0].m;
MaZiFAU
committed
call_method = call_method.MakeGenericMethod(
call_method.GetGenericArguments()
.Select(arg => recipe[0].method_types[arg.Name].To)
.ToArray()
);
}
ParameterInfo[] call_para = call_method.GetParameters();
for (int i = 0; i < lambda_args_new.Length; i++)
{
if (!call_para[i].ParameterType
.GetGenericArguments()
.Any(arg => recipe[0].method_types[arg.Name].Dirty)
)
continue;
MaZiFAU
committed
;
}
return Expression.Lambda(
self
? Expression.Call(lambda_applicant[0].Body, call_method, call_args)
: Expression.Call(call_method, call_args),
bound_params
);
MaZiFAU
committed
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
static
(bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)
IsCompatibleType(Type generic, Type from_me, Expression source_from_me, int dirty)
{
Dictionary<string, (Type To, int Dirty)> retDic = new();
(bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials) retVal =
(false, retDic, source_from_me);
if (generic.HasElementType
&& from_me.HasElementType)
{
generic = generic.GetElementType();
from_me = from_me.GetElementType();
}
if (generic.IsAssignableFrom(from_me))
return (true, new(), source_from_me);
if (generic.IsGenericType)
{
(bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)[] recursion;
if (generic.IsInterface
&& !from_me.IsInterface)
{
if (generic.Equals(typeof(IEnumerable<>))
&& from_me.IsArray) // GetInterfaces() drops here the generic type?
return IsCompatibleType(
generic.GetGenericArguments()[0],
from_me.GetElementType(),
source_from_me, //will not be Func[], atmost(?) List<Func>
dirty
);
recursion = generic.GetInterfaces().Select(gi =>
from_me.GetInterfaces()
.Select(fi => IsCompatibleType(gi, fi, source_from_me, dirty))
.FirstOrDefault(t => t.Success)
).ToArray();
}
else
{
if (FuncExtensions.IsFuncType(generic, out int gene_sig)
&& FuncExtensions.IsFuncType(from_me, out int from_sig)
&& from_me == source_from_me.Type) // from_me <within_or_equal> source_from_me.Type
{ // lets pretend C# uses lambda logic like MMT
if (gene_sig > from_sig)
return (false, new(), source_from_me);
bool IsDirty = gene_sig != from_sig;
if (IsDirty)
dirty++;
bool IsParame = source_from_me is ParameterExpression;
bool IsLambda = source_from_me is LambdaExpression;
if (!IsParame && !IsLambda)
{
Debug.LogWarning($"Unexpected Expression for Parameter \"{nameof(source_from_me)}\"");
if (IsDirty)
return (false, new(), source_from_me);
else
goto GeneralGenericCase;
}
Expression[] expr_decomp = null;
if (IsParame)
{
Type[] from_args = from_me.GetGenericArguments();
Type partial_type = generic
.GetGenericTypeDefinition()
.MakeGenericType(
from_args[..(gene_sig - 1)]
.Append(FuncExtensions.CreateFuncType(from_args[(gene_sig - 1)..]))
.ToArray()
);
//[(a)=>c]
//[(a,b)=>c]
//
//[(a)=>[(a,b)=>c](b)]
expr_decomp = // schould it be (from_me)=>generic
partial_type.GetGenericArguments()
.Select(typ => Expression.Parameter(typ))
.ToArray();
}
else
if (IsLambda)
{
LambdaExpression source_lambda = source_from_me as LambdaExpression;
ReadOnlyCollection<ParameterExpression> from_para = source_lambda.Parameters;
expr_decomp =
from_para.Take<Expression>(gene_sig - 1)
.Append(!IsDirty
? source_lambda.Body
: Expression.Lambda(source_lambda.Body, from_para.Skip(gene_sig - 1)))
.ToArray();
}
recursion =
generic.GetGenericArguments()
.Zip(expr_decomp, (par, expr) => IsCompatibleType(par, expr.Type, expr, dirty))
.ToArray();
if (!recursion.All(t => t.Success)
|| !recursion.All(t => retDic.TryAddAllFrom(t.GenericParameters)))
return (false, new(), source_from_me);
Expression source_partial =
IsParame ?
Expression.Parameter(
FuncExtensions.CreateFuncType(
recursion.Select(t => t.partials.Type).ToArray())) :
IsLambda ?
Expression.Lambda(
recursion[^1].partials,
recursion[..^1].Select(t => t.partials as ParameterExpression)
) :
null; // For compiler
return (true, retDic, source_partial);
}
GeneralGenericCase:
if (!from_me.IsGenericType
|| !generic.GetGenericTypeDefinition().Equals(from_me.GetGenericTypeDefinition()))
return (false, new(), source_from_me);
recursion =
generic.GetGenericArguments()
.Zip(from_me.GetGenericArguments(), (par, typ) => IsCompatibleType(par, typ, source_from_me, dirty))
.ToArray();
}
if (!recursion.All(t => t.Success)
|| !recursion.All(t => retDic.TryAddAllFrom(t.GenericParameters)))
return (false, new(), source_from_me);
else
return (true, retDic, source_from_me);
}
if (generic.IsGenericParameter)
{ // TODO? .GetGenericParameterConstraints()
return (true, new() { { generic.Name, (from_me, dirty) } }, source_from_me);
}
return (false, new(), source_from_me);
}
};
private static CustomFunction GetPropertyOrField(string property_name)
=> (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
=> Expression.Lambda(
Expression.PropertyOrField(lambda_applicant[0].Body, property_name),
bound_params
);