Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions src/Bonsai.Sgen/CSharpClassTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,27 +436,5 @@ private void BuildDefaultPropertyInitializer(
finally { defaultPropertySchema.Default = schemaDefault; }
}
}

static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "bool", "System.Boolean" },
{ "byte", "System.Byte" },
{ "sbyte", "System.SByte" },
{ "char", "System.Char" },
{ "decimal", "System.Decimal" },
{ "double", "System.Double" },
{ "float", "System.Single" },
{ "int", "System.Int32" },
{ "uint", "System.UInt32" },
{ "nint", "System.IntPtr" },
{ "nuint", "System.UIntPtr" },
{ "long", "System.Int64" },
{ "ulong", "System.UInt64" },
{ "short", "System.Int16" },
{ "ushort", "System.UInt16" },

{ "object", "System.Object" },
{ "string", "System.String" }
};
}
}
7 changes: 7 additions & 0 deletions src/Bonsai.Sgen/CSharpCodeDomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ public override IEnumerable<CodeArtifact> GenerateTypes()
extraTypes.Add(GenerateClass(serializer));
extraTypes.Add(GenerateClass(deserializer));
}
if (Settings.SerializerLibraries.HasFlag(SerializerLibraries.PythonNet))
{
var codec = new CSharpPythonCodecTemplate(classTypes, _provider, _options, Settings);
var deserializer = new CSharpPythonDeserializerTemplate(schema, classTypes, _provider, _options, Settings);
extraTypes.Add(GenerateClass(codec));
extraTypes.Add(GenerateClass(deserializer));
}

return types
.Where(type => type.Type != CodeArtifactType.Undefined)
Expand Down
26 changes: 25 additions & 1 deletion src/Bonsai.Sgen/CSharpCodeDomTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ private string GetVersionString()
{
serializerLibraries.Add(GetVersionString(YamlDotNetAssemblyName));
}
return $"{GeneratorAssemblyName.Version} ({string.Join(", ", serializerLibraries)})";
return serializerLibraries.Count > 0
? $"{GeneratorAssemblyName.Version} ({string.Join(", ", serializerLibraries)})"
: GeneratorAssemblyName.Version!.ToString();
}

public string Render()
Expand All @@ -66,5 +68,27 @@ public string Render()
Provider.GenerateCodeFromType(type, writer, Options);
return writer.ToString();
}

protected static readonly Dictionary<string, string> PrimitiveTypes = new()
{
{ "bool", "System.Boolean" },
{ "byte", "System.Byte" },
{ "sbyte", "System.SByte" },
{ "char", "System.Char" },
{ "decimal", "System.Decimal" },
{ "double", "System.Double" },
{ "float", "System.Single" },
{ "int", "System.Int32" },
{ "uint", "System.UInt32" },
{ "nint", "System.IntPtr" },
{ "nuint", "System.UIntPtr" },
{ "long", "System.Int64" },
{ "ulong", "System.UInt64" },
{ "short", "System.Int16" },
{ "ushort", "System.UInt16" },

{ "object", "System.Object" },
{ "string", "System.String" }
};
}
}
127 changes: 127 additions & 0 deletions src/Bonsai.Sgen/CSharpPythonCodecTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.CodeDom;
using System.CodeDom.Compiler;
using NJsonSchema.CodeGeneration;

