Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-20 16:09:00 +01:00
parent e5257d8413
commit b684704bf8
139 changed files with 95678 additions and 499 deletions

View File

@@ -0,0 +1,7 @@
namespace Kalk.Core
{
public interface IKalkDisplayable
{
void Display(KalkEngine engine, KalkDisplayMode mode);
}
}

View 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();
}
}
}

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

View 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.");
}
}
}

View 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);
}
}
}

View 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);
}
}
}

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

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

View 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());
}
}
}

View 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);
}
}
}

View 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");
}
}

View 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);
}
}

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

View 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);
}
}
}

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

View 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();
}
}
}

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

View 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));
}
}
}

View 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);
}
}
}
}

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

View 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);
}
}
}