1582 lines
64 KiB
C#
1582 lines
64 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using Kalk.Core.Helpers;
|
|
using Scriban;
|
|
using Scriban.Functions;
|
|
using Scriban.Helpers;
|
|
using Scriban.Runtime;
|
|
using Scriban.Syntax;
|
|
|
|
namespace Kalk.Core
|
|
{
|
|
/// <summary>
|
|
/// Misc module (builtin).
|
|
/// </summary>
|
|
public sealed partial class MiscModule : KalkModuleWithFunctions
|
|
{
|
|
public const string CategoryMisc = "Misc Functions";
|
|
|
|
public static readonly Encoding EncodingExtendedAscii = CodePagesEncodingProvider.Instance.GetEncoding(1252);
|
|
|
|
public MiscModule()
|
|
{
|
|
IsBuiltin = true;
|
|
DateObject = new DateTimeFunctions {Format = "%D %T"};
|
|
RegisterFunctionsAuto();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The date object used by the <see cref="Date"/> function.
|
|
/// </summary>
|
|
public DateTimeFunctions DateObject { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the current date, parse the input date or return the date object, depending on use cases.
|
|
/// - If this function doesn't have any parameter and is not used to index a member, it returns the current date. It is equivalent of `date.now`
|
|
///
|
|
/// The return date object has the following properties:
|
|
///
|
|
/// | Name | Description
|
|
/// |-------------- |-----------------
|
|
/// | `.year` | Gets the year of a date object
|
|
/// | `.month` | Gets the month of a date object
|
|
/// | `.day` | Gets the day in the month of a date object
|
|
/// | `.day_of_year` | Gets the day within the year
|
|
/// | `.hour` | Gets the hour of the date object
|
|
/// | `.minute` | Gets the minute of the date object
|
|
/// | `.second` | Gets the second of the date object
|
|
/// | `.millisecond` | Gets the millisecond of the date object
|
|
///
|
|
/// - If this function has a string parameter, it will try to parse the string as a date
|
|
/// - Otherwise, if this function provides the following sub functions and members:
|
|
/// - `date.add_days`: Example `'2016/01/05' |> date |> date.add_days 1`
|
|
/// - `date.add_months`: Example `'2016/01/05' |> date |> date.add_months 1`
|
|
/// - `date.add_years`: Example `'2016/01/05' |> date |> date.add_years 1`
|
|
/// - `date.add_hours`
|
|
/// - `date.add_minutes`
|
|
/// - `date.add_seconds`
|
|
/// - `date.add_milliseconds`
|
|
/// - `date.to_string`: Converts a datetime object to a textual representation using the specified format string.
|
|
///
|
|
/// By default, if you are using a date, it will use the format specified by `date.format` which defaults to `date.default_format` (readonly) which default to `%d %b %Y`
|
|
///
|
|
/// You can override the format used for formatting all dates by assigning the a new format: `date.format = '%a %b %e %T %Y';`
|
|
///
|
|
/// You can recover the default format by using `date.format = date.default_format;`
|
|
///
|
|
/// By default, the to_string format is using the **current culture**, but you can switch to an invariant culture by using the modifier `%g`
|
|
///
|
|
/// For example, using `%g %d %b %Y` will output the date using an invariant culture.
|
|
///
|
|
/// If you are using `%g` alone, it will output the date with `date.format` using an invariant culture.
|
|
///
|
|
/// Suppose that `date.now` would return the date `2013-09-12 22:49:27 +0530`, the following table explains the format modifiers:
|
|
///
|
|
/// | Format | Result | Description
|
|
/// |--------|-------------------|--------------------------------------------
|
|
/// | `"%a"` | `"Thu"` | Name of week day in short form of the
|
|
/// | `"%A"` | `"Thursday"` | Week day in full form of the time
|
|
/// | `"%b"` | `"Sep"` | Month in short form of the time
|
|
/// | `"%B"` | `"September"` | Month in full form of the time
|
|
/// | `"%c"` | | Date and time (%a %b %e %T %Y)
|
|
/// | `"%C"` | `"20"` | Century of the time
|
|
/// | `"%d"` | `"12"` | Day of the month of the time
|
|
/// | `"%D"` | `"09/12/13"` | Date (%m/%d/%y)
|
|
/// | `"%e"` | `"12"` | Day of the month, blank-padded ( 1..31)
|
|
/// | `"%F"` | `"2013-09-12"` | ISO 8601 date (%Y-%m-%d)
|
|
/// | `"%h"` | `"Sep"` | Alias for %b
|
|
/// | `"%H"` | `"22"` | Hour of the time in 24 hour clock format
|
|
/// | `"%I"` | `"10"` | Hour of the time in 12 hour clock format
|
|
/// | `"%j"` | `"255"` | Day of the year (001..366) (3 digits, left padded with zero)
|
|
/// | `"%k"` | `"22"` | Hour of the time in 24 hour clock format, blank-padded ( 0..23)
|
|
/// | `"%l"` | `"10"` | Hour of the time in 12 hour clock format, blank-padded ( 0..12)
|
|
/// | `"%L"` | `"000"` | Millisecond of the time (3 digits, left padded with zero)
|
|
/// | `"%m"` | `"09"` | Month of the time
|
|
/// | `"%M"` | `"49"` | Minutes of the time (2 digits, left padded with zero e.g 01 02)
|
|
/// | `"%n"` | | Newline character (\n)
|
|
/// | `"%N"` | `"000000000"` | Nanoseconds of the time (9 digits, left padded with zero)
|
|
/// | `"%p"` | `"PM"` | Gives AM / PM of the time
|
|
/// | `"%P"` | `"pm"` | Gives am / pm of the time
|
|
/// | `"%r"` | `"10:49:27 PM"` | Long time in 12 hour clock format (%I:%M:%S %p)
|
|
/// | `"%R"` | `"22:49"` | Short time in 24 hour clock format (%H:%M)
|
|
/// | `"%s"` | | Number of seconds since 1970-01-01 00:00:00 +0000
|
|
/// | `"%S"` | `"27"` | Seconds of the time
|
|
/// | `"%t"` | | Tab character (\t)
|
|
/// | `"%T"` | `"22:49:27"` | Long time in 24 hour clock format (%H:%M:%S)
|
|
/// | `"%u"` | `"4"` | Day of week of the time (from 1 for Monday to 7 for Sunday)
|
|
/// | `"%U"` | `"36"` | Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)
|
|
/// | `"%v"` | `"12-SEP-2013"` | VMS date (%e-%b-%Y) (culture invariant)
|
|
/// | `"%V"` | `"37"` | Week number of the current year according to ISO 8601 (01..53)
|
|
/// | `"%W"` | `"36"` | Week number of the current year, starting with the first Monday as the first day of the first week (00..53)
|
|
/// | `"%w"` | `"4"` | Day of week of the time (from 0 for Sunday to 6 for Saturday)
|
|
/// | `"%x"` | | Preferred representation for the date alone, no time
|
|
/// | `"%X"` | | Preferred representation for the time alone, no date
|
|
/// | `"%y"` | `"13"` | Gives year without century of the time
|
|
/// | `"%Y"` | `"2013"` | Year of the time
|
|
/// | `"%Z"` | `"IST"` | Gives Time Zone of the time
|
|
/// | `"%%"` | `"%"` | Output the character `%`
|
|
///
|
|
/// Note that the format is using a good part of the ruby format ([source](http://apidock.com/ruby/DateTime/strftime))
|
|
/// </summary>
|
|
/// <returns>The current date, parse the input date or return the date object, depending on use cases.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> today = date
|
|
/// # today = date
|
|
/// today = 11/22/20 10:13:00
|
|
/// >>> today.year
|
|
/// # today.year
|
|
/// out = 2_020
|
|
/// >>> today.month
|
|
/// # today.month
|
|
/// out = 11
|
|
/// >>> "30 Nov 2020" |> date
|
|
/// # "30 Nov 2020" |> date
|
|
/// out = 11/30/20 00:00:00
|
|
/// >>> out |> date.add_days 4
|
|
/// # out |> date.add_days(4)
|
|
/// out = 12/04/20 00:00:00
|
|
/// >>> date.format = "%F"
|
|
/// >>> date
|
|
/// # date
|
|
/// out = 2020-11-22
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("date", CategoryMisc)]
|
|
public object Date(string date = null)
|
|
{
|
|
if (!string.IsNullOrEmpty(date))
|
|
{
|
|
return DateTimeFunctions.Parse(Engine, date);
|
|
}
|
|
|
|
// If we are used in the context an expression that doesn't use a member of the date object
|
|
// then return the current date
|
|
var parentNode = Engine.CurrentNode.Parent;
|
|
if (parentNode != null && !(parentNode is ScriptMemberExpression || parentNode is ScriptIndexerExpression))
|
|
{
|
|
// In testing, we return a fake method
|
|
return Engine.IsTesting ? new DateTime(2020, 11, 22, 10, 13, 00) : DateTimeFunctions.Now();
|
|
}
|
|
|
|
return DateObject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prints the ascii table or convert an input string to an ascii array, or an ascii array to a string.
|
|
/// </summary>
|
|
/// <param name="obj">An optional input (string or array of numbers or directly an integer).</param>
|
|
/// <returns>Depending on the input:
|
|
/// - If no input, it will display the ascii table
|
|
/// - If the input is an integer, it will convert it to the equivalent ascii char.
|
|
/// - If the input is a string, it will convert the string to a byte buffer containing the corresponding ascii bytes.
|
|
/// - If the input is an array of integer, it will convert each element to the equivalent ascii char.
|
|
/// </returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> ascii 65
|
|
/// # ascii(65)
|
|
/// out = "A"
|
|
/// >>> ascii 97
|
|
/// # ascii(97)
|
|
/// out = "a"
|
|
/// >>> ascii "A"
|
|
/// # ascii("A")
|
|
/// out = 65
|
|
/// >>> ascii "kalk"
|
|
/// # ascii("kalk")
|
|
/// out = bytebuffer([107, 97, 108, 107])
|
|
/// >>> ascii out
|
|
/// # ascii(out)
|
|
/// out = "kalk"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("ascii", CategoryMisc)]
|
|
public object Ascii(object obj = null)
|
|
{
|
|
if (obj == null && Engine.CurrentNode.Parent is ScriptExpressionStatement)
|
|
{
|
|
var builder = new StringBuilder();
|
|
|
|
const int alignControls = -38;
|
|
const int alignStandard = 13;
|
|
const int columnWidth = 3 + 4 + 1;
|
|
|
|
for (int y = 0; y < 32; y++)
|
|
{
|
|
builder.Length = 0;
|
|
for (int x = 0; x < 8; x++)
|
|
{
|
|
var c = x * 32 + y;
|
|
if (x > 0) builder.Append(" ");
|
|
|
|
var index = $"{c,3}";
|
|
var valueAsString = StringFunctions.Escape(ConvertAscii(c));
|
|
if (char.GetUnicodeCategory(valueAsString[0]) == UnicodeCategory.Control)
|
|
{
|
|
valueAsString = "?";
|
|
}
|
|
var strValue = $"\"{valueAsString}\"";
|
|
var column = x == 0 ? $"{index} {strValue,-6} {$"({AsciiSpecialCodes[y]})",-27}" : $"{index} {strValue,-4}";
|
|
|
|
OutputColumn(builder, x, column);
|
|
}
|
|
|
|
if (y == 0)
|
|
{
|
|
Engine.WriteHighlightLine($" {"ASCII controls",alignControls} {"ASCII printable characters",-(columnWidth * 2 + alignStandard + 1)} {"Extended ASCII Characters"}");
|
|
}
|
|
|
|
Engine.WriteHighlightLine(builder.ToString());
|
|
}
|
|
|
|
void OutputColumn(StringBuilder output, int columnIndex, string text)
|
|
{
|
|
output.Append(columnIndex == 0 ? $"{text,-alignControls}" : columnIndex == 3 ? $"{text,-alignStandard}" : $"{text}");
|
|
}
|
|
|
|
|
|
return null;
|
|
}
|
|
|
|
// Otherwise convert the argument.
|
|
return ConvertAscii(Engine, obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process each element of the input array with the specified function.
|
|
/// </summary>
|
|
/// <param name="list">A list of element to process.</param>
|
|
/// <param name="func">A reference to a function that takes 1 parameters and return a value. The function must be passed via the prefix @ to pass a function pointer.</param>
|
|
/// <returns>The value transformed.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> foreach([1, 2, 3, 4], @hex)
|
|
/// # foreach([1, 2, 3, 4], @hex)
|
|
/// out = ["01", "02", "03", "04"]
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("foreach", CategoryMisc)]
|
|
public object Foreach(TemplateContext context, IEnumerable list, IScriptCustomFunction func)
|
|
{
|
|
if (list is null) return null;
|
|
if (func is null) return list;
|
|
var array = new ScriptArray();
|
|
var args = new ScriptArray(1);
|
|
foreach (var item in list)
|
|
{
|
|
args[0] = item;
|
|
var result = ScriptFunctionCall.Call(context, context.CurrentNode, func, args);
|
|
array.Add(result);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the keys of the specified object.
|
|
/// </summary>
|
|
/// <param name="obj">An object to get the keys from.</param>
|
|
/// <returns>The keys of the parameter obj.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> obj = {m: 1, n: 2}; keys obj
|
|
/// # obj = {m: 1, n: 2}; keys(obj)
|
|
/// obj = {m: 1, n: 2}
|
|
/// out = ["m", "n"]
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("keys", CategoryMisc)]
|
|
public IEnumerable Keys(object obj)
|
|
{
|
|
return ObjectFunctions.Keys(Engine, obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new GUID as a string.
|
|
/// </summary>
|
|
/// <returns>A new GUID as a string.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> guid
|
|
/// # guid
|
|
/// out = "0deafe30-de4d-47c3-9631-2d3292afbb8e"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("guid", CategoryMisc)]
|
|
public string Guid()
|
|
{
|
|
return Engine.IsTesting ? "0deafe30-de4d-47c3-9631-2d3292afbb8e" : System.Guid.NewGuid().ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the size of the specified object.
|
|
/// </summary>
|
|
/// <param name="obj">The object value.</param>
|
|
/// <returns>The size of the object.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> size 1
|
|
/// # size(1)
|
|
/// out = 0
|
|
/// >>> size "kalk"
|
|
/// # size("kalk")
|
|
/// out = 4
|
|
/// >>> size float4(1,2,3,4)
|
|
/// # size(float4(1, 2, 3, 4))
|
|
/// out = 4
|
|
/// >>> size [1, 2, 3]
|
|
/// # size([1, 2, 3])
|
|
/// out = 3
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("size", CategoryMisc)]
|
|
public int Size(object obj)
|
|
{
|
|
return ObjectFunctions.Size(obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the values of the specified object.
|
|
/// </summary>
|
|
/// <param name="obj">An object to get the values from.</param>
|
|
/// <returns>The values of the parameter obj.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> obj = {m: 1, n: 2}; values obj
|
|
/// # obj = {m: 1, n: 2}; values(obj)
|
|
/// obj = {m: 1, n: 2}
|
|
/// out = [1, 2]
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("values", CategoryMisc)]
|
|
public IEnumerable Values(TemplateContext context, object obj)
|
|
{
|
|
switch (obj)
|
|
{
|
|
case IDictionary<string, object> dict:
|
|
return ObjectFunctions.Values(context, dict);
|
|
case IEnumerable list:
|
|
return new ScriptArray(list);
|
|
default:
|
|
return new ScriptArray() {obj};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an integral/bytebuffer input to an hexadecimal representation or convert an hexadecimal input string
|
|
/// to an integral/bytebuffer representation.
|
|
/// </summary>
|
|
/// <param name="value">The input value.</param>
|
|
/// <param name="prefix">Output the prefix `0x` in front of each hexadecimal bytes when converting
|
|
/// from integral to hexadecimal.</param>
|
|
/// <param name="separator">The character used to separate hexadecimal bytes when converting
|
|
/// from integral to hexadecimal.</param>
|
|
/// <returns>The hexadecimal representation of the input or convert the hexadecimal input string
|
|
/// to an integral representation.</returns>
|
|
/// <remarks> When converting from a hexadecimal string to an integral representation, this method
|
|
/// will skip any white-space characters, comma `,`, colon `:`, semi-colon `;`, underscore `_` and
|
|
/// dash `-`.
|
|
/// When the hexadecimal input string can be converted to an integral less than or equal 8 bytes (64 bits)
|
|
/// it will convert it to a single integral result, otherwise it will convert to a bytebuffer.
|
|
/// See the following examples.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> hex 10
|
|
/// # hex(10)
|
|
/// out = "0A"
|
|
/// >>> hex "12c"
|
|
/// # hex("12c")
|
|
/// out = 300
|
|
/// >>> hex "0a"
|
|
/// # hex("0a")
|
|
/// out = 10
|
|
/// >>> hex "0xff030201"
|
|
/// # hex("0xff030201")
|
|
/// out = 4_278_387_201
|
|
/// >>> hex out
|
|
/// # hex(out)
|
|
/// out = "01 02 03 FF"
|
|
/// >>> hex "01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F"
|
|
/// # hex("01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F")
|
|
/// out = bytebuffer([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
|
/// >>> hex(out, true, ",")
|
|
/// # hex(out, true, ",")
|
|
/// out = "0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F"
|
|
/// >>> hex out
|
|
/// # hex(out)
|
|
/// out = bytebuffer([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
|
|
/// >>> hex("1a,2b;3c 4d-5e_6f")
|
|
/// # hex("1a,2b;3c 4d-5e_6f")
|
|
/// out = 103_832_130_169_626
|
|
/// >>> hex out
|
|
/// # hex(out)
|
|
/// out = "1A 2B 3C 4D 6F 5E 00 00"
|
|
/// >>> hex float4(1,2,3,4)
|
|
/// # hex(float4(1, 2, 3, 4))
|
|
/// out = "00 00 80 3F 00 00 00 40 00 00 40 40 00 00 80 40"
|
|
/// ```
|
|
/// </example>
|
|
/// <test>
|
|
/// ```kalk
|
|
/// >>> hex short(12345)
|
|
/// # hex(short(12345))
|
|
/// out = "39 30"
|
|
/// >>> hex int (12345789)
|
|
/// # hex(int(12345789))
|
|
/// out = "BD 61 BC 00"
|
|
/// ```
|
|
/// </test>
|
|
[KalkExport("hex", CategoryMisc)]
|
|
public object Hexadecimal(object value, bool prefix = false, string separator = " ")
|
|
{
|
|
return Hexadecimal(value, prefix, separator, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an integral/bytebuffer input to a binary representation or convert a binary input string
|
|
/// to an integral/bytebuffer representation.
|
|
/// </summary>
|
|
/// <param name="value">The input value.</param>
|
|
/// <param name="prefix">Output the prefix `0x` in front of each binary bytes when converting
|
|
/// from integral to binary.</param>
|
|
/// <param name="separator">The character used to separate binary bytes when converting
|
|
/// from integral to binary.</param>
|
|
/// <returns>The binary representation of the input or convert the binary input string
|
|
/// to an integral representation.</returns>
|
|
/// <remarks> When converting from a binary string to an integral representation, this method
|
|
/// will skip any white-space characters, comma `,`, colon `:`, semi-colon `;`, underscore `_` and
|
|
/// dash `-`.
|
|
/// When the binary input string can be converted to an integral less than or equal 8 bytes (64 bits)
|
|
/// it will convert it to a single integral result, otherwise it will convert to a bytebuffer.
|
|
/// See the following examples.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> bin 13
|
|
/// # bin(13)
|
|
/// out = "00001101 00000000 00000000 00000000"
|
|
/// >>> bin out
|
|
/// # bin(out)
|
|
/// out = 13
|
|
/// >>> bin "111111111011"
|
|
/// # bin("111111111011")
|
|
/// out = 4_091
|
|
/// >>> bin 0xff030201
|
|
/// # bin(-16580095)
|
|
/// out = "00000001 00000010 00000011 11111111"
|
|
/// >>> bin out
|
|
/// # bin(out)
|
|
/// out = 4_278_387_201
|
|
/// >>> bin "11111111000000110000001000000001"
|
|
/// # bin("11111111000000110000001000000001")
|
|
/// out = 4_278_387_201
|
|
/// >>> bin(byte(5))
|
|
/// # bin(byte(5))
|
|
/// out = "00000101"
|
|
/// >>> bin(long(6))
|
|
/// # bin(long(6))
|
|
/// out = "00000110 00000000 00000000 00000000 00000000 00000000 00000000 00000000"
|
|
/// >>> bin(out)
|
|
/// # bin(out)
|
|
/// out = 6
|
|
/// >>> kind(out)
|
|
/// # kind(out)
|
|
/// out = "long"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("bin", CategoryMisc)]
|
|
public object Binary(object value, bool prefix = false, string separator = " ")
|
|
{
|
|
return Binary(value, prefix, separator, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a string to an UTF8 bytebuffer or convert a bytebuffer of UTF8 bytes to a string.
|
|
/// </summary>
|
|
/// <param name="value">The specified input.</param>
|
|
/// <returns>The UTF8 bytebuffer representation of the input string or the string representation of the input UTF8 bytebuffer.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> utf8 "kalk"
|
|
/// # utf8("kalk")
|
|
/// out = bytebuffer([107, 97, 108, 107])
|
|
/// >>> utf8 out
|
|
/// # utf8(out)
|
|
/// out = "kalk"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("utf8", CategoryMisc)]
|
|
public object GetUtf8(object value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case string str:
|
|
{
|
|
var buffer = Encoding.UTF8.GetBytes(str);
|
|
return KalkNativeBuffer.AsBytes(buffer.Length, in buffer[0]);
|
|
}
|
|
case IEnumerable it:
|
|
{
|
|
var bytes = new MemoryStream();
|
|
foreach (var b in it)
|
|
{
|
|
bytes.WriteByte(Engine.ToObject<byte>(0, b));
|
|
}
|
|
return Encoding.UTF8.GetString(bytes.GetBuffer(), 0, (int) bytes.Length);
|
|
}
|
|
default:
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(value)} is not supported ", nameof(value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a string to an UTF16 bytebuffer or convert a bytebuffer of UTF16 bytes to a string.
|
|
/// </summary>
|
|
/// <param name="value">The specified input.</param>
|
|
/// <returns>The UTF16 bytebuffer representation of the input string or the string representation of the input UTF16 bytebuffer.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> utf16 "kalk"
|
|
/// # utf16("kalk")
|
|
/// out = bytebuffer([107, 0, 97, 0, 108, 0, 107, 0])
|
|
/// >>> utf16 out
|
|
/// # utf16(out)
|
|
/// out = "kalk"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("utf16", CategoryMisc)]
|
|
public object GetUtf16(object value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case string str:
|
|
{
|
|
unsafe
|
|
{
|
|
fixed (void* pBuffer = str)
|
|
{
|
|
return KalkNativeBuffer.AsBytes(str.Length * 2, in *(byte*)pBuffer);
|
|
}
|
|
}
|
|
}
|
|
case IEnumerable it:
|
|
{
|
|
var bytes = new MemoryStream();
|
|
foreach (var b in it)
|
|
{
|
|
|
|
bytes.WriteByte(Engine.ToObject<byte>(0, b));
|
|
}
|
|
unsafe
|
|
{
|
|
fixed (void* pBuffer = bytes.GetBuffer())
|
|
return new string((char*) pBuffer, 0, (int) bytes.Length / 2);
|
|
}
|
|
}
|
|
default:
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(value)} is not supported ", nameof(value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a string to an UTF32 bytebuffer or convert a bytebuffer of UTF32 bytes to a string.
|
|
/// </summary>
|
|
/// <param name="value">The specified input.</param>
|
|
/// <returns>The UTF32 bytebuffer representation of the input string or the string representation of the input UTF32 bytebuffer.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> utf32 "kalk"
|
|
/// # utf32("kalk")
|
|
/// out = bytebuffer([107, 0, 0, 0, 97, 0, 0, 0, 108, 0, 0, 0, 107, 0, 0, 0])
|
|
/// >>> utf32 out
|
|
/// # utf32(out)
|
|
/// out = "kalk"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("utf32", CategoryMisc)]
|
|
public object GetUtf32(object value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case string str:
|
|
{
|
|
var buffer = Encoding.UTF32.GetBytes(str);
|
|
return KalkNativeBuffer.AsBytes(buffer.Length, in buffer[0]);
|
|
}
|
|
case IEnumerable it:
|
|
{
|
|
var bytes = new MemoryStream();
|
|
foreach (var b in it)
|
|
{
|
|
bytes.WriteByte(Engine.ToObject<byte>(0, b));
|
|
}
|
|
return Encoding.UTF32.GetString(bytes.GetBuffer(), 0, (int)bytes.Length);
|
|
}
|
|
default:
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(value)} is not supported ", nameof(value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts an item into a string or list at the specified index.
|
|
/// </summary>
|
|
/// <param name="list">A string or list to insert an item into.</param>
|
|
/// <param name="index">The index at which to insert the item.</param>
|
|
/// <param name="item">The item to insert.</param>
|
|
/// <returns>A new string with the item inserted, or a new list with the item inserted at the specified index.</returns>
|
|
/// <remarks>The index is adjusted at the modulo of the length of the input value.
|
|
/// If the index is < 0, then the index starts from the end of the string/list length + 1. A value of -1 for the index would insert the item at the end, after the last element of the string or list.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> insert_at("kalk", 0, "YES")
|
|
/// # insert_at("kalk", 0, "YES")
|
|
/// out = "YESkalk"
|
|
/// >>> insert_at("kalk", -1, "YES")
|
|
/// # insert_at("kalk", -1, "YES")
|
|
/// out = "kalkYES"
|
|
/// >>> insert_at(0..10, 1, 50)
|
|
/// # insert_at(0..10, 1, 50)
|
|
/// out = [0, 50, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
/// >>> insert_at(0..9, 21, 50) # final index is 21 % 10 = 1
|
|
/// # insert_at(0..9, 21, 50) # final index is 21 % 10 = 1
|
|
/// out = [0, 50, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
/// >>> insert_at([], 3, 1)
|
|
/// # insert_at([], 3, 1)
|
|
/// out = [1]
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("insert_at", CategoryMisc)]
|
|
public object InsertAt(object list, int index, object item)
|
|
{
|
|
if (list == null) throw new ArgumentNullException(nameof(list));
|
|
if (item == null) throw new ArgumentNullException(nameof(item));
|
|
if (list is string valueStr)
|
|
{
|
|
var itemStr = Engine.ObjectToString(item);
|
|
index = valueStr.Length == 0 ? 0 : index < 0 ? valueStr.Length + (index + 1) % valueStr.Length : index % valueStr.Length;
|
|
|
|
var builder = new StringBuilder(valueStr.Substring(0, index));
|
|
builder.Append(itemStr);
|
|
if (index < valueStr.Length)
|
|
{
|
|
builder.Append(valueStr.Substring(index));
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
else if (list is KalkNativeBuffer buffer)
|
|
{
|
|
var byteItem = Engine.ToObject<byte>(2, item);
|
|
index = buffer.Count == 0 ? 0 : index < 0 ? buffer.Count + (index + 1) % buffer.Count : index % buffer.Count;
|
|
|
|
var newBuffer = new KalkNativeBuffer(buffer.Count + 1);
|
|
for (int i = 0; i < index; i++) newBuffer[i] = buffer[i];
|
|
newBuffer[index] = byteItem;
|
|
for (int i = index + 1; i < newBuffer.Count; i++) newBuffer[i] = buffer[i - 1];
|
|
return newBuffer;
|
|
}
|
|
else if (list is IList listCollection)
|
|
{
|
|
index = listCollection.Count == 0 ? 0 : index < 0 ? listCollection.Count + (index + 1) % listCollection.Count : index % listCollection.Count;
|
|
listCollection.Insert(index, item);
|
|
return listCollection;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(list)} is not supported ", nameof(list));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item from a string or list at the specified index.
|
|
/// </summary>
|
|
/// <param name="list">A string or list to remove an item from.</param>
|
|
/// <param name="index">The index at which to remove the item.</param>
|
|
/// <returns>A new string/list with the item at the specified index removed.</returns>
|
|
/// <remarks>The index is adjusted at the modulo of the length of the input value.
|
|
/// If the index is < 0, then the index starts from the end of the string/list length. A value of -1 for the index would remove the last element.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> remove_at("kalk", 0)
|
|
/// # remove_at("kalk", 0)
|
|
/// out = "alk"
|
|
/// >>> remove_at("kalk", -1)
|
|
/// # remove_at("kalk", -1)
|
|
/// out = "kal"
|
|
/// >>> remove_at(0..9, 5)
|
|
/// # remove_at(0..9, 5)
|
|
/// out = [0, 1, 2, 3, 4, 6, 7, 8, 9]
|
|
/// >>> remove_at(0..9, -1)
|
|
/// # remove_at(0..9, -1)
|
|
/// out = [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
/// >>> remove_at(asbytes(0x04030201), 1)
|
|
/// # remove_at(asbytes(67305985), 1)
|
|
/// out = bytebuffer([1, 3, 4])
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("remove_at", CategoryMisc)]
|
|
public object RemoveAt(object list, int index)
|
|
{
|
|
if (list == null) throw new ArgumentNullException(nameof(list));
|
|
if (list is string valueStr)
|
|
{
|
|
if (valueStr == string.Empty) return valueStr;
|
|
index = index < 0 ? valueStr.Length + index % valueStr.Length : index % valueStr.Length;
|
|
|
|
var builder = new StringBuilder(valueStr.Substring(0, index));
|
|
if (index < valueStr.Length)
|
|
{
|
|
builder.Append(valueStr.Substring(index + 1));
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
else if (list is KalkNativeBuffer buffer)
|
|
{
|
|
if (buffer.Count == 0) return buffer;
|
|
|
|
index = index < 0 ? buffer.Count + index % buffer.Count : index % buffer.Count;
|
|
var newBuffer = new KalkNativeBuffer(buffer.Count - 1);
|
|
|
|
for (int i = 0; i < index; i++) newBuffer[i] = buffer[i];
|
|
for (int i = index; i < newBuffer.Count; i++) newBuffer[i] = buffer[i + 1];
|
|
return newBuffer;
|
|
}
|
|
else if (list is IList listCollection)
|
|
{
|
|
if (listCollection.Count == 0) return listCollection;
|
|
|
|
index = index < 0 ? listCollection.Count + index % listCollection.Count : index % listCollection.Count;
|
|
listCollection.RemoveAt(index);
|
|
return listCollection;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(list)} is not supported ", nameof(list));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if an object (string, list, vector types, bytebuffer...) is containing the specified value.
|
|
/// </summary>
|
|
/// <param name="list">The list to search into.</param>
|
|
/// <param name="value">The value to search into the list.</param>
|
|
/// <returns>true if value was found in the list input; otherwise false.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> contains("kalk", "l")
|
|
/// # contains("kalk", "l")
|
|
/// out = true
|
|
/// >>> contains("kalk", "e")
|
|
/// # contains("kalk", "e")
|
|
/// out = false
|
|
/// >>> contains([1,2,3,4,5], 3)
|
|
/// # contains([1,2,3,4,5], 3)
|
|
/// out = true
|
|
/// >>> contains([1,2,3,4,5], 6)
|
|
/// # contains([1,2,3,4,5], 6)
|
|
/// out = false
|
|
/// >>> contains(float4(1,2,3,4), 3)
|
|
/// # contains(float4(1, 2, 3, 4), 3)
|
|
/// out = true
|
|
/// >>> contains(float4(1,2,3,4), 6)
|
|
/// # contains(float4(1, 2, 3, 4), 6)
|
|
/// out = false
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("contains", CategoryMisc)]
|
|
public KalkBool Contains(object list, object value)
|
|
{
|
|
if (list == null) throw new ArgumentNullException(nameof(list));
|
|
if (value == null) throw new ArgumentNullException(nameof(value));
|
|
if (list is string valueStr)
|
|
{
|
|
var matchStr = Engine.ObjectToString(value);
|
|
return valueStr.Contains(matchStr, StringComparison.Ordinal);
|
|
}
|
|
|
|
var composite = new KalkCompositeValue(list);
|
|
bool contains = false;
|
|
|
|
// Force the evaluation of the object
|
|
composite.Visit(Engine, Engine.CurrentSpan, input =>
|
|
{
|
|
var result = (bool) ScriptBinaryExpression.Evaluate(Engine, Engine.CurrentSpan, ScriptBinaryOperator.CompareEqual, input, value);
|
|
if (result)
|
|
{
|
|
contains = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
return contains;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reverse a list of values.
|
|
/// </summary>
|
|
/// <param name="list">The list to reverse.</param>
|
|
/// <returns>The list of values reversed.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> reverse([1,2,3,4,5])
|
|
/// # reverse([1,2,3,4,5])
|
|
/// out = [5, 4, 3, 2, 1]
|
|
/// >>> reverse("abc")
|
|
/// # reverse("abc")
|
|
/// out = "cba"
|
|
/// >>> reverse(asbytes(0x04030201))
|
|
/// # reverse(asbytes(67305985))
|
|
/// out = bytebuffer([4, 3, 2, 1])
|
|
/// ```
|
|
/// </example> remove_at(asbytes(0x04030201), 1)
|
|
[KalkExport("reverse", CategoryMisc)]
|
|
public object Reverse(object list)
|
|
{
|
|
if (list is string valueStr)
|
|
{
|
|
var builder = new StringBuilder(valueStr.Length);
|
|
for (var i = valueStr.Length - 1; i >= 0 ; i--)
|
|
{
|
|
var c = valueStr[i];
|
|
builder.Append(c);
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
|
|
if (list is KalkNativeBuffer nativeBuffer)
|
|
{
|
|
var reversedNativeBuffer = new KalkNativeBuffer(nativeBuffer.Count);
|
|
int j = 0;
|
|
for (int i = nativeBuffer.Count - 1; i >= 0; i--)
|
|
{
|
|
reversedNativeBuffer[j++] = nativeBuffer[i];
|
|
}
|
|
|
|
return reversedNativeBuffer;
|
|
}
|
|
|
|
if (list is IEnumerable it)
|
|
{
|
|
var array = new ScriptArray();
|
|
foreach (var b in it.Cast<object>().Reverse())
|
|
{
|
|
array.Add(b);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces in an object (string, list, vector types, bytebuffer...) an item of the specified value by another value.
|
|
/// </summary>
|
|
/// <param name="list">The list to search into to replace an element.</param>
|
|
/// <param name="value">The item to replace.</param>
|
|
/// <param name="by">The value to replace with.</param>
|
|
/// <returns>The modified object.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> replace("kalk", "k", "woo")
|
|
/// # replace("kalk", "k", "woo")
|
|
/// out = "wooalwoo"
|
|
/// >>> replace([1,2,3,4], 3, 5)
|
|
/// # replace([1,2,3,4], 3, 5)
|
|
/// out = [1, 2, 5, 4]
|
|
/// >>> replace(float4(1,2,3,4), 3, 5)
|
|
/// # replace(float4(1, 2, 3, 4), 3, 5)
|
|
/// out = float4(1, 2, 5, 4)
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("replace", CategoryMisc)]
|
|
public object Replace(object list, object value, object by)
|
|
{
|
|
if (list == null) throw new ArgumentNullException(nameof(list));
|
|
if (value == null) throw new ArgumentNullException(nameof(value));
|
|
if (@by == null) throw new ArgumentNullException(nameof(@by));
|
|
if (list is string valueStr)
|
|
{
|
|
var matchStr = Engine.ObjectToString(value);
|
|
var byStr = Engine.ObjectToString(by);
|
|
return valueStr.Replace(matchStr, byStr);
|
|
}
|
|
|
|
var composite = new KalkCompositeValue(list);
|
|
return composite.Transform(Engine, Engine.CurrentSpan, input => ReplaceImpl(input, value, @by), typeof(object));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a slice of an object (string, list, vector types, bytebuffer...) starting at the specified index and with the specified length;
|
|
/// </summary>
|
|
/// <param name="list">The object to create a slice from.</param>
|
|
/// <param name="index">The index into the object.</param>
|
|
/// <param name="length">The optional length of the slice. If the length is not defined, the length will start from index with the remaining elements.</param>
|
|
/// <returns>A slice of the input object.</returns>
|
|
/// <remarks>The index is adjusted at the modulo of the specified length of the input object.
|
|
/// If the index is < 0, then the index starts from the end of the input object length. A value of -1 for the index would take a slice with the only the last element.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> slice("kalk", 1)
|
|
/// # slice("kalk", 1)
|
|
/// out = "alk"
|
|
/// >>> slice("kalk", -2)
|
|
/// # slice("kalk", -2)
|
|
/// out = "lk"
|
|
/// >>> slice("kalk", 1, 2)
|
|
/// # slice("kalk", 1, 2)
|
|
/// out = "al"
|
|
/// >>> slice([1,2,3,4], 1)
|
|
/// # slice([1,2,3,4], 1)
|
|
/// out = [2, 3, 4]
|
|
/// >>> slice([1,2,3,4], -1)
|
|
/// # slice([1,2,3,4], -1)
|
|
/// out = [4]
|
|
/// >>> slice([1,2,3,4], -1, 3) # length is bigger than expected, no errors
|
|
/// # slice([1,2,3,4], -1, 3) # length is bigger than expected, no errors
|
|
/// out = [4]
|
|
/// >>> slice(asbytes(0x04030201), 1, 2)
|
|
/// # slice(asbytes(67305985), 1, 2)
|
|
/// out = slice(bytebuffer([2, 3]), 1, 2)
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("slice", CategoryMisc)]
|
|
public object Slice(object list, int index, int? length = null)
|
|
{
|
|
if (list is string str)
|
|
{
|
|
return StringFunctions.Slice(str, index, length);
|
|
}
|
|
|
|
var listCollection = list as IList;
|
|
if (listCollection == null)
|
|
{
|
|
if (list is IEnumerable it)
|
|
{
|
|
listCollection = new ScriptRange(it);
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException("The argument is not a string, bytebuffer or array.", nameof(list));
|
|
}
|
|
}
|
|
|
|
// Wrap index
|
|
index = index < 0 ? listCollection.Count + index % listCollection.Count : index % listCollection.Count;
|
|
|
|
// Compute length
|
|
length ??= listCollection.Count;
|
|
if (length < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(length), "Length must be >= 0");
|
|
}
|
|
|
|
// Trim length if required
|
|
if (index + length > listCollection.Count)
|
|
{
|
|
length = listCollection.Count - index;
|
|
}
|
|
|
|
if (list is KalkNativeBuffer nativeBuffer)
|
|
{
|
|
return nativeBuffer.Slice(index, length.Value);
|
|
}
|
|
|
|
return new ScriptRange(listCollection.Cast<object>().Skip(index).Take(length.Value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract lines from the specified string.
|
|
/// </summary>
|
|
/// <param name="text">A string to extract lines from.</param>
|
|
/// <returns>Lines extracted from the input string.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> lines("k\na\nl\nk")
|
|
/// # lines("k\na\nl\nk")
|
|
/// out = ["k", "a", "l", "k"]
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("lines", CategoryMisc)]
|
|
public ScriptRange Lines(string text)
|
|
{
|
|
if (text == null) return new ScriptRange();
|
|
return new ScriptRange(new LineReader(() => new StringReader(text)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display or returns the known CSS colors.
|
|
/// </summary>
|
|
/// <returns>Prints known CSS colors or return a list if this function is used in an expression.</returns>
|
|
/// <example>
|
|
/// ```kalk
|
|
/// >>> colors[0]
|
|
/// # colors[0]
|
|
/// out = rgb(240, 248, 255) ## F0F8FF AliceBlue ##
|
|
/// >>> mycolor = colors["AliceBlue"]; mycolor.name
|
|
/// # mycolor = colors["AliceBlue"]; mycolor.name
|
|
/// mycolor = rgb(240, 248, 255) ## F0F8FF AliceBlue ##
|
|
/// out = "AliceBlue"
|
|
/// ```
|
|
/// </example>
|
|
[KalkExport("colors", CategoryMisc)]
|
|
public object Colors()
|
|
{
|
|
var knownColorsInternal = KalkColorRgb.GetKnownColors();
|
|
if (!(Engine?.CurrentNode is ScriptVariable) || !(Engine?.CurrentNode?.Parent is ScriptExpressionStatement))
|
|
{
|
|
var colors = new ScriptArray(knownColorsInternal);
|
|
foreach (var color in knownColorsInternal)
|
|
{
|
|
colors.SetValue(color.Name, color, false);
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
int count = 0;
|
|
const int PerColumn = 2;
|
|
foreach (var knownColor in knownColorsInternal)
|
|
{
|
|
var colorName = knownColor.ToString("aligned", Engine);
|
|
builder.Append(colorName);
|
|
count++;
|
|
|
|
if (count == PerColumn)
|
|
{
|
|
Engine.WriteHighlightLine(builder.ToString());
|
|
builder.Clear();
|
|
count = 0;
|
|
}
|
|
else
|
|
{
|
|
builder.Append(" ");
|
|
}
|
|
}
|
|
|
|
if (builder.Length > 0)
|
|
{
|
|
Engine.WriteHighlightLine(builder.ToString());
|
|
builder.Clear();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private object ReplaceImpl(object value, object match, object by)
|
|
{
|
|
var result = (bool)ScriptBinaryExpression.Evaluate(Engine, Engine.CurrentSpan, ScriptBinaryOperator.CompareEqual, value, match);
|
|
return result ? @by : value;
|
|
}
|
|
|
|
private object Hexadecimal(object value, bool prefix, string separator, bool returnString)
|
|
{
|
|
Engine.CheckAbort();
|
|
switch (value)
|
|
{
|
|
case string str when returnString:
|
|
throw new ArgumentException($"Cannot convert a string to hexadecimal inside a list", nameof(value));
|
|
case string str:
|
|
{
|
|
var array = new List<byte>();
|
|
var temp = new List<byte>();
|
|
int count = 0;
|
|
bool hasPrefix = false;
|
|
int hexa = 0;
|
|
|
|
void FlushBytes()
|
|
{
|
|
bool isOdd = count > 0;
|
|
if (isOdd)
|
|
{
|
|
temp.Add((byte)(hexa << 4));
|
|
}
|
|
count = 0;
|
|
|
|
if (temp.Count > 0)
|
|
{
|
|
if (isOdd)
|
|
{
|
|
for (int j = temp.Count - 1; j >= 0; j--)
|
|
{
|
|
var hb = j - 1 >= 0 ? (temp[j - 1] & 0xF) << 4 : 0;
|
|
var lb = temp[j] >> 4;
|
|
var fullByte = (byte)(hb | lb);
|
|
array.Add(fullByte);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = temp.Count - 1; j >= 0; j--)
|
|
{
|
|
array.Add(temp[j]);
|
|
}
|
|
}
|
|
|
|
temp.Clear();
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < str.Length; i++)
|
|
{
|
|
var c = str[i];
|
|
if (char.IsWhiteSpace(c) || c == ',' || c == ':' || c == ';' || c == '_' || c == '-')
|
|
{
|
|
if (c != '_')
|
|
{
|
|
FlushBytes();
|
|
}
|
|
hasPrefix = c == '_' && hasPrefix; // reset prefix after parsing but not for `_`
|
|
continue;
|
|
}
|
|
|
|
// Skip 0x prefix
|
|
if (!hasPrefix && c == '0' && i + 1 < str.Length && str[i + 1] == 'x')
|
|
{
|
|
FlushBytes();
|
|
hasPrefix = true;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
//if (c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c )
|
|
if (CharHelper.TryHexaToInt(c, out var n))
|
|
{
|
|
// TODO: Add parsing for 0x00
|
|
hexa = (hexa << 4) | n;
|
|
if (count == 1)
|
|
{
|
|
temp.Add((byte)hexa);
|
|
hexa = 0;
|
|
count = 0;
|
|
}
|
|
else
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException($"Invalid character found `{StringFunctions.Escape(c.ToString())}`. Expecting only hexadecimal.", nameof(value));
|
|
}
|
|
}
|
|
|
|
FlushBytes();
|
|
|
|
if (array.Count <= 8)
|
|
{
|
|
long ulongValue = 0;
|
|
unsafe
|
|
{
|
|
for (int i = 0; i < array.Count; i++)
|
|
{
|
|
((byte*)&ulongValue)[i] = array[i];
|
|
}
|
|
}
|
|
return ulongValue;
|
|
}
|
|
|
|
return new KalkNativeBuffer(array);
|
|
}
|
|
case byte vbyte:
|
|
return HexaFromBytes(1, vbyte, prefix, separator);
|
|
case sbyte vsbyte:
|
|
return HexaFromBytes(1, vsbyte, prefix, separator);
|
|
case short vshort:
|
|
{
|
|
int size = 2;
|
|
if (vshort >= sbyte.MinValue && vshort <= byte.MaxValue) size = 1;
|
|
return HexaFromBytes(size, vshort, prefix, separator);
|
|
}
|
|
case ushort vushort:
|
|
{
|
|
int size = 2;
|
|
if (vushort <= byte.MaxValue) size = 1;
|
|
return HexaFromBytes(size, vushort, prefix, separator);
|
|
}
|
|
case int vint:
|
|
{
|
|
int size = 4;
|
|
if (vint >= sbyte.MinValue && vint <= byte.MaxValue) size = 1;
|
|
else if (vint >= short.MinValue && vint <= ushort.MaxValue) size = 2;
|
|
return HexaFromBytes(size, vint, prefix, separator);
|
|
}
|
|
case uint vuint:
|
|
{
|
|
int size = 4;
|
|
if (vuint <= byte.MaxValue) size = 1;
|
|
else if (vuint <= ushort.MaxValue) size = 2;
|
|
return HexaFromBytes(size, vuint, prefix, separator);
|
|
}
|
|
case long vlong:
|
|
{
|
|
int size = 8;
|
|
if (vlong >= sbyte.MinValue && vlong <= byte.MaxValue) size = 1;
|
|
else if (vlong >= short.MinValue && vlong <= ushort.MaxValue) size = 2;
|
|
else if (vlong >= int.MinValue && vlong <= uint.MaxValue) size = 4;
|
|
return HexaFromBytes(size, vlong, prefix, separator);
|
|
}
|
|
case ulong vulong:
|
|
{
|
|
int size = 8;
|
|
if (vulong <= byte.MaxValue) size = 1;
|
|
else if (vulong <= ushort.MaxValue) size = 2;
|
|
else if (vulong <= uint.MaxValue) size = 4;
|
|
return HexaFromBytes(size, vulong, prefix, separator);
|
|
}
|
|
case float vfloat:
|
|
{
|
|
var floatAsInt = BitConverter.SingleToInt32Bits(vfloat);
|
|
return HexaFromBytes(4, floatAsInt, prefix, separator);
|
|
}
|
|
case double vdouble:
|
|
{
|
|
var doubleAsLong = BitConverter.DoubleToInt64Bits(vdouble);
|
|
return HexaFromBytes(8, doubleAsLong, prefix, separator);
|
|
}
|
|
case BigInteger bigInt:
|
|
{
|
|
var array = bigInt.ToByteArray();
|
|
return HexaFromBytes(array.Length, array[0], prefix, separator);
|
|
}
|
|
case KalkValue kalkValue:
|
|
{
|
|
var span = kalkValue.AsSpan();
|
|
return HexaFromBytes(span.Length, span[0], prefix, separator);
|
|
}
|
|
case IEnumerable list:
|
|
{
|
|
var builder = new StringBuilder();
|
|
bool isFirst = true;
|
|
foreach (var item in list)
|
|
{
|
|
if (!isFirst)
|
|
{
|
|
builder.Append(separator);
|
|
}
|
|
isFirst = false;
|
|
byte byteItem = Engine.ToObject<byte>(0, item);
|
|
if (prefix) builder.Append("0x");
|
|
builder.Append(byteItem.ToString("X2"));
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
default:
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(value)} is not supported ", nameof(value));
|
|
}
|
|
}
|
|
|
|
private static string HexaFromBytes<T>(int byteCount, in T element, bool prefix, string separator)
|
|
{
|
|
var builder = new StringBuilder(byteCount * 2);
|
|
for (int i = 0; i < byteCount; i++)
|
|
{
|
|
if (i > 0) builder.Append(separator);
|
|
var b = Unsafe.As<T, byte>(ref Unsafe.AddByteOffset(ref Unsafe.AsRef(element), new IntPtr(i)));
|
|
if (prefix) builder.Append("0x");
|
|
builder.Append(b.ToString("X2"));
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
|
|
private object Binary(object value, bool prefix, string separator, bool returnString)
|
|
{
|
|
Engine.CheckAbort();
|
|
switch (value)
|
|
{
|
|
case string str when returnString:
|
|
throw new ArgumentException($"Cannot convert a string to binary inside a list", nameof(value));
|
|
case string str:
|
|
{
|
|
var array = new List<byte>();
|
|
var temp = new List<byte>();
|
|
int count = 0;
|
|
bool hasPrefix = false;
|
|
int bin = 0;
|
|
|
|
void FlushBytes()
|
|
{
|
|
bool isOdd = count > 0;
|
|
var shift = 8 - count;
|
|
if (count > 0)
|
|
{
|
|
temp.Add((byte)(bin << shift));
|
|
}
|
|
bin = 0;
|
|
|
|
if (temp.Count > 0)
|
|
{
|
|
if (isOdd)
|
|
{
|
|
for (int j = temp.Count - 1; j >= 0; j--)
|
|
{
|
|
var hb = j - 1 >= 0 ? (byte)((temp[j - 1]) << shift) : (byte)0;
|
|
var lb = temp[j] >> count;
|
|
var fullByte = (byte)(hb | lb);
|
|
array.Add(fullByte);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = temp.Count - 1; j >= 0; j--)
|
|
{
|
|
array.Add(temp[j]);
|
|
}
|
|
}
|
|
|
|
temp.Clear();
|
|
}
|
|
|
|
count = 0;
|
|
}
|
|
|
|
for (int i = 0; i < str.Length; i++)
|
|
{
|
|
var c = str[i];
|
|
if (char.IsWhiteSpace(c) || c == ',' || c == ':' || c == ';' || c == '_' || c == '-')
|
|
{
|
|
if (c != '_')
|
|
{
|
|
FlushBytes();
|
|
}
|
|
hasPrefix = c == '_' && hasPrefix; // reset prefix after parsing but not for `_`
|
|
continue;
|
|
}
|
|
|
|
// Skip 0b prefix
|
|
if (!hasPrefix && c == '0' && i + 1 < str.Length && str[i + 1] == 'b')
|
|
{
|
|
FlushBytes();
|
|
hasPrefix = true;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
if (c == '0' || c == '1')
|
|
{
|
|
bin = (bin << 1) | (c - '0');
|
|
if (count == 7)
|
|
{
|
|
temp.Add((byte)bin);
|
|
bin = 0;
|
|
count = 0;
|
|
}
|
|
else
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException($"Invalid character found `{StringFunctions.Escape(c.ToString())}`. Expecting only binary 0 or 1.", nameof(value));
|
|
}
|
|
}
|
|
|
|
FlushBytes();
|
|
|
|
if (array.Count <= 8)
|
|
{
|
|
long ulongValue = 0;
|
|
unsafe
|
|
{
|
|
for (int i = 0; i < array.Count; i++)
|
|
{
|
|
((byte*)&ulongValue)[i] = array[i];
|
|
}
|
|
}
|
|
return ulongValue;
|
|
}
|
|
|
|
return new KalkNativeBuffer(array);
|
|
}
|
|
case byte vbyte:
|
|
return BinaryFromBytes(1, vbyte, prefix, separator);
|
|
case sbyte vsbyte:
|
|
return BinaryFromBytes(1, vsbyte, prefix, separator);
|
|
case short vshort:
|
|
{
|
|
int size = 2;
|
|
return BinaryFromBytes(size, vshort, prefix, separator);
|
|
}
|
|
case ushort vushort:
|
|
{
|
|
int size = 2;
|
|
return BinaryFromBytes(size, vushort, prefix, separator);
|
|
}
|
|
case int vint:
|
|
{
|
|
int size = 4;
|
|
return BinaryFromBytes(size, vint, prefix, separator);
|
|
}
|
|
case uint vuint:
|
|
{
|
|
int size = 4;
|
|
return BinaryFromBytes(size, vuint, prefix, separator);
|
|
}
|
|
case long vlong:
|
|
{
|
|
int size = 8;
|
|
return BinaryFromBytes(size, vlong, prefix, separator);
|
|
}
|
|
case ulong vulong:
|
|
{
|
|
int size = 8;
|
|
return BinaryFromBytes(size, vulong, prefix, separator);
|
|
}
|
|
case float vfloat:
|
|
{
|
|
var floatAsInt = BitConverter.SingleToInt32Bits(vfloat);
|
|
return BinaryFromBytes(4, floatAsInt, prefix, separator);
|
|
}
|
|
case double vdouble:
|
|
{
|
|
var doubleAsLong = BitConverter.DoubleToInt64Bits(vdouble);
|
|
return BinaryFromBytes(8, doubleAsLong, prefix, separator);
|
|
}
|
|
case BigInteger bigInt:
|
|
{
|
|
var array = bigInt.ToByteArray();
|
|
return BinaryFromBytes(array.Length, array[0], prefix, separator);
|
|
}
|
|
case KalkValue kalkValue:
|
|
{
|
|
var span = kalkValue.AsSpan();
|
|
return BinaryFromBytes(span.Length, span[0], prefix, separator);
|
|
}
|
|
case IEnumerable list:
|
|
{
|
|
var builder = new StringBuilder();
|
|
bool isFirst = true;
|
|
foreach (var item in list)
|
|
{
|
|
if (!isFirst)
|
|
{
|
|
builder.Append(separator);
|
|
}
|
|
isFirst = false;
|
|
byte byteItem = Engine.ToObject<byte>(0, item);
|
|
if (prefix) builder.Append("0b");
|
|
AppendBinaryByte(builder, byteItem);
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
default:
|
|
throw new ArgumentException($"The type {Engine.GetTypeName(value)} is not supported ", nameof(value));
|
|
}
|
|
}
|
|
|
|
private static void AppendBinaryByte(StringBuilder builder, byte byteItem)
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
var b = (byteItem >> (7 - i)) & 1;
|
|
builder.Append(b == 1 ? '1' : '0');
|
|
}
|
|
}
|
|
|
|
private static string BinaryFromBytes<T>(int byteCount, in T element, bool prefix, string separator)
|
|
{
|
|
var builder = new StringBuilder(byteCount * 8);
|
|
for (int i = 0; i < byteCount; i++)
|
|
{
|
|
if (i > 0) builder.Append(separator);
|
|
var b = Unsafe.As<T, byte>(ref Unsafe.AddByteOffset(ref Unsafe.AsRef(element), new IntPtr(i)));
|
|
if (prefix) builder.Append("0b");
|
|
AppendBinaryByte(builder, b);
|
|
}
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static object ConvertAscii(KalkEngine context, object argument)
|
|
{
|
|
if (argument is string text)
|
|
{
|
|
var bytes = EncodingExtendedAscii.GetBytes(text);
|
|
if (bytes.Length == 1)
|
|
{
|
|
return bytes[0];
|
|
}
|
|
|
|
return new KalkNativeBuffer(bytes);
|
|
}
|
|
else if (argument is KalkNativeBuffer buffer)
|
|
{
|
|
return EncodingExtendedAscii.GetString(buffer.AsSpan());
|
|
}
|
|
else if (argument is IEnumerable it)
|
|
{
|
|
return new ScriptRange(ConvertAsciiIteration(context, it));
|
|
}
|
|
else
|
|
{
|
|
return ConvertAscii(context.ToInt(context.CurrentSpan, argument));
|
|
}
|
|
}
|
|
|
|
private static IEnumerable ConvertAsciiIteration(KalkEngine context, IEnumerable it)
|
|
{
|
|
var iterator = it.GetEnumerator();
|
|
while (iterator.MoveNext())
|
|
{
|
|
yield return ConvertAscii(context, iterator.Current);
|
|
}
|
|
}
|
|
|
|
private static unsafe string ConvertAscii(int c)
|
|
{
|
|
var value = (byte)c;
|
|
return EncodingExtendedAscii.GetString(&value, 1);
|
|
}
|
|
|
|
private static readonly string[] AsciiSpecialCodes = new string[32]
|
|
{
|
|
"NUL / Null",
|
|
"SOH / Start of Heading",
|
|
"STX / Start of Text",
|
|
"ETX / End of Text",
|
|
"EOT / End of Transmission",
|
|
"ENQ / Enquiry",
|
|
"ACK / Acknowledgment",
|
|
"BEL / Bell",
|
|
"BS / Backspace",
|
|
"HT / Horizontal Tab",
|
|
"LF / Line Feed",
|
|
"VT / Vertical Tab",
|
|
"FF / Form Feed",
|
|
"CR / Carriage Return",
|
|
"SO / Shift Out",
|
|
"SI / Shift In",
|
|
"DLE / Data Link Escape",
|
|
"DC1 / Device Control 1",
|
|
"DC2 / Device Control 2",
|
|
"DC3 / Device Control 3",
|
|
"DC4 / Device Control 4",
|
|
"NAK / Negative Ack",
|
|
"SYN / Synchronous Idle",
|
|
"ETB / End of Trans Block",
|
|
"CAN / Cancel",
|
|
"EM / End of Medium",
|
|
"SUB / Substitute",
|
|
"ESC / Escape",
|
|
"FS / File Separator",
|
|
"GS / Group Separator",
|
|
"RS / Record Separator",
|
|
"US / Unit Separator",
|
|
};
|
|
}
|
|
} |