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
{
///
/// Misc module (builtin).
///
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();
}
///
/// The date object used by the function.
///
public DateTimeFunctions DateObject { get; }
///
/// 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))
///
/// The current date, parse the input date or return the date object, depending on use cases.
///
/// ```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
/// ```
///
[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;
}
///
/// Prints the ascii table or convert an input string to an ascii array, or an ascii array to a string.
///
/// An optional input (string or array of numbers or directly an integer).
/// 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.
///
///
/// ```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"
/// ```
///
[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);
}
///
/// Process each element of the input array with the specified function.
///
/// A list of element to process.
/// 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.
/// The value transformed.
///
/// ```kalk
/// >>> foreach([1, 2, 3, 4], @hex)
/// # foreach([1, 2, 3, 4], @hex)
/// out = ["01", "02", "03", "04"]
/// ```
///
[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;
}
///
/// Returns the keys of the specified object.
///
/// An object to get the keys from.
/// The keys of the parameter obj.
///
/// ```kalk
/// >>> obj = {m: 1, n: 2}; keys obj
/// # obj = {m: 1, n: 2}; keys(obj)
/// obj = {m: 1, n: 2}
/// out = ["m", "n"]
/// ```
///
[KalkExport("keys", CategoryMisc)]
public IEnumerable Keys(object obj)
{
return ObjectFunctions.Keys(Engine, obj);
}
///
/// Returns a new GUID as a string.
///
/// A new GUID as a string.
///
/// ```kalk
/// >>> guid
/// # guid
/// out = "0deafe30-de4d-47c3-9631-2d3292afbb8e"
/// ```
///
[KalkExport("guid", CategoryMisc)]
public string Guid()
{
return Engine.IsTesting ? "0deafe30-de4d-47c3-9631-2d3292afbb8e" : System.Guid.NewGuid().ToString();
}
///
/// Returns the size of the specified object.
///
/// The object value.
/// The size of the object.
///
/// ```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
/// ```
///
[KalkExport("size", CategoryMisc)]
public int Size(object obj)
{
return ObjectFunctions.Size(obj);
}
///
/// Returns the values of the specified object.
///
/// An object to get the values from.
/// The values of the parameter obj.
///
/// ```kalk
/// >>> obj = {m: 1, n: 2}; values obj
/// # obj = {m: 1, n: 2}; values(obj)
/// obj = {m: 1, n: 2}
/// out = [1, 2]
/// ```
///
[KalkExport("values", CategoryMisc)]
public IEnumerable Values(TemplateContext context, object obj)
{
switch (obj)
{
case IDictionary dict:
return ObjectFunctions.Values(context, dict);
case IEnumerable list:
return new ScriptArray(list);
default:
return new ScriptArray() {obj};
}
}
///
/// Converts an integral/bytebuffer input to an hexadecimal representation or convert an hexadecimal input string
/// to an integral/bytebuffer representation.
///
/// The input value.
/// Output the prefix `0x` in front of each hexadecimal bytes when converting
/// from integral to hexadecimal.
/// The character used to separate hexadecimal bytes when converting
/// from integral to hexadecimal.
/// The hexadecimal representation of the input or convert the hexadecimal input string
/// to an integral representation.
/// 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.
///
///
/// ```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"
/// ```
///
///
/// ```kalk
/// >>> hex short(12345)
/// # hex(short(12345))
/// out = "39 30"
/// >>> hex int (12345789)
/// # hex(int(12345789))
/// out = "BD 61 BC 00"
/// ```
///
[KalkExport("hex", CategoryMisc)]
public object Hexadecimal(object value, bool prefix = false, string separator = " ")
{
return Hexadecimal(value, prefix, separator, false);
}
///
/// Converts an integral/bytebuffer input to a binary representation or convert a binary input string
/// to an integral/bytebuffer representation.
///
/// The input value.
/// Output the prefix `0x` in front of each binary bytes when converting
/// from integral to binary.
/// The character used to separate binary bytes when converting
/// from integral to binary.
/// The binary representation of the input or convert the binary input string
/// to an integral representation.
/// 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.
///
///
/// ```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"
/// ```
///
[KalkExport("bin", CategoryMisc)]
public object Binary(object value, bool prefix = false, string separator = " ")
{
return Binary(value, prefix, separator, false);
}
///
/// Converts a string to an UTF8 bytebuffer or convert a bytebuffer of UTF8 bytes to a string.
///
/// The specified input.
/// The UTF8 bytebuffer representation of the input string or the string representation of the input UTF8 bytebuffer.
///
/// ```kalk
/// >>> utf8 "kalk"
/// # utf8("kalk")
/// out = bytebuffer([107, 97, 108, 107])
/// >>> utf8 out
/// # utf8(out)
/// out = "kalk"
/// ```
///
[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(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));
}
}
///
/// Converts a string to an UTF16 bytebuffer or convert a bytebuffer of UTF16 bytes to a string.
///
/// The specified input.
/// The UTF16 bytebuffer representation of the input string or the string representation of the input UTF16 bytebuffer.
///
/// ```kalk
/// >>> utf16 "kalk"
/// # utf16("kalk")
/// out = bytebuffer([107, 0, 97, 0, 108, 0, 107, 0])
/// >>> utf16 out
/// # utf16(out)
/// out = "kalk"
/// ```
///
[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(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));
}
}
///
/// Converts a string to an UTF32 bytebuffer or convert a bytebuffer of UTF32 bytes to a string.
///
/// The specified input.
/// The UTF32 bytebuffer representation of the input string or the string representation of the input UTF32 bytebuffer.
///
/// ```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"
/// ```
///
[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(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));
}
}
///
/// Inserts an item into a string or list at the specified index.
///
/// A string or list to insert an item into.
/// The index at which to insert the item.
/// The item to insert.
/// A new string with the item inserted, or a new list with the item inserted at the specified index.
/// 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.
///
///
/// ```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]
/// ```
///
[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(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));
}
}
///
/// Removes an item from a string or list at the specified index.
///
/// A string or list to remove an item from.
/// The index at which to remove the item.
/// A new string/list with the item at the specified index removed.
/// 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.
///
///
/// ```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])
/// ```
///
[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));
}
}
///
/// Checks if an object (string, list, vector types, bytebuffer...) is containing the specified value.
///
/// The list to search into.
/// The value to search into the list.
/// true if value was found in the list input; otherwise false.
///
/// ```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
/// ```
///
[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;
}
///
/// Reverse a list of values.
///
/// The list to reverse.
/// The list of values reversed.
///
/// ```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])
/// ```
/// 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