224 lines
10 KiB
C#
224 lines
10 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Threading.Tasks;
|
|
using MathNet.Numerics.LinearAlgebra.Complex;
|
|
using Scriban;
|
|
using Scriban.Helpers;
|
|
using Scriban.Parsing;
|
|
using Scriban.Runtime;
|
|
using Scriban.Syntax;
|
|
|
|
namespace Kalk.Core
|
|
{
|
|
|
|
|
|
public abstract class KalkExpression : KalkObject, IScriptCustomBinaryOperation, IScriptCustomUnaryOperation, IFormattable, IScriptTransformable
|
|
{
|
|
protected KalkExpression()
|
|
{
|
|
OriginalExpression = this;
|
|
}
|
|
|
|
public abstract object GetValue();
|
|
|
|
public abstract KalkUnit GetUnit();
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return ToString(null, CultureInfo.CurrentCulture);
|
|
}
|
|
|
|
public abstract string ToString(string format, IFormatProvider formatProvider);
|
|
|
|
public static bool Equals(TemplateContext context, KalkExpression left, KalkExpression right)
|
|
{
|
|
if (ReferenceEquals(null, right)) return false;
|
|
if (ReferenceEquals(left, right)) return true;
|
|
return right.GetType() == left.GetType() && left.EqualsImpl(context, right);
|
|
}
|
|
|
|
public KalkExpression OriginalExpression { get; set; }
|
|
|
|
public sealed override IScriptObject Clone(bool deep)
|
|
{
|
|
throw new NotSupportedException("This expression cannot be cloned.");
|
|
}
|
|
|
|
protected abstract bool EqualsImpl(TemplateContext context, KalkExpression right);
|
|
|
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
|
{
|
|
var leftExpr = leftValue as KalkExpression;
|
|
if (leftExpr is null && !KalkValue.IsNumber(leftValue))
|
|
{
|
|
throw new ScriptRuntimeException(leftSpan, "Expecting a number, vector or matrix");
|
|
}
|
|
var rightExpr = rightValue as KalkExpression;
|
|
if (rightExpr is null && !KalkValue.IsNumber(rightValue))
|
|
{
|
|
throw new ScriptRuntimeException(rightSpan, "Expecting a number, vector or matrix");
|
|
}
|
|
|
|
result = null;
|
|
switch (op)
|
|
{
|
|
case ScriptBinaryOperator.CompareEqual:
|
|
case ScriptBinaryOperator.CompareNotEqual:
|
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
|
case ScriptBinaryOperator.CompareLess:
|
|
case ScriptBinaryOperator.CompareGreater:
|
|
if (leftExpr != null && rightExpr != null)
|
|
{
|
|
var leftSimplifier = new KalkExpressionSimplifier(context);
|
|
var (leftValueMultiplier, newLeftExpr) = leftSimplifier.Canonical(leftExpr);
|
|
|
|
var rightSimplifier = new KalkExpressionSimplifier(context);
|
|
var (rightValueMultiplier, newRightExpr) = rightSimplifier.Canonical(rightExpr);
|
|
|
|
var exprEquals = Equals(context, newLeftExpr, newRightExpr);
|
|
if (exprEquals)
|
|
{
|
|
var almostEqual = KalkValue.AlmostEqual(leftValueMultiplier, rightValueMultiplier);
|
|
switch (op)
|
|
{
|
|
case ScriptBinaryOperator.CompareEqual:
|
|
result = almostEqual;
|
|
break;
|
|
case ScriptBinaryOperator.CompareNotEqual:
|
|
result = !almostEqual;
|
|
break;
|
|
case ScriptBinaryOperator.CompareLessOrEqual:
|
|
result = almostEqual || ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v1 && v1;
|
|
break;
|
|
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
|
result = almostEqual || ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v2 && v2;
|
|
break;
|
|
case ScriptBinaryOperator.CompareLess:
|
|
result = ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v3 && v3;
|
|
break;
|
|
case ScriptBinaryOperator.CompareGreater:
|
|
result = ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier) is bool v4 && v4;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = op == ScriptBinaryOperator.CompareNotEqual;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (op == ScriptBinaryOperator.CompareEqual || op == ScriptBinaryOperator.CompareNotEqual)
|
|
{
|
|
result = op == ScriptBinaryOperator.CompareNotEqual;
|
|
}
|
|
else
|
|
{
|
|
throw NotMatching(leftSpan, leftExpr, rightSpan, rightExpr);
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case ScriptBinaryOperator.Multiply:
|
|
case ScriptBinaryOperator.Divide:
|
|
case ScriptBinaryOperator.Power:
|
|
{
|
|
if (op != ScriptBinaryOperator.Power || rightExpr == null)
|
|
{
|
|
var simplifier = new KalkExpressionSimplifier(context);
|
|
var (valueMul, valueExpr) = simplifier.Canonical(new KalkBinaryExpression(leftValue, op, rightValue));
|
|
var valueBinExpr = valueExpr as KalkBinaryExpression;
|
|
|
|
result = KalkValue.AlmostEqual(valueMul, 1.0) && valueBinExpr == null ? valueExpr ?? (object)1.0 : valueExpr == null ? valueMul : (object)new KalkBinaryExpression(valueMul, ScriptBinaryOperator.Multiply, valueExpr)
|
|
{
|
|
OriginalExpression = new KalkBinaryExpression(leftValue, op, rightValue)
|
|
};
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
throw new ScriptRuntimeException(rightSpan, "Cannot use a unit as an exponent.");
|
|
}
|
|
}
|
|
|
|
case ScriptBinaryOperator.Add:
|
|
case ScriptBinaryOperator.Subtract:
|
|
|
|
if (leftExpr != null && rightExpr != null)
|
|
{
|
|
var leftSimplifier = new KalkExpressionSimplifier(context);
|
|
var (leftValueMultiplier, newLeftExpr) = leftSimplifier.Canonical(leftExpr);
|
|
|
|
var rightSimplifier = new KalkExpressionSimplifier(context);
|
|
var (rightValueMultiplier, newRightExpr) = rightSimplifier.Canonical(rightExpr);
|
|
|
|
if (!Equals(context, newLeftExpr, newRightExpr))
|
|
{
|
|
throw new ScriptRuntimeException(span, $"Cannot {(op == ScriptBinaryOperator.Add ? "add" : "subtract")} the expression. Units are not matching. The left expression with unit `{newLeftExpr}` is not matching the right expression with unit `{newRightExpr}`.");
|
|
}
|
|
|
|
result = new KalkBinaryExpression(ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValueMultiplier, rightSpan, rightValueMultiplier), ScriptBinaryOperator.Multiply, newLeftExpr)
|
|
{
|
|
OriginalExpression = new KalkBinaryExpression(leftValue, op, rightValue)
|
|
};
|
|
}
|
|
else
|
|
{
|
|
throw NotMatching(leftSpan, leftExpr, rightSpan, rightExpr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
private static ScriptRuntimeException NotMatching(SourceSpan leftSpan, KalkExpression leftExpr, SourceSpan rightSpan, KalkExpression rightExpr)
|
|
{
|
|
return new ScriptRuntimeException(leftExpr == null ? leftSpan : rightSpan, $"Missing unit for the {(leftExpr == null ? "left" : "right")} expression. It must match the unit of the other expression `{leftExpr ?? rightExpr}`.");
|
|
}
|
|
|
|
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
|
{
|
|
result = null;
|
|
return false;
|
|
}
|
|
|
|
public KalkExpression ConvertTo(TemplateContext context, KalkExpression dst)
|
|
{
|
|
var src = this;
|
|
|
|
var destExpr = dst.OriginalExpression ?? dst;
|
|
|
|
var span = context.CurrentSpan;
|
|
if (!this.TryEvaluate(context, span, ScriptBinaryOperator.Divide, span, this, span, dst, out var result) || result is KalkExpression)
|
|
{
|
|
throw new ScriptRuntimeException(context.CurrentSpan, $"Cannot convert the expression {src} to the unit `{destExpr}`. Units are not matching.");
|
|
}
|
|
|
|
var value = context.ToObject<double>(span, result);
|
|
return new KalkBinaryExpression(value, ScriptBinaryOperator.Multiply, dst.OriginalExpression);
|
|
}
|
|
|
|
public Type ElementType => typeof(double);
|
|
|
|
public bool CanTransform(Type transformType)
|
|
{
|
|
return transformType == typeof(double) || transformType == typeof(float);
|
|
}
|
|
|
|
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
|
{
|
|
return visit(GetValue());
|
|
}
|
|
|
|
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
|
{
|
|
return apply(GetValue());
|
|
}
|
|
|
|
}
|
|
} |