namespace Bonsai.Sgen
{
internal class CSharpPythonCodecTemplate : CSharpSerializerTemplate
{
const string DataVariableName = "value";
static readonly CodeTypeReference PyObjectTypeReference = new("Python.Runtime.PyObject");

public CSharpPythonCodecTemplate(
IEnumerable<CSharpClassCodeArtifact> modelTypes,
CodeDomProvider provider,
CodeGeneratorOptions options,
CSharpCodeDomGeneratorSettings settings)
: base(modelTypes, provider, options, settings)
{
}

public override string Description => "Provides a collection of methods for converting generic Python objects into data model objects.";

public override string TypeName => "PyObjectConverter";

public override void BuildType(CodeTypeDeclaration type)
{
base.BuildType(type);
type.BaseTypes.Add("Bonsai.Sink");
type.BaseTypes.Add("Python.Runtime.IPyObjectDecoder");

var decoderItems = new List<string>();
var decoderDictionaryMember = new CodeSnippetTypeMember();
type.Members.Add(decoderDictionaryMember);

type.Members.Add(new CodeSnippetTypeMember(
@" private static T GetAttr<T>(Python.Runtime.PyObject pyObj, string attributeName)
{
using (var attr = pyObj.GetAttr(attributeName))
{
return attr.As<T>();
}
}

static System.Func<Python.Runtime.PyObject, object> CreateDecoder<T>(System.Action<Python.Runtime.PyObject, T> decode)
where T : new()
{
return pyObj =>
{
T value = new T();
decode(pyObj, value);
return value;
};
}

public bool CanDecode(Python.Runtime.PyType objectType, System.Type targetType)
{
return decoders.ContainsKey(targetType);
}

public bool TryDecode<T>(Python.Runtime.PyObject pyObj, out T value)
{
System.Func<Python.Runtime.PyObject, object> decoder;
if (decoders.TryGetValue(typeof(T), out decoder))
{
value = (T)decoder(pyObj);
return true;
}
value = default(T);
return false;
}"));

foreach (var modelType in ModelTypes.Cast<CSharpClassCodeArtifact>())
{
var model = modelType.Model;
var modelTypeReference = new CodeTypeReference(model.ClassName);
var decodeMethod = new CodeMemberMethod
{
Name = $"Decode{model.ClassName}",
Attributes = MemberAttributes.Static,
Parameters = { new(PyObjectTypeReference, "pyObj"), new(modelTypeReference, DataVariableName) },
};

var modelObjectReference = new CodeVariableReferenceExpression(DataVariableName);
var pyObjectReference = new CodeVariableReferenceExpression("pyObj");
if (model.BaseClass != null)
{
decodeMethod.Statements.Add(new CodeMethodInvokeExpression(
targetObject: null,
$"Decode{model.BaseClassName}",
pyObjectReference,
modelObjectReference));
}

foreach (var property in model.Properties)
{
var isPrimitive = PrimitiveTypes.TryGetValue(property.Type, out string? underlyingType);
var propertyTypeReference = new CodeTypeReference(isPrimitive ? underlyingType : property.Type);
decodeMethod.Statements.Add(new CodeAssignStatement(
new CodePropertyReferenceExpression(modelObjectReference, property.PropertyName),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(null, "GetAttr", propertyTypeReference),
pyObjectReference,
new CodePrimitiveExpression(property.Name))));
}
type.Members.Add(decodeMethod);
if (!model.IsAbstract)
{
decoderItems.Add($" {{ typeof({model.ClassName}), CreateDecoder<{model.ClassName}>({decodeMethod.Name}) }}");
}
}

decoderDictionaryMember.Text =
@$" static readonly System.Collections.Generic.Dictionary<System.Type, System.Func<Python.Runtime.PyObject, object>> decoders =
new System.Collections.Generic.Dictionary<System.Type, System.Func<Python.Runtime.PyObject, object>>
{{
{string.Join($",{Environment.NewLine}", decoderItems)}
}};
";

type.Members.Add(new CodeSnippetTypeMember(
@" public override System.IObservable<TSource> Process<TSource>(System.IObservable<TSource> source)
{
return System.Reactive.Linq.Observable.Do(source, _ => Python.Runtime.PyObjectConversions.RegisterDecoder(this));
}"));
}
}
}
33 changes: 33 additions & 0 deletions src/Bonsai.Sgen/CSharpPythonDeserializerTemplate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.CodeDom;
using System.CodeDom.Compiler;
using NJsonSchema;

namespace Bonsai.Sgen
{
internal class CSharpPythonDeserializerTemplate : CSharpDeserializerTemplate
{
public CSharpPythonDeserializerTemplate(
JsonSchema schema,
IEnumerable<CSharpClassCodeArtifact> modelTypes,
CodeDomProvider provider,
CodeGeneratorOptions options,
CSharpCodeDomGeneratorSettings settings)
: base(schema, modelTypes, provider, options, settings)
{
}

public override string Description => "Converts a sequence of generic Python objects into data model objects.";

public override string TypeName => "FromPython";

public override void BuildType(CodeTypeDeclaration type)
{
base.BuildType(type);
type.Members.Add(new CodeSnippetTypeMember(
@" private static System.IObservable<T> Process<T>(System.IObservable<Python.Runtime.PyObject> source)
{
return System.Reactive.Linq.Observable.Select(source, value => value.As<T>());
}"));
}
}
}
3 changes: 2 additions & 1 deletion src/Bonsai.Sgen/SerializerLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum SerializerLibraries
{
None = 0x0,
NewtonsoftJson = 0x1,
YamlDotNet = 0x4
YamlDotNet = 0x4,
PythonNet = 0x8
}
}