Skip to content

Commit

Permalink
Merge branch 'main' of github.com:tonybaloney/CSnakes
Browse files Browse the repository at this point in the history
  • Loading branch information
tonybaloney committed Aug 21, 2024
2 parents 75851bc + ec90c9c commit bf3fff0
Show file tree
Hide file tree
Showing 19 changed files with 231 additions and 82 deletions.
17 changes: 16 additions & 1 deletion src/CSnakes.Runtime/CPython/Import.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,24 @@ internal static PyObject Import(string name)
nint pyName = AsPyUnicodeObject(name);
nint module = PyImport_Import(pyName);
Py_DecRefRaw(pyName);
return new(module);
return PyObject.Create(module);
}

protected static nint GetBuiltin(string name)
{
nint pyName = AsPyUnicodeObject("builtins");
nint pyAttrName = AsPyUnicodeObject(name);
nint module = PyImport_Import(pyName);
nint attr = PyObject_GetAttrRaw(module, pyAttrName);
if (attr == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
}
Py_DecRefRaw(pyName);
Py_DecRefRaw(pyAttrName);
return attr;
}

/// <summary>
/// Import and return a reference to the module `name`
/// </summary>
Expand Down
7 changes: 2 additions & 5 deletions src/CSnakes.Runtime/CPython/Init.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSnakes.Runtime.Python;
using CSnakes.Runtime.Python.Interns;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -79,11 +80,7 @@ private void InitializeEmbeddedPython()
PyDictType = GetTypeRaw(PyDict_New());
PyBytesType = GetTypeRaw(PyBytes_FromByteSpan(new byte[] { }));
ItemsStrIntern = AsPyUnicodeObject("items");

// Import builtins module
var builtinsMod = Import("builtins");
PyNone = GetAttr(builtinsMod, "None");
Py_DecRef(builtinsMod);
PyNone = GetBuiltin("None");
}
PyEval_SaveThread();
}
Expand Down
12 changes: 11 additions & 1 deletion src/CSnakes.Runtime/CPython/None.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace CSnakes.Runtime.CPython;
using CSnakes.Runtime.Python;
namespace CSnakes.Runtime.CPython;

internal unsafe partial class CPythonAPI
{
Expand All @@ -10,7 +11,16 @@ internal unsafe partial class CPythonAPI
/// <returns>A new reference to None. In newer versions of Python, None is immortal anyway.</returns>
internal static nint GetNone()
{
if (PyNone == IntPtr.Zero)
{
throw new InvalidOperationException("Python is not initialized. You cannot call this method outside of a Python Environment context.");
}
Py_IncRefRaw(PyNone);
return PyNone;
}

internal static bool IsNone(PyObject o)
{
return PyNone == o.DangerousGetHandle();
}
}
3 changes: 3 additions & 0 deletions src/CSnakes.Runtime/CPython/Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ internal static bool HasAttr(PyObject ob, string name)
[LibraryImport(PythonLibraryName)]
internal static partial IntPtr PyObject_GetAttr(PyObject ob, IntPtr attr);

[LibraryImport(PythonLibraryName, EntryPoint = "PyObject_GetAttr")]
private static partial IntPtr PyObject_GetAttrRaw(IntPtr ob, IntPtr attr);

/// <summary>
/// Does the object ob have the attr `attr`?
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal partial class PyObjectTypeConverter

private PyObject ConvertFromBigInteger(ITypeDescriptorContext? context, CultureInfo? culture, BigInteger integer)
{
using PyObject pyUnicode = new PyObject(CPythonAPI.AsPyUnicodeObject(integer.ToString()));
return new PyObject(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10));
using PyObject pyUnicode = PyObject.Create(CPythonAPI.AsPyUnicodeObject(integer.ToString()));
return PyObject.Create(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10));
}
}
32 changes: 21 additions & 11 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,49 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToDictionary(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture, bool useMappingProtocol = false)
{
using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject));
using PyObject items = useMappingProtocol ? PyObject.Create(CPythonAPI.PyMapping_Items(pyObject)) : PyObject.Create(CPythonAPI.PyDict_Items(pyObject));

Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
IDictionary dict = (IDictionary)Activator.CreateInstance(dictType)!;

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);

typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IDictionary dict = (IDictionary)typeInfo.TransientTypeConstructor!.Invoke([]);
nint itemsLength = CPythonAPI.PyList_Size(items);

