// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
[assembly: PythonModule("_struct", typeof(IronPython.Modules.PythonStruct))]
namespace IronPython.Modules {
public static class PythonStruct {
[SpecialName]
public static void PerformModuleReload(PythonContext/*!*/ context, PythonDictionary/*!*/ dict) {
context.EnsureModuleException("structerror", dict, "error", "struct");
}
#region Public API
public const string __doc__ = null;
[PythonType, Documentation("Represents a compiled struct pattern")]
public class Struct : IWeakReferenceable {
private Format[] _formats; // the various formatting options for the compiled struct
private bool _isStandardized; // true if the format is in standardized mode
private bool _isLittleEndian; // true if the format is in little endian mode
private int _encodingCount = -1; // the number of objects consumed/produced by the format
private int _encodingSize = -1; // the number of bytes read/produced by the format
private WeakRefTracker _tracker; // storage for weak proxy's
private void InitializeFrom(Struct s) {
format = s.format;
_formats = s._formats;
_isStandardized = s._isStandardized;
_isLittleEndian = s._isLittleEndian;
_encodingCount = s._encodingCount;
_encodingSize = s._encodingSize;
_tracker = s._tracker;
}
internal Struct(CodeContext/*!*/ context, [NotNull]string/*!*/ fmt) {
__init__(context, fmt);
}
#region Python construction
[Documentation("creates a new uninitialized struct object - all arguments are ignored")]
public Struct(params object[] args) {
}
[Documentation("creates a new uninitialized struct object - all arguments are ignored")]
public Struct([ParamDictionary]IDictionary kwArgs, params object[] args) {
}
[Documentation("initializes or re-initializes the compiled struct object with a new format")]
public void __init__(CodeContext/*!*/ context, object fmt) {
format = FormatObjectToString(fmt);
Struct s;
bool gotIt;
lock (_cache) {
gotIt = _cache.TryGetValue(format, out s);
}
InitializeFrom(gotIt ? s : CompileAndCache(context, format));
}
#endregion
#region Public API
[Documentation("gets the current format string for the compiled Struct")]
public string format { get; private set; }
[Documentation("returns a string consisting of the values serialized according to the format of the struct object")]
public Bytes/*!*/ pack(CodeContext/*!*/ context, params object[] values) {
if (values.Length != _encodingCount) {
throw Error(context, $"pack requires exactly {_encodingCount} arguments");
}
int curObj = 0;
var buffer = new byte[_encodingSize];
using var res = new MemoryStream(buffer);
foreach (Format curFormat in _formats) {
if (!_isStandardized) {
// In native mode, align to {size}-byte boundaries
int nativeSize = curFormat.NativeSize;
int alignLength = Align((int)res.Position, nativeSize);
int padLength = alignLength - (int)res.Position;
res.WriteByteCount((byte)'\0', padLength);
}
switch (curFormat.Type) {
case FormatType.PadByte:
res.WriteByteCount((byte)'\0', curFormat.Count);
break;
case FormatType.Bool:
res.WriteByte((byte)(GetBoolValue(context, curObj++, values) ? 1 : 0));
break;
case FormatType.Char:
for (int j = 0; j < curFormat.Count; j++) {
res.WriteByte((byte)GetCharValue(context, curObj++, values));
}
break;
case FormatType.SignedChar:
for (int j = 0; j < curFormat.Count; j++) {
res.WriteByte((byte)GetSByteValue(context, curObj++, values));
}
break;
case FormatType.UnsignedChar:
for (int j = 0; j < curFormat.Count; j++) {
res.WriteByte(GetByteValue(context, curObj++, values));
}
break;
case FormatType.Short:
for (int j = 0; j < curFormat.Count; j++) {
WriteShort(res, _isLittleEndian, GetShortValue(context, curObj++, values));
}
break;
case FormatType.UnsignedShort:
for (int j = 0; j < curFormat.Count; j++) {
WriteUShort(res, _isLittleEndian, GetUShortValue(context, curObj++, values));
}
break;
case FormatType.Int:
for (int j = 0; j < curFormat.Count; j++) {
WriteInt(res, _isLittleEndian, GetIntValue(context, curObj++, values));
}
break;
case FormatType.UnsignedInt:
for (int j = 0; j < curFormat.Count; j++) {
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values, "unsigned int"));
}
break;
case FormatType.UnsignedLong:
for (int j = 0; j < curFormat.Count; j++) {
WriteUInt(res, _isLittleEndian, GetULongValue(context, curObj++, values, "unsigned long"));
}
break;
case FormatType.Pointer:
for (int j = 0; j < curFormat.Count; j++) {
WritePointer(res, _isLittleEndian, GetPointer(context, curObj++, values));
}
break;
case FormatType.LongLong:
for (int j = 0; j < curFormat.Count; j++) {
WriteLong(res, _isLittleEndian, GetLongValue(context, curObj++, values));
}
break;
case FormatType.UnsignedLongLong:
for (int j = 0; j < curFormat.Count; j++) {
WriteULong(res, _isLittleEndian, GetULongLongValue(context, curObj++, values));
}
break;
case FormatType.Double:
for (int j = 0; j < curFormat.Count; j++) {
WriteDouble(res, _isLittleEndian, GetDoubleValue(context, curObj++, values));
}
break;
case FormatType.Float:
for (int j = 0; j < curFormat.Count; j++) {
WriteFloat(res, _isLittleEndian, (float)GetDoubleValue(context, curObj++, values));
}
break;
case FormatType.CString:
WriteString(res, curFormat.Count, GetStringValue(context, curObj++, values));
break;
case FormatType.PascalString:
WritePascalString(res, curFormat.Count - 1, GetStringValue(context, curObj++, values));
break;
}
}
return Bytes.Make(buffer);
}
[Documentation("Stores the deserialized data into the provided array")]
public void pack_into(CodeContext/*!*/ context, [NotNull]ArrayModule.array/*!*/ buffer, int offset, params object[] args) {
byte[] existing = buffer.ToByteArray();
if (offset + size > existing.Length) {
throw Error(context, $"pack_into requires a buffer of at least {size} bytes");
}
var data = pack(context, args).UnsafeByteArray;
for (int i = 0; i < data.Length; i++) {
existing[i + offset] = data[i];
}
buffer.Clear();
buffer.FromStream(new MemoryStream(existing));
}
public void pack_into(CodeContext/*!*/ context, [NotNull]ByteArray/*!*/ buffer, int offset, params object[] args) {
IList existing = buffer.UnsafeByteList;
if (offset + size > existing.Count) {
throw Error(context, $"pack_into requires a buffer of at least {size} bytes");
}
var data = pack(context, args).UnsafeByteArray;
for (int i = 0; i < data.Length; i++) {
existing[i + offset] = data[i];
}
}
[Documentation("deserializes the string using the structs specified format")]
public PythonTuple/*!*/ unpack(CodeContext/*!*/ context, [BytesConversion][NotNull]IList @string) {
if (@string.Count != size) {
throw Error(context, $"unpack requires a string argument of length {size}");
}
var data = @string;
int curIndex = 0;
var res = new object[_encodingCount];
var res_idx = 0;
foreach (Format curFormat in _formats) {
if (!_isStandardized) {
// In native mode, align to {size}-byte boundaries
int nativeSize = curFormat.NativeSize;
if (nativeSize > 0) {
curIndex = Align(curIndex, nativeSize);
}
}
switch (curFormat.Type) {
case FormatType.PadByte:
curIndex += curFormat.Count;
break;
case FormatType.Bool:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = CreateBoolValue(context, ref curIndex, data);
}
break;
case FormatType.Char:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = Bytes.FromByte(CreateCharValue(context, ref curIndex, data));
}
break;
case FormatType.SignedChar:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = (int)(sbyte)CreateCharValue(context, ref curIndex, data);
}
break;
case FormatType.UnsignedChar:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = (int)CreateCharValue(context, ref curIndex, data);
}
break;
case FormatType.Short:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = (int)CreateShortValue(context, ref curIndex, _isLittleEndian, data);
}
break;
case FormatType.UnsignedShort:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = (int)CreateUShortValue(context, ref curIndex, _isLittleEndian, data);
}
break;
case FormatType.Int:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = CreateIntValue(context, ref curIndex, _isLittleEndian, data);
}
break;
case FormatType.UnsignedInt:
case FormatType.UnsignedLong:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = BigIntegerOps.__int__(CreateUIntValue(context, ref curIndex, _isLittleEndian, data));
}
break;
case FormatType.Pointer:
for (int j = 0; j < curFormat.Count; j++) {
if (IntPtr.Size == 4) {
res[res_idx++] = CreateIntValue(context, ref curIndex, _isLittleEndian, data);
} else {
res[res_idx++] = BigIntegerOps.__int__(CreateLongValue(context, ref curIndex, _isLittleEndian, data));
}
}
break;
case FormatType.LongLong:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = BigIntegerOps.__int__(CreateLongValue(context, ref curIndex, _isLittleEndian, data));
}
break;
case FormatType.UnsignedLongLong:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = BigIntegerOps.__int__(CreateULongValue(context, ref curIndex, _isLittleEndian, data));
}
break;
case FormatType.Float:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = CreateFloatValue(context, ref curIndex, _isLittleEndian, data);
}
break;
case FormatType.Double:
for (int j = 0; j < curFormat.Count; j++) {
res[res_idx++] = CreateDoubleValue(context, ref curIndex, _isLittleEndian, data);
}
break;
case FormatType.CString:
res[res_idx++] = CreateString(context, ref curIndex, curFormat.Count, data);
break;
case FormatType.PascalString:
res[res_idx++] = CreatePascalString(context, ref curIndex, curFormat.Count - 1, data);
break;
}
}
System.Diagnostics.Debug.Assert(res_idx == res.Length);
return PythonTuple.MakeTuple(res);
}
public PythonTuple/*!*/ unpack(CodeContext/*!*/ context, [NotNull]ArrayModule.array/*!*/ buffer)
=> unpack(context, buffer.ToByteArray());
[Documentation("reads the current format from the specified array")]
public PythonTuple/*!*/ unpack_from(CodeContext/*!*/ context, [BytesConversion][NotNull]IList/*!*/ buffer, int offset = 0) {
int bytesAvail = buffer.Count - offset;
if (bytesAvail < size) {
throw Error(context, $"unpack_from requires a buffer of at least {size} bytes");
}
return unpack(context, buffer.Substring(offset, size));
}
[Documentation("reads the current format from the specified array")]
public PythonTuple/*!*/ unpack_from(CodeContext/*!*/ context, [NotNull]ArrayModule.array/*!*/ buffer, int offset = 0) {
return unpack_from(context, buffer.ToByteArray(), offset);
}
[Documentation("gets the number of bytes that the serialized string will occupy or are required to deserialize the data")]
public int size {
get {
return _encodingSize;
}
}
#endregion
#region IWeakReferenceable Members
WeakRefTracker IWeakReferenceable.GetWeakRef() {
return _tracker;
}
bool IWeakReferenceable.SetWeakRef(WeakRefTracker value) {
return Interlocked.CompareExchange(ref _tracker, value, null) == null;
}
void IWeakReferenceable.SetFinalizer(WeakRefTracker value) {
_tracker = value;
}
#endregion
#region Implementation Details
private static Struct CompileAndCache(CodeContext/*!*/ context, string/*!*/ fmt) {
List res = new List();
int count = 1;
bool fLittleEndian = BitConverter.IsLittleEndian;
bool fStandardized = false;
for (int i = 0; i < fmt.Length; i++) {
switch (fmt[i]) {
case 'x': // pad byte
res.Add(new Format(FormatType.PadByte, count));
count = 1;
break;
case '?': // bool
res.Add(new Format(FormatType.Bool, count));
count = 1;
break;
case 'c': // char
res.Add(new Format(FormatType.Char, count));
count = 1;
break;
case 'b': // signed char
res.Add(new Format(FormatType.SignedChar, count));
count = 1;
break;
case 'B': // unsigned char
res.Add(new Format(FormatType.UnsignedChar, count));
count = 1;
break;
case 'h': // short
res.Add(new Format(FormatType.Short, count));
count = 1;
break;
case 'H': // unsigned short
res.Add(new Format(FormatType.UnsignedShort, count));
count = 1;
break;
case 'i': // int
case 'l': // long
res.Add(new Format(FormatType.Int, count));
count = 1;
break;
case 'I': // unsigned int
res.Add(new Format(FormatType.UnsignedInt, count));
count = 1;
break;
case 'L': // unsigned long
res.Add(new Format(FormatType.UnsignedLong, count));
count = 1;
break;
case 'q': // long long
res.Add(new Format(FormatType.LongLong, count));
count = 1;
break;
case 'Q': // unsigned long long
res.Add(new Format(FormatType.UnsignedLongLong, count));
count = 1;
break;
case 'f': // float
res.Add(new Format(FormatType.Float, count));
count = 1;
break;
case 'd': // double
res.Add(new Format(FormatType.Double, count));
count = 1;
break;
case 's': // char[]
res.Add(new Format(FormatType.CString, count));
count = 1;
break;
case 'p': // pascal char[]
res.Add(new Format(FormatType.PascalString, count));
count = 1;
break;
case 'P': // void *
res.Add(new Format(FormatType.Pointer, count));
count = 1;
break;
case ' ': // white space, ignore
case '\t':
break;
case '=': // native
if (i != 0) throw Error(context, "unexpected byte order");
fStandardized = true;
break;
case '@': // native
if (i != 0) throw Error(context, "unexpected byte order");
break;
case '<': // little endian
if (i != 0) throw Error(context, "unexpected byte order");
fLittleEndian = true;
fStandardized = true;
break;
case '>': // big endian
case '!': // big endian
if (i != 0) throw Error(context, "unexpected byte order");
fLittleEndian = false;
fStandardized = true;
break;
default:
if (char.IsDigit(fmt[i])) {
count = 0;
while (char.IsDigit(fmt[i])) {
count = count * 10 + (fmt[i] - '0');
i++;
}
if (char.IsWhiteSpace(fmt[i])) Error(context, "white space not allowed between count and format");
i--;
break;
}
throw Error(context, "bad char in struct format");
}
}
// store the new formats
var s = new Struct() {
format = fmt,
_formats = res.ToArray(),
_isStandardized = fStandardized,
_isLittleEndian = fLittleEndian,
};
s.InitCountAndSize();
lock (_cache) {
_cache.Add(fmt, s);
}
return s;
}
private void InitCountAndSize() {
var encodingCount = 0;
var encodingSize = 0;
foreach (Format format in _formats) {
if (format.Type != FormatType.PadByte) {
if (format.Type != FormatType.CString && format.Type != FormatType.PascalString) {
encodingCount += format.Count;
} else {
encodingCount++;
}
}
if (!_isStandardized) {
// In native mode, align to {size}-byte boundaries
encodingSize = Align(encodingSize, format.NativeSize);
}
encodingSize += GetNativeSize(format.Type) * format.Count;
}
_encodingCount = encodingCount;
_encodingSize = encodingSize;
}
#endregion
#region Internal helpers
internal static Struct Create(string/*!*/ format) {
Struct res = new Struct();
res.__init__(DefaultContext.Default, format); // default context is only used for errors, this better be an error free format.
return res;
}
#endregion
}
#endregion
#region Compiled Format
///
/// Enum which specifies the format type for a compiled struct
///
private enum FormatType {
PadByte,
Bool,
Char,
SignedChar,
UnsignedChar,
Short,
UnsignedShort,
Int,
UnsignedInt,
UnsignedLong,
Float,
LongLong,
UnsignedLongLong,
Double,
CString,
PascalString,
Pointer,
}
private static int GetNativeSize(FormatType c) {
switch (c) {
case FormatType.Char:
case FormatType.SignedChar:
case FormatType.UnsignedChar:
case FormatType.PadByte:
case FormatType.Bool:
case FormatType.CString:
case FormatType.PascalString:
return 1;
case FormatType.Short:
case FormatType.UnsignedShort:
return 2;
case FormatType.Int:
case FormatType.UnsignedInt:
case FormatType.UnsignedLong:
case FormatType.Float:
return 4;
case FormatType.LongLong:
case FormatType.UnsignedLongLong:
case FormatType.Double:
return 8;
case FormatType.Pointer:
return IntPtr.Size;
default:
throw new InvalidOperationException(c.ToString());
}
}
///
/// Struct used to store the format and the number of times it should be repeated.
///
private readonly struct Format {
public readonly FormatType Type;
public readonly int Count;
public Format(FormatType type, int count) {
Type = type;
Count = count;
}
public int NativeSize => GetNativeSize(Type);
}
#endregion
#region Cache of compiled struct patterns
private const int MAX_CACHE_SIZE = 1024;
private static CacheDict _cache = new CacheDict(MAX_CACHE_SIZE);
private static string FormatObjectToString(object fmt) {
if (Converter.TryConvertToString(fmt, out string res)) {
return res;
}
if (fmt is IList b) {
return PythonOps.MakeString(b);
}
throw PythonOps.TypeError("Struct() argument 1 must be a str or bytes object, not {0}", DynamicHelpers.GetPythonType(fmt).Name);
}
private static Struct GetStructFromCache(CodeContext/*!*/ context, object fmt) {
var formatString = FormatObjectToString(fmt);
Struct s;
bool gotIt;
lock (_cache) {
gotIt = _cache.TryGetValue(formatString, out s);
}
if (!gotIt) {
s = new Struct(context, formatString);
}
return s;
}
[Documentation("Clear the internal cache.")]
public static void _clearcache() {
_cache = new CacheDict(MAX_CACHE_SIZE);
}
[Documentation("int(x[, base]) -> integer\n\nConvert a string or number to an integer, if possible. A floating point\nargument will be truncated towards zero (this does not include a string\nrepresentation of a floating point number!) When converting a string, use\nthe optional base. It is an error to supply a base when converting a\nnon-string. If base is zero, the proper base is guessed based on the\nstring content. If the argument is outside the integer range a\nlong object will be returned instead.")]
public static int calcsize(CodeContext/*!*/ context, object fmt) {
return GetStructFromCache(context, fmt).size;
}
[Documentation("Return string containing values v1, v2, ... packed according to fmt.")]
public static Bytes/*!*/ pack(CodeContext/*!*/ context, object fmt/*!*/, params object[] values) {
return GetStructFromCache(context, fmt).pack(context, values);
}
[Documentation("Pack the values v1, v2, ... according to fmt.\nWrite the packed bytes into the writable buffer buf starting at offset.")]
public static void pack_into(CodeContext/*!*/ context, object fmt, [NotNull]ArrayModule.array/*!*/ buffer, int offset, params object[] args) {
GetStructFromCache(context, fmt).pack_into(context, buffer, offset, args);
}
public static void pack_into(CodeContext/*!*/ context, object fmt, [NotNull]ByteArray/*!*/ buffer, int offset, params object[] args) {
GetStructFromCache(context, fmt).pack_into(context, buffer, offset, args);
}
[Documentation("Unpack the string containing packed C structure data, according to fmt.\nRequires len(string) == calcsize(fmt).")]
public static PythonTuple/*!*/ unpack(CodeContext/*!*/ context, object fmt, [BytesConversion][NotNull]IList/*!*/ buffer) {
return GetStructFromCache(context, fmt).unpack(context, buffer);
}
[Documentation("Unpack the string containing packed C structure data, according to fmt.\nRequires len(string) == calcsize(fmt).")]
public static PythonTuple/*!*/ unpack(CodeContext/*!*/ context, object fmt, [NotNull]ArrayModule.array/*!*/ buffer) {
return GetStructFromCache(context, fmt).unpack(context, buffer);
}
[Documentation("Unpack the buffer, containing packed C structure data, according to\nfmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt).")]
public static PythonTuple/*!*/ unpack_from(CodeContext/*!*/ context, object fmt, [BytesConversion][NotNull]IList/*!*/ buffer, int offset = 0) {
return GetStructFromCache(context, fmt).unpack_from(context, buffer, offset);
}
[Documentation("Unpack the buffer, containing packed C structure data, according to\nfmt, starting at offset. Requires len(buffer[offset:]) >= calcsize(fmt).")]
public static PythonTuple/*!*/ unpack_from(CodeContext/*!*/ context, object fmt, [NotNull]ArrayModule.array/*!*/ buffer, int offset = 0) {
return GetStructFromCache(context, fmt).unpack_from(context, buffer, offset);
}
#endregion
#region Write Helpers
private static void WriteByteCount(this MemoryStream stream, byte value, int repeatCount) {
while (repeatCount > 0) {
stream.WriteByte(value);
--repeatCount;
}
}
private static void WriteShort(this MemoryStream res, bool fLittleEndian, short val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
} else {
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WriteUShort(this MemoryStream res, bool fLittleEndian, ushort val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
} else {
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WriteInt(this MemoryStream res, bool fLittleEndian, int val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
} else {
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WriteUInt(this MemoryStream res, bool fLittleEndian, uint val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
} else {
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WritePointer(this MemoryStream res, bool fLittleEndian, IntPtr val) {
if (IntPtr.Size == 4) {
res.WriteInt(fLittleEndian, val.ToInt32());
} else {
res.WriteLong(fLittleEndian, val.ToInt64());
}
}
private static void WriteFloat(this MemoryStream res, bool fLittleEndian, float val) {
byte[] bytes = BitConverter.GetBytes(val);
if (BitConverter.IsLittleEndian == fLittleEndian) {
res.Write(bytes, 0, bytes.Length);
} else {
res.WriteByte(bytes[3]);
res.WriteByte(bytes[2]);
res.WriteByte(bytes[1]);
res.WriteByte(bytes[0]);
}
}
private static void WriteLong(this MemoryStream res, bool fLittleEndian, long val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 32) & 0xff));
res.WriteByte((byte)((val >> 40) & 0xff));
res.WriteByte((byte)((val >> 48) & 0xff));
res.WriteByte((byte)((val >> 56) & 0xff));
} else {
res.WriteByte((byte)((val >> 56) & 0xff));
res.WriteByte((byte)((val >> 48) & 0xff));
res.WriteByte((byte)((val >> 40) & 0xff));
res.WriteByte((byte)((val >> 32) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WriteULong(this MemoryStream res, bool fLittleEndian, ulong val) {
if (fLittleEndian) {
res.WriteByte((byte)(val & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 32) & 0xff));
res.WriteByte((byte)((val >> 40) & 0xff));
res.WriteByte((byte)((val >> 48) & 0xff));
res.WriteByte((byte)((val >> 56) & 0xff));
} else {
res.WriteByte((byte)((val >> 56) & 0xff));
res.WriteByte((byte)((val >> 48) & 0xff));
res.WriteByte((byte)((val >> 40) & 0xff));
res.WriteByte((byte)((val >> 32) & 0xff));
res.WriteByte((byte)((val >> 24) & 0xff));
res.WriteByte((byte)((val >> 16) & 0xff));
res.WriteByte((byte)((val >> 8) & 0xff));
res.WriteByte((byte)(val & 0xff));
}
}
private static void WriteDouble(this MemoryStream res, bool fLittleEndian, double val) {
byte[] bytes = BitConverter.GetBytes(val);
if (BitConverter.IsLittleEndian == fLittleEndian) {
res.Write(bytes, 0, bytes.Length);
} else {
res.WriteByte(bytes[7]);
res.WriteByte(bytes[6]);
res.WriteByte(bytes[5]);
res.WriteByte(bytes[4]);
res.WriteByte(bytes[3]);
res.WriteByte(bytes[2]);
res.WriteByte(bytes[1]);
res.WriteByte(bytes[0]);
}
}
private static void WriteString(this MemoryStream res, int len, IList val) {
for (int i = 0; i < val.Count && i < len; i++) {
res.WriteByte(val[i]);
}
for (int i = val.Count; i < len; i++) {
res.WriteByte(0);
}
}
private static void WritePascalString(this MemoryStream res, int len, IList val) {
byte lenByte = (byte)Math.Min(255, Math.Min(val.Count, len));
res.WriteByte(lenByte);
for (int i = 0; i < val.Count && i < len; i++) {
res.WriteByte(val[i]);
}
for (int i = val.Count; i < len; i++) {
res.WriteByte(0);
}
}
#endregion
#region Data getter helpers
internal static bool GetBoolValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvert(val, typeof(bool), out object res)) {
return (bool)res;
}
// Should never happen
throw Error(context, "expected bool value got " + val.ToString());
}
internal static char GetCharValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (val is string strVal && strVal.Length == 1) return strVal[0];
if (val is IList byteVal && byteVal.Count == 1) return (char)byteVal[0];
throw Error(context, "char format requires string of length 1");
}
internal static sbyte GetSByteValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToSByte(val, out sbyte res)) {
return res;
}
throw Error(context, "expected sbyte value got " + val.ToString());
}
internal static byte GetByteValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToByte(val, out byte res)) return res;
if (Converter.TryConvertToChar(val, out char cres)) return (byte)cres;
throw Error(context, "expected byte value got " + val.ToString());
}
internal static short GetShortValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToInt16(val, out short res)) return res;
throw Error(context, "expected short value");
}
internal static ushort GetUShortValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToUInt16(val, out ushort res)) return res;
throw Error(context, "expected ushort value");
}
internal static int GetIntValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToInt32(val, out int res)) return res;
throw Error(context, "expected int value");
}
internal static uint GetULongValue(CodeContext/*!*/ context, int index, object[] args, string type) {
object val = GetValue(context, index, args);
if (val is int) {
CheckRange(context, (int)val, type);
return (uint)(int)val;
} else if (val is BigInteger) {
CheckRange(context, (BigInteger)val, type);
return (uint)(BigInteger)val;
} else if (val is Extensible) {
CheckRange(context, ((Extensible)val).Value, type);
return (uint)((Extensible)val).Value;
} else if (val is Extensible) {
CheckRange(context, ((Extensible)val).Value, type);
return (uint)((Extensible)val).Value;
} else {
if (PythonTypeOps.TryInvokeUnaryOperator(DefaultContext.Default, val, "__int__", out object objres)) {
if (objres is int) {
CheckRange(context, (int)objres, type);
return (uint)(int)objres;
}
}
if (Converter.TryConvertToUInt32(val, out uint res)) {
return res;
}
}
throw Error(context, "cannot convert argument to integer");
}
private static void CheckRange(CodeContext context, int val, string type) {
if (val < 0) {
OutOfRange(context, type);
}
}
private static void CheckRange(CodeContext context, BigInteger bi, string type) {
if (bi < 0 || bi > 4294967295) {
OutOfRange(context, type);
}
}
private static void OutOfRange(CodeContext context, string type) {
throw Error(context, $"integer out of range for '{(type == "unsigned long" ? "L" : "I")}' format code");
}
internal static IntPtr GetPointer(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (IntPtr.Size == 4) {
if (Converter.TryConvertToUInt32(val, out uint res)) {
return new IntPtr(res);
}
} else {
if (Converter.TryConvertToInt64(val, out long res)) {
return new IntPtr(res);
}
}
throw Error(context, "expected pointer value");
}
internal static long GetLongValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToInt64(val, out long res)) return res;
throw Error(context, "expected long value");
}
internal static ulong GetULongLongValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToUInt64(val, out ulong res)) return res;
throw Error(context, "expected ulong value");
}
internal static double GetDoubleValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (Converter.TryConvertToDouble(val, out double res)) return res;
throw Error(context, "expected double value");
}
internal static IList GetStringValue(CodeContext/*!*/ context, int index, object[] args) {
object val = GetValue(context, index, args);
if (val is IList res) return res;
throw Error(context, "expected string value");
}
internal static object GetValue(CodeContext/*!*/ context, int index, object[] args) {
if (index >= args.Length) throw Error(context, "not enough arguments");
return args[index];
}
#endregion
#region Data creater helpers
internal static bool CreateBoolValue(CodeContext/*!*/ context, ref int index, IList data) {
return (int)ReadData(context, ref index, data) != 0;
}
internal static byte CreateCharValue(CodeContext/*!*/ context, ref int index, IList data) {
return ReadData(context, ref index, data);
}
internal static short CreateShortValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte b1 = (byte)ReadData(context, ref index, data);
byte b2 = (byte)ReadData(context, ref index, data);
if (fLittleEndian) {
return (short)((b2 << 8) | b1);
} else {
return (short)((b1 << 8) | b2);
}
}
internal static ushort CreateUShortValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte b1 = (byte)ReadData(context, ref index, data);
byte b2 = (byte)ReadData(context, ref index, data);
if (fLittleEndian) {
return (ushort)((b2 << 8) | b1);
} else {
return (ushort)((b1 << 8) | b2);
}
}
internal static float CreateFloatValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte[] bytes = new byte[4];
if (fLittleEndian) {
bytes[0] = (byte)ReadData(context, ref index, data);
bytes[1] = (byte)ReadData(context, ref index, data);
bytes[2] = (byte)ReadData(context, ref index, data);
bytes[3] = (byte)ReadData(context, ref index, data);
} else {
bytes[3] = (byte)ReadData(context, ref index, data);
bytes[2] = (byte)ReadData(context, ref index, data);
bytes[1] = (byte)ReadData(context, ref index, data);
bytes[0] = (byte)ReadData(context, ref index, data);
}
float res = BitConverter.ToSingle(bytes, 0);
if (context.LanguageContext.FloatFormat == FloatFormat.Unknown) {
if (float.IsNaN(res) || float.IsInfinity(res)) {
throw PythonOps.ValueError("can't unpack IEEE 754 special value on non-IEEE platform");
}
}
return res;
}
internal static int CreateIntValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte b1 = (byte)ReadData(context, ref index, data);
byte b2 = (byte)ReadData(context, ref index, data);
byte b3 = (byte)ReadData(context, ref index, data);
byte b4 = (byte)ReadData(context, ref index, data);
if (fLittleEndian)
return (int)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
else
return (int)((b1 << 24) | (b2 << 16) | (b3 << 8) | b4);
}
internal static uint CreateUIntValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte b1 = (byte)ReadData(context, ref index, data);
byte b2 = (byte)ReadData(context, ref index, data);
byte b3 = (byte)ReadData(context, ref index, data);
byte b4 = (byte)ReadData(context, ref index, data);
if (fLittleEndian)
return (uint)((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
else
return (uint)((b1 << 24) | (b2 << 16) | (b3 << 8) | b4);
}
internal static long CreateLongValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
long b1 = (byte)ReadData(context, ref index, data);
long b2 = (byte)ReadData(context, ref index, data);
long b3 = (byte)ReadData(context, ref index, data);
long b4 = (byte)ReadData(context, ref index, data);
long b5 = (byte)ReadData(context, ref index, data);
long b6 = (byte)ReadData(context, ref index, data);
long b7 = (byte)ReadData(context, ref index, data);
long b8 = (byte)ReadData(context, ref index, data);
if (fLittleEndian)
return (long)((b8 << 56) | (b7 << 48) | (b6 << 40) | (b5 << 32) |
(b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
else
return (long)((b1 << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32) |
(b5 << 24) | (b6 << 16) | (b7 << 8) | b8);
}
internal static ulong CreateULongValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
ulong b1 = (byte)ReadData(context, ref index, data);
ulong b2 = (byte)ReadData(context, ref index, data);
ulong b3 = (byte)ReadData(context, ref index, data);
ulong b4 = (byte)ReadData(context, ref index, data);
ulong b5 = (byte)ReadData(context, ref index, data);
ulong b6 = (byte)ReadData(context, ref index, data);
ulong b7 = (byte)ReadData(context, ref index, data);
ulong b8 = (byte)ReadData(context, ref index, data);
if (fLittleEndian)
return (ulong)((b8 << 56) | (b7 << 48) | (b6 << 40) | (b5 << 32) |
(b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
else
return (ulong)((b1 << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32) |
(b5 << 24) | (b6 << 16) | (b7 << 8) | b8);
}
internal static double CreateDoubleValue(CodeContext/*!*/ context, ref int index, bool fLittleEndian, IList data) {
byte[] bytes = new byte[8];
if (fLittleEndian) {
bytes[0] = (byte)ReadData(context, ref index, data);
bytes[1] = (byte)ReadData(context, ref index, data);
bytes[2] = (byte)ReadData(context, ref index, data);
bytes[3] = (byte)ReadData(context, ref index, data);
bytes[4] = (byte)ReadData(context, ref index, data);
bytes[5] = (byte)ReadData(context, ref index, data);
bytes[6] = (byte)ReadData(context, ref index, data);
bytes[7] = (byte)ReadData(context, ref index, data);
} else {
bytes[7] = (byte)ReadData(context, ref index, data);
bytes[6] = (byte)ReadData(context, ref index, data);
bytes[5] = (byte)ReadData(context, ref index, data);
bytes[4] = (byte)ReadData(context, ref index, data);
bytes[3] = (byte)ReadData(context, ref index, data);
bytes[2] = (byte)ReadData(context, ref index, data);
bytes[1] = (byte)ReadData(context, ref index, data);
bytes[0] = (byte)ReadData(context, ref index, data);
}
double res = BitConverter.ToDouble(bytes, 0);
if (context.LanguageContext.DoubleFormat == FloatFormat.Unknown) {
if (double.IsNaN(res) || double.IsInfinity(res)) {
throw PythonOps.ValueError("can't unpack IEEE 754 special value on non-IEEE platform");
}
}
return res;
}
internal static Bytes CreateString(CodeContext/*!*/ context, ref int index, int count, IList data) {
using var res = new MemoryStream();
for (int i = 0; i < count; i++) {
res.WriteByte(ReadData(context, ref index, data));
}
return Bytes.Make(res.ToArray());
}
internal static Bytes CreatePascalString(CodeContext/*!*/ context, ref int index, int count, IList data) {
int realLen = (int)ReadData(context, ref index, data);
using var res = new MemoryStream();
for (int i = 0; i < realLen; i++) {
res.WriteByte(ReadData(context, ref index, data));
}
for (int i = realLen; i < count; i++) {
// throw away null bytes
ReadData(context, ref index, data);
}
return Bytes.Make(res.ToArray());
}
private static byte ReadData(CodeContext/*!*/ context, ref int index, IList data) {
if (index >= data.Count) throw Error(context, "not enough data while reading");
return data[index++];
}
#endregion
#region Misc. Private APIs
internal static int Align(int length, int size) {
return length + (size - 1) & ~(size - 1);
}
private static Exception Error(CodeContext/*!*/ context, string msg) {
return PythonExceptions.CreateThrowable((PythonType)context.LanguageContext.GetModuleState("structerror"), msg);
}
#endregion
}
}