#nullable enable
#pragma warning disable CS1591
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Text;
namespace Hyperlight.Component
{
public interface IGuestCaller
{
byte[] Call(string function, byte[][] args);
}
public interface IHostFunctionRegistry
{
void Register(string function, int parameterCount, Func<byte[][], byte[]> implementation);
}
public sealed class WireFormatException : Exception
{
public WireFormatException(string message) : base(message) { }
}
public readonly struct Unit : IEquatable<Unit>
{
public bool Equals(Unit other) => true;
public override bool Equals(object? obj) => obj is Unit;
public override int GetHashCode() => 0;
public override string ToString() => "()";
public static bool operator ==(Unit left, Unit right) => true;
public static bool operator !=(Unit left, Unit right) => false;
}
public readonly struct Option<T> : IEquatable<Option<T>>
{
private readonly T _value;
private Option(T value)
{
HasValue = true;
_value = value;
}
public static Option<T> None => default;
public static Option<T> Some(T value) => new(value);
public bool HasValue { get; }
public T Value => HasValue ? _value : throw new InvalidOperationException("option has no value");
public bool Equals(Option<T> other) =>
HasValue == other.HasValue && (!HasValue || EqualityComparer<T>.Default.Equals(_value, other._value));
public override bool Equals(object? obj) => obj is Option<T> other && Equals(other);
public override int GetHashCode() => HasValue ? HashCode.Combine(true, _value) : 0;
public override string ToString() => HasValue ? $"Some({_value})" : "None";
public static bool operator ==(Option<T> left, Option<T> right) => left.Equals(right);
public static bool operator !=(Option<T> left, Option<T> right) => !left.Equals(right);
}
public readonly struct Result<TOk, TErr> : IEquatable<Result<TOk, TErr>>
{
private readonly TOk _ok;
private readonly TErr _err;
private Result(bool isOk, TOk ok, TErr err)
{
IsOk = isOk;
_ok = ok;
_err = err;
}
public static Result<TOk, TErr> FromOk(TOk value) => new(true, value, default!);
public static Result<TOk, TErr> FromErr(TErr value) => new(false, default!, value);
public bool IsOk { get; }
public TOk Ok => IsOk ? _ok : throw new InvalidOperationException("result is an error");
public TErr Err => IsOk ? throw new InvalidOperationException("result is not an error") : _err;
public bool Equals(Result<TOk, TErr> other) =>
IsOk == other.IsOk && (IsOk
? EqualityComparer<TOk>.Default.Equals(_ok, other._ok)
: EqualityComparer<TErr>.Default.Equals(_err, other._err));
public override bool Equals(object? obj) => obj is Result<TOk, TErr> other && Equals(other);
public override int GetHashCode() =>
IsOk ? HashCode.Combine(true, _ok) : HashCode.Combine(false, _err);
public override string ToString() => IsOk ? $"Ok({_ok})" : $"Err({_err})";
public static bool operator ==(Result<TOk, TErr> left, Result<TOk, TErr> right) => left.Equals(right);
public static bool operator !=(Result<TOk, TErr> left, Result<TOk, TErr> right) => !left.Equals(right);
}
public sealed class WireWriter
{
private static readonly UTF8Encoding StrictUtf8 = new(false, true);
private byte[] _buffer;
private int _length;
public WireWriter(int capacity = 64)
{
_buffer = new byte[Math.Max(capacity, 1)];
}
public object? Resources { get; init; }
public CallScope? Scope { get; init; }
public byte[] ToArray() => _buffer.AsSpan(0, _length).ToArray();
public void Bool(bool value) => U8(value ? (byte)1 : (byte)0);
public void U8(byte value) => Grow(1)[0] = value;
public void S8(sbyte value) => Grow(1)[0] = unchecked((byte)value);
public void U16(ushort value) => BinaryPrimitives.WriteUInt16LittleEndian(Grow(2), value);
public void S16(short value) => BinaryPrimitives.WriteInt16LittleEndian(Grow(2), value);
public void U32(uint value) => BinaryPrimitives.WriteUInt32LittleEndian(Grow(4), value);
public void S32(int value) => BinaryPrimitives.WriteInt32LittleEndian(Grow(4), value);
public void U64(ulong value) => BinaryPrimitives.WriteUInt64LittleEndian(Grow(8), value);
public void S64(long value) => BinaryPrimitives.WriteInt64LittleEndian(Grow(8), value);
public void F32(float value) => BinaryPrimitives.WriteSingleLittleEndian(Grow(4), value);
public void F64(double value) => BinaryPrimitives.WriteDoubleLittleEndian(Grow(8), value);
public void Char(Rune value) => U32((uint)value.Value);
public void String(string value)
{
ArgumentNullException.ThrowIfNull(value);
var length = StrictUtf8.GetByteCount(value);
Length(length);
StrictUtf8.GetBytes(value, Grow(length));
}
public void Length(int count) => U32(checked((uint)count));
public void Bytes(byte[] value)
{
ArgumentNullException.ThrowIfNull(value);
Length(value.Length);
value.CopyTo(Grow(value.Length));
}
public void FixedBytes(byte[] value, int count)
{
CheckFixedLength(value, count);
value.CopyTo(Grow(count));
}
public void Flags(uint bits, int byteCount)
{
var bytes = Grow(byteCount);
for (var i = 0; i < byteCount; i++)
{
bytes[i] = (byte)(bits >> (8 * i));
}
}
public static void CheckFixedLength<T>(IReadOnlyCollection<T> value, int count)
{
ArgumentNullException.ThrowIfNull(value);
if (value.Count != count)
{
throw new ArgumentException($"fixed-length list needs {count} elements, got {value.Count}");
}
}
private Span<byte> Grow(int count)
{
if (_buffer.Length - _length < count)
{
Array.Resize(ref _buffer, Math.Max(checked(_length + count), _buffer.Length * 2));
}
var span = _buffer.AsSpan(_length, count);
_length += count;
return span;
}
}
public ref struct WireReader
{
private static readonly UTF8Encoding StrictUtf8 = new(false, true);
private readonly ReadOnlySpan<byte> _data;
private int _position;
public WireReader(ReadOnlySpan<byte> data)
{
_data = data;
_position = 0;
}
public object? Resources { get; init; }
public bool Bool() => U8() != 0;
public byte U8() => Take(1)[0];
public sbyte S8() => unchecked((sbyte)Take(1)[0]);
public ushort U16() => BinaryPrimitives.ReadUInt16LittleEndian(Take(2));
public short S16() => BinaryPrimitives.ReadInt16LittleEndian(Take(2));
public uint U32() => BinaryPrimitives.ReadUInt32LittleEndian(Take(4));
public int S32() => BinaryPrimitives.ReadInt32LittleEndian(Take(4));
public ulong U64() => BinaryPrimitives.ReadUInt64LittleEndian(Take(8));
public long S64() => BinaryPrimitives.ReadInt64LittleEndian(Take(8));
public float F32() => BinaryPrimitives.ReadSingleLittleEndian(Take(4));
public double F64() => BinaryPrimitives.ReadDoubleLittleEndian(Take(8));
public Rune Char()
{
var value = U32();
if (!Rune.IsValid(value))
{
throw new WireFormatException($"invalid char {value:x}");
}
return new Rune(value);
}
public string String()
{
var bytes = Take(Length());
try
{
return StrictUtf8.GetString(bytes);
}
catch (DecoderFallbackException e)
{
throw new WireFormatException($"invalid UTF-8: {e.Message}");
}
}
public int Length()
{
var count = U32();
if (count > (uint)(_data.Length - _position))
{
throw new WireFormatException($"list length {count} exceeds remaining input");
}
return (int)count;
}
public byte[] Bytes() => Take(Length()).ToArray();
public byte[] FixedBytes(int count) => Take(count).ToArray();
public uint Tag32(uint cases)
{
var tag = U32();
if (tag >= cases)
{
throw new WireFormatException($"invalid tag {tag}");
}
return tag;
}
public uint Flags(int byteCount, uint mask)
{
var bytes = Take(byteCount);
uint bits = 0;
for (var i = 0; i < byteCount; i++)
{
bits |= (uint)bytes[i] << (8 * i);
}
return bits & mask;
}
private ReadOnlySpan<byte> Take(int count)
{
if ((uint)count > (uint)(_data.Length - _position))
{
throw new WireFormatException("unexpected end of input");
}
var span = _data.Slice(_position, count);
_position += count;
return span;
}
}
public sealed class ResourceTable<T> where T : class
{
private readonly List<Entry> _entries = new();
private sealed class Entry
{
public T? Value;
public bool Lent;
}
public uint Give(T value, CallScope? scope = null)
{
var handle = Add(value, lent: false);
scope?.AddGift(() => Clear(handle));
return handle;
}
public uint Lend(T value, CallScope scope)
{
ArgumentNullException.ThrowIfNull(scope);
var handle = Add(value, lent: true);
scope.AddLoan(() => Clear(handle));
return handle;
}
private void Clear(uint handle)
{
lock (_entries)
{
_entries[(int)handle].Value = null;
}
}
public T Take(uint handle)
{
lock (_entries)
{
var entry = Get(handle);
if (entry.Lent || entry.Value is null)
{
throw new WireFormatException($"resource handle {handle} is not owned");
}
var value = entry.Value;
entry.Value = null;
return value;
}
}
public T Borrow(uint handle)
{
lock (_entries)
{
return Get(handle).Value ?? throw new WireFormatException($"resource handle {handle} is not valid");
}
}
private uint Add(T value, bool lent)
{
ArgumentNullException.ThrowIfNull(value);
lock (_entries)
{
_entries.Add(new Entry { Value = value, Lent = lent });
return checked((uint)(_entries.Count - 1));
}
}
private Entry Get(uint handle) =>
handle < (uint)_entries.Count
? _entries[(int)handle]
: throw new WireFormatException($"invalid resource handle {handle}");
}
public sealed class CallScope
{
private List<Action>? _loans;
private List<Action>? _gifts;
internal void AddLoan(Action revoke) => (_loans ??= new()).Add(revoke);
internal void AddGift(Action undo) => (_gifts ??= new()).Add(undo);
public void Commit() => _gifts?.Clear();
public void End()
{
Run(_gifts);
Run(_loans);
}
private static void Run(List<Action>? actions)
{
if (actions is null)
{
return;
}
foreach (var action in actions)
{
action();
}
actions.Clear();
}
}
}