for (nint i = 0; i < itemsLength; i++)
{
using PyObject item = new(CPythonAPI.PyList_GetItem(items, i));
using PyObject item = PyObject.Create(CPythonAPI.PyList_GetItem(items, i));

using PyObject item1 = new(CPythonAPI.PyTuple_GetItem(item, 0));
using PyObject item2 = new(CPythonAPI.PyTuple_GetItem(item, 1));
using PyObject item1 = PyObject.Create(CPythonAPI.PyTuple_GetItem(item, 0));
using PyObject item2 = PyObject.Create(CPythonAPI.PyTuple_GetItem(item, 1));

object? convertedItem1 = AsManagedObject(item1Type, item1, context, culture);
object? convertedItem2 = AsManagedObject(item2Type, item2, context, culture);
object? convertedItem1 = ConvertTo(context, culture, item1, item1Type);
object? convertedItem2 = ConvertTo(context, culture, item2, item2Type);

dict.Add(convertedItem1!, convertedItem2);
}

Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);
return Activator.CreateInstance(returnType, dict);
return typeInfo.ReturnTypeConstructor.Invoke([dict]);
}

private PyObject ConvertFromDictionary(ITypeDescriptorContext? context, CultureInfo? culture, IDictionary dictionary)
{
PyObject pyDict = new(CPythonAPI.PyDict_New());
PyObject pyDict = PyObject.Create(CPythonAPI.PyDict_New());

foreach (DictionaryEntry kvp in dictionary)
{
Expand Down
18 changes: 12 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ internal partial class PyObjectTypeConverter
{
private object? ConvertToGeneratorIterator(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
return ctor.Invoke([pyObject.Clone()]);
if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

return typeInfo.ReturnTypeConstructor.Invoke([pyObject.Clone()]);
}
}
31 changes: 22 additions & 9 deletions src/CSnakes.Runtime/PyObjectTypeConverter.List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ internal partial class PyObjectTypeConverter
private object? ConvertToList(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);

IList list = (IList)Activator.CreateInstance(listType)!;
if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);
for (var i = 0; i < CPythonAPI.PyList_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PyList_GetItem(pyObject, i));
list.Add(AsManagedObject(genericArgument, item, context, culture));
using PyObject item = PyObject.Create(CPythonAPI.PyList_GetItem(pyObject, i));
list.Add(ConvertTo(context, culture, item, genericArgument));
}

return list;
Expand All @@ -25,21 +31,28 @@ internal partial class PyObjectTypeConverter
private object? ConvertToListFromSequence(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);

IList list = (IList)Activator.CreateInstance(listType)!;
if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);

for (var i = 0; i < CPythonAPI.PySequence_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PySequence_GetItem(pyObject, i));
list.Add(AsManagedObject(genericArgument, item, context, culture));
using PyObject item = PyObject.Create(CPythonAPI.PySequence_GetItem(pyObject, i));
list.Add(ConvertTo(context, culture, item, genericArgument));
}

return list;
}

private PyObject ConvertFromList(ITypeDescriptorContext? context, CultureInfo? culture, IEnumerable e)
{
PyObject pyList = new(CPythonAPI.PyList_New(0));
PyObject pyList = PyObject.Create(CPythonAPI.PyList_New(0));

foreach (var item in e)
{
Expand Down
18 changes: 12 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo?
var tupleValues = new List<PyObject>();
for (nint i = 0; i < CPythonAPI.PyTuple_Size(pyObj); i++)
{
PyObject value = new(CPythonAPI.PyTuple_GetItem(pyObj, i));
PyObject value = PyObject.Create(CPythonAPI.PyTuple_GetItem(pyObj, i));
tupleValues.Add(value);
}

// If we have more than 8 python values, we are going to have a nested tuple, which we have to unpack.
if (tupleValues.Count > 8)
{
// We are hitting nested tuples here, which will be treated in a different way.
object?[] firstSeven = tupleValues.Take(7).Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray();
object?[] firstSeven = tupleValues.Take(7).Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray();

// Get the rest of the values and convert them to a nested tuple.
IEnumerable<PyObject> rest = tupleValues.Skip(7);
Expand All @@ -53,23 +53,29 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo?

// Use the decoder pipeline to decode the nested tuple (and its values).
// We do this because that means if we have nested nested tuples, they'll be decoded as well.
object? nestedTuple = AsManagedObject(types[7], pyTuple, context, culture);
object? nestedTuple = ConvertTo(context, culture, pyTuple, types[7]);

// Append our nested tuple to the first seven values.
clrValues = [.. firstSeven, nestedTuple];
}
else
{
clrValues = tupleValues.Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray();
clrValues = tupleValues.Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray();
}

// Dispose of all the Python values that we captured from the Tuple.
foreach (var value in tupleValues)
{
value.Dispose();
}

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
return (ITuple)ctor.Invoke(clrValues);
return (ITuple)typeInfo.ReturnTypeConstructor.Invoke(clrValues);
}
}
27 changes: 14 additions & 13 deletions src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Numerics;
using System.Collections.Concurrent;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter : TypeConverter
{
private readonly ConcurrentDictionary<Type, DynamicTypeInfo> knownDynamicTypes = [];

/// <summary>
/// Convert a Python object to a CLR managed object.
Expand Down Expand Up @@ -118,25 +121,21 @@ internal partial class PyObjectTypeConverter : TypeConverter
throw new InvalidCastException($"Attempting to cast {destinationType} from {pyObject.GetPythonType()}");
}

private object? AsManagedObject(Type type, PyObject p, ITypeDescriptorContext? context, CultureInfo? culture)
{
return ConvertTo(context, culture, p, type);
}

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) =>
value switch
{
string str => new PyObject(CPythonAPI.AsPyUnicodeObject(str)),
byte[] bytes => new PyObject(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())),
long l => new PyObject(CPythonAPI.PyLong_FromLongLong(l)),
int i => new PyObject(CPythonAPI.PyLong_FromLong(i)),
bool b => new PyObject(CPythonAPI.PyBool_FromLong(b ? 1 : 0)),
double d => new PyObject(CPythonAPI.PyFloat_FromDouble(d)),
string str => PyObject.Create(CPythonAPI.AsPyUnicodeObject(str)),
byte[] bytes => PyObject.Create(CPythonAPI.PyBytes_FromByteSpan(bytes.AsSpan())),
long l => PyObject.Create(CPythonAPI.PyLong_FromLongLong(l)),
int i => PyObject.Create(CPythonAPI.PyLong_FromLong(i)),
bool b => PyObject.Create(CPythonAPI.PyBool_FromLong(b ? 1 : 0)),
double d => PyObject.Create(CPythonAPI.PyFloat_FromDouble(d)),
IDictionary dictionary => ConvertFromDictionary(context, culture, dictionary),
ITuple t => ConvertFromTuple(context, culture, t),
IEnumerable e => ConvertFromList(context, culture, e),
BigInteger b => ConvertFromBigInteger(context, culture, b),
null => new PyObject(CPythonAPI.GetNone()),
PyObject pyObject => pyObject.Clone(),
null => PyObject.None,
_ => base.ConvertFrom(context, culture, value)
};

Expand Down Expand Up @@ -179,11 +178,13 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf
{
if (o is null)
{
return new PyObject(CPythonAPI.GetNone());
return PyObject.None;
}

var result = ConvertFrom(context, culture, o);

return result is null ? throw new NotImplementedException() : (PyObject)result;
}

record DynamicTypeInfo(ConstructorInfo ReturnTypeConstructor, ConstructorInfo? TransientTypeConstructor = null);
}
16 changes: 16 additions & 0 deletions src/CSnakes.Runtime/Python/Interns/ImmortalPyObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CSnakes.Runtime.Python.Interns;

internal class ImmortalPyObject : PyObject
{
internal ImmortalPyObject(nint handle) : base(handle)
{
}

protected override bool ReleaseHandle() => true;


protected override void Dispose(bool disposing)
{
// I am immortal!!
}
}
18 changes: 18 additions & 0 deletions src/CSnakes.Runtime/Python/Interns/PyNoneObject.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CSnakes.Runtime.CPython;

namespace CSnakes.Runtime.Python.Interns;

internal sealed class PyNoneObject : ImmortalPyObject
{
public PyNoneObject() : base(CPythonAPI.GetNone())
{
}

public override bool IsNone() => true;

public override string GetRepr() => ToString();

public override string ToString() => "None";

internal override PyObject Clone() => this;
}
Loading

0 comments on commit bf3fff0

Please sign in to comment.