Files
FSI.BT.IR.Tools/Kalk/Kalk.Core/KalkEngine.Functions.Units.cs
Maier Stephan SI b684704bf8 Sicherung
2023-01-20 16:09:00 +01:00

270 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Scriban.Syntax;
namespace Kalk.Core
{
public partial class KalkEngine
{
public const string CategoryUnits = "Unit Functions";
/// <summary>
/// If used in an expression, returns an object containing all units defined.
/// Otherwise it will display units in a friendly format.
/// By default, no units are defined. You can define units by using the `unit` function
/// and you can also import predefined units or currencies via `import StandardUnits` or
/// `import Currencies`.
/// </summary>
/// <example>
/// ```kalk
/// >>> unit(tomato, "A tomato unit", prefix: "decimal")
/// # unit(tomato, "A tomato unit", prefix: "decimal")
/// out = tomato
/// >>> unit(ketchup, "A ketchup unit", kup, 5 tomato, prefix: "decimal")
/// # unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
/// out = kup
/// >>> units
/// # User Defined Units
/// unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
/// - yottaketchup/Ykup, zettaketchup/Zkup, exaketchup/Ekup, petaketchup/Pkup, teraketchup/Tkup,
/// gigaketchup/Gkup, megaketchup/Mkup, kiloketchup/kkup, hectoketchup/hkup, decaketchup/dakup,
/// deciketchup/dkup, centiketchup/ckup, milliketchup/mkup, microketchup/µkup, nanoketchup/nkup,
/// picoketchup/pkup, femtoketchup/fkup, attoketchup/akup, zeptoketchup/zkup, yoctoketchup/ykup
///
/// unit(tomato, "A tomato unit", tomato, prefix: "decimal")
/// - yottatomato/Ytomato, zettatomato/Ztomato, exatomato/Etomato, petatomato/Ptomato,
/// teratomato/Ttomato, gigatomato/Gtomato, megatomato/Mtomato, kilotomato/ktomato,
/// hectotomato/htomato, decatomato/datomato, decitomato/dtomato, centitomato/ctomato,
/// millitomato/mtomato, microtomato/µtomato, nanotomato/ntomato, picotomato/ptomato,
/// femtotomato/ftomato, attotomato/atomato, zeptotomato/ztomato, yoctotomato/ytomato
/// ```
/// </example>
[KalkExport("units", CategoryUnits)]
public KalkUnits Units { get; }
/// <summary>
/// Converts from one value unit to a destination unit.
///
/// The pipe operator |> can be used between the src and destination unit to make it
/// more readable. Example: `105 g |> to kg`
/// </summary>
/// <param name="src">The source value with units.</param>
/// <param name="dst">The destination unit.</param>
/// <returns>The result of the calculation.</returns>
/// <example>
/// ```kalk
/// >>> import StandardUnits
/// # 1294 units successfully imported from module `StandardUnits`.
/// >>> 10 kg/s |> to kg/h
/// # ((10 * kg) / s) |> to(kg / h)
/// out = 36000 * kg / h
/// >>> 50 kg/m |> to g/km
/// # ((50 * kg) / m) |> to(g / km)
/// out = 50000000 * g / km
/// ```
/// </example>
[KalkExport("to", CategoryUnits)]
public KalkExpression ConvertTo(KalkExpression src, KalkExpression dst)
{
return src.ConvertTo(this, dst);
}
/// <summary>
/// Defines a unit with the specified name and characteristics.
/// </summary>
/// <param name="name">Long name of the unit.</param>
/// <param name="description">A description of the unit. This value is optional.</param>
/// <param name="symbol">Short name (symbol) of the unit. This value is optional.</param>
/// <param name="value">The expression value of this unit. This value is optional.</param>
/// <param name="plural">The plural name of this unit. This value is optional.</param>
/// <param name="prefix">A comma list separated of prefix kinds:
/// - "decimal": Defines the twenty prefixes for the International System of Units (SI). Example: Yotta/Y, kilo/k, milli/m...
/// - "binary": Defines the binary prefixes. See https://en.wikipedia.org/wiki/Binary_prefix. Example: kibbi/Ki, mebi/Mi...
/// - Individual prefixes:
/// Decimal prefixes:
/// - `Y` - `Yotta` (10^24)
/// - `Z` - `Zetta` (10^21)
/// - `E` - `Exa` (10^18)
/// - `P` - `Peta` (10^15)
/// - `T` - `Tera` (10^12)
/// - `G` - `Giga` (10^9)
/// - `M` - `Mega` (10^6)
/// - `k` - `kilo` (10^3)
/// - `h` - `hecto` (10^2)
/// - `da` - `deca` (10^1)
/// - `d` - `deci` (10^)-1
/// - `c` - `centi` (10^)-2
/// - `m` - `milli` (10^)-3
/// - `µ` - `micro` (10^-6)
/// - `n` - `nano` (10^)-9
/// - `p` - `pico` (10^)-12
/// - `f` - `femto` (10^)-15
/// - `a` - `atto` (10^)-18
/// - `z` - `zepto` (10^)-21
/// - `y` - `yocto` (10^)-24
///
/// Binary prefixes:
/// - `Ki` - `Kibi` (2^10)
/// - `Mi` - `Mibi` (2^20)
/// - `Gi` - `Gibi` (2^30)
/// - `Ti` - `Tibi` (2^40)
/// - `Pi` - `Pibi` (2^50)
/// - `Ei` - `Eibi` (2^60)
/// - `Zi` - `Zibi` (2^70)
/// - `Yi` - `Yibi` (2^80)
/// </param>
/// <returns>The associated unit object.</returns>
/// <example>
/// ```kalk
/// >>> unit(tomato, "A tomato unit", prefix: "decimal")
/// # unit(tomato, "A tomato unit", prefix: "decimal")
/// out = tomato
/// >>> unit(ketchup, "A ketchup unit", kup, 5 tomato, prefix: "decimal")
/// # unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
/// out = kup
/// >>> 4 kup
/// # 4 * kup
/// out = 20 * tomato
/// >>> tomato
/// unit(tomato, "A tomato unit", tomato, prefix: "decimal")
/// - yottatomato/Ytomato, zettatomato/Ztomato, exatomato/Etomato, petatomato/Ptomato,
/// teratomato/Ttomato, gigatomato/Gtomato, megatomato/Mtomato, kilotomato/ktomato,
/// hectotomato/htomato, decatomato/datomato, decitomato/dtomato, centitomato/ctomato,
/// millitomato/mtomato, microtomato/µtomato, nanotomato/ntomato, picotomato/ptomato,
/// femtotomato/ftomato, attotomato/atomato, zeptotomato/ztomato, yoctotomato/ytomato
/// >>> ketchup
/// unit(ketchup, "A ketchup unit", kup, 5 * tomato, prefix: "decimal")
/// - yottaketchup/Ykup, zettaketchup/Zkup, exaketchup/Ekup, petaketchup/Pkup, teraketchup/Tkup,
/// gigaketchup/Gkup, megaketchup/Mkup, kiloketchup/kkup, hectoketchup/hkup, decaketchup/dakup,
/// deciketchup/dkup, centiketchup/ckup, milliketchup/mkup, microketchup/µkup, nanoketchup/nkup,
/// picoketchup/pkup, femtoketchup/fkup, attoketchup/akup, zeptoketchup/zkup, yoctoketchup/ykup
/// ```
/// </example>
///
[KalkExport("unit", CategoryUnits)]
public KalkExpression DefineUserUnit(ScriptVariable name, string description = null, ScriptVariable symbol = null, KalkExpression value = null, string plural = null, string prefix = null)
{
if (name == null || string.IsNullOrEmpty(name.Name)) throw new ArgumentNullException(nameof(name));
return RegisterUnit(new KalkUnit(name.Name), description, symbol?.Name, value, plural, prefix, isUser: true);
}
public KalkExpression RegisterUnit(KalkUnit unit, string description = null, string symbol = null, KalkExpression value = null, string plural = null, string prefix = null, bool isUser = false)
{
if (unit == null) throw new ArgumentNullException(nameof(unit));
var name = unit.Name;
symbol ??= name;
// Override isUser
if (_registerAsSystem) isUser = false;
CheckVariableAvailable(name, nameof(name), false);
var prefixList = new List<KalkUnitPrefix>();
if (prefix != null)
{
var prefixes = prefix.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
foreach(var prefixItem in prefixes)
{
if (prefixItem == "decimal")
{
prefixList.AddRange(KalkUnitPrefix.GetDecimals());
}
else if (prefixItem == "binary")
{
prefixList.AddRange(KalkUnitPrefix.GetBinaries());
}
else if (KalkUnitPrefix.TryGet(prefixItem, out var descriptor))
{
prefixList.Add(descriptor);
}
else
{
throw new ArgumentException($"The prefix `{prefixItem}` does not exist.", nameof(prefix));
}
}
prefixList = prefixList.Distinct().ToList();
}
// Pre-check all prefix with name/symbol
foreach (var prefixDesc in prefixList)
{
var prefixWithName = $"{prefixDesc.Name}{name}";
CheckVariableAvailable(prefixWithName, nameof(name), false);
var prefixWithSymbol = $"{prefixDesc.Prefix}{symbol}";
CheckVariableAvailable(prefixWithSymbol, nameof(name), false);
}
unit.Description = description;
unit.Symbol = symbol;
unit.Value = value;
unit.IsUser = isUser;
unit.Prefix = prefix;
if (plural != null)
{
unit.Plural = plural;
}
if (unit.Symbol != unit.Name)
{
CheckVariableAvailable(unit.Symbol, nameof(symbol), false);
}
if (unit.Plural != unit.Name)
{
CheckVariableAvailable(unit.Plural, nameof(plural), false);
}
// Here we are all done after checking everything
Units.Add(name, unit);
if (unit.Symbol != unit.Name)
{
Units.Add(unit.Symbol, unit);
}
if (unit.Plural != unit.Name)
{
Units.Add(unit.Plural, unit);
}
// Register prefixes
foreach (var prefixDesc in prefixList)
{
var prefixWithName = $"{prefixDesc.Name}{unit.Name}";
var prefixWithSymbol = $"{prefixDesc.Prefix}{unit.Symbol}";
var unitPrefix = new KalkUnit(prefixWithName)
{
Description = description,
Symbol = prefixWithSymbol,
Value = new KalkBinaryExpression(Math.Pow(prefixDesc.Base, prefixDesc.Exponent), ScriptBinaryOperator.Multiply, unit),
IsUser = isUser,
Parent = unit,
};
unit.Derived.Add(unitPrefix);
Units.Add(prefixWithName, unitPrefix);
Units.Add(prefixWithSymbol, unitPrefix);
}
return unit;
}
private void CheckVariableAvailable(string name, string nameOf, bool prefix)
{
if (Units.ContainsKey(name))
{
throw new ArgumentException(prefix ? $"The name with prefix `{name}` is already used by another unit." : $"The name `{name}` is already used by another unit.", nameOf);
}
if (Builtins.ContainsKey(name))
{
throw new ArgumentException(prefix ? $"The name with prefix `{name}` is already used a builtin variable or function." : $"The name `{name}` is already used a builtin variable or function.", nameOf);
}
}
}
}