Sicherung
This commit is contained in:
7
Kalk/Kalk.Core/Model/IKalkDisplayable.cs
Normal file
7
Kalk/Kalk.Core/Model/IKalkDisplayable.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public interface IKalkDisplayable
|
||||
{
|
||||
void Display(KalkEngine engine, KalkDisplayMode mode);
|
||||
}
|
||||
}
|
||||
43
Kalk/Kalk.Core/Model/KalkAlias.cs
Normal file
43
Kalk/Kalk.Core/Model/KalkAlias.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Scriban.Runtime;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public sealed class KalkAlias : ScriptObject
|
||||
{
|
||||
public KalkAlias(string name, IEnumerable<string> aliases, bool isUser)
|
||||
{
|
||||
if (aliases == null) throw new ArgumentNullException(nameof(aliases));
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Aliases = new ScriptArray(aliases) {IsReadOnly = true};
|
||||
SetValue("name", Name, true);
|
||||
SetValue("aliases", Aliases, true);
|
||||
IsReadOnly = true;
|
||||
IsUser = isUser;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public ScriptArray Aliases { get; }
|
||||
|
||||
public bool IsUser { get; }
|
||||
|
||||
public override string ToString(string format, IFormatProvider formatProvider)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append($"alias({Name}, ");
|
||||
for (var i = 0; i < Aliases.Count; i++)
|
||||
{
|
||||
var alias = Aliases[i];
|
||||
if (i > 0) builder.Append(", ");
|
||||
builder.Append(alias);
|
||||
}
|
||||
builder.Append(")");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Kalk/Kalk.Core/Model/KalkBinaryExpression.cs
Normal file
104
Kalk/Kalk.Core/Model/KalkBinaryExpression.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public class KalkBinaryExpression : KalkExpressionWithMembers
|
||||
{
|
||||
private static readonly (string, Func<KalkExpressionWithMembers, object> getter)[] MemberDefs = {
|
||||
("kind", unit => unit.TypeName),
|
||||
("value", unit => ((KalkBinaryExpression)unit).Value),
|
||||
("operator", unit => ((KalkBinaryExpression)unit).OperatorText),
|
||||
("unit", unit => ((KalkBinaryExpression)unit).Unit),
|
||||
};
|
||||
|
||||
public KalkBinaryExpression()
|
||||
{
|
||||
}
|
||||
|
||||
public KalkBinaryExpression(object value, ScriptBinaryOperator @operator, object unit)
|
||||
{
|
||||
Value = value;
|
||||
Operator = @operator;
|
||||
Unit = unit;
|
||||
}
|
||||
|
||||
public override string TypeName => "binary expression";
|
||||
|
||||
public object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the operator. Supported: /, *, ^
|
||||
/// </summary>
|
||||
public ScriptBinaryOperator Operator { get; set; }
|
||||
|
||||
public string OperatorText => Operator.ToText();
|
||||
|
||||
public object Unit { get; set; }
|
||||
|
||||
public override object GetValue() => Value;
|
||||
|
||||
public override KalkUnit GetUnit() => Unit as KalkUnit;
|
||||
|
||||
public override string ToString(string format, IFormatProvider formatProvider)
|
||||
{
|
||||
switch (Operator)
|
||||
{
|
||||
case ScriptBinaryOperator.Power:
|
||||
return (Value is ScriptBinaryExpression) ? $"({string.Format(formatProvider, "{0}",Value)}){OperatorText}{string.Format(formatProvider, "{0}", Unit)}" : $"{string.Format(formatProvider, "{0}", Value)}{OperatorText}{string.Format(formatProvider, "{0}", Unit)}";
|
||||
default:
|
||||
return $"{string.Format(formatProvider, "{0}", Value)} {OperatorText} {string.Format(formatProvider, "{0}", Unit)}";
|
||||
}
|
||||
}
|
||||
|
||||
protected override (string, Func<KalkExpressionWithMembers, object> getter)[] Members => MemberDefs;
|
||||
|
||||
protected override bool EqualsImpl(TemplateContext context, KalkExpression other)
|
||||
{
|
||||
var otherBin = (KalkBinaryExpression) other;
|
||||
if (Operator != otherBin.Operator) return false;
|
||||
|
||||
if (Value is KalkExpression && otherBin.Value.GetType() != Value.GetType()) return false;
|
||||
if (Unit is KalkExpression && otherBin.Unit.GetType() != Unit.GetType()) return false;
|
||||
|
||||
if (Value is KalkExpression leftExpr)
|
||||
{
|
||||
if (!Equals(context, leftExpr, (KalkExpression) otherBin.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var leftValue = context.ToObject<double>(context.CurrentSpan, Value);
|
||||
var otherLeftValue = context.ToObject<double>(context.CurrentSpan, otherBin.Value);
|
||||
if (!KalkValue.AlmostEqual(leftValue, otherLeftValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Unit is KalkExpression rightExpr)
|
||||
{
|
||||
if (!Equals(context, rightExpr, (KalkExpression)otherBin.Unit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var rightValue = context.ToObject<double>(context.CurrentSpan, Unit);
|
||||
var otherRightValue = context.ToObject<double>(context.CurrentSpan, otherBin.Unit);
|
||||
if (!KalkValue.AlmostEqual(rightValue, otherRightValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
Kalk/Kalk.Core/Model/KalkComplex.cs
Normal file
214
Kalk/Kalk.Core/Model/KalkComplex.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public struct KalkComplex : IScriptCustomType, IKalkSpannable
|
||||
{
|
||||
private Complex _value;
|
||||
private const double MaxRoundToZero = 1e-14;
|
||||
|
||||
public KalkComplex(double real, double im)
|
||||
{
|
||||
_value = new Complex(real, im);
|
||||
}
|
||||
|
||||
public KalkComplex(Complex value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public string TypeName => "complex";
|
||||
|
||||
public double Re => _value.Real;
|
||||
|
||||
public double Im => _value.Imaginary;
|
||||
|
||||
public double Phase => _value.Phase;
|
||||
|
||||
public double Magnitude => _value.Magnitude;
|
||||
|
||||
public bool HasIm => _value.Imaginary > MaxRoundToZero;
|
||||
|
||||
internal Complex Value => _value;
|
||||
|
||||
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||
{
|
||||
result = null;
|
||||
if (leftValue is KalkComplex leftComplex && rightValue is KalkComplex rightComplex)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.Add:
|
||||
result = (KalkComplex) (leftComplex._value + rightComplex._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Subtract:
|
||||
result = (KalkComplex)(leftComplex._value - rightComplex._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Divide:
|
||||
result = (KalkComplex)(leftComplex._value / rightComplex._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.DivideRound:
|
||||
result = (KalkComplex)(leftComplex._value / rightComplex._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Multiply:
|
||||
result = (KalkComplex)(leftComplex._value * rightComplex._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Power:
|
||||
result = (KalkComplex)(Complex.Pow(leftComplex._value, rightComplex._value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (rightValue is KalkComplex rightComplex2)
|
||||
{
|
||||
var leftAsDouble = (double)context.ToObject(span, leftValue, typeof(double));
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.Add:
|
||||
result = (KalkComplex)(leftAsDouble + rightComplex2._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Subtract:
|
||||
result = (KalkComplex)(leftAsDouble - rightComplex2._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Divide:
|
||||
result = (KalkComplex)(leftAsDouble / rightComplex2._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.DivideRound:
|
||||
result = (KalkComplex)(leftAsDouble / rightComplex2._value);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Multiply:
|
||||
result = (KalkComplex)(leftAsDouble * rightComplex2._value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (leftValue is KalkComplex leftComplex2)
|
||||
{
|
||||
var rightAsDouble = (double)context.ToObject(span, rightValue, typeof(double));
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.Add:
|
||||
result = (KalkComplex)(leftComplex2._value + rightAsDouble);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Subtract:
|
||||
result = (KalkComplex)(leftComplex2._value - rightAsDouble);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Divide:
|
||||
result = (KalkComplex)(leftComplex2._value / rightAsDouble);
|
||||
return true;
|
||||
case ScriptBinaryOperator.DivideRound:
|
||||
result = (KalkComplex)(leftComplex2._value / rightAsDouble);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Multiply:
|
||||
result = (KalkComplex)(leftComplex2._value * rightAsDouble);
|
||||
return true;
|
||||
case ScriptBinaryOperator.Power:
|
||||
result = (KalkComplex)(Complex.Pow(leftComplex2._value, rightAsDouble));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
||||
{
|
||||
result = null;
|
||||
switch (op)
|
||||
{
|
||||
case ScriptUnaryOperator.Negate:
|
||||
result = (KalkComplex) (-((KalkComplex) rightValue)._value);
|
||||
return true;
|
||||
case ScriptUnaryOperator.Plus:
|
||||
result = rightValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static implicit operator KalkComplex(Complex complex)
|
||||
{
|
||||
return new KalkComplex(complex);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
// We round the number to make the display nicer
|
||||
var re = Math.Round(Re, 10, MidpointRounding.AwayFromZero);
|
||||
var im = Math.Round(Im, 10, MidpointRounding.AwayFromZero);
|
||||
|
||||
if (Math.Abs(re) > 1e-14) builder.AppendFormat(CultureInfo.CurrentCulture, "{0}", re);
|
||||
|
||||
if (HasIm)
|
||||
{
|
||||
if (builder.Length > 0) builder.Append(" + ");
|
||||
|
||||
if (Math.Abs(Math.Abs(im) - 1.0) < MaxRoundToZero)
|
||||
{
|
||||
builder.Append("i");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendFormat(CultureInfo.CurrentCulture, "{0}i", im);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.Length == 0) builder.Append("0");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
return MemoryMarshal.CreateSpan(ref Unsafe.As<KalkComplex, byte>(ref Unsafe.AsRef(this)), Unsafe.SizeOf<KalkComplex>());
|
||||
}
|
||||
|
||||
public bool TryConvertTo(TemplateContext context, SourceSpan span, Type type, out object value)
|
||||
{
|
||||
if (type == typeof(object))
|
||||
{
|
||||
value = this;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(int))
|
||||
{
|
||||
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to an integer as it has an imaginary part.");
|
||||
value = (int) Re;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(float))
|
||||
{
|
||||
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a float as it has an imaginary part.");
|
||||
value = (float)Re;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(double))
|
||||
{
|
||||
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a double as it has an imaginary part.");
|
||||
value = (double)Re;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(decimal))
|
||||
{
|
||||
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a decimal as it has an imaginary part.");
|
||||
value = (decimal)Re;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(BigInteger))
|
||||
{
|
||||
if (HasIm) throw new ScriptRuntimeException(span, $"Cannot convert {this} to a big integer as it has an imaginary part.");
|
||||
value = (BigInteger)Re;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ScriptRuntimeException(span, $"Cannot convert {this} to an {type} as it has an imaginary part.");
|
||||
}
|
||||
}
|
||||
}
|
||||
217
Kalk/Kalk.Core/Model/KalkCompositeValue.cs
Normal file
217
Kalk/Kalk.Core/Model/KalkCompositeValue.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System;
|
||||
using Scriban;
|
||||
using Scriban.Helpers;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public class KalkCompositeValue : IScriptConvertibleFrom, IScriptTransformable
|
||||
{
|
||||
public IScriptTransformable Transformable;
|
||||
|
||||
public object Value;
|
||||
|
||||
public KalkCompositeValue()
|
||||
{
|
||||
}
|
||||
|
||||
public KalkCompositeValue(object value)
|
||||
{
|
||||
Transformable = value as IScriptTransformable;
|
||||
Value = Transformable == null ? value : null;
|
||||
}
|
||||
|
||||
public KalkCompositeValue(IScriptTransformable transformable)
|
||||
{
|
||||
Transformable = transformable;
|
||||
}
|
||||
|
||||
public object TransformArg<TFrom, TTo>(TemplateContext context, Func<TFrom, TTo> apply)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Transform(context, apply);
|
||||
}
|
||||
catch (ScriptRuntimeException ex)
|
||||
{
|
||||
throw new ScriptArgumentException(0, ex.OriginalMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ScriptArgumentException(0, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public object Transform<TFrom, TTo>(TemplateContext context, Func<TFrom, TTo> apply)
|
||||
{
|
||||
if (Transformable != null)
|
||||
{
|
||||
return Transformable.Transform(context, context.CurrentSpan, value =>
|
||||
{
|
||||
var nestedValue = (KalkCompositeValue)context.ToObject(context.CurrentSpan, value, this.GetType());
|
||||
// Recursively apply the transformation with the same value
|
||||
return nestedValue.Transform(context, apply);
|
||||
}, typeof(TTo));
|
||||
}
|
||||
|
||||
return apply(context.ToObject<TFrom>(context.CurrentSpan, Value));
|
||||
}
|
||||
|
||||
|
||||
public virtual Type ElementType => typeof(object);
|
||||
|
||||
public virtual bool CanTransform(Type transformType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
||||
{
|
||||
if (Transformable != null)
|
||||
{
|
||||
return Transformable.Visit(context, span, visit);
|
||||
}
|
||||
return visit(Value);
|
||||
}
|
||||
|
||||
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
||||
{
|
||||
if (Transformable != null) return Transformable.Transform(context, span, apply, destType);
|
||||
return apply(Value);
|
||||
}
|
||||
|
||||
public bool TryConvertFrom(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
if (value is IScriptTransformable valuable)
|
||||
{
|
||||
if (!CanTransform(valuable))
|
||||
{
|
||||
throw new ScriptRuntimeException(span, $"Cannot transform the value `{value}` between the element type `{valuable.ElementType.ScriptPrettyName()}` and the target function `{ElementType.ScriptPrettyName()}`.");
|
||||
}
|
||||
|
||||
Transformable = valuable;
|
||||
}
|
||||
else Value = Accept(context, span, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool CanTransform(IScriptTransformable valuable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual object Accept(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class KalkDoubleValue : KalkCompositeValue
|
||||
{
|
||||
public KalkDoubleValue()
|
||||
{
|
||||
}
|
||||
|
||||
public KalkDoubleValue(IScriptTransformable transformable) : base(transformable)
|
||||
{
|
||||
}
|
||||
|
||||
public KalkDoubleValue(object value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override Type ElementType => typeof(double);
|
||||
|
||||
public override bool CanTransform(Type transformType)
|
||||
{
|
||||
return transformType == typeof(double) || transformType == typeof(float) || transformType == typeof(KalkHalf);
|
||||
}
|
||||
|
||||
protected override bool CanTransform(IScriptTransformable valuable)
|
||||
{
|
||||
return valuable.CanTransform(typeof(double));
|
||||
}
|
||||
|
||||
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
return context.ToObject<double>(span, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class KalkTrigDoubleValue : KalkDoubleValue
|
||||
{
|
||||
public KalkTrigDoubleValue()
|
||||
{
|
||||
}
|
||||
|
||||
public KalkTrigDoubleValue(IScriptTransformable transformable) : base(transformable)
|
||||
{
|
||||
}
|
||||
|
||||
public KalkTrigDoubleValue(object value) : base(value)
|
||||
{
|
||||
}
|
||||
|
||||
public override Type ElementType => typeof(double);
|
||||
|
||||
public override bool CanTransform(Type transformType)
|
||||
{
|
||||
return transformType == typeof(double) || transformType == typeof(float) || transformType == typeof(KalkHalf);
|
||||
}
|
||||
|
||||
protected override bool CanTransform(IScriptTransformable valuable)
|
||||
{
|
||||
return valuable.CanTransform(typeof(double));
|
||||
}
|
||||
|
||||
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
return context.ToObject<double>(span, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class KalkLongValue : KalkCompositeValue
|
||||
{
|
||||
public override Type ElementType => typeof(long);
|
||||
|
||||
public override bool CanTransform(Type transformType)
|
||||
{
|
||||
return transformType == typeof(long) || transformType == typeof(int);
|
||||
}
|
||||
|
||||
protected override bool CanTransform(IScriptTransformable valuable)
|
||||
{
|
||||
return valuable.CanTransform(typeof(long));
|
||||
}
|
||||
|
||||
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
return context.ToObject<long>(span, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class KalkIntValue : KalkCompositeValue
|
||||
{
|
||||
public override Type ElementType => typeof(int);
|
||||
|
||||
public override bool CanTransform(Type transformType)
|
||||
{
|
||||
return transformType == typeof(int);
|
||||
}
|
||||
|
||||
protected override bool CanTransform(IScriptTransformable valuable)
|
||||
{
|
||||
return valuable.CanTransform(typeof(int));
|
||||
}
|
||||
|
||||
protected override object Accept(TemplateContext context, SourceSpan span, object value)
|
||||
{
|
||||
return context.ToObject<int>(span, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
233
Kalk/Kalk.Core/Model/KalkConsoleKey.cs
Normal file
233
Kalk/Kalk.Core/Model/KalkConsoleKey.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Scriban.Runtime;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public readonly struct KalkConsoleKey : IEquatable<KalkConsoleKey>
|
||||
{
|
||||
// Maps of name to ConsoleKey
|
||||
private static readonly Dictionary<string, ConsoleKey> MapNameToConsoleKey = new Dictionary<string, ConsoleKey>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Maps of ConsoleKey to name
|
||||
private static readonly Dictionary<ConsoleKey, string> MapConsoleKeyToName = new Dictionary<ConsoleKey, string>();
|
||||
|
||||
static KalkConsoleKey()
|
||||
{
|
||||
var names = Enum.GetNames<ConsoleKey>();
|
||||
var values = Enum.GetValues<ConsoleKey>();
|
||||
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = StandardMemberRenamer.Rename(names[i]);
|
||||
var key = values[i];
|
||||
if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9)
|
||||
{
|
||||
name = ('0' + (key - ConsoleKey.D0)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
MapKey(name, key);
|
||||
}
|
||||
|
||||
MapKey("left", ConsoleKey.LeftArrow);
|
||||
MapKey("right", ConsoleKey.RightArrow);
|
||||
MapKey("up", ConsoleKey.UpArrow);
|
||||
MapKey("down", ConsoleKey.DownArrow);
|
||||
}
|
||||
|
||||
private static void MapKey(string name, ConsoleKey key)
|
||||
{
|
||||
MapNameToConsoleKey.Add(name, key);
|
||||
MapConsoleKeyToName[key] = name;
|
||||
}
|
||||
|
||||
public KalkConsoleKey(ConsoleModifiers modifiers, ConsoleKey key, char keyChar)
|
||||
{
|
||||
Modifiers = modifiers;
|
||||
Key = key;
|
||||
// Normalize keychar, whenever there is a modifier, we don't store the character
|
||||
if (modifiers == ConsoleModifiers.Shift && key >= ConsoleKey.A && key <= ConsoleKey.Z)
|
||||
{
|
||||
Modifiers = 0;
|
||||
KeyChar = keyChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to force char to 0 when backspace, as it is not consistent between Windows and macOS/Linux
|
||||
KeyChar = Modifiers != 0 || key == ConsoleKey.Backspace ? (char)0 : keyChar;
|
||||
}
|
||||
}
|
||||
|
||||
public ConsoleModifiers Modifiers { get; }
|
||||
|
||||
public ConsoleKey Key { get; }
|
||||
|
||||
public char KeyChar { get; }
|
||||
|
||||
public bool Equals(KalkConsoleKey other)
|
||||
{
|
||||
return Key == other.Key &&
|
||||
KeyChar == other.KeyChar &&
|
||||
Modifiers == other.Modifiers;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is KalkConsoleKey other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = (int)Modifiers;
|
||||
hashCode = (hashCode * 397) ^ (int) Key;
|
||||
hashCode = (hashCode * 397) ^ (int) KeyChar;
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public static bool operator ==(KalkConsoleKey left, KalkConsoleKey right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(KalkConsoleKey left, KalkConsoleKey right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
|
||||
public static KalkConsoleKey Parse(string keyText)
|
||||
{
|
||||
if (keyText == null) throw new ArgumentNullException(nameof(keyText));
|
||||
|
||||
ConsoleModifiers modifiers = 0;
|
||||
ConsoleKey key = 0;
|
||||
char keyChar = (char)0;
|
||||
int index = 0;
|
||||
bool expectingPlus = false;
|
||||
while (index < keyText.Length)
|
||||
{
|
||||
if (expectingPlus)
|
||||
{
|
||||
if (keyText[index] != '+')
|
||||
{
|
||||
throw new ArgumentException($"Unexpected character `{keyText[index]}`. Expecting a + between keys.", nameof(keyText));
|
||||
}
|
||||
index++;
|
||||
expectingPlus = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.Compare(keyText, index, "CTRL", 0, "CTRL".Length, true) == 0)
|
||||
{
|
||||
if ((modifiers & ConsoleModifiers.Control) != 0) throw new ArgumentException("Cannot have multiple CTRL keys in a shortcut.", nameof(keyText));
|
||||
modifiers |= ConsoleModifiers.Control;
|
||||
index += "CTRL".Length;
|
||||
expectingPlus = true;
|
||||
}
|
||||
else if (string.Compare(keyText, index, "ALT", 0, "ALT".Length, true) == 0)
|
||||
{
|
||||
if ((modifiers & ConsoleModifiers.Alt) != 0) throw new ArgumentException("Cannot have multiple ALT keys in a shortcut.", nameof(keyText));
|
||||
modifiers |= ConsoleModifiers.Alt;
|
||||
index += "ALT".Length;
|
||||
expectingPlus = true;
|
||||
}
|
||||
else if (string.Compare(keyText, index, "SHIFT", 0, "SHIFT".Length, true) == 0)
|
||||
{
|
||||
// Skip the shift but don't expect it's modifiers
|
||||
index += "SHIFT".Length;
|
||||
expectingPlus = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var remainingText = keyText.Substring(index);
|
||||
expectingPlus = false;
|
||||
if (!MapNameToConsoleKey.TryGetValue(remainingText, out key))
|
||||
{
|
||||
throw new ArgumentException($"Invalid key found `{remainingText}`. Expecting: {string.Join(", ", Enum.GetNames(typeof(ConsoleKey)))}.", nameof(keyText));
|
||||
}
|
||||
|
||||
if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9)
|
||||
{
|
||||
keyChar = (char) ('0' + (int) key - (int) ConsoleKey.D0);
|
||||
}
|
||||
else if (key >= ConsoleKey.A && key <= ConsoleKey.Z)
|
||||
{
|
||||
keyChar = remainingText[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
keyChar = key switch
|
||||
{
|
||||
ConsoleKey.Add => '+',
|
||||
ConsoleKey.Attention => '!',
|
||||
ConsoleKey.Divide => '/',
|
||||
ConsoleKey.Multiply => '*',
|
||||
ConsoleKey.Subtract => '-',
|
||||
ConsoleKey.OemPlus => '=',
|
||||
ConsoleKey.OemMinus => '-',
|
||||
ConsoleKey.OemPeriod => ';',
|
||||
ConsoleKey.OemComma => ',',
|
||||
ConsoleKey.Tab => '\t',
|
||||
ConsoleKey.Enter => '\r',
|
||||
ConsoleKey.Spacebar => ' ',
|
||||
ConsoleKey.Backspace => (char)0,
|
||||
ConsoleKey.Escape => (char) 0x1b,
|
||||
_ => keyChar
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new KalkConsoleKey(modifiers, key, keyChar);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var modifiers = Modifiers;
|
||||
if ((modifiers & ConsoleModifiers.Control) != 0)
|
||||
{
|
||||
builder.Append("ctrl");
|
||||
}
|
||||
if ((modifiers & ConsoleModifiers.Alt) != 0)
|
||||
{
|
||||
if (builder.Length > 0) builder.Append('+');
|
||||
builder.Append("alt");
|
||||
}
|
||||
if ((modifiers & ConsoleModifiers.Shift) != 0)
|
||||
{
|
||||
if (builder.Length > 0) builder.Append('+');
|
||||
builder.Append("shift");
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
builder.Append('+');
|
||||
}
|
||||
|
||||
if (Key >= ConsoleKey.A && Key <= ConsoleKey.Z && KeyChar != 0)
|
||||
{
|
||||
builder.Append(KeyChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(MapConsoleKeyToName[Key]);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static implicit operator KalkConsoleKey(ConsoleKeyInfo keyInfo)
|
||||
{
|
||||
return new KalkConsoleKey(keyInfo.Modifiers, keyInfo.Key, keyInfo.KeyChar);
|
||||
}
|
||||
|
||||
public static implicit operator ConsoleKeyInfo(KalkConsoleKey key)
|
||||
{
|
||||
return new ConsoleKeyInfo(key.KeyChar, key.Key, (key.Modifiers & ConsoleModifiers.Shift) != 0, (key.Modifiers & ConsoleModifiers.Alt) != 0, (key.Modifiers & ConsoleModifiers.Control) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Kalk/Kalk.Core/Model/KalkConstructor.cs
Normal file
12
Kalk/Kalk.Core/Model/KalkConstructor.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Scriban;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public abstract class KalkConstructor
|
||||
{
|
||||
}
|
||||
}
|
||||
50
Kalk/Kalk.Core/Model/KalkDescriptor.cs
Normal file
50
Kalk/Kalk.Core/Model/KalkDescriptor.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public class KalkDescriptor
|
||||
{
|
||||
public KalkDescriptor()
|
||||
{
|
||||
Names = new List<string>();
|
||||
Params = new List<KalkParamDescriptor>();
|
||||
}
|
||||
|
||||
public List<string> Names { get;}
|
||||
|
||||
public bool IsCommand { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public List<KalkParamDescriptor> Params { get; }
|
||||
|
||||
public string Syntax { get; set; }
|
||||
|
||||
public string Returns { get; set; }
|
||||
|
||||
public string Remarks { get; set; }
|
||||
|
||||
public string Example { get; set; }
|
||||
}
|
||||
|
||||
public class KalkParamDescriptor
|
||||
{
|
||||
public KalkParamDescriptor()
|
||||
{
|
||||
}
|
||||
|
||||
public KalkParamDescriptor(string name, string description)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
}
|
||||
}
|
||||
224
Kalk/Kalk.Core/Model/KalkExpression.cs
Normal file
224
Kalk/Kalk.Core/Model/KalkExpression.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
208
Kalk/Kalk.Core/Model/KalkExpressionSimplifier.cs
Normal file
208
Kalk/Kalk.Core/Model/KalkExpressionSimplifier.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Scriban;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
internal class KalkExpressionSimplifier
|
||||
{
|
||||
private readonly TemplateContext _context;
|
||||
private object _value;
|
||||
private List<KalkBinaryExpression> _powers;
|
||||
|
||||
public KalkExpressionSimplifier(TemplateContext context)
|
||||
{
|
||||
_context = context;
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
public (object, KalkExpression) Canonical(KalkExpression value)
|
||||
{
|
||||
Collect(value);
|
||||
SquashPowers();
|
||||
|
||||
// Compute the final canonical value
|
||||
KalkExpression leftValue = null;
|
||||
if (_powers != null)
|
||||
{
|
||||
for (var i = 0; i < _powers.Count; i++)
|
||||
{
|
||||
var power = _powers[i];
|
||||
var powerValue = _context.ToObject<double>(_context.CurrentSpan, power.Unit);
|
||||
if (powerValue < 0)
|
||||
{
|
||||
powerValue = -powerValue;
|
||||
power.Unit = powerValue;
|
||||
object left = leftValue ?? (object)1.0;
|
||||
leftValue = new KalkBinaryExpression(left, ScriptBinaryOperator.Divide, KalkValue.AlmostEqual(powerValue, 1.0) ? power.Value : power);
|
||||
}
|
||||
else
|
||||
{
|
||||
var nextMul = KalkValue.AlmostEqual(powerValue, 1.0) ? (KalkUnit) power.Value : (KalkExpression) power;
|
||||
leftValue = leftValue == null ? nextMul : new KalkBinaryExpression(leftValue, ScriptBinaryOperator.Multiply, nextMul);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (_value, leftValue);
|
||||
}
|
||||
|
||||
private int SortPowerPerSymbol(KalkBinaryExpression left, KalkBinaryExpression right)
|
||||
{
|
||||
var leftSymbol = (KalkUnit) left.Value;
|
||||
var rightSymbol = (KalkUnit) right.Value;
|
||||
|
||||
Debug.Assert(leftSymbol.Value == null);
|
||||
Debug.Assert(rightSymbol.Value == null);
|
||||
|
||||
return string.Compare(leftSymbol.Symbol, rightSymbol.Symbol, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private int SortPowerPerPower(KalkBinaryExpression left, KalkBinaryExpression right)
|
||||
{
|
||||
var leftPowerValue = _context.ToObject<double>(_context.CurrentSpan, left.Unit);
|
||||
var rightPowerValue = _context.ToObject<double>(_context.CurrentSpan, right.Unit);
|
||||
|
||||
var comparePower = rightPowerValue.CompareTo(leftPowerValue);
|
||||
if (comparePower != 0) return comparePower;
|
||||
|
||||
// Then compare per symbol
|
||||
return SortPowerPerSymbol(left, right);
|
||||
}
|
||||
|
||||
private void SquashPowers()
|
||||
{
|
||||
if (_powers == null) return;
|
||||
_powers.Sort(SortPowerPerSymbol);
|
||||
|
||||
// Compress the powers: a^x * a^y = a^(x+y)
|
||||
KalkBinaryExpression previousPower = null;
|
||||
for (var i = 0; i < _powers.Count; i++)
|
||||
{
|
||||
var power = _powers[i];
|
||||
if (previousPower != null && ReferenceEquals(power.Value, previousPower.Value))
|
||||
{
|
||||
var powerResult = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Add, power.Unit, previousPower.Unit);
|
||||
_powers[i - 1] = new KalkBinaryExpression(power.Value, ScriptBinaryOperator.Power, powerResult);
|
||||
_powers.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
previousPower = power;
|
||||
}
|
||||
|
||||
// Remove any powers a^0.0
|
||||
for (var i = 0; i < _powers.Count; i++)
|
||||
{
|
||||
var power = _powers[i];
|
||||
var powerValue = _context.ToObject<double>(_context.CurrentSpan, power.Unit);
|
||||
if (Math.Abs(powerValue) < (float.Epsilon * 4))
|
||||
{
|
||||
_powers.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort power per Power first and symbol after
|
||||
// From largest to smallest
|
||||
_powers.Sort(SortPowerPerPower);
|
||||
}
|
||||
|
||||
private void Collect(KalkExpressionSimplifier simplifier)
|
||||
{
|
||||
Collect(simplifier._value);
|
||||
if (simplifier._powers != null)
|
||||
{
|
||||
foreach (var power in simplifier._powers)
|
||||
{
|
||||
Collect(power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Collect(object value)
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
if (value is KalkUnit symbol)
|
||||
{
|
||||
if (symbol.Value != null)
|
||||
{
|
||||
Collect(symbol.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_powers == null) _powers = new List<KalkBinaryExpression>();
|
||||
_powers.Add(new KalkBinaryExpression(symbol, ScriptBinaryOperator.Power, 1));
|
||||
}
|
||||
}
|
||||
else if (value is KalkBinaryExpression binary)
|
||||
{
|
||||
if (binary.Operator == ScriptBinaryOperator.Multiply)
|
||||
{
|
||||
var leftSimplifier = new KalkExpressionSimplifier(_context);
|
||||
leftSimplifier.Collect(binary.Value);
|
||||
Collect(leftSimplifier);
|
||||
|
||||
var rightSimplifier = new KalkExpressionSimplifier(_context);
|
||||
rightSimplifier.Collect(binary.Unit);
|
||||
Collect(rightSimplifier);
|
||||
}
|
||||
else if (binary.Operator == ScriptBinaryOperator.Divide)
|
||||
{
|
||||
Collect(binary.Value);
|
||||
if (binary.Unit is KalkExpression)
|
||||
{
|
||||
Collect(new KalkBinaryExpression(binary.Unit, ScriptBinaryOperator.Power, -1));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Divide, 1, binary.Unit);
|
||||
SquashValue(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(binary.Operator == ScriptBinaryOperator.Power);
|
||||
if (_powers == null) _powers = new List<KalkBinaryExpression>();
|
||||
|
||||
// (a * b^2) ^ 5
|
||||
// a ^ 5 * b ^ 10
|
||||
var subSimplifier = new KalkExpressionSimplifier(_context);
|
||||
subSimplifier.Collect(binary.Value);
|
||||
|
||||
var subValue = _context.ToObject<double>(_context.CurrentSpan, subSimplifier._value);
|
||||
|
||||
if (!KalkValue.AlmostEqual(subValue, 1.0))
|
||||
{
|
||||
var result = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Power, subSimplifier._value, binary.Unit);
|
||||
SquashValue(result);
|
||||
}
|
||||
|
||||
if (subSimplifier._powers != null)
|
||||
{
|
||||
foreach (var powerExpression in subSimplifier._powers)
|
||||
{
|
||||
var powerResult = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Multiply, powerExpression.Unit, binary.Unit);
|
||||
var newPower = new KalkBinaryExpression(powerExpression.Value, ScriptBinaryOperator.Power, powerResult);
|
||||
_powers.Add(newPower);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SquashValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void SquashValue(object value)
|
||||
{
|
||||
_value = ScriptBinaryExpression.Evaluate(_context, _context.CurrentSpan, ScriptBinaryOperator.Multiply, _value, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Kalk/Kalk.Core/Model/KalkExpressionWithMembers.cs
Normal file
56
Kalk/Kalk.Core/Model/KalkExpressionWithMembers.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public abstract class KalkExpressionWithMembers : KalkExpression
|
||||
{
|
||||
protected abstract (string, Func<KalkExpressionWithMembers, object> getter)[] Members { get; }
|
||||
|
||||
public override int Count => Members.Length;
|
||||
|
||||
public override IEnumerable<string> GetMembers()
|
||||
{
|
||||
foreach (var memberPair in Members) yield return memberPair.Item1;
|
||||
}
|
||||
|
||||
public override bool Contains(string member)
|
||||
{
|
||||
foreach (var memberPair in Members)
|
||||
if (memberPair.Item1 == member)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsReadOnly { get; set; }
|
||||
|
||||
public override bool TryGetValue(TemplateContext context, SourceSpan span, string member, out object value)
|
||||
{
|
||||
value = null;
|
||||
foreach (var memberPair in Members)
|
||||
{
|
||||
if (memberPair.Item1 == member)
|
||||
{
|
||||
value = memberPair.Item2(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanWrite(string member) => false;
|
||||
|
||||
public override bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly) => false;
|
||||
|
||||
public override bool Remove(string member) => false;
|
||||
|
||||
public override void SetReadOnly(string member, bool readOnly) => throw new NotSupportedException("A member of Unit is readonly by default and cannot be modified");
|
||||
|
||||
}
|
||||
}
|
||||
258
Kalk/Kalk.Core/Model/KalkHalf.cs
Normal file
258
Kalk/Kalk.Core/Model/KalkHalf.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a IEE-754 half-float 16-bit
|
||||
/// </summary>
|
||||
public readonly struct KalkHalf : IScriptCustomType, IKalkSpannable, IFormattable, IScriptTransformable, IKalkDisplayable
|
||||
{
|
||||
private readonly Half _value;
|
||||
|
||||
public KalkHalf(float value)
|
||||
{
|
||||
_value = (Half) value;
|
||||
}
|
||||
|
||||
public KalkHalf(Half value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public string TypeName => "half";
|
||||
|
||||
internal Half Value => _value;
|
||||
|
||||
|
||||
public static float[] ToFloatValues(KalkHalf[] values)
|
||||
{
|
||||
if (values == null) return null;
|
||||
var floatValues = new float[values.Length];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
floatValues[i] = (float)values[i];
|
||||
}
|
||||
return floatValues;
|
||||
}
|
||||
|
||||
public static KalkHalf[] ToHalfValues(float[] values)
|
||||
{
|
||||
if (values == null) return null;
|
||||
var halfValues = new KalkHalf[values.Length];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
halfValues[i] = (KalkHalf)values[i];
|
||||
}
|
||||
return halfValues;
|
||||
}
|
||||
|
||||
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||
{
|
||||
result = null;
|
||||
if (leftValue is KalkHalf leftHalf && rightValue is KalkHalf rightHalf)
|
||||
{
|
||||
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, (float) leftHalf, rightSpan, (float)rightHalf);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (leftValue is KalkHalf leftHalf1)
|
||||
{
|
||||
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, (float)leftHalf1, rightSpan, rightValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rightValue is KalkHalf rightHalf1)
|
||||
{
|
||||
result = (KalkHalf)(float)ScriptBinaryExpression.Evaluate(context, span, op, leftSpan, leftValue, rightSpan, rightHalf1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptUnaryOperator op, object rightValue, out object result)
|
||||
{
|
||||
result = ScriptUnaryExpression.Evaluate(context, span, op, (float)(KalkHalf)rightValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static implicit operator KalkHalf(Half half)
|
||||
{
|
||||
return new KalkHalf(half);
|
||||
}
|
||||
|
||||
public static explicit operator KalkHalf(float value)
|
||||
{
|
||||
return new KalkHalf(value);
|
||||
}
|
||||
|
||||
|
||||
public static explicit operator float(KalkHalf half)
|
||||
{
|
||||
return (float)half._value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(null, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public void Display(KalkEngine engine, KalkDisplayMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case KalkDisplayMode.Raw:
|
||||
case KalkDisplayMode.Standard:
|
||||
break;
|
||||
case KalkDisplayMode.Developer:
|
||||
DisplayDetailed(engine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayDetailed(KalkEngine engine)
|
||||
{
|
||||
var u16 = Unsafe.As<Half, ushort>(ref Unsafe.AsRef(_value));
|
||||
engine.WriteHighlightLine($" # IEEE 754 - half float - 16-bit");
|
||||
engine.WriteHighlightLine($" #");
|
||||
engine.WriteInt16(u16, true);
|
||||
|
||||
// Prints the 16 bits indices
|
||||
engine.WriteHighlightLine($" # 15 8 0");
|
||||
|
||||
engine.WriteHighlightLine($" #");
|
||||
engine.WriteHighlightLine($" # sign exponent |-- fraction -|");
|
||||
// # 1 * 2 ^ (15 - 15) * 0b1.1000000000
|
||||
|
||||
var sign = (u16 >> 15);
|
||||
var exponent = (u16 >> 10) & 0b11111;
|
||||
var fraction = u16 & 0b1111111111;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(sign != 0 ? " -1" : " 1");
|
||||
builder.Append(" * 2 ^ (");
|
||||
builder.Append($"{(exponent == 0 ? 1 : exponent),2} - 15) * 0b{(exponent == 0 ? '0' : '1')}.");
|
||||
for (int i = 9; i >= 0; i--)
|
||||
{
|
||||
builder.Append(((fraction >> i) & 1) == 0 ? '0' : '1');
|
||||
}
|
||||
engine.WriteHighlightLine($" = {builder}f");
|
||||
}
|
||||
|
||||
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||
{
|
||||
return _value.ToString(format, formatProvider);
|
||||
}
|
||||
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
return MemoryMarshal.CreateSpan(ref Unsafe.As<KalkHalf, byte>(ref Unsafe.AsRef(this)), Unsafe.SizeOf<KalkHalf>());
|
||||
}
|
||||
|
||||
public bool TryConvertTo(TemplateContext context, SourceSpan span, Type type, out object value)
|
||||
{
|
||||
if (type == typeof(KalkDoubleValue))
|
||||
{
|
||||
value = new KalkDoubleValue(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == typeof(KalkTrigDoubleValue))
|
||||
{
|
||||
value = new KalkTrigDoubleValue(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == typeof(object))
|
||||
{
|
||||
value = this;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(int))
|
||||
{
|
||||
value = (int)(float)_value;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(float))
|
||||
{
|
||||
value = (float)_value;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(long))
|
||||
{
|
||||
value = (long)(float)_value;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(double))
|
||||
{
|
||||
value = (double)(float)_value;
|
||||
return true;
|
||||
}
|
||||
if (type == typeof(decimal))
|
||||
{
|
||||
value = (decimal) (float) _value;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ScriptRuntimeException(span, $"Cannot convert {this} to an {type} as it has an imaginary part.");
|
||||
}
|
||||
|
||||
public bool CanTransform(Type transformType)
|
||||
{
|
||||
return transformType == typeof(float) || transformType == typeof(double) || transformType == typeof(int);
|
||||
}
|
||||
|
||||
public bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType)
|
||||
{
|
||||
if (destType == typeof(KalkHalf))
|
||||
{
|
||||
return new KalkHalf((float) apply((float) _value));
|
||||
}
|
||||
else if (destType == typeof(float))
|
||||
{
|
||||
return (KalkHalf)(float) apply((float) _value);
|
||||
}
|
||||
else if (destType == typeof(double))
|
||||
{
|
||||
return (KalkHalf)(double)apply((float)_value);
|
||||
}
|
||||
else if (destType == typeof(int))
|
||||
{
|
||||
return (KalkHalf)(int)apply((float)_value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static KalkHalf FromObject(KalkEngine engine, SourceSpan span, object value)
|
||||
{
|
||||
if (value == null) return new KalkHalf();
|
||||
|
||||
if (value is KalkHalf half) return half;
|
||||
|
||||
if (value is IConvertible convertible)
|
||||
{
|
||||
return (KalkHalf) convertible.ToSingle(engine);
|
||||
}
|
||||
|
||||
throw new ScriptRuntimeException(span, $"The type {engine.GetTypeName(value)} is not convertible to half");
|
||||
}
|
||||
|
||||
public Type ElementType => typeof(float);
|
||||
}
|
||||
}
|
||||
103
Kalk/Kalk.Core/Model/KalkMatrixConstructor.cs
Normal file
103
Kalk/Kalk.Core/Model/KalkMatrixConstructor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Kalk.Core.Helpers;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public class KalkMatrixConstructor<T> : KalkConstructor where T : unmanaged
|
||||
{
|
||||
public KalkMatrixConstructor(int row, int column)
|
||||
{
|
||||
RowCount = row;
|
||||
ColumnCount = column;
|
||||
}
|
||||
|
||||
public int RowCount { get; }
|
||||
|
||||
public int ColumnCount { get; }
|
||||
|
||||
public KalkMatrix<T> Invoke(KalkEngine context, params object[] arguments)
|
||||
{
|
||||
var matrix = new KalkMatrix<T>(RowCount, ColumnCount);
|
||||
if (arguments.Length == 0) return matrix;
|
||||
|
||||
int index = 0;
|
||||
int columnIndex = 0;
|
||||
var maxTotal = ColumnCount * RowCount;
|
||||
if (arguments.Length == 1)
|
||||
{
|
||||
var arg0 = arguments[0];
|
||||
if (arg0 is KalkMatrix m)
|
||||
{
|
||||
var values = m.Values;
|
||||
for (int j = 0; j < values.Length; j++)
|
||||
{
|
||||
matrix[index++] = context.ToObject<T>(0, values.GetValue(j));
|
||||
}
|
||||
}
|
||||
else if (arg0 is IList list)
|
||||
{
|
||||
for (int j = 0; j < list.Count; j++)
|
||||
{
|
||||
matrix[index++] = context.ToObject<T>(0, list[j]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = context.ToObject<T>(0, arg0);
|
||||
for (int j = 0; j < ColumnCount * RowCount; j++)
|
||||
{
|
||||
matrix[index++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
var argLength = arg is IList list ? list.Count : 1;
|
||||
|
||||
var length = columnIndex + argLength;
|
||||
if (length > ColumnCount)
|
||||
{
|
||||
throw new ScriptArgumentException(i, $"Invalid number of arguments crossing a row. Expecting {ColumnCount} arguments instead of {length} for {matrix.TypeName}.");
|
||||
}
|
||||
|
||||
if (length == ColumnCount)
|
||||
{
|
||||
columnIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnIndex += argLength;
|
||||
}
|
||||
|
||||
if (arg is IList listToAdd)
|
||||
{
|
||||
for (int j = 0; j < argLength; j++)
|
||||
{
|
||||
matrix[index++] = context.ToObject<T>(i, listToAdd[j]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = context.ToObject<T>(i, arg);
|
||||
matrix[index++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index != maxTotal)
|
||||
{
|
||||
throw new ScriptArgumentException(arguments.Length - 1, $"Invalid number of arguments. Expecting {maxTotal} arguments instead of {index} for {matrix.TypeName}.");
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
472
Kalk/Kalk.Core/Model/KalkNativeBuffer.cs
Normal file
472
Kalk/Kalk.Core/Model/KalkNativeBuffer.cs
Normal file
@@ -0,0 +1,472 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Scriban;
|
||||
using Scriban.Helpers;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
[DebuggerDisplay("Count={Count}")]
|
||||
public unsafe class KalkNativeBuffer : IList<byte>, IList, IScriptCustomBinaryOperation, IScriptCustomTypeInfo, IKalkSpannable, IFormattable
|
||||
{
|
||||
private readonly SharedBuffer _buffer;
|
||||
private readonly int _offset;
|
||||
private readonly int _length;
|
||||
|
||||
public KalkNativeBuffer(int size)
|
||||
{
|
||||
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));
|
||||
_buffer = new SharedBuffer(size);
|
||||
_offset = 0;
|
||||
_length = size;
|
||||
}
|
||||
|
||||
private KalkNativeBuffer(SharedBuffer buffer, int offset, int length)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_length = length;
|
||||
}
|
||||
|
||||
public string TypeName => "buffer";
|
||||
|
||||
public IntPtr GetPointer()
|
||||
{
|
||||
return new IntPtr(_buffer.Buffer + _offset);
|
||||
}
|
||||
|
||||
public KalkNativeBuffer(IEnumerable<byte> buffer)
|
||||
{
|
||||
var array = new List<byte>(buffer).ToArray(); ;
|
||||
_buffer = new SharedBuffer(array.Length);
|
||||
array.CopyTo(_buffer.AsSpan());
|
||||
_offset = 0;
|
||||
_length = _buffer.Length;
|
||||
}
|
||||
|
||||
public KalkNativeBuffer(byte[] buffer)
|
||||
{
|
||||
_buffer = new SharedBuffer(buffer.Length);
|
||||
buffer.CopyTo(_buffer.AsSpan());
|
||||
_offset = 0;
|
||||
_length = _buffer.Length;
|
||||
}
|
||||
|
||||
public static KalkNativeBuffer AsBytes<T>(int byteCount, in T element)
|
||||
{
|
||||
var buffer = new KalkNativeBuffer(byteCount);
|
||||
Unsafe.CopyBlockUnaligned(ref buffer.AsSpan()[0], ref Unsafe.As<T, byte>(ref Unsafe.AsRef(element)), (uint)byteCount);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public KalkNativeBuffer Slice(int index)
|
||||
{
|
||||
AssertIndex(index);
|
||||
return Slice(index, _length - index);
|
||||
}
|
||||
|
||||
public KalkNativeBuffer Slice(int index, int length)
|
||||
{
|
||||
AssertIndex(index);
|
||||
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
|
||||
if ((uint)(length + index) > (uint)_length) throw new ArgumentOutOfRangeException($"End of slice {index + length - 1} is out of range ({_length}).");
|
||||
|
||||
return new KalkNativeBuffer(_buffer, _offset + index, length);
|
||||
}
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < _length; i++)
|
||||
{
|
||||
yield return _buffer[_offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < _length; i++)
|
||||
{
|
||||
yield return _buffer[_offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(byte item) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||
|
||||
int IList.Add(object? value) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
AsSpan().Clear();
|
||||
}
|
||||
|
||||
bool IList.Contains(object? value) => throw new NotImplementedException();
|
||||
|
||||
int IList.IndexOf(object? value) => throw new NotImplementedException();
|
||||
|
||||
void IList.Insert(int index, object? value) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||
|
||||
void IList.Remove(object? value) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||
|
||||
public bool Contains(byte item)
|
||||
{
|
||||
return IndexOf(item) >= 0;
|
||||
}
|
||||
|
||||
public void CopyTo(byte[] array, int arrayIndex)
|
||||
{
|
||||
AsSpan().CopyTo(new Span<byte>(array, arrayIndex, array.Length - arrayIndex));
|
||||
}
|
||||
|
||||
public bool Remove(byte item) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
for (int i = 0; i < _length; i++)
|
||||
{
|
||||
array.SetValue(index + i, _buffer[_offset + i]);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _length;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => null;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
object? IList.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertIndex(index);
|
||||
return _buffer[_offset + index];
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertIndex(index);
|
||||
if (value is byte tValue)
|
||||
{
|
||||
this[_offset + index] = tValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this[_offset + index] = Convert.ToByte(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(byte item)
|
||||
{
|
||||
return AsSpan().IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, byte item) => throw new NotSupportedException("Cannot add to a fixed buffer");
|
||||
|
||||
public void RemoveAt(int index) => throw new NotSupportedException("Cannot remove from a fixed buffer");
|
||||
|
||||
bool IList.IsFixedSize => true;
|
||||
|
||||
public byte this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
AssertIndex(index);
|
||||
return _buffer[_offset + index];
|
||||
}
|
||||
set
|
||||
{
|
||||
AssertIndex(index);
|
||||
_buffer[_offset + index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertIndex(int index)
|
||||
{
|
||||
if ((uint)index >= (uint)_length) throw new ArgumentOutOfRangeException($"Index {index} is out of range ({_length}).");
|
||||
}
|
||||
|
||||
public bool TryEvaluate(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, SourceSpan leftSpan, object leftValue, SourceSpan rightSpan, object rightValue, out object result)
|
||||
{
|
||||
result = null;
|
||||
var leftArray = TryGetArray(leftValue);
|
||||
var rightArray = TryGetArray(rightValue);
|
||||
int intModifier = 0;
|
||||
var intSpan = leftSpan;
|
||||
|
||||
var errorSpan = span;
|
||||
string reason = null;
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.BinaryOr:
|
||||
case ScriptBinaryOperator.BinaryAnd:
|
||||
case ScriptBinaryOperator.CompareEqual:
|
||||
case ScriptBinaryOperator.CompareNotEqual:
|
||||
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||
case ScriptBinaryOperator.CompareLess:
|
||||
case ScriptBinaryOperator.CompareGreater:
|
||||
break;
|
||||
case ScriptBinaryOperator.Multiply:
|
||||
if (leftArray == null && rightArray == null || leftArray != null && rightArray != null)
|
||||
{
|
||||
reason = " Expecting only one array for the left or right argument.";
|
||||
}
|
||||
else
|
||||
{
|
||||
intModifier = context.ToInt(span, leftArray == null ? leftValue : rightValue);
|
||||
if (rightArray == null) intSpan = rightSpan;
|
||||
}
|
||||
break;
|
||||
case ScriptBinaryOperator.Add:
|
||||
case ScriptBinaryOperator.Divide:
|
||||
case ScriptBinaryOperator.DivideRound:
|
||||
case ScriptBinaryOperator.Modulus:
|
||||
if (leftArray == null)
|
||||
{
|
||||
errorSpan = leftSpan;
|
||||
reason = " Expecting an array for the left argument.";
|
||||
}
|
||||
else
|
||||
{
|
||||
intModifier = context.ToInt(span, rightValue);
|
||||
intSpan = rightSpan;
|
||||
}
|
||||
break;
|
||||
case ScriptBinaryOperator.ShiftLeft:
|
||||
case ScriptBinaryOperator.ShiftRight:
|
||||
reason = " Cannot modify a fixed buffer.";
|
||||
break;
|
||||
default:
|
||||
reason = String.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
if (intModifier < 0)
|
||||
{
|
||||
errorSpan = intSpan;
|
||||
reason = $" Integer {intModifier} cannot be negative when multiplying";
|
||||
}
|
||||
|
||||
if (reason != null)
|
||||
{
|
||||
throw new ScriptRuntimeException(errorSpan, $"The operator `{op.ToText()}` is not supported between {leftValue?.GetType().ScriptPrettyName()} and {rightValue?.GetType().ScriptPrettyName()}.{reason}");
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.BinaryOr:
|
||||
result = new KalkNativeBuffer(leftArray.Union(rightArray));
|
||||
return true;
|
||||
|
||||
case ScriptBinaryOperator.BinaryAnd:
|
||||
result = new KalkNativeBuffer(leftArray.Intersect(rightArray));
|
||||
return true;
|
||||
|
||||
case ScriptBinaryOperator.Add:
|
||||
try
|
||||
{
|
||||
result = Slice(intModifier);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ScriptRuntimeException(errorSpan, $"Cannot shift pointer array at index {intModifier}. Reason: {ex.Message}");
|
||||
}
|
||||
return true;
|
||||
|
||||
case ScriptBinaryOperator.CompareEqual:
|
||||
case ScriptBinaryOperator.CompareNotEqual:
|
||||
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||
case ScriptBinaryOperator.CompareLess:
|
||||
case ScriptBinaryOperator.CompareGreater:
|
||||
result = CompareTo(context, span, op, leftArray, rightArray);
|
||||
return true;
|
||||
|
||||
case ScriptBinaryOperator.Multiply:
|
||||
{
|
||||
// array with integer
|
||||
var array = leftArray ?? rightArray;
|
||||
if (intModifier == 0)
|
||||
{
|
||||
result = new KalkNativeBuffer(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
var newArray = new List<byte>(array);
|
||||
for (int i = 0; i < intModifier; i++)
|
||||
{
|
||||
newArray.AddRange(array);
|
||||
}
|
||||
|
||||
result = new KalkNativeBuffer(newArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
case ScriptBinaryOperator.Divide:
|
||||
case ScriptBinaryOperator.DivideRound:
|
||||
{
|
||||
// array with integer
|
||||
var array = leftArray ?? rightArray;
|
||||
if (intModifier == 0) throw new ScriptRuntimeException(intSpan, "Cannot divide by 0");
|
||||
|
||||
var newLength = array.Count / intModifier;
|
||||
var newArray = new List<byte>(newLength);
|
||||
for (int i = 0; i < newLength; i++)
|
||||
{
|
||||
newArray.Add(array[i]);
|
||||
}
|
||||
|
||||
result = new KalkNativeBuffer(newArray);
|
||||
return true;
|
||||
}
|
||||
|
||||
case ScriptBinaryOperator.Modulus:
|
||||
{
|
||||
// array with integer
|
||||
var array = leftArray ?? rightArray;
|
||||
if (intModifier == 0) throw new ScriptRuntimeException(intSpan, "Cannot divide by 0");
|
||||
|
||||
var newArray = new List<byte>(array.Count);
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
if ((i % intModifier) == 0)
|
||||
{
|
||||
newArray.Add(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
result = new KalkNativeBuffer(newArray);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IList<byte> TryGetArray(object rightValue)
|
||||
{
|
||||
var rightArray = rightValue as IList<byte>;
|
||||
return rightArray;
|
||||
}
|
||||
|
||||
private static bool CompareTo(TemplateContext context, SourceSpan span, ScriptBinaryOperator op, IList<byte> left, IList<byte> right)
|
||||
{
|
||||
// Compare the length first
|
||||
var compare = left.Count.CompareTo(right.Count);
|
||||
switch (op)
|
||||
{
|
||||
case ScriptBinaryOperator.CompareEqual:
|
||||
if (compare != 0) return false;
|
||||
break;
|
||||
case ScriptBinaryOperator.CompareNotEqual:
|
||||
if (compare != 0) return true;
|
||||
if (left.Count == 0) return false;
|
||||
break;
|
||||
case ScriptBinaryOperator.CompareLessOrEqual:
|
||||
case ScriptBinaryOperator.CompareLess:
|
||||
if (compare < 0) return true;
|
||||
if (compare > 0) return false;
|
||||
if (left.Count == 0 && op == ScriptBinaryOperator.CompareLess) return false;
|
||||
break;
|
||||
case ScriptBinaryOperator.CompareGreaterOrEqual:
|
||||
case ScriptBinaryOperator.CompareGreater:
|
||||
if (compare < 0) return false;
|
||||
if (compare > 0) return true;
|
||||
if (left.Count == 0 && op == ScriptBinaryOperator.CompareGreater) return false;
|
||||
break;
|
||||
default:
|
||||
throw new ScriptRuntimeException(span, $"The operator `{op.ToText()}` is not supported between {left?.GetType().ScriptPrettyName()} and {right?.GetType().ScriptPrettyName()}.");
|
||||
}
|
||||
|
||||
// Otherwise we need to compare each element
|
||||
for (int i = 0; i < left.Count; i++)
|
||||
{
|
||||
var result = (bool) ScriptBinaryExpression.Evaluate(context, span, op, left[i], right[i]);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Span<byte> AsSpan()
|
||||
{
|
||||
return new Span<byte>(_buffer.Buffer + _offset, _length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(null, null);
|
||||
}
|
||||
|
||||
public string ToString(string? format, IFormatProvider? formatProvider)
|
||||
{
|
||||
var engine = formatProvider as KalkEngine;
|
||||
if (engine != null)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
bool isSlice = _offset > 0 || _length != this._buffer.Length;
|
||||
if (isSlice)
|
||||
{
|
||||
builder.Append("slice(");
|
||||
}
|
||||
builder.Append("bytebuffer(");
|
||||
builder.Append(engine.ObjectToString(new ScriptRange(this)));
|
||||
builder.Append(")");
|
||||
if (isSlice)
|
||||
{
|
||||
builder.Append($", {_offset.ToString(CultureInfo.InvariantCulture)}");
|
||||
if (_offset + _length < _buffer.Length)
|
||||
{
|
||||
builder.Append($", {_length}");
|
||||
}
|
||||
builder.Append(")");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
private sealed class SharedBuffer
|
||||
{
|
||||
private const int AlignmentInBytes = 64; // 512-bits
|
||||
private readonly byte* _bufferUnaligned;
|
||||
|
||||
public SharedBuffer(int length)
|
||||
{
|
||||
// We allocate enough space before and after the buffer to manage overflow
|
||||
var allocatedSize = length + AlignmentInBytes * 2;
|
||||
_bufferUnaligned = (byte*)Marshal.AllocHGlobal(allocatedSize);
|
||||
Unsafe.InitBlockUnaligned(_bufferUnaligned, 0, (uint)allocatedSize);
|
||||
if (_bufferUnaligned == null) throw new OutOfMemoryException($"Cannot allocate a buffer of {length} bytes.");
|
||||
Buffer = (byte*) ((long) (_bufferUnaligned + AlignmentInBytes - 1) & ~(long) (AlignmentInBytes - 1));
|
||||
Length = length;
|
||||
}
|
||||
|
||||
~SharedBuffer()
|
||||
{
|
||||
Marshal.FreeHGlobal((IntPtr)_bufferUnaligned);
|
||||
}
|
||||
|
||||
public readonly byte* Buffer;
|
||||
|
||||
public readonly int Length;
|
||||
|
||||
public ref byte this[int index] => ref Buffer[index];
|
||||
|
||||
public Span<byte> AsSpan() => new Span<byte>(Buffer, Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Kalk/Kalk.Core/Model/KalkObject.cs
Normal file
22
Kalk/Kalk.Core/Model/KalkObject.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Scriban;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public abstract class KalkObject : IScriptObject
|
||||
{
|
||||
public abstract int Count { get; }
|
||||
public abstract IEnumerable<string> GetMembers();
|
||||
public abstract bool Contains(string member);
|
||||
public abstract bool IsReadOnly { get; set; }
|
||||
public abstract bool TryGetValue(TemplateContext context, SourceSpan span, string member, out object value);
|
||||
public abstract bool CanWrite(string member);
|
||||
public abstract bool TrySetValue(TemplateContext context, SourceSpan span, string member, object value, bool readOnly);
|
||||
public abstract bool Remove(string member);
|
||||
public abstract void SetReadOnly(string member, bool readOnly);
|
||||
public abstract IScriptObject Clone(bool deep);
|
||||
public abstract string TypeName { get; }
|
||||
}
|
||||
}
|
||||
83
Kalk/Kalk.Core/Model/KalkShortcut.cs
Normal file
83
Kalk/Kalk.Core/Model/KalkShortcut.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public sealed class KalkShortcut : ScriptObject
|
||||
{
|
||||
public KalkShortcut(string name, IEnumerable<KalkShortcutKeySequence> shortcuts, bool isUser)
|
||||
{
|
||||
if (shortcuts == null) throw new ArgumentNullException(nameof(shortcuts));
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Keys = new ScriptArray(shortcuts) {IsReadOnly = true};
|
||||
SetValue("name", Name, true);
|
||||
SetValue("keys", Keys, true);
|
||||
IsReadOnly = true;
|
||||
IsUser = isUser;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public new ScriptArray Keys { get; }
|
||||
|
||||
public bool IsUser { get; }
|
||||
|
||||
public override string ToString(string format, IFormatProvider formatProvider)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append($"shortcut({Name}, ");
|
||||
|
||||
var mapExpressionToKeySequences = new Dictionary<ScriptExpression, List<KalkShortcutKeySequence>>();
|
||||
|
||||
for (var i = 0; i < Keys.Count; i++)
|
||||
{
|
||||
var shortcut = (KalkShortcutKeySequence)Keys[i];
|
||||
if (!mapExpressionToKeySequences.TryGetValue(shortcut.Expression, out var list))
|
||||
{
|
||||
list = new List<KalkShortcutKeySequence>();
|
||||
mapExpressionToKeySequences.Add(shortcut.Expression, list);
|
||||
}
|
||||
list.Add(shortcut);
|
||||
}
|
||||
|
||||
int seqIndex = 0;
|
||||
foreach (var exprToSequence in mapExpressionToKeySequences)
|
||||
{
|
||||
var expr = exprToSequence.Key;
|
||||
var sequences = exprToSequence.Value;
|
||||
if (seqIndex > 0) builder.Append(", ");
|
||||
builder.Append('"');
|
||||
for (var j = 0; j < sequences.Count; j++)
|
||||
{
|
||||
var sequence = sequences[j];
|
||||
if (j > 0) builder.Append(", ");
|
||||
builder.Append($"{string.Join(" ", sequence.Keys)}");
|
||||
}
|
||||
|
||||
builder.Append($"\", {expr}");
|
||||
seqIndex++;
|
||||
}
|
||||
builder.Append(")");
|
||||
|
||||
const int align = 60;
|
||||
if (builder.Length < align)
|
||||
{
|
||||
builder.Append(new string(' ', align - builder.Length));
|
||||
}
|
||||
|
||||
builder.Append(" # ");
|
||||
for (var i = 0; i < Keys.Count; i++)
|
||||
{
|
||||
var shortcut = (KalkShortcutKeySequence)Keys[i];
|
||||
if (i > 0) builder.Append(", ");
|
||||
builder.Append($"{string.Join(" ", shortcut.Keys)} => {shortcut.Expression}");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Kalk/Kalk.Core/Model/KalkShortcutKeySequence.cs
Normal file
60
Kalk/Kalk.Core/Model/KalkShortcutKeySequence.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public sealed class KalkShortcutKeySequence : ScriptObject
|
||||
{
|
||||
public KalkShortcutKeySequence(IEnumerable<KalkConsoleKey> keys, ScriptExpression expression)
|
||||
{
|
||||
if (keys == null) throw new ArgumentNullException(nameof(keys));
|
||||
Keys = new List<KalkConsoleKey>(keys);
|
||||
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
|
||||
var readonlyKeys = new ScriptArray(Keys) {IsReadOnly = true};
|
||||
SetValue("keys", readonlyKeys, true);
|
||||
SetValue("expression", Expression.ToString(), true);
|
||||
IsReadOnly = true;
|
||||
}
|
||||
|
||||
public new List<KalkConsoleKey> Keys { get; }
|
||||
|
||||
public ScriptExpression Expression { get; }
|
||||
|
||||
public static List<KalkShortcutKeySequence> Parse(ScriptExpression keys, ScriptExpression expression)
|
||||
{
|
||||
if (keys == null) throw new ArgumentNullException(nameof(keys));
|
||||
|
||||
var keyAsString = keys is ScriptLiteral literal && literal.Value is string strValue? strValue : keys.ToString();
|
||||
|
||||
|
||||
var keySequencesAsText = keyAsString.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var keySequences = new List<KalkShortcutKeySequence>();
|
||||
|
||||
foreach (var keySequence in keySequencesAsText)
|
||||
{
|
||||
var keyList = keySequence.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var resultKeys = new List<KalkConsoleKey>();
|
||||
|
||||
foreach (var keyText in keyList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = KalkConsoleKey.Parse(keyText);
|
||||
resultKeys.Add(key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ScriptRuntimeException(keys.Span, $"Unable to parse key. Reason: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
keySequences.Add(new KalkShortcutKeySequence(resultKeys, expression));
|
||||
}
|
||||
|
||||
return keySequences;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Kalk/Kalk.Core/Model/KalkUnit.cs
Normal file
125
Kalk/Kalk.Core/Model/KalkUnit.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Scriban;
|
||||
using Scriban.Runtime;
|
||||
using Scriban.Syntax;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public class KalkUnit : KalkExpressionWithMembers, IScriptCustomFunction, IScriptCustomImplicitMultiplyPrecedence
|
||||
{
|
||||
private static readonly (string, Func<KalkExpressionWithMembers, object> getter)[] MemberDefs = {
|
||||
("kind", unit => unit.TypeName),
|
||||
("name", unit => ((KalkUnit)unit).Name),
|
||||
("symbol", unit => ((KalkUnit)unit).Symbol),
|
||||
("description", unit => ((KalkUnit)unit).Description),
|
||||
("prefix", unit => ((KalkUnit)unit).Prefix),
|
||||
("plural", unit => ((KalkUnit)unit).Plural),
|
||||
("value", unit => ((KalkUnit)unit).Value),
|
||||
};
|
||||
|
||||
public KalkUnit(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Symbol = name;
|
||||
Plural = name + "s";
|
||||
Derived = new List<KalkUnit>();
|
||||
}
|
||||
|
||||
public override string TypeName => "symbol definition";
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Symbol { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public bool IsUser { get; set; }
|
||||
|
||||
public string Plural { get; set; }
|
||||
|
||||
public KalkExpression Value { get; set; }
|
||||
|
||||
public List<KalkUnit> Derived { get; }
|
||||
|
||||
public KalkUnit Parent { get; set; }
|
||||
|
||||
public override object GetValue() => 1.0;
|
||||
|
||||
public override KalkUnit GetUnit() => this;
|
||||
|
||||
public override string ToString(string format, IFormatProvider formatProvider)
|
||||
{
|
||||
return Symbol;
|
||||
}
|
||||
|
||||
protected override (string, Func<KalkExpressionWithMembers, object> getter)[] Members => MemberDefs;
|
||||
|
||||
protected override bool EqualsImpl(TemplateContext context, KalkExpression right)
|
||||
{
|
||||
// If we reach here, that means that the symbols are necessarily not equal
|
||||
return false;
|
||||
}
|
||||
|
||||
public int RequiredParameterCount => 0;
|
||||
|
||||
public int ParameterCount => 0;
|
||||
|
||||
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.None;
|
||||
|
||||
public Type ReturnType => typeof(object);
|
||||
|
||||
public ScriptParameterInfo GetParameterInfo(int index)
|
||||
{
|
||||
throw new NotSupportedException("A Unit symbol does not support arguments.");
|
||||
}
|
||||
|
||||
public virtual object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||
{
|
||||
if (!(callerContext.Parent is ScriptExpressionStatement))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var engine = (KalkEngine) context;
|
||||
var builder = new StringBuilder();
|
||||
builder.Length = 0;
|
||||
builder.Append($"unit({Name}, \"{Description.Replace("\"", @"\""")}\", {Symbol}");
|
||||
if (Value != null)
|
||||
{
|
||||
builder.Append($", {Value.OriginalExpression ?? Value}");
|
||||
}
|
||||
if (Prefix != null)
|
||||
{
|
||||
builder.Append($", prefix: \"{Prefix}\"");
|
||||
}
|
||||
|
||||
builder.Append(")");
|
||||
engine.WriteHighlightLine(builder.ToString());
|
||||
|
||||
if (Derived.Count > 0)
|
||||
{
|
||||
builder.Length = 0;
|
||||
for (var i = 0; i < Derived.Count; i++)
|
||||
{
|
||||
var derived = Derived[i];
|
||||
if (i > 0) builder.Append(", ");
|
||||
builder.Append($"{derived.Name}/{derived.Symbol}");
|
||||
}
|
||||
|
||||
engine.WriteHighlightAligned(" - ", builder.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValueTask<object> InvokeAsync(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
|
||||
{
|
||||
return new ValueTask<object>(Invoke(context, callerContext, arguments, blockStatement));
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Kalk/Kalk.Core/Model/KalkUnitPrefix.cs
Normal file
182
Kalk/Kalk.Core/Model/KalkUnitPrefix.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public sealed class KalkUnitPrefix
|
||||
{
|
||||
private static readonly string[] PrefixNames = Enum.GetNames(typeof(KalkUnitPrefixCode));
|
||||
private static readonly Array PrefixValues = Enum.GetValues(typeof(KalkUnitPrefixCode));
|
||||
private static readonly KalkUnitPrefix[] DescriptorArray = new KalkUnitPrefix[PrefixNames.Length - 1];
|
||||
private static readonly List<KalkUnitPrefix> Decimals = new List<KalkUnitPrefix>();
|
||||
private static readonly List<KalkUnitPrefix> Binaries = new List<KalkUnitPrefix>();
|
||||
private static readonly Dictionary<string, KalkUnitPrefix> MapAbbreviationToDescriptor = new Dictionary<string, KalkUnitPrefix>();
|
||||
|
||||
static KalkUnitPrefix()
|
||||
{
|
||||
for (int i = 1; i < PrefixValues.Length; i++)
|
||||
{
|
||||
var toMatch = (KalkUnitPrefixCode)PrefixValues.GetValue(i);
|
||||
var descriptor = GetDescriptor(toMatch);
|
||||
DescriptorArray[i - 1] = descriptor;
|
||||
MapAbbreviationToDescriptor[descriptor.Abbreviation] = descriptor;
|
||||
if (descriptor.Base == 10) Decimals.Add(descriptor);
|
||||
else if (descriptor.Base == 2) Binaries.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
public KalkUnitPrefix(KalkUnitPrefixCode code, int exponent)
|
||||
{
|
||||
Code = code;
|
||||
Name = code.ToString();
|
||||
Abbreviation = Name[0].ToString(CultureInfo.InvariantCulture);
|
||||
Name = Name.ToLowerInvariant();
|
||||
Prefix = Abbreviation;
|
||||
Base = 10;
|
||||
Exponent = exponent;
|
||||
}
|
||||
|
||||
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, int exponent) : this(code, abbreviation, abbreviation, 10, exponent)
|
||||
{
|
||||
}
|
||||
|
||||
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, int @base, int exponent) : this(code, abbreviation, abbreviation, @base, exponent)
|
||||
{
|
||||
}
|
||||
|
||||
public KalkUnitPrefix(KalkUnitPrefixCode code, string abbreviation, string prefix, int @base, int exponent)
|
||||
{
|
||||
Code = code;
|
||||
Name = code.ToString().ToLowerInvariant();
|
||||
Abbreviation = abbreviation;
|
||||
Prefix = prefix;
|
||||
Base = @base;
|
||||
Exponent = exponent;
|
||||
}
|
||||
|
||||
public KalkUnitPrefixCode Code { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Abbreviation { get; }
|
||||
|
||||
public string Prefix { get; }
|
||||
|
||||
public int Base { get; }
|
||||
|
||||
public int Exponent { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name,10} - {Abbreviation,2} '{Prefix,2}' {Base,2}^{Exponent}";
|
||||
}
|
||||
|
||||
public static bool TryGet(string abbreviation, out KalkUnitPrefix descriptor)
|
||||
{
|
||||
if (abbreviation == null) throw new ArgumentNullException(nameof(abbreviation));
|
||||
|
||||
return MapAbbreviationToDescriptor.TryGetValue(abbreviation, out descriptor);
|
||||
}
|
||||
|
||||
public static IReadOnlyList<KalkUnitPrefix> GetDecimals()
|
||||
{
|
||||
return Decimals;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<KalkUnitPrefix> GetBinaries()
|
||||
{
|
||||
return Binaries;
|
||||
}
|
||||
|
||||
public static List<KalkUnitPrefix> GetListFromCode(KalkUnitPrefixCode prefixCode)
|
||||
{
|
||||
var descriptors = new List<KalkUnitPrefix>();
|
||||
for (int i = 0; i < PrefixValues.Length; i++)
|
||||
{
|
||||
var toMatch = (KalkUnitPrefixCode)PrefixValues.GetValue(i);
|
||||
if ((toMatch & prefixCode) != 0)
|
||||
{
|
||||
descriptors.Add(DescriptorArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static KalkUnitPrefix GetDescriptor(KalkUnitPrefixCode singlePrefixCode)
|
||||
{
|
||||
switch (singlePrefixCode)
|
||||
{
|
||||
case KalkUnitPrefixCode.Zetta:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Zetta, 24);
|
||||
case KalkUnitPrefixCode.Yotta:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Yotta, 21);
|
||||
case KalkUnitPrefixCode.Exa:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Exa, 18);
|
||||
case KalkUnitPrefixCode.Peta:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Peta, 15);
|
||||
case KalkUnitPrefixCode.Tera:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Tera, 12);
|
||||
case KalkUnitPrefixCode.Giga:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Giga, 9);
|
||||
case KalkUnitPrefixCode.Mega:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Mega, 6);
|
||||
case KalkUnitPrefixCode.kilo:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.kilo, 3);
|
||||
case KalkUnitPrefixCode.hecto:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.hecto, 2);
|
||||
case KalkUnitPrefixCode.deca:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.deca, "da", 10, 1);
|
||||
case KalkUnitPrefixCode.deci:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.deci, -1);
|
||||
case KalkUnitPrefixCode.centi:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.centi, -2);
|
||||
case KalkUnitPrefixCode.milli:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.milli, -3);
|
||||
case KalkUnitPrefixCode.micro:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.micro, "µ", -6);
|
||||
case KalkUnitPrefixCode.nano:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.nano, -9);
|
||||
case KalkUnitPrefixCode.pico:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.pico, -12);
|
||||
case KalkUnitPrefixCode.femto:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.femto, -15);
|
||||
case KalkUnitPrefixCode.atto:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.atto, -18);
|
||||
case KalkUnitPrefixCode.zepto:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.zepto, -21);
|
||||
case KalkUnitPrefixCode.yocto:
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.yocto, -24);
|
||||
|
||||
case KalkUnitPrefixCode.Kibi: // - Ki 2^10 kibibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Kibi, "Ki", 2, 10);
|
||||
case KalkUnitPrefixCode.Mibi: // - Mi 2^20 mibibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Mibi, "Mi", 2, 20);
|
||||
case KalkUnitPrefixCode.Gibi: // - Gi 2^30 gibibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Gibi, "Gi", 2, 30);
|
||||
case KalkUnitPrefixCode.Tibi: // - Ti 2^40 tebibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Tibi, "Ti", 2, 40);
|
||||
case KalkUnitPrefixCode.Pibi: // - Pi 2^50 pebibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Pibi, "Pi", 2, 50);
|
||||
case KalkUnitPrefixCode.Eibi: // - Ei 2^60 exbibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Eibi, "Ei", 2, 60);
|
||||
case KalkUnitPrefixCode.Zibi: // - Zi 2^70 zebibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Zibi, "Zi", 2, 70);
|
||||
case KalkUnitPrefixCode.Yibi: // - Yi 2^80 yobibit
|
||||
return new KalkUnitPrefix(KalkUnitPrefixCode.Yibi, "Yi", 2, 80);
|
||||
|
||||
//case KalkUnitPrefixCode.Kbit: // - Kb 2^10 kilobit
|
||||
// return new KalkUnitPrefix(KalkUnitPrefixCode.Kbit, "Kb", "K", 2, 10);
|
||||
//case KalkUnitPrefixCode.Mbit: // - Mb 2^20 megabit
|
||||
// return new KalkUnitPrefix(KalkUnitPrefixCode.Mbit, "Mb", "M", 2, 20);
|
||||
//case KalkUnitPrefixCode.Gbi: // - Gb 2^30 gigabit
|
||||
// return new KalkUnitPrefix(KalkUnitPrefixCode.Gbi, "Gb", "G", 2, 30);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(singlePrefixCode), singlePrefixCode, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Kalk/Kalk.Core/Model/KalkUnitPrefixCode.cs
Normal file
48
Kalk/Kalk.Core/Model/KalkUnitPrefixCode.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
[Flags]
|
||||
public enum KalkUnitPrefixCode
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Decimal
|
||||
Yotta ,// - Y 10^24
|
||||
Zetta,// - Z 10^21
|
||||
Exa, // - E 10^18
|
||||
Peta, // - P 10^15
|
||||
Tera, // - T 10^12
|
||||
Giga, // - G 10^9
|
||||
Mega, // - M 10^6
|
||||
kilo, // - k 10^3
|
||||
hecto, // - h 10^2
|
||||
deca, // - da 10^1
|
||||
deci, // - d 10^-1
|
||||
centi, // - c 10^-2
|
||||
milli, // - m 10^-3
|
||||
micro, // - µ 10^-6
|
||||
nano, // - n 10^-9
|
||||
pico, // - p 10^-12
|
||||
femto, // - f 10^-15
|
||||
atto, // - a 10^-18
|
||||
zepto, // - z 10^-21
|
||||
yocto, // - y 10^-24
|
||||
|
||||
// Binary
|
||||
Kibi, // - Ki 2^10 kibibit
|
||||
Mibi, // - Mi 2^20 mibibit
|
||||
Gibi, // - Gi 2^30 gibibit
|
||||
Tibi, // - Ti 2^40 tebibit
|
||||
Pibi, // - Pi 2^50 pebibit
|
||||
Eibi, // - Ei 2^60 exbibit
|
||||
Zibi, // - Zi 2^70 zebibit
|
||||
Yibi, // - Yi 2^80 yobibit
|
||||
|
||||
//Kbit, // - Kb 2^10 kilobit
|
||||
//Mbit, // - Mb 2^20 megabit
|
||||
//Gbi, // - Gb 2^30 gigabit
|
||||
}
|
||||
}
|
||||
57
Kalk/Kalk.Core/Model/KalkValue.cs
Normal file
57
Kalk/Kalk.Core/Model/KalkValue.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using MathNet.Numerics;
|
||||
using Scriban;
|
||||
using Scriban.Helpers;
|
||||
using Scriban.Parsing;
|
||||
using Scriban.Runtime;
|
||||
|
||||
namespace Kalk.Core
|
||||
{
|
||||
public interface IKalkSpannable
|
||||
{
|
||||
Span<byte> AsSpan();
|
||||
}
|
||||
|
||||
|
||||
public abstract class KalkValue : KalkObject, IScriptTransformable, IKalkSpannable
|
||||
{
|
||||
public abstract Type ElementType { get; }
|
||||
|
||||
public abstract bool CanTransform(Type transformType);
|
||||
|
||||
public abstract Span<byte> AsSpan();
|
||||
|
||||
public void BitCastFrom(Span<byte> bytes)
|
||||
{
|
||||
var destByte = AsSpan();
|
||||
var minLength = Math.Min(destByte.Length, bytes.Length);
|
||||
Unsafe.CopyBlockUnaligned(ref destByte[0], ref bytes[0], (uint)minLength);
|
||||
}
|
||||
|
||||
public abstract bool Visit(TemplateContext context, SourceSpan span, Func<object, bool> visit);
|
||||
|
||||
public abstract object Transform(TemplateContext context, SourceSpan span, Func<object, object> apply, Type destType);
|
||||
|
||||
public static bool IsNumber(object value)
|
||||
{
|
||||
return value is KalkValue || (value != null && value.GetType().IsNumber());
|
||||
}
|
||||
|
||||
public static bool AlmostEqual(object left, object right)
|
||||
{
|
||||
if (!IsNumber(left)) throw new ArgumentOutOfRangeException(nameof(left), $"The value `{left}` is not a number");
|
||||
if (!IsNumber(right)) throw new ArgumentOutOfRangeException(nameof(left), $"The value `{right}` is not a number");
|
||||
|
||||
var leftDouble = Convert.ToDouble(left);
|
||||
var rightDouble = Convert.ToDouble(right);
|
||||
return leftDouble.AlmostEqual(rightDouble);
|
||||
}
|
||||
|
||||
public static bool AlmostEqual(double left, double right)
|
||||
{
|
||||
return left.AlmostEqual(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user