From 43d9bd1d0d2c4a32ec97a6290bf7437570bd2c36 Mon Sep 17 00:00:00 2001 From: liquidiert Date: Mon, 6 Nov 2023 00:34:17 +0100 Subject: [PATCH 1/6] feat: #56 Python support - added Python generator - added Python runtime --- .vscode/launch.json | 4 +- Compiler/CommandLineFlags.cs | 7 +- Core/Generators/GeneratorUtils.cs | 6 +- Core/Generators/Python/PythonGenerator.cs | 506 ++++++++++++ .../TypeScript/TypeScriptGenerator.cs | 2 +- Laboratory/Integration/.gitignore | 1 + Laboratory/Integration/Python/src/decode.py | 21 + Laboratory/Integration/Python/src/encode.py | 6 + Laboratory/Integration/Python/src/lib.py | 48 ++ Laboratory/Integration/run_test.js | 6 +- Laboratory/Integration/yarn.lock | 628 +++++++++++++++ Laboratory/Python/.gitignore | 176 +++++ Laboratory/Python/test/schemas.py | 741 ++++++++++++++++++ Laboratory/Python/test/test_basic_types.py | 41 + Laboratory/Python/test/test_map_types.py | 27 + Laboratory/Python/test/test_union_type.py | 67 ++ Laboratory/Schemas/.gitignore | 1 + Laboratory/Schemas/ShouldFail/bebop.json | 4 +- Laboratory/Schemas/bebop.json | 9 +- Repl/Pages/Index.razor | 1 + Runtime/Python/.gitignore | 154 ++++ Runtime/Python/LICENSE | 21 + Runtime/Python/README.md | 3 + Runtime/Python/pyproject.toml | 3 + Runtime/Python/setup.cfg | 24 + Runtime/Python/src/python_bebop/__init__.py | 1 + Runtime/Python/src/python_bebop/bebop.py | 228 ++++++ Runtime/TypeScript/yarn.lock | 320 ++++---- 28 files changed, 2886 insertions(+), 170 deletions(-) create mode 100644 Core/Generators/Python/PythonGenerator.cs create mode 100644 Laboratory/Integration/Python/src/decode.py create mode 100644 Laboratory/Integration/Python/src/encode.py create mode 100644 Laboratory/Integration/Python/src/lib.py create mode 100644 Laboratory/Integration/yarn.lock create mode 100644 Laboratory/Python/.gitignore create mode 100644 Laboratory/Python/test/schemas.py create mode 100644 Laboratory/Python/test/test_basic_types.py create mode 100644 Laboratory/Python/test/test_map_types.py create mode 100644 Laboratory/Python/test/test_union_type.py create mode 100644 Runtime/Python/.gitignore create mode 100644 Runtime/Python/LICENSE create mode 100644 Runtime/Python/README.md create mode 100644 Runtime/Python/pyproject.toml create mode 100644 Runtime/Python/setup.cfg create mode 100644 Runtime/Python/src/python_bebop/__init__.py create mode 100644 Runtime/Python/src/python_bebop/bebop.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 1aba5071..1833c7f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,8 +10,8 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/Compiler/bin/Debug/net7.0/bebopc.dll", - "args": [], + "program": "${workspaceFolder}/bin/compiler/Debug/artifacts/bebopc.dll", + "args": ["--config", "bebop.json"], "cwd": "${workspaceFolder}/Compiler", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", diff --git a/Compiler/CommandLineFlags.cs b/Compiler/CommandLineFlags.cs index 85b987b9..6d055416 100644 --- a/Compiler/CommandLineFlags.cs +++ b/Compiler/CommandLineFlags.cs @@ -198,6 +198,10 @@ public class CommandLineFlags "--ts ./my/output/HelloWorld.ts", true)] public string? TypeScriptOutput { get; private set; } + [CommandLineFlag("py", "Generate Python source code to the specified file", + "--py ./my/output/HelloWorld.py", true)] + public string? PythonOutput { get; private set; } + [CommandLineFlag("namespace", "When this option is specified generated code will use namespaces", "--cs --namespace [package]")] public string? Namespace { get; private set; } @@ -277,7 +281,8 @@ public class CommandLineFlags ["cs"] = TempoServices.Both, ["dart"] = TempoServices.Both, ["rust"] = TempoServices.Both, - ["ts"] = TempoServices.Both + ["ts"] = TempoServices.Both, + ["py"] = TempoServices.Both }; /// diff --git a/Core/Generators/GeneratorUtils.cs b/Core/Generators/GeneratorUtils.cs index 6f99915b..c7a74e3a 100644 --- a/Core/Generators/GeneratorUtils.cs +++ b/Core/Generators/GeneratorUtils.cs @@ -1,4 +1,5 @@ -using System; +using System.Runtime.Serialization; +using System; using System.Collections.Generic; using System.Text; using Core.Generators.CPlusPlus; @@ -6,6 +7,7 @@ using Core.Generators.Dart; using Core.Generators.Rust; using Core.Generators.TypeScript; +using Core.Generators.Python; using Core.Meta; using Core.Meta.Extensions; @@ -121,6 +123,7 @@ public static string BaseClassName(this Definition definition) { "dart", s => new DartGenerator(s) }, { "rust", s => new RustGenerator(s) }, { "ts", s => new TypeScriptGenerator(s) }, + { "py", s => new PythonGenerator(s) } }; public static Dictionary ImplementedGeneratorNames = new() @@ -130,6 +133,7 @@ public static string BaseClassName(this Definition definition) { "dart", "Dart" }, { "rust", "Rust" }, { "ts", "TypeScript" }, + { "py", "Python" } }; /// diff --git a/Core/Generators/Python/PythonGenerator.cs b/Core/Generators/Python/PythonGenerator.cs new file mode 100644 index 00000000..e3f7e2e3 --- /dev/null +++ b/Core/Generators/Python/PythonGenerator.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; +using Core.Meta; +using Core.Meta.Extensions; + +namespace Core.Generators.Python +{ + public class PythonGenerator : BaseGenerator + { + const int indentStep = 4; + + public PythonGenerator(BebopSchema schema) : base(schema) { } + + private string FormatDocumentation(string documentation){ + var builder = new StringBuilder(); + foreach (var line in documentation.GetLines()) + { + builder.AppendLine($"# {line}"); + } + return builder.ToString(); + } + + private static string FormatDeprecationDoc(string deprecationReason, int spaces) + { + var builder = new IndentedStringBuilder(); + builder.Indent(spaces); + builder.AppendLine("\"\"\""); + builder.Indent(1); + builder.AppendLine($"@deprecated {deprecationReason}"); + builder.AppendLine("\"\"\""); + return builder.ToString(); + } + + /// + /// Generate the body of the encode function for the given . + /// + /// The definition to generate code for. + /// The generated Python encode function body. + public string CompileEncode(RecordDefinition definition) + { + return definition switch + { + MessageDefinition d => CompileEncodeMessage(d), + StructDefinition d => CompileEncodeStruct(d), + UnionDefinition d => CompileEncodeUnion(d), + _ => throw new InvalidOperationException($"invalid CompileEncode value: {definition}"), + }; + } + + private string CompileEncodeMessage(MessageDefinition definition) + { + var builder = new IndentedStringBuilder(4); + builder.AppendLine($"pos = writer.reserve_message_length()"); + builder.AppendLine($"start = writer.length"); + foreach (var field in definition.Fields) + { + if (field.DeprecatedAttribute != null) + { + continue; + } + builder.AppendLine($"if hasattr(message, \"{field.Name}\"):"); + builder.AppendLine($" writer.write_byte({field.ConstantValue})"); + builder.AppendLine($" {CompileEncodeField(field.Type, $"message.{field.Name}", 0, 1)}"); + } + builder.AppendLine("writer.write_byte(0)"); + builder.AppendLine("end = writer.length"); + builder.AppendLine("writer.fill_message_length(pos, end - start)"); + return builder.ToString(); + } + + private string CompileEncodeStruct(StructDefinition definition) + { + var builder = new IndentedStringBuilder(4); + foreach (var field in definition.Fields) + { + builder.AppendLine(CompileEncodeField(field.Type, $"message.{field.Name}")); + builder.AppendLine(""); + } + if (definition.Fields.Count == 0) { + builder.AppendLine("pass"); + } + return builder.ToString(); + } + + private string CompileEncodeUnion(UnionDefinition definition) { + var builder = new IndentedStringBuilder(4); + builder.AppendLine($"pos = writer.reserve_message_length()"); + builder.AppendLine($"start = writer.length + 1"); + builder.AppendLine($"writer.write_byte(message.data.discriminator)"); + builder.AppendLine("discriminator = message.data.discriminator"); + builder.AppendLine($"if discriminator == 1:"); + builder.AppendLine($" {definition.Branches.ElementAt(0).ClassName()}.encode_into(message.data.value, writer)"); + foreach (var branch in definition.Branches.Skip(1)) + { + builder.AppendLine($"elif discriminator == {branch.Discriminator}:"); + builder.AppendLine($" {branch.ClassName()}.encode_into(message.data.value, writer)"); + } + builder.AppendLine("end = writer.length"); + builder.AppendLine("writer.fill_message_length(pos, end - start)"); + return builder.ToString(); + } + + private string CompileEncodeField(TypeBase type, string target, int depth = 0, int indentDepth = 0) + { + var tab = new string(' ', indentStep); + var nl = "\n" + new string(' ', indentDepth * indentStep); + var i = GeneratorUtils.LoopVariable(depth); + return type switch + { + ArrayType at when at.IsBytes() => $"writer.writeBytes({target})", + ArrayType at => + $"length{depth} = len({target})" + nl + + $"writer.write_uint32(length{depth})" + nl + + $"for {i} in range(length{depth}):" + nl + + $"{tab}{CompileEncodeField(at.MemberType, $"{target}[{i}]", depth + 1, indentDepth + 1)}", + MapType mt => + $"writer.write_uint32(len({target}))" + nl + + $"for key{depth}, val{depth} in {target}.items():" + nl + + $"{tab}{CompileEncodeField(mt.KeyType, $"key{depth}", depth + 1, indentDepth + 1)}" + nl + + $"{tab}{CompileEncodeField(mt.ValueType, $"val{depth}", depth + 1, indentDepth + 1)}" + nl, + ScalarType st => st.BaseType switch + { + BaseType.Bool => $"writer.write_bool({target})", + BaseType.Byte => $"writer.write_byte({target})", + BaseType.UInt16 => $"writer.write_uint16({target})", + BaseType.Int16 => $"writer.write_int16({target})", + BaseType.UInt32 => $"writer.write_uint32({target})", + BaseType.Int32 => $"writer.write_int32({target})", + BaseType.UInt64 => $"writer.write_uint64({target})", + BaseType.Int64 => $"writer.write_int64({target})", + BaseType.Float32 => $"writer.write_float32({target})", + BaseType.Float64 => $"writer.write_float64({target})", + BaseType.String => $"writer.write_string({target})", + BaseType.Guid => $"writer.write_guid({target})", + BaseType.Date => $"writer.write_date({target})", + _ => throw new ArgumentOutOfRangeException(st.BaseType.ToString()) + }, + DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition => + $"writer.write_enum({target})", + DefinedType dt => $"{dt.Name}.encode_into({target}, writer)", + _ => throw new InvalidOperationException($"CompileEncodeField: {type}") + }; + } + + /// + /// Generate the body of the decode function for the given . + /// + /// The definition to generate code for. + /// The generated Dart decode function body. + public string CompileDecode(RecordDefinition definition) + { + return definition switch + { + MessageDefinition d => CompileDecodeMessage(d), + StructDefinition d => CompileDecodeStruct(d), + UnionDefinition d => CompileDecodeUnion(d), + _ => throw new InvalidOperationException($"invalid CompileDecode value: {definition}"), + }; + } + + /// + /// Generate the body of the decode function for the given . + /// + /// The message definition to generate code for. + /// The generated Dart decode function body. + private string CompileDecodeMessage(MessageDefinition definition) + { + var builder = new IndentedStringBuilder(4); + builder.AppendLine($"message = {definition.Name}()"); + builder.AppendLine("length = reader.read_message_length()"); + builder.AppendLine("end = reader.index + length"); + builder.AppendLine("while True:"); + builder.Indent(2); + builder.AppendLine("byte = reader.read_byte()"); + builder.AppendLine("if byte == 0:"); + builder.AppendLine(" return message"); + foreach (var field in definition.Fields) + { + builder.AppendLine($"elif byte == {field.ConstantValue}:"); + builder.AppendLine($" {CompileDecodeField(field.Type, $"message.{field.Name}", 1)}"); + } + builder.AppendLine("else:"); + builder.AppendLine(" reader.index = end"); + builder.AppendLine(" return message"); + builder.Dedent(2); + return builder.ToString(); + } + + private string CompileDecodeStruct(StructDefinition definition) + { + var builder = new IndentedStringBuilder(4); + int i = 0; + foreach (var field in definition.Fields) + { + builder.AppendLine(CompileDecodeField(field.Type, $"field{i}")); + builder.AppendLine(""); + i++; + } + var args = string.Join(", ", definition.Fields.Select((field, i) => $"{field.Name}=field{i}")); + builder.AppendLine($"return {definition.Name}({args})"); + return builder.ToString(); + } + + private string CompileDecodeUnion(UnionDefinition definition) { + var builder = new IndentedStringBuilder(4); + builder.AppendLine("length = reader.read_message_length()"); + builder.AppendLine("end = reader.index + 1 + length"); + builder.AppendLine("discriminator = reader.read_byte()"); + builder.AppendLine("if discriminator == 1:"); + builder.AppendLine($" return {definition.ClassName()}.from{definition.Branches.ElementAt(0).Definition.ClassName()}({definition.Branches.ElementAt(0).Definition.ClassName()}.read_from(reader))"); + foreach (var branch in definition.Branches.Skip(1)) + { + builder.AppendLine($"elif discriminator == {branch.Discriminator}:"); + builder.AppendLine($" return {definition.ClassName()}.from{branch.Definition.ClassName()}({branch.Definition.ClassName()}.read_from(reader))"); + } + builder.AppendLine("else:"); + builder.AppendLine(" reader.index = end"); + builder.AppendLine($" raise Exception(\"Unrecognized discriminator while decoding {definition.ClassName()}\")"); + return builder.ToString(); + } + + private string CompileDecodeField(TypeBase type, string target, int depth = 0) + { + var tab = new string(' ', indentStep); + var nl = "\n" + new string(' ', depth * indentStep); + var i = GeneratorUtils.LoopVariable(depth); + return type switch + { + ArrayType at when at.IsBytes() => $"{target} = reader.readBytes()", + ArrayType at => + $"length{depth} = reader.read_uint32()" + nl + + $"{target} = []" + nl + + $"for {i} in range(length{depth}):" + nl + + $"{tab}{CompileDecodeField(at.MemberType, $"x{depth}", depth + 1)}" + nl + + $"{tab}{target}.append(x{depth})" + nl, + MapType mt => + $"length{depth} = reader.read_uint32()" + nl + + $"{target} = {{}}" + nl + + $"for {i} in range(length{depth}):" + nl + + $"{tab}{CompileDecodeField(mt.KeyType, $"k{depth}", depth + 1)}" + nl + + $"{tab}{CompileDecodeField(mt.ValueType, $"v{depth}", depth + 1)}" + nl + + $"{tab}{target}[k{depth}] = v{depth}" + nl, + ScalarType st => st.BaseType switch + { + BaseType.Bool => $"{target} = reader.read_bool()", + BaseType.Byte => $"{target} = reader.read_byte()", + BaseType.UInt16 => $"{target} = reader.read_uint16()", + BaseType.Int16 => $"{target} = reader.read_int16()", + BaseType.UInt32 => $"{target} = reader.read_uint32()", + BaseType.Int32 => $"{target} = reader.read_int32()", + BaseType.UInt64 => $"{target} = reader.read_uint64()", + BaseType.Int64 => $"{target} = reader.read_int64()", + BaseType.Float32 => $"{target} = reader.read_float32()", + BaseType.Float64 => $"{target} = reader.read_float64()", + BaseType.String => $"{target} = reader.read_string()", + BaseType.Guid => $"{target} = reader.read_guid()", + BaseType.Date => $"{target} = reader.read_date()", + _ => throw new ArgumentOutOfRangeException(st.BaseType.ToString()) + }, + DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition => + $"{target} = {dt.Name}(reader.read_uint32())", + DefinedType dt => $"{target} = {dt.Name}.read_from(reader)", + _ => throw new InvalidOperationException($"CompileDecodeField: {type}") + }; + } + + /// + /// Generate a Python type name for the given . + /// + /// The field type to generate code for. + /// The Python type name. + private string TypeName(in TypeBase type) + { + switch (type) + { + case ScalarType st: + return st.BaseType switch + { + BaseType.Bool => "bool", + BaseType.Byte or BaseType.UInt16 or BaseType.Int16 or BaseType.UInt32 or BaseType.Int32 or BaseType.UInt64 or BaseType.Int64 => "int", + BaseType.Float32 or BaseType.Float64 => "float", + BaseType.String => "str", + BaseType.Guid => "UUID", + BaseType.Date => "datetime", + _ => throw new ArgumentOutOfRangeException(st.BaseType.ToString()) + }; + case ArrayType at when at.IsBytes(): + return "bytearray"; + case ArrayType at: + return $"list[{TypeName(at.MemberType)}]"; + case MapType mt: + return $"dict[{TypeName(mt.KeyType)}, {TypeName(mt.ValueType)}]"; + case DefinedType dt: + var isEnum = Schema.Definitions[dt.Name] is EnumDefinition; + return dt.Name; + } + throw new InvalidOperationException($"GetTypeName: {type}"); + } + + private static string EscapeStringLiteral(string value) + { + // Dart accepts \u0000 style escape sequences, so we can escape the string JSON-style. + var options = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; + return JsonSerializer.Serialize(value, options); + } + + private string EmitLiteral(Literal literal) { + return literal switch + { + BoolLiteral bl => bl.Value ? "True" : "False", + IntegerLiteral il => il.Value, + FloatLiteral fl when fl.Value == "inf" => $"math.inf", + FloatLiteral fl when fl.Value == "-inf" => $"-math.inf", + FloatLiteral fl when fl.Value == "nan" => $"math.nan", + FloatLiteral fl => fl.Value, + StringLiteral sl => EscapeStringLiteral(sl.Value), + GuidLiteral gl => EscapeStringLiteral(gl.Value.ToString("D")), + _ => throw new ArgumentOutOfRangeException(literal.ToString()), + }; + } + + /// + /// Generate code for a Bebop schema. + /// + /// The generated code. + public override string Compile(Version? languageVersion, TempoServices services = TempoServices.Both, bool writeGeneratedNotice = true, bool emitBinarySchema = false) + { + var builder = new IndentedStringBuilder(); + builder.AppendLine("from enum import Enum"); + builder.AppendLine("from python_bebop import BebopWriter, BebopReader, UnionType, UnionDefinition"); + builder.AppendLine("from uuid import UUID"); + builder.AppendLine("import math"); + builder.AppendLine("import json"); + builder.AppendLine("from datetime import datetime"); + builder.AppendLine("from typing import Any, TypeVar"); + builder.AppendLine(""); + + foreach (var definition in Schema.Definitions.Values) + { + if (!string.IsNullOrWhiteSpace(definition.Documentation)) + { + builder.Append(FormatDocumentation(definition.Documentation)); + builder.AppendLine(""); + } + switch (definition) + { + case EnumDefinition ed: + builder.AppendLine($"class {ed.Name}(Enum):"); + builder.Indent(indentStep); + for (var i = 0; i < ed.Members.Count; i++) + { + var field = ed.Members.ElementAt(i); + if (!string.IsNullOrWhiteSpace(field.Documentation)) + { + builder.Append(FormatDocumentation(field.Documentation)); + builder.AppendLine(""); + } + if (field.DeprecatedAttribute != null) + { + + builder.AppendLine($"\"\"\" @deprecated {field.DeprecatedAttribute.Value} \"\"\""); + } + builder.AppendLine($"{field.Name.ToUpper()} = {field.ConstantValue}"); + } + builder.AppendLine(""); + builder.Dedent(indentStep); + break; + case RecordDefinition rd: + if (rd is FieldsDefinition fd) { + builder.AppendLine($"class {fd.Name}:"); + builder.Indent(indentStep); + for (var i = 0; i < fd.Fields.Count; i++) { + var field = fd.Fields.ElementAt(i); + var type = TypeName(field.Type); + if (!string.IsNullOrWhiteSpace(field.Documentation)) + { + builder.Append(FormatDocumentation(field.Documentation)); + builder.AppendLine(""); + } + if (field.DeprecatedAttribute != null) + { + builder.AppendLine($"# @deprecated {field.DeprecatedAttribute.Value}"); + } + builder.AppendLine($"{field.Name}: {type}"); + } + if (rd.OpcodeAttribute != null) { + builder.AppendLine($"opcode = {rd.OpcodeAttribute.Value}"); + builder.AppendLine(""); + } + builder.AppendLine(""); + if (!(fd is MessageDefinition)) { + List fields = new List(); + foreach (var field in fd.Fields) + { + fields.Add($" {field.Name}: {TypeName(field.Type)}"); + } + if (fields.Count != 0) { + builder.Append("def __init__(self, "); + builder.Append(string.Join(",", fields)); + builder.AppendLine("):"); + builder.Indent(indentStep); + foreach (var field in fd.Fields) { + builder.AppendLine($"self.{field.Name} = {field.Name}"); + } + builder.Dedent(indentStep); + } + } + } else if (rd is UnionDefinition ud) { + builder.CodeBlock($"class {ud.ClassName()}:", indentStep, () => + { + builder.AppendLine(); + builder.AppendLine($"data: UnionType"); + builder.AppendLine(); + builder.CodeBlock($"def __init__(self, data: UnionType):", indentStep, () => + { + builder.AppendLine($"self.data = data"); + }, open: string.Empty, close: string.Empty); + builder.AppendLine("@property"); + builder.CodeBlock($"def discriminator(self):", indentStep, () => + { + builder.AppendLine($"return self.data.discriminator"); + }, open: string.Empty, close: string.Empty); + builder.AppendLine("@property"); + builder.CodeBlock($"def value(self):", indentStep, () => + { + builder.AppendLine($"return self.data.value"); + }, open: string.Empty, close: string.Empty); + foreach (var b in ud.Branches) + { + builder.AppendLine("@staticmethod"); + builder.CodeBlock($"def from{b.ClassName()}(value: {b.ClassName()}):", indentStep, () => + { + builder.AppendLine($"return {definition.ClassName()}(UnionDefinition({b.Discriminator}, value))" ); + }, open: string.Empty, close: string.Empty); + builder.CodeBlock($"def is{b.ClassName()}(self):", indentStep, () => + { + builder.AppendLine($"return isinstance(self.value, {b.ClassName()})"); + }, open: string.Empty, close: string.Empty); + } + }, close: string.Empty, open: string.Empty); + builder.Indent(indentStep); + } + builder.AppendLine(""); + + builder.AppendLine("@staticmethod"); + builder.AppendLine($"def encode(message: \"{rd.Name}\"):"); + builder.Indent(indentStep); + builder.AppendLine("writer = BebopWriter()"); + builder.AppendLine($"{rd.Name}.encode_into(message, writer)"); + builder.AppendLine("return writer.to_list()"); + builder.Dedent(indentStep); + builder.AppendLine(""); + builder.AppendLine(""); + + builder.AppendLine("@staticmethod"); + builder.AppendLine($"def encode_into(message: \"{rd.Name}\", writer: BebopWriter):"); + builder.Append(CompileEncode(rd)); + builder.AppendLine(""); + builder.AppendLine(""); + + builder.AppendLine("@classmethod"); + builder.AppendLine($"def read_from(cls, reader: BebopReader):"); + builder.Append(CompileDecode(rd)); + builder.AppendLine(""); + builder.AppendLine(""); + + builder.AppendLine("@staticmethod"); + builder.AppendLine($"def decode(buffer) -> \"{rd.Name}\":"); + builder.Indent(indentStep); + builder.AppendLine($"return {rd.Name}.read_from(BebopReader(buffer))"); + builder.Dedent(indentStep); + builder.AppendLine(""); + // representation + builder.CodeBlock($"def __repr__(self):", indentStep, () => + { + builder.AppendLine($"return json.dumps(self, default=lambda o: o.value if isinstance(o, Enum) else dict(sorted(o.__dict__.items())) if hasattr(o, \"__dict__\") else str(o))"); + }, open: string.Empty, close: string.Empty); + builder.AppendLine(""); + builder.AppendLine(""); + + builder.Dedent(indentStep); + break; + case ConstDefinition cd: + builder.AppendLine($"{cd.Name} = {EmitLiteral(cd.Value)}"); + builder.AppendLine(""); + break; + case ServiceDefinition: + break; + default: + throw new InvalidOperationException($"unsupported definition {definition}"); + } + } + + return builder.ToString(); + } + + public override void WriteAuxiliaryFiles(string outputPath) + { + // There is nothing to do here. + } + } +} diff --git a/Core/Generators/TypeScript/TypeScriptGenerator.cs b/Core/Generators/TypeScript/TypeScriptGenerator.cs index db30ece3..5c8ba3a7 100644 --- a/Core/Generators/TypeScript/TypeScriptGenerator.cs +++ b/Core/Generators/TypeScript/TypeScriptGenerator.cs @@ -179,7 +179,7 @@ public string CompileDecode(Definition definition) } /// - /// Generate the body of the decode function for the given , + /// Generate the body of the decode function for the given , /// /// The message definition to generate code for. /// The generated TypeScript decode function body. diff --git a/Laboratory/Integration/.gitignore b/Laboratory/Integration/.gitignore index b7041092..07a42376 100644 --- a/Laboratory/Integration/.gitignore +++ b/Laboratory/Integration/.gitignore @@ -4,4 +4,5 @@ schema.ts bebop.hpp *.enc Rust/src/schema.rs +Python/src/schema.py target diff --git a/Laboratory/Integration/Python/src/decode.py b/Laboratory/Integration/Python/src/decode.py new file mode 100644 index 00000000..fc0d407a --- /dev/null +++ b/Laboratory/Integration/Python/src/decode.py @@ -0,0 +1,21 @@ +from schema import Library +from lib import make_lib +import sys +import json + +with open(sys.argv[1], "rb") as buffer_file: + buffer = buffer_file.read() + + de = Library.decode(buffer) + ex = make_lib() + + eq = json.loads(repr(de)) == json.loads(repr(ex)) + + if not eq: + print("decoded:") + print(repr(de)) + print() + print("expected:") + print(repr(ex)) + + sys.exit(0 if eq else 1) \ No newline at end of file diff --git a/Laboratory/Integration/Python/src/encode.py b/Laboratory/Integration/Python/src/encode.py new file mode 100644 index 00000000..ce4cda3b --- /dev/null +++ b/Laboratory/Integration/Python/src/encode.py @@ -0,0 +1,6 @@ +from lib import make_lib +from schema import Library +import sys + +with open("py.enc", "wb+") as enc_file: + enc_file.write(bytearray(Library.encode(make_lib()))) \ No newline at end of file diff --git a/Laboratory/Integration/Python/src/lib.py b/Laboratory/Integration/Python/src/lib.py new file mode 100644 index 00000000..04881436 --- /dev/null +++ b/Laboratory/Integration/Python/src/lib.py @@ -0,0 +1,48 @@ +from schema import Library, Instrument, Album, StudioAlbum, LiveAlbum, Song, Musician +from datetime import datetime + +def make_lib(): + giant_steps_song = Song() + giant_steps_song.title = "Giant Steps" + giant_steps_song.year = 1959 + giant_steps_song.performers = [ + Musician(name="John Coltrane", plays=Instrument.PIANO) + ] + + a_night_in_tunisia_song = Song() + a_night_in_tunisia_song.title = "A Night in Tunisia" + a_night_in_tunisia_song.year = 1942 + a_night_in_tunisia_song.performers = [ + Musician(name="Dizzy Gillespie", plays=Instrument.TRUMPET), + Musician(name="Count Basie", plays=Instrument.PIANO), + ] + + groovin_high_song = Song() + groovin_high_song.title = "Groovin' High" + + adams_apple_album = LiveAlbum() + adams_apple_album.venueName = "Tunisia" + adams_apple_album.concertDate = datetime.fromtimestamp(528205479) + + unnamed_song = Song() + unnamed_song.year = 1965 + unnamed_song.performers = [ + Musician(name="Carmell Jones", plays=Instrument.TRUMPET), + Musician(name="Joe Henderson", plays=Instrument.SAX), + Musician(name="Teddy Smith", plays=Instrument.CLARINET) + ] + + brilliant_corners_album = LiveAlbum() + brilliant_corners_album.venueName = "Night's Palace" + brilliant_corners_album.tracks = [ + unnamed_song + ] + + return Library(albums={ + "Giant Steps": Album.fromStudioAlbum(StudioAlbum(tracks=[ + giant_steps_song, a_night_in_tunisia_song, groovin_high_song + ])), + "Adam's Apple": Album.fromLiveAlbum(adams_apple_album), + "Milestones": Album.fromStudioAlbum(StudioAlbum(tracks=[])), + "Brilliant Corners": Album.fromLiveAlbum(brilliant_corners_album) + }) \ No newline at end of file diff --git a/Laboratory/Integration/run_test.js b/Laboratory/Integration/run_test.js index dd9d55fc..106bb7ed 100644 --- a/Laboratory/Integration/run_test.js +++ b/Laboratory/Integration/run_test.js @@ -1,9 +1,10 @@ const shell = require("shelljs"); const cxx = shell.env["CXX"] || "g++"; const aout = process.platform === "win32" ? "a.exe" : "./a.out"; +const py = process.platform === "win32" ? "py" : "python3"; shell.echo("Compiling schema..."); -if (shell.exec("dotnet run --project ../../Compiler --files schema.bop --cs schema.cs --ts schema.ts --cpp schema.hpp --rust Rust/src/schema.rs").code !== 0) { +if (shell.exec("dotnet run --project ../../Compiler --files schema.bop --cs schema.cs --ts schema.ts --cpp schema.hpp --rust Rust/src/schema.rs --py Python/src/schema.py").code !== 0) { shell.echo("Error: bebopc failed"); shell.exit(1); } @@ -18,6 +19,8 @@ if (shell.exec(`${cxx} --std=c++17 encode.cpp`).code !== 0) { shell.exec(`${aout} > cpp.enc`); shell.rm("-f", aout); shell.exec("cargo run --manifest-path Rust/Cargo.toml --example encode > rs.enc"); +shell.exec(`${py} Python/src/encode.py`) + // Files can have some variance and still be equivalent because of ordering in maps and C# dates. // Perform full matrix testing because it seems like a good idea @@ -34,6 +37,7 @@ const languages = [ {name: "C#", cmd: "dotnet run decode", file: "cs.enc"}, {name: "TypeScript", cmd: "npx ts-node decode.ts", file: "ts.enc"}, {name: "C++", cmd: aout, file: "cpp.enc"}, + {name: "Python", cmd: `${py} Python/src/decode.py`, file: "py.enc"}, ]; var failed = false; diff --git a/Laboratory/Integration/yarn.lock b/Laboratory/Integration/yarn.lock new file mode 100644 index 00000000..a2d62a31 --- /dev/null +++ b/Laboratory/Integration/yarn.lock @@ -0,0 +1,628 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/deep-equal@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.3.tgz#f8315c99a85cc7a286b15cd9cac5e4a52e98d59d" + integrity sha512-xP1pB67eLrRdMLKpXUXhYN/3uiJN1gNalcVOSY5kdJbxhGuZTTE8awSWOD9LCJDzQZVsuZZcnMMLRrbf+nOUSQ== + +"@types/node@^20.2.3": + version "20.8.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.10.tgz#a5448b895c753ae929c26ce85cab557c6d4a365e" + integrity sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w== + dependencies: + undici-types "~5.26.4" + +acorn-walk@^8.1.1: + version "8.3.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" + integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== + +acorn@^8.4.1: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +"bebop@file:../../Runtime/TypeScript": + version "2.0.2" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +deep-equal@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" + integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.1" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +glob@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +has-bigints@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-property-descriptors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" + integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== + dependencies: + get-intrinsic "^1.2.2" + hasown "^2.0.0" + side-channel "^1.0.4" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +regexp.prototype.flags@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +resolve@^1.1.6: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +set-function-length@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" + integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + dependencies: + define-data-property "^1.1.1" + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +set-function-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + +shelljs@0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^5.0.4: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + +which-typed-array@^1.1.11, which-typed-array@^1.1.9: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.4" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/Laboratory/Python/.gitignore b/Laboratory/Python/.gitignore new file mode 100644 index 00000000..90f8fbb3 --- /dev/null +++ b/Laboratory/Python/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git a/Laboratory/Python/test/schemas.py b/Laboratory/Python/test/schemas.py new file mode 100644 index 00000000..f0c1db5f --- /dev/null +++ b/Laboratory/Python/test/schemas.py @@ -0,0 +1,741 @@ +from enum import Enum +from python_bebop import BebopWriter, BebopReader, UnionType, UnionDefinition +from uuid import UUID +import math +from datetime import datetime +from typing import Any, TypeVar + +class BasicTypes: + a_bool: bool + a_byte: int + a_int16: int + a_uint16: int + a_int32: int + a_uint32: int + a_int64: int + a_uint64: int + a_float32: float + a_float64: float + a_string: str + a_guid: UUID + a_date: datetime + + def __init__(self, a_bool: bool, a_byte: int, a_int16: int, a_uint16: int, a_int32: int, a_uint32: int, a_int64: int, a_uint64: int, a_float32: float, a_float64: float, a_string: str, a_guid: UUID, a_date: datetime ): + self.a_bool = a_bool + self.a_byte = a_byte + self.a_int16 = a_int16 + self.a_uint16 = a_uint16 + self.a_int32 = a_int32 + self.a_uint32 = a_uint32 + self.a_int64 = a_int64 + self.a_uint64 = a_uint64 + self.a_float32 = a_float32 + self.a_float64 = a_float64 + self.a_string = a_string + self.a_guid = a_guid + self.a_date = a_date + + @staticmethod + def encode(message: "BasicTypes"): + writer = BebopWriter() + BasicTypes.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "BasicTypes", writer: BebopWriter): + writer.write_bool(message.a_bool) + + writer.write_byte(message.a_byte) + + writer.write_int16(message.a_int16) + + writer.write_uint16(message.a_uint16) + + writer.write_int32(message.a_int32) + + writer.write_uint32(message.a_uint32) + + writer.write_int64(message.a_int64) + + writer.write_uint64(message.a_uint64) + + writer.write_float32(message.a_float32) + + writer.write_float64(message.a_float64) + + writer.write_string(message.a_string) + + writer.write_guid(message.a_guid) + + writer.write_date(message.a_date) + + @classmethod + def read_from(cls, reader: BebopReader): + field0 = reader.read_bool() + + field1 = reader.read_byte() + + field2 = reader.read_int16() + + field3 = reader.read_uint16() + + field4 = reader.read_int32() + + field5 = reader.read_uint32() + + field6 = reader.read_int64() + + field7 = reader.read_uint64() + + field8 = reader.read_float32() + + field9 = reader.read_float64() + + field10 = reader.read_string() + + field11 = reader.read_guid() + + field12 = reader.read_date() + + return BasicTypes(a_bool=field0, a_byte=field1, a_int16=field2, a_uint16=field3, a_int32=field4, a_uint32=field5, a_int64=field6, a_uint64=field7, a_float32=field8, a_float64=field9, a_string=field10, a_guid=field11, a_date=field12) + + @staticmethod + def decode(buffer) -> "BasicTypes": + return BasicTypes.read_from(BebopReader(buffer)) + + +class InnerM2: + x: int + + + @staticmethod + def encode(message: "InnerM2"): + writer = BebopWriter() + InnerM2.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "InnerM2", writer: BebopWriter): + pos = writer.reserve_message_length() + start = writer.length + if message.x is not None: + writer.write_byte(1) + writer.write_int32(message.x) + writer.write_byte(0) + end = writer.length + writer.fill_message_length(pos, end - start) + + @classmethod + def read_from(cls, reader: BebopReader): + message = InnerM2() + length = reader.read_message_length() + end = reader.index + length + while True: + byte = reader.read_byte() + if byte == 0: + return message + elif byte == 1: + message.x = reader.read_int32() + else: + reader.index = end + return message + + @staticmethod + def decode(buffer) -> "InnerM2": + return InnerM2.read_from(BebopReader(buffer)) + + +class A: + b: int + + + @staticmethod + def encode(message: "A"): + writer = BebopWriter() + A.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "A", writer: BebopWriter): + pos = writer.reserve_message_length() + start = writer.length + if message.b is not None: + writer.write_byte(1) + writer.write_uint32(message.b) + writer.write_byte(0) + end = writer.length + writer.fill_message_length(pos, end - start) + + @classmethod + def read_from(cls, reader: BebopReader): + message = A() + length = reader.read_message_length() + end = reader.index + length + while True: + byte = reader.read_byte() + if byte == 0: + return message + elif byte == 1: + message.b = reader.read_uint32() + else: + reader.index = end + return message + + @staticmethod + def decode(buffer) -> "A": + return A.read_from(BebopReader(buffer)) + + +# This branch is, too! +class B: + c: bool + + def __init__(self, c: bool ): + self.c = c + + @staticmethod + def encode(message: "B"): + writer = BebopWriter() + B.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "B", writer: BebopWriter): + writer.write_bool(message.c) + + @classmethod + def read_from(cls, reader: BebopReader): + field0 = reader.read_bool() + + return B(c=field0) + + @staticmethod + def decode(buffer) -> "B": + return B.read_from(BebopReader(buffer)) + + +class C: + + + @staticmethod + def encode(message: "C"): + writer = BebopWriter() + C.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "C", writer: BebopWriter): + pass + + @classmethod + def read_from(cls, reader: BebopReader): + return C() + + @staticmethod + def decode(buffer) -> "C": + return C.read_from(BebopReader(buffer)) + + +class D: + msg: InnerM2 + + def __init__(self, msg: InnerM2 ): + self.msg = msg + + @staticmethod + def encode(message: "D"): + writer = BebopWriter() + D.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "D", writer: BebopWriter): + InnerM2.encode_into(message.msg, writer) + + @classmethod + def read_from(cls, reader: BebopReader): + field0 = InnerM2.read_from(reader) + + return D(msg=field0) + + @staticmethod + def decode(buffer) -> "D": + return D.read_from(BebopReader(buffer)) + + +# This union is so documented! +class U: + + data: UnionType + + def __init__(self, data: UnionType): + self.data = data + + @property + def discriminator(self): + return self.data.discriminator + + @property + def value(self): + return self.data.value + + @staticmethod + def fromA(value: A): + return U(UnionDefinition(1, value)) + + def isA(self): + return isinstance(self.value, A) + + @staticmethod + def fromB(value: B): + return U(UnionDefinition(2, value)) + + def isB(self): + return isinstance(self.value, B) + + @staticmethod + def fromC(value: C): + return U(UnionDefinition(3, value)) + + def isC(self): + return isinstance(self.value, C) + + @staticmethod + def fromD(value: D): + return U(UnionDefinition(4, value)) + + def isD(self): + return isinstance(self.value, D) + + + + @staticmethod + def encode(message: "U"): + writer = BebopWriter() + U.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "U", writer: BebopWriter): + pos = writer.reserve_message_length() + start = writer.length + 1 + writer.write_byte(message.data.discriminator) + discriminator = message.data.discriminator + if discriminator == 1: + A.encode_into(message.data.value, writer) + elif discriminator == 2: + B.encode_into(message.data.value, writer) + elif discriminator == 3: + C.encode_into(message.data.value, writer) + elif discriminator == 4: + D.encode_into(message.data.value, writer) + end = writer.length + writer.fill_message_length(pos, end - start) + + @classmethod + def read_from(cls, reader: BebopReader): + length = reader.read_message_length() + end = reader.index + 1 + length + discriminator = reader.read_byte() + if discriminator == 1: + return U.fromA(A.read_from(reader)) + elif discriminator == 2: + return U.fromB(B.read_from(reader)) + elif discriminator == 3: + return U.fromC(C.read_from(reader)) + elif discriminator == 4: + return U.fromD(D.read_from(reader)) + else: + reader.index = end + raise Exception("Unrecognized discriminator while decoding U") + + @staticmethod + def decode(buffer) -> "U": + return U.read_from(BebopReader(buffer)) + + +class TwoComesFirst: + b: int + + def __init__(self, b: int ): + self.b = b + + @staticmethod + def encode(message: "TwoComesFirst"): + writer = BebopWriter() + TwoComesFirst.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "TwoComesFirst", writer: BebopWriter): + writer.write_byte(message.b) + + @classmethod + def read_from(cls, reader: BebopReader): + field0 = reader.read_byte() + + return TwoComesFirst(b=field0) + + @staticmethod + def decode(buffer) -> "TwoComesFirst": + return TwoComesFirst.read_from(BebopReader(buffer)) + + +class ThreeIsSkipped: + + + @staticmethod + def encode(message: "ThreeIsSkipped"): + writer = BebopWriter() + ThreeIsSkipped.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "ThreeIsSkipped", writer: BebopWriter): + pass + + @classmethod + def read_from(cls, reader: BebopReader): + return ThreeIsSkipped() + + @staticmethod + def decode(buffer) -> "ThreeIsSkipped": + return ThreeIsSkipped.read_from(BebopReader(buffer)) + + +class OneComesLast: + + + @staticmethod + def encode(message: "OneComesLast"): + writer = BebopWriter() + OneComesLast.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "OneComesLast", writer: BebopWriter): + pass + + @classmethod + def read_from(cls, reader: BebopReader): + return OneComesLast() + + @staticmethod + def decode(buffer) -> "OneComesLast": + return OneComesLast.read_from(BebopReader(buffer)) + + +class WeirdOrder: + + data: UnionType + + def __init__(self, data: UnionType): + self.data = data + + @property + def discriminator(self): + return self.data.discriminator + + @property + def value(self): + return self.data.value + + @staticmethod + def fromTwoComesFirst(value: TwoComesFirst): + return WeirdOrder(UnionDefinition(2, value)) + + def isTwoComesFirst(self): + return isinstance(self.value, TwoComesFirst) + + @staticmethod + def fromThreeIsSkipped(value: ThreeIsSkipped): + return WeirdOrder(UnionDefinition(4, value)) + + def isThreeIsSkipped(self): + return isinstance(self.value, ThreeIsSkipped) + + @staticmethod + def fromOneComesLast(value: OneComesLast): + return WeirdOrder(UnionDefinition(1, value)) + + def isOneComesLast(self): + return isinstance(self.value, OneComesLast) + + + + @staticmethod + def encode(message: "WeirdOrder"): + writer = BebopWriter() + WeirdOrder.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "WeirdOrder", writer: BebopWriter): + pos = writer.reserve_message_length() + start = writer.length + 1 + writer.write_byte(message.data.discriminator) + discriminator = message.data.discriminator + if discriminator == 1: + TwoComesFirst.encode_into(message.data.value, writer) + elif discriminator == 4: + ThreeIsSkipped.encode_into(message.data.value, writer) + elif discriminator == 1: + OneComesLast.encode_into(message.data.value, writer) + end = writer.length + writer.fill_message_length(pos, end - start) + + @classmethod + def read_from(cls, reader: BebopReader): + length = reader.read_message_length() + end = reader.index + 1 + length + discriminator = reader.read_byte() + if discriminator == 1: + return WeirdOrder.fromTwoComesFirst(TwoComesFirst.read_from(reader)) + elif discriminator == 4: + return WeirdOrder.fromThreeIsSkipped(ThreeIsSkipped.read_from(reader)) + elif discriminator == 1: + return WeirdOrder.fromOneComesLast(OneComesLast.read_from(reader)) + else: + reader.index = end + raise Exception("Unrecognized discriminator while decoding WeirdOrder") + + @staticmethod + def decode(buffer) -> "WeirdOrder": + return WeirdOrder.read_from(BebopReader(buffer)) + + +class M: + a: float + b: float + + + @staticmethod + def encode(message: "M"): + writer = BebopWriter() + M.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "M", writer: BebopWriter): + pos = writer.reserve_message_length() + start = writer.length + if message.a is not None: + writer.write_byte(1) + writer.write_float32(message.a) + if message.b is not None: + writer.write_byte(2) + writer.write_float64(message.b) + writer.write_byte(0) + end = writer.length + writer.fill_message_length(pos, end - start) + + @classmethod + def read_from(cls, reader: BebopReader): + message = M() + length = reader.read_message_length() + end = reader.index + length + while True: + byte = reader.read_byte() + if byte == 0: + return message + elif byte == 1: + message.a = reader.read_float32() + elif byte == 2: + message.b = reader.read_float64() + else: + reader.index = end + return message + + @staticmethod + def decode(buffer) -> "M": + return M.read_from(BebopReader(buffer)) + + +class S: + x: int + y: int + + def __init__(self, x: int, y: int ): + self.x = x + self.y = y + + @staticmethod + def encode(message: "S"): + writer = BebopWriter() + S.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "S", writer: BebopWriter): + writer.write_int32(message.x) + + writer.write_int32(message.y) + + @classmethod + def read_from(cls, reader: BebopReader): + field0 = reader.read_int32() + + field1 = reader.read_int32() + + return S(x=field0, y=field1) + + @staticmethod + def decode(buffer) -> "S": + return S.read_from(BebopReader(buffer)) + + +class SomeMaps: + m1: dict[bool, bool] + m2: dict[str, dict[str, str]] + m3: list[dict[int, list[dict[bool, S]]]] + m4: list[dict[str, list[float]]] + m5: dict[UUID, M] + + def __init__(self, m1: dict[bool, bool], m2: dict[str, dict[str, str]], m3: list[dict[int, list[dict[bool, S]]]], m4: list[dict[str, list[float]]], m5: dict[UUID, M] ): + self.m1 = m1 + self.m2 = m2 + self.m3 = m3 + self.m4 = m4 + self.m5 = m5 + + @staticmethod + def encode(message: "SomeMaps"): + writer = BebopWriter() + SomeMaps.encode_into(message, writer) + return writer.to_list() + + + @staticmethod + def encode_into(message: "SomeMaps", writer: BebopWriter): + writer.write_uint32(len(message.m1)) + for key0, val0 in message.m1.items(): + writer.write_bool(key0) + writer.write_bool(val0) + + writer.write_uint32(len(message.m2)) + for key0, val0 in message.m2.items(): + writer.write_string(key0) + writer.write_uint32(len(val0)) + for key1, val1 in val0.items(): + writer.write_string(key1) + writer.write_string(val1) + + length0 = len(message.m3) + writer.write_uint32(length0) + for i0 in range(length0): + writer.write_uint32(len(message.m3[i0])) + for key1, val1 in message.m3[i0].items(): + writer.write_int32(key1) + length2 = len(val1) + writer.write_uint32(length2) + for i2 in range(length2): + writer.write_uint32(len(val1[i2])) + for key3, val3 in val1[i2].items(): + writer.write_bool(key3) + S.encode_into(val3, writer) + + length0 = len(message.m4) + writer.write_uint32(length0) + for i0 in range(length0): + writer.write_uint32(len(message.m4[i0])) + for key1, val1 in message.m4[i0].items(): + writer.write_string(key1) + length2 = len(val1) + writer.write_uint32(length2) + for i2 in range(length2): + writer.write_float32(val1[i2]) + + writer.write_uint32(len(message.m5)) + for key0, val0 in message.m5.items(): + writer.write_guid(key0) + M.encode_into(val0, writer) + + @classmethod + def read_from(cls, reader: BebopReader): + length0 = reader.read_uint32() + field0 = {} + for i0 in range(length0): + k0 = reader.read_bool() + v0 = reader.read_bool() + field0[k0] = v0 + + length0 = reader.read_uint32() + field1 = {} + for i0 in range(length0): + k0 = reader.read_string() + length1 = reader.read_uint32() + v0 = {} + for i1 in range(length1): + k1 = reader.read_string() + v1 = reader.read_string() + v0[k1] = v1 + + field1[k0] = v0 + + length0 = reader.read_uint32() + field2 = [] + for i0 in range(length0): + length1 = reader.read_uint32() + x0 = {} + for i1 in range(length1): + k1 = reader.read_int32() + length2 = reader.read_uint32() + v1 = [] + for i2 in range(length2): + length3 = reader.read_uint32() + x2 = {} + for i3 in range(length3): + k3 = reader.read_bool() + v3 = S.read_from(reader) + x2[k3] = v3 + + v1.append(x2) + + x0[k1] = v1 + + field2.append(x0) + + length0 = reader.read_uint32() + field3 = [] + for i0 in range(length0): + length1 = reader.read_uint32() + x0 = {} + for i1 in range(length1): + k1 = reader.read_string() + length2 = reader.read_uint32() + v1 = [] + for i2 in range(length2): + x2 = reader.read_float32() + v1.append(x2) + + x0[k1] = v1 + + field3.append(x0) + + length0 = reader.read_uint32() + field4 = {} + for i0 in range(length0): + k0 = reader.read_guid() + v0 = M.read_from(reader) + field4[k0] = v0 + + return SomeMaps(m1=field0, m2=field1, m3=field2, m4=field3, m5=field4) + + @staticmethod + def decode(buffer) -> "SomeMaps": + return SomeMaps.read_from(BebopReader(buffer)) + + diff --git a/Laboratory/Python/test/test_basic_types.py b/Laboratory/Python/test/test_basic_types.py new file mode 100644 index 00000000..d9e5376f --- /dev/null +++ b/Laboratory/Python/test/test_basic_types.py @@ -0,0 +1,41 @@ +from schemas import BasicTypes +from datetime import datetime +from uuid import UUID + + +def test_write_read(): + bt = BasicTypes( + a_bool=True, + a_byte=1, + a_int16=-2, + a_uint16=2, + a_int32=-4, + a_uint32=4, + a_int64=-8, + a_uint64=8, + a_float32=2.0, + a_float64=2.0, + a_string="a_string", + a_guid=UUID("ff1ed055-1839-4d7f-84d3-5ca28fa298b9"), + a_date=datetime(1999, 5, 17), + ) + + encoded = BasicTypes.encode(bt) + + decoded = BasicTypes.decode(encoded) + + assert decoded.a_bool == True + assert decoded.a_byte == 1 + assert decoded.a_int16 == -2 + assert decoded.a_uint16 == 2 + assert decoded.a_int32 == -4 + assert decoded.a_uint32 == 4 + assert decoded.a_int64 == -8 + assert decoded.a_uint64 == 8 + assert decoded.a_float32 == 2.0 + assert decoded.a_float64 == 2.0 + assert decoded.a_string == "a_string" + assert decoded.a_guid == UUID("ff1ed055-1839-4d7f-84d3-5ca28fa298b9") + assert decoded.a_date == datetime(1999, 5, 17) + + diff --git a/Laboratory/Python/test/test_map_types.py b/Laboratory/Python/test/test_map_types.py new file mode 100644 index 00000000..c6f2517f --- /dev/null +++ b/Laboratory/Python/test/test_map_types.py @@ -0,0 +1,27 @@ +from schemas import M, S, SomeMaps +from uuid import UUID + + +def test_map_types(): + + m1 = {True: False} + m2 = {"key": {"key2": "val"}} + m3 = [{1: [{True: S(x=1, y=2)}]}] + m4 = [{"key": [2.0]}] + m = M() + m.a = 0 + m.b = 1 + m5 = {UUID("ff1ed055-1839-4d7f-84d3-5ca28fa298b9"): m} + + sm = SomeMaps(m1, m2, m3, m4, m5) + + encoded = SomeMaps.encode(sm) + + decoded = SomeMaps.decode(encoded) + + assert decoded.m1.get(True) == False + assert decoded.m2.get("key") == {"key2": "val"} + assert len(decoded.m3[0].get(1)) == 1 + assert decoded.m3[0].get(1)[0].get(True).y == 2 + assert decoded.m4[0].get("key")[0] == 2.0 + assert decoded.m5.get(UUID("ff1ed055-1839-4d7f-84d3-5ca28fa298b9")).a == 0 diff --git a/Laboratory/Python/test/test_union_type.py b/Laboratory/Python/test/test_union_type.py new file mode 100644 index 00000000..77b9eb28 --- /dev/null +++ b/Laboratory/Python/test/test_union_type.py @@ -0,0 +1,67 @@ +from schemas import U, A, B, C, D, InnerM2 + +def test_write_read_A(): + a = A() + a.b = 90 + + u = U.fromA(a) + + assert u.isA() == True + + encoded = U.encode(u) + + assert encoded == [10, 0, 0, 0, 1, 6, 0, 0, 0, 1, 90, 0, 0, 0, 0] + + decoded = U.decode(encoded) + + assert decoded.discriminator == 1 + assert decoded.value.b == 90 + +def test_write_read_B(): + b = B(True) + + u = U.fromB(b) + + assert u.isB() == True + + encoded = U.encode(u) + + assert encoded == [1, 0, 0, 0, 2, 1] + + decoded = U.decode(encoded) + + assert decoded.discriminator == 2 + assert decoded.value.c == True + +def test_write_read_C(): + c = C() + + u = U.fromC(c) + + assert u.isC() == True + + encoded = U.encode(u) + + assert encoded == [0, 0, 0, 0, 3] + + decoded = U.decode(encoded) + + assert decoded.discriminator == 3 + +def test_write_read_D(): + msg = InnerM2() + msg.x = 90 + d = D(msg) + + u = U.fromD(d) + + assert u.isD() == True + + encoded = U.encode(u) + + assert encoded == [10, 0, 0, 0, 4, 6, 0, 0, 0, 1, 90, 0, 0, 0, 0] + + decoded = U.decode(encoded) + + assert decoded.discriminator == 4 + assert decoded.value.msg.x == 90 diff --git a/Laboratory/Schemas/.gitignore b/Laboratory/Schemas/.gitignore index ff29d4e6..2c2e1d48 100644 --- a/Laboratory/Schemas/.gitignore +++ b/Laboratory/Schemas/.gitignore @@ -1,2 +1,3 @@ csoutput tsoutput +pyoutput diff --git a/Laboratory/Schemas/ShouldFail/bebop.json b/Laboratory/Schemas/ShouldFail/bebop.json index 39501682..f63d5674 100644 --- a/Laboratory/Schemas/ShouldFail/bebop.json +++ b/Laboratory/Schemas/ShouldFail/bebop.json @@ -3,11 +3,11 @@ "generators": [ { "alias": "cs", - "outputFile": "csoutput/models.cs" + "outFile": "csoutput/models.cs" }, { "alias": "ts", - "outputFile": "tsoutput/models.ts" + "outFile": "tsoutput/models.ts" } ] } diff --git a/Laboratory/Schemas/bebop.json b/Laboratory/Schemas/bebop.json index e5c9c205..8f14e25f 100644 --- a/Laboratory/Schemas/bebop.json +++ b/Laboratory/Schemas/bebop.json @@ -1,13 +1,18 @@ { "inputDirectory": "./Valid", + "exclude": ["./ShouldFail"], "generators": [ { "alias": "cs", - "outputFile": "csoutput/models.cs" + "outFile": "csoutput/models.cs" }, { "alias": "ts", - "outputFile": "tsoutput/models.ts" + "outFile": "tsoutput/models.ts" + }, + { + "alias": "py", + "outFile": "pyoutput/models.py" } ] } diff --git a/Repl/Pages/Index.razor b/Repl/Pages/Index.razor index ca7a42ad..8ed8e0cc 100644 --- a/Repl/Pages/Index.razor +++ b/Repl/Pages/Index.razor @@ -209,6 +209,7 @@ struct Library { "cpp" => "cpp", "rust" => "rust", "dart" => "dart", + "py" => "python" }; var model = await _previewEditor.GetModel(); await _previewEditor.UpdateOptions(new EditorUpdateOptions diff --git a/Runtime/Python/.gitignore b/Runtime/Python/.gitignore new file mode 100644 index 00000000..2631c35e --- /dev/null +++ b/Runtime/Python/.gitignore @@ -0,0 +1,154 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +# End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git a/Runtime/Python/LICENSE b/Runtime/Python/LICENSE new file mode 100644 index 00000000..87d9fa15 --- /dev/null +++ b/Runtime/Python/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Korbinian Habereder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Runtime/Python/README.md b/Runtime/Python/README.md new file mode 100644 index 00000000..da9ed2c2 --- /dev/null +++ b/Runtime/Python/README.md @@ -0,0 +1,3 @@ +# Python bebop + +Bebop is a schema-based binary serialization technology, similar to Protocol Buffers or MessagePack. In particular, Bebop tries to be a good fit for client–server or distributed web apps that need something faster, more concise, and more type-safe than JSON or MessagePack, while also avoiding some of the complexity of Protocol Buffers, FlatBuffers and the like. \ No newline at end of file diff --git a/Runtime/Python/pyproject.toml b/Runtime/Python/pyproject.toml new file mode 100644 index 00000000..8cf32563 --- /dev/null +++ b/Runtime/Python/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/Runtime/Python/setup.cfg b/Runtime/Python/setup.cfg new file mode 100644 index 00000000..5bc05a4d --- /dev/null +++ b/Runtime/Python/setup.cfg @@ -0,0 +1,24 @@ +[metadata] +name = python_bebop +version = 1.2.0 +author = Korbinian Habereder +author_email = korbinian.habereder@gmail.com +description = bebop runtime bindings for Python +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/liquidiert/bebop +project_urls = + Bug Tracker = https://github.com/liquidiert/bebop +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + Operating System :: OS Independent + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.8 + +[options.packages.find] +where = src \ No newline at end of file diff --git a/Runtime/Python/src/python_bebop/__init__.py b/Runtime/Python/src/python_bebop/__init__.py new file mode 100644 index 00000000..95a989fc --- /dev/null +++ b/Runtime/Python/src/python_bebop/__init__.py @@ -0,0 +1 @@ +from .bebop import BebopWriter, BebopReader, UnionType, UnionDefinition \ No newline at end of file diff --git a/Runtime/Python/src/python_bebop/bebop.py b/Runtime/Python/src/python_bebop/bebop.py new file mode 100644 index 00000000..891d5322 --- /dev/null +++ b/Runtime/Python/src/python_bebop/bebop.py @@ -0,0 +1,228 @@ +from struct import pack, unpack +from uuid import UUID +from datetime import datetime +from enum import Enum +from typing import Any, TypeVar + +ticksBetweenEpochs = 621355968000000000 +dateMask = 0x3fffffffffffffff + +class UnionDefinition: + """ + A utility class for unions + """ + discriminator: int + value: Any + + def __init__(self, discriminator: int, value: Any): + self.discriminator = discriminator + self.value = value + +UnionType = TypeVar("UnionType", (), UnionDefinition) + +class BebopReader: + """ + A wrapper around a bytearray for reading Bebop base types from it. + + It is used by the code that `bebopc --lang python` generates. + You shouldn't need to use it directly. + """ + + _emptyByteList = bytearray() + _emptyString = "" + + def __init__(self, buffer=None): + self._buffer = buffer if buffer is not None else bytearray() + self.index = 0 + + @classmethod + def from_buffer(cls, buffer: bytearray): + return cls(buffer) + + def _skip(self, amount: int): + self.index += amount + + def read_byte(self): + byte = self._buffer[self.index] + self.index += 1 + return byte + + def read_uint16(self): + v = self._buffer[self.index : self.index + 2] + self.index += 2 + return int.from_bytes(v, byteorder="little") + + def read_int16(self): + v = self._buffer[self.index : self.index + 2] + self.index += 2 + return int.from_bytes(v, byteorder="little", signed=True) + + def read_uint32(self): + v = self._buffer[self.index : self.index + 4] + self.index += 4 + return int.from_bytes(v, byteorder="little") + + def read_int32(self): + v = self._buffer[self.index : self.index + 4] + self.index += 4 + return int.from_bytes(v, byteorder="little", signed=True) + + def read_uint64(self): + v = self._buffer[self.index : self.index + 8] + self.index += 8 + return int.from_bytes(v, byteorder="little") + + def read_int64(self): + v = self._buffer[self.index : self.index + 8] + self.index += 8 + return int.from_bytes(v, byteorder="little", signed=True) + + def read_float32(self): + v = self._buffer[self.index : self.index + 4] + self.index += 4 + return unpack(" UUID: + g = UUID(bytes_le=bytes(self._buffer[self.index : self.index + 16])) + self.index += 16 + return g + + def read_date(self) -> datetime: + ticks = self.read_uint64() & dateMask + ms = (ticks - ticksBetweenEpochs) / 10000000 + return datetime.fromtimestamp(ms) + + def read_enum(self, values: list): + return values[self.read_uint32()] + + read_message_length = read_uint32 + + +class BebopWriter: + """ + A wrapper around a bytearray for writing Bebop base types from it. + + It is used by the code that `bebopc --lang python` generates. + You shouldn't need to use it directly. + """ + + def __init__(self): + self._buffer = bytearray() + self.length = 0 + + def _guarantee_buffer_length(self): + """ + This is only needed when message length is unknown; only occurs when _grow_by is used + """ + if self.length > len(self._buffer): + data = bytearray([0] * self.length) + data[:len(self._buffer)] = self._buffer + self._buffer = data + + def _grow_by(self, amount: int): + self.length += amount + self._guarantee_buffer_length() + + def write_byte(self, val: bytes): + self.length += 1 + self._buffer.append(val) + + def write_uint16(self, val: int): + self.length += 2 + self._buffer += pack("= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" time-zone@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d" + resolved "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz" integrity sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA== tinybench@^2.5.0: version "2.5.0" - resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5" + resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz" integrity sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA== tinypool@^0.5.0: version "0.5.0" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.5.0.tgz#3861c3069bf71e4f1f5aa2d2e6b3aaacc278961e" + resolved "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz" integrity sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ== tinyspy@^2.1.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.1.1.tgz#9e6371b00c259e5c5b301917ca18c01d40ae558c" + resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz" integrity sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" tr46@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + resolved "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz" integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== dependencies: punycode "^2.1.0" tree-kill@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== ts-interface-checker@^0.1.9: version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== tsup@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/tsup/-/tsup-7.0.0.tgz#5cfcc6b1c78d87c6b986e9087a6ec8bc6ea6e374" + resolved "https://registry.npmjs.org/tsup/-/tsup-7.0.0.tgz" integrity sha512-yYARDRkPq07mO3YUXTvF12ISwWQG57Odve8aFEgLdHyeGungxuKxb19yf9G0W8y59SZFkLnRj1gkoVk1gd5fbQ== dependencies: bundle-require "^4.0.0" @@ -1191,22 +1191,22 @@ tsup@^7.0.0: type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== typescript@^5.1.3: version "5.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== ufo@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" + resolved "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz" integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ== vite-node@0.32.2: version "0.32.2" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.32.2.tgz#bfccdfeb708b2309ea9e5fe424951c75bb9c0096" + resolved "https://registry.npmjs.org/vite-node/-/vite-node-0.32.2.tgz" integrity sha512-dTQ1DCLwl2aEseov7cfQ+kDMNJpM1ebpyMMMwWzBvLbis8Nla/6c9WQcqpPssTwS6Rp/+U6KwlIj8Eapw4bLdA== dependencies: cac "^6.7.14" @@ -1218,7 +1218,7 @@ vite-node@0.32.2: "vite@^3.0.0 || ^4.0.0": version "4.3.9" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d" + resolved "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz" integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg== dependencies: esbuild "^0.17.5" @@ -1229,7 +1229,7 @@ vite-node@0.32.2: vitest@^0.32.2: version "0.32.2" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.32.2.tgz#758ce2220f609e240ac054eca7ad11a5140679ab" + resolved "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz" integrity sha512-hU8GNNuQfwuQmqTLfiKcqEhZY72Zxb7nnN07koCUNmntNxbKQnVbeIS6sqUgR3eXSlbOpit8+/gr1KpqoMgWCQ== dependencies: "@types/chai" "^4.3.5" @@ -1260,17 +1260,17 @@ vitest@^0.32.2: webidl-conversions@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== well-known-symbols@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5" + resolved "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz" integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== whatwg-url@^7.0.0: version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz" integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== dependencies: lodash.sortby "^4.7.0" @@ -1279,14 +1279,14 @@ whatwg-url@^7.0.0: which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" why-is-node-running@^2.2.2: version "2.2.2" - resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e" + resolved "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz" integrity sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA== dependencies: siginfo "^2.0.0" @@ -1294,20 +1294,20 @@ why-is-node-running@^2.2.2: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.1.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== yocto-queue@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== From a4a4cd358ec7d7bff00a0e6ebc9d94297a174fa7 Mon Sep 17 00:00:00 2001 From: liquidiert Date: Mon, 6 Nov 2023 00:52:52 +0100 Subject: [PATCH 2/6] chore: fixed integration test workflow --- .github/workflows/integration-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 5fccd864..5ed2f6a5 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -27,6 +27,10 @@ jobs: with: toolchain: stable profile: minimal + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" - name: Setup Node uses: actions/setup-node@v1 with: @@ -41,6 +45,8 @@ jobs: yarn test yarn build working-directory: ./Runtime/TypeScript + - name: Install python runtime + run: pip install python-bebop - name: yarn install run: yarn install working-directory: ${{env.TEST_ROOT}} From 39f353d48930378b3c4604d581cf7adc728a0276 Mon Sep 17 00:00:00 2001 From: liquidiert Date: Mon, 6 Nov 2023 01:01:53 +0100 Subject: [PATCH 3/6] chore: updated runtime --- Runtime/Python/setup.cfg | 2 +- Runtime/Python/src/python_bebop/bebop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Python/setup.cfg b/Runtime/Python/setup.cfg index 5bc05a4d..3c187869 100644 --- a/Runtime/Python/setup.cfg +++ b/Runtime/Python/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = python_bebop -version = 1.2.0 +version = 1.2.1 author = Korbinian Habereder author_email = korbinian.habereder@gmail.com description = bebop runtime bindings for Python diff --git a/Runtime/Python/src/python_bebop/bebop.py b/Runtime/Python/src/python_bebop/bebop.py index 891d5322..ffb3a5fd 100644 --- a/Runtime/Python/src/python_bebop/bebop.py +++ b/Runtime/Python/src/python_bebop/bebop.py @@ -18,7 +18,7 @@ def __init__(self, discriminator: int, value: Any): self.discriminator = discriminator self.value = value -UnionType = TypeVar("UnionType", (), UnionDefinition) +UnionType = TypeVar("UnionType", bound=UnionDefinition) class BebopReader: """ From 72ae3ca00156e00d507c7a8c81d5a566ce069410 Mon Sep 17 00:00:00 2001 From: Korbinian Habereder Date: Tue, 7 Nov 2023 23:01:19 +0100 Subject: [PATCH 4/6] chore: implemented requested PR changes --- .gitignore | 1 + Core/Generators/Python/PythonGenerator.cs | 170 ++++++++++------- Laboratory/Integration/Python/src/lib.py | 13 +- Runtime/Python/LICENSE | 222 ++++++++++++++++++++-- Runtime/Python/setup.cfg | 4 +- Runtime/Python/src/python_bebop/bebop.py | 21 +- 6 files changed, 323 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index c4e98a22..8d75866b 100644 --- a/.gitignore +++ b/.gitignore @@ -273,3 +273,4 @@ Laboratory/C#/Benchmarks/BenchmarkDotNet.Artifacts Core/Meta/VersionInfo.cs .DS_Store +.vscode diff --git a/Core/Generators/Python/PythonGenerator.cs b/Core/Generators/Python/PythonGenerator.cs index e3f7e2e3..7d476ffa 100644 --- a/Core/Generators/Python/PythonGenerator.cs +++ b/Core/Generators/Python/PythonGenerator.cs @@ -6,6 +6,7 @@ using System.Text.Json; using Core.Meta; using Core.Meta.Extensions; +using Core.Meta.Attributes; namespace Core.Generators.Python { @@ -15,22 +16,16 @@ public class PythonGenerator : BaseGenerator public PythonGenerator(BebopSchema schema) : base(schema) { } - private string FormatDocumentation(string documentation){ + private string FormatDocumentation(string documentation, BaseAttribute? deprecated){ var builder = new StringBuilder(); + builder.AppendLine("\"\"\""); foreach (var line in documentation.GetLines()) { - builder.AppendLine($"# {line}"); + builder.AppendLine(line); + } + if (deprecated != null) { + builder.AppendLine($"@deprecated {deprecated.Value}"); } - return builder.ToString(); - } - - private static string FormatDeprecationDoc(string deprecationReason, int spaces) - { - var builder = new IndentedStringBuilder(); - builder.Indent(spaces); - builder.AppendLine("\"\"\""); - builder.Indent(1); - builder.AppendLine($"@deprecated {deprecationReason}"); builder.AppendLine("\"\"\""); return builder.ToString(); } @@ -104,11 +99,12 @@ private string CompileEncodeUnion(UnionDefinition definition) { return builder.ToString(); } - private string CompileEncodeField(TypeBase type, string target, int depth = 0, int indentDepth = 0) + private string CompileEncodeField(TypeBase type, string target, int depth = 0, int indentDepth = 0, bool isEnum = false) { var tab = new string(' ', indentStep); var nl = "\n" + new string(' ', indentDepth * indentStep); var i = GeneratorUtils.LoopVariable(depth); + var enumAppendix = isEnum ? ".value" : ""; return type switch { ArrayType at when at.IsBytes() => $"writer.writeBytes({target})", @@ -124,23 +120,23 @@ ArrayType at when at.IsBytes() => $"writer.writeBytes({target})", $"{tab}{CompileEncodeField(mt.ValueType, $"val{depth}", depth + 1, indentDepth + 1)}" + nl, ScalarType st => st.BaseType switch { - BaseType.Bool => $"writer.write_bool({target})", - BaseType.Byte => $"writer.write_byte({target})", - BaseType.UInt16 => $"writer.write_uint16({target})", - BaseType.Int16 => $"writer.write_int16({target})", - BaseType.UInt32 => $"writer.write_uint32({target})", - BaseType.Int32 => $"writer.write_int32({target})", - BaseType.UInt64 => $"writer.write_uint64({target})", - BaseType.Int64 => $"writer.write_int64({target})", - BaseType.Float32 => $"writer.write_float32({target})", - BaseType.Float64 => $"writer.write_float64({target})", - BaseType.String => $"writer.write_string({target})", - BaseType.Guid => $"writer.write_guid({target})", - BaseType.Date => $"writer.write_date({target})", + BaseType.Bool => $"writer.write_bool({target}{enumAppendix})", + BaseType.Byte => $"writer.write_byte({target}{enumAppendix})", + BaseType.UInt16 => $"writer.write_uint16({target}{enumAppendix})", + BaseType.Int16 => $"writer.write_int16({target}{enumAppendix})", + BaseType.UInt32 => $"writer.write_uint32({target}{enumAppendix})", + BaseType.Int32 => $"writer.write_int32({target}{enumAppendix})", + BaseType.UInt64 => $"writer.write_uint64({target}{enumAppendix})", + BaseType.Int64 => $"writer.write_int64({target}{enumAppendix})", + BaseType.Float32 => $"writer.write_float32({target}{enumAppendix})", + BaseType.Float64 => $"writer.write_float64({target}{enumAppendix})", + BaseType.String => $"writer.write_string({target}{enumAppendix})", + BaseType.Guid => $"writer.write_guid({target}{enumAppendix})", + BaseType.Date => $"writer.write_date({target}{enumAppendix})", _ => throw new ArgumentOutOfRangeException(st.BaseType.ToString()) }, - DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition => - $"writer.write_enum({target})", + DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition ed => + CompileEncodeField(ed.ScalarType, target, depth, indentDepth, true), DefinedType dt => $"{dt.Name}.encode_into({target}, writer)", _ => throw new InvalidOperationException($"CompileEncodeField: {type}") }; @@ -223,6 +219,27 @@ private string CompileDecodeUnion(UnionDefinition definition) { return builder.ToString(); } + private string ReadBaseType(BaseType baseType) + { + return baseType switch + { + BaseType.Bool => "reader.read_bool()", + BaseType.Byte => "reader.read_byte()", + BaseType.UInt16 => "reader.read_uint16()", + BaseType.Int16 => "reader.read_int16()", + BaseType.UInt32 => "reader.read_uint32()", + BaseType.Int32 => "reader.read_int32()", + BaseType.UInt64 => "reader.read_uint64()", + BaseType.Int64 => "reader.read_int64()", + BaseType.Float32 => "reader.read_float32()", + BaseType.Float64 => "reader.read_float64()", + BaseType.String => "reader.read_string()", + BaseType.Guid => "reader.read_guid()", + BaseType.Date => "reader.read_date()", + _ => throw new ArgumentOutOfRangeException(baseType.ToString()) + }; + } + private string CompileDecodeField(TypeBase type, string target, int depth = 0) { var tab = new string(' ', indentStep); @@ -244,25 +261,9 @@ ArrayType at when at.IsBytes() => $"{target} = reader.readBytes()", $"{tab}{CompileDecodeField(mt.KeyType, $"k{depth}", depth + 1)}" + nl + $"{tab}{CompileDecodeField(mt.ValueType, $"v{depth}", depth + 1)}" + nl + $"{tab}{target}[k{depth}] = v{depth}" + nl, - ScalarType st => st.BaseType switch - { - BaseType.Bool => $"{target} = reader.read_bool()", - BaseType.Byte => $"{target} = reader.read_byte()", - BaseType.UInt16 => $"{target} = reader.read_uint16()", - BaseType.Int16 => $"{target} = reader.read_int16()", - BaseType.UInt32 => $"{target} = reader.read_uint32()", - BaseType.Int32 => $"{target} = reader.read_int32()", - BaseType.UInt64 => $"{target} = reader.read_uint64()", - BaseType.Int64 => $"{target} = reader.read_int64()", - BaseType.Float32 => $"{target} = reader.read_float32()", - BaseType.Float64 => $"{target} = reader.read_float64()", - BaseType.String => $"{target} = reader.read_string()", - BaseType.Guid => $"{target} = reader.read_guid()", - BaseType.Date => $"{target} = reader.read_date()", - _ => throw new ArgumentOutOfRangeException(st.BaseType.ToString()) - }, - DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition => - $"{target} = {dt.Name}(reader.read_uint32())", + ScalarType st => $"{target} = {ReadBaseType(st.BaseType)}", + DefinedType dt when Schema.Definitions[dt.Name] is EnumDefinition ed => + $"{target} = {dt.Name}({ReadBaseType(ed.BaseType)})", DefinedType dt => $"{target} = {dt.Name}.read_from(reader)", _ => throw new InvalidOperationException($"CompileDecodeField: {type}") }; @@ -336,35 +337,29 @@ public override string Compile(Version? languageVersion, TempoServices services builder.AppendLine("import math"); builder.AppendLine("import json"); builder.AppendLine("from datetime import datetime"); - builder.AppendLine("from typing import Any, TypeVar"); builder.AppendLine(""); foreach (var definition in Schema.Definitions.Values) { - if (!string.IsNullOrWhiteSpace(definition.Documentation)) - { - builder.Append(FormatDocumentation(definition.Documentation)); - builder.AppendLine(""); - } switch (definition) { case EnumDefinition ed: builder.AppendLine($"class {ed.Name}(Enum):"); builder.Indent(indentStep); + if (!string.IsNullOrWhiteSpace(definition.Documentation)) + { + builder.Append(FormatDocumentation(definition.Documentation, null)); + builder.AppendLine(); + } for (var i = 0; i < ed.Members.Count; i++) { var field = ed.Members.ElementAt(i); + builder.AppendLine($"{field.Name.ToUpper()} = {field.ConstantValue}"); if (!string.IsNullOrWhiteSpace(field.Documentation)) { - builder.Append(FormatDocumentation(field.Documentation)); + builder.Append(FormatDocumentation(field.Documentation, field.DeprecatedAttribute)); builder.AppendLine(""); } - if (field.DeprecatedAttribute != null) - { - - builder.AppendLine($"\"\"\" @deprecated {field.DeprecatedAttribute.Value} \"\"\""); - } - builder.AppendLine($"{field.Name.ToUpper()} = {field.ConstantValue}"); } builder.AppendLine(""); builder.Dedent(indentStep); @@ -373,19 +368,22 @@ public override string Compile(Version? languageVersion, TempoServices services if (rd is FieldsDefinition fd) { builder.AppendLine($"class {fd.Name}:"); builder.Indent(indentStep); + if (!string.IsNullOrWhiteSpace(definition.Documentation)) + { + builder.Append(FormatDocumentation(definition.Documentation, null)); + builder.AppendLine(); + } + var isReadonlyStruct = rd is StructDefinition sd ? sd.IsReadOnly : false; + var fieldPrepend = isReadonlyStruct ? "_" : ""; for (var i = 0; i < fd.Fields.Count; i++) { var field = fd.Fields.ElementAt(i); var type = TypeName(field.Type); + builder.AppendLine($"{fieldPrepend}{field.Name}: {type}"); if (!string.IsNullOrWhiteSpace(field.Documentation)) { - builder.Append(FormatDocumentation(field.Documentation)); - builder.AppendLine(""); - } - if (field.DeprecatedAttribute != null) - { - builder.AppendLine($"# @deprecated {field.DeprecatedAttribute.Value}"); + builder.Append(FormatDocumentation(field.Documentation, field.DeprecatedAttribute)); } - builder.AppendLine($"{field.Name}: {type}"); + builder.AppendLine(); } if (rd.OpcodeAttribute != null) { builder.AppendLine($"opcode = {rd.OpcodeAttribute.Value}"); @@ -403,20 +401,48 @@ public override string Compile(Version? languageVersion, TempoServices services builder.Append(string.Join(",", fields)); builder.AppendLine("):"); builder.Indent(indentStep); + builder.AppendLine("self.encode = self._encode"); foreach (var field in fd.Fields) { - builder.AppendLine($"self.{field.Name} = {field.Name}"); + builder.AppendLine($"self.{fieldPrepend}{field.Name} = {field.Name}"); } builder.Dedent(indentStep); + } else { + builder.AppendLine("def __init__(self):"); + builder.AppendLine(" self.encode = self._encode"); + } + builder.AppendLine(); + } else { + builder.CodeBlock("def __init__(self):", indentStep, () => { + builder.AppendLine("self.encode = self._encode"); + }, open: string.Empty, close: string.Empty); + } + + if (isReadonlyStruct) { + for (var i = 0; i < fd.Fields.Count; i++) { + var field = fd.Fields.ElementAt(i); + builder.AppendLine("@property"); + builder.CodeBlock($"def {field.Name}(self):", indentStep, () => { + builder.AppendLine($"return self._{field.Name}"); + }, close: string.Empty, open: string.Empty); } } } else if (rd is UnionDefinition ud) { builder.CodeBlock($"class {ud.ClassName()}:", indentStep, () => { builder.AppendLine(); + if (!string.IsNullOrWhiteSpace(definition.Documentation)) + { + builder.Append(FormatDocumentation(definition.Documentation, null)); + } + if (rd.OpcodeAttribute != null) { + builder.AppendLine($"opcode = {rd.OpcodeAttribute.Value}"); + builder.AppendLine(""); + } builder.AppendLine($"data: UnionType"); builder.AppendLine(); builder.CodeBlock($"def __init__(self, data: UnionType):", indentStep, () => { + builder.AppendLine("self.encode = self._encode"); builder.AppendLine($"self.data = data"); }, open: string.Empty, close: string.Empty); builder.AppendLine("@property"); @@ -444,6 +470,14 @@ public override string Compile(Version? languageVersion, TempoServices services }, close: string.Empty, open: string.Empty); builder.Indent(indentStep); } + builder.AppendLine($"def _encode(self):"); + builder.Indent(indentStep); + builder.AppendLine("\"\"\"Fake class method for allowing instance encode\"\"\""); + builder.AppendLine("writer = BebopWriter()"); + builder.AppendLine($"{rd.Name}.encode_into(self, writer)"); + builder.AppendLine("return writer.to_list()"); + builder.Dedent(indentStep); + builder.AppendLine(""); builder.AppendLine(""); builder.AppendLine("@staticmethod"); diff --git a/Laboratory/Integration/Python/src/lib.py b/Laboratory/Integration/Python/src/lib.py index 04881436..e65f2dd5 100644 --- a/Laboratory/Integration/Python/src/lib.py +++ b/Laboratory/Integration/Python/src/lib.py @@ -1,20 +1,21 @@ from schema import Library, Instrument, Album, StudioAlbum, LiveAlbum, Song, Musician from datetime import datetime +from uuid import UUID def make_lib(): giant_steps_song = Song() giant_steps_song.title = "Giant Steps" giant_steps_song.year = 1959 giant_steps_song.performers = [ - Musician(name="John Coltrane", plays=Instrument.PIANO) + Musician(name="John Coltrane", plays=Instrument.PIANO, id=UUID("ff990458-a276-4b71-b2e3-57d49470b949")) ] a_night_in_tunisia_song = Song() a_night_in_tunisia_song.title = "A Night in Tunisia" a_night_in_tunisia_song.year = 1942 a_night_in_tunisia_song.performers = [ - Musician(name="Dizzy Gillespie", plays=Instrument.TRUMPET), - Musician(name="Count Basie", plays=Instrument.PIANO), + Musician(name="Dizzy Gillespie", plays=Instrument.TRUMPET, id=UUID("84f4b320-0f1e-463e-982c-78772fabd74d")), + Musician(name="Count Basie", plays=Instrument.PIANO, id=UUID("b28d54d6-a3f7-48bf-a07a-117c15cf33ef")), ] groovin_high_song = Song() @@ -27,9 +28,9 @@ def make_lib(): unnamed_song = Song() unnamed_song.year = 1965 unnamed_song.performers = [ - Musician(name="Carmell Jones", plays=Instrument.TRUMPET), - Musician(name="Joe Henderson", plays=Instrument.SAX), - Musician(name="Teddy Smith", plays=Instrument.CLARINET) + Musician(name="Carmell Jones", plays=Instrument.TRUMPET, id=UUID("f7c31724-0387-4ac9-b6f0-361bb9415c1b")), + Musician(name="Joe Henderson", plays=Instrument.SAX, id=UUID("bb4facf3-c65a-46dd-a96f-73ca6d1cf3f6")), + Musician(name="Teddy Smith", plays=Instrument.CLARINET, id=UUID("91ffb47f-2a38-4876-8186-1f267cc21706")) ] brilliant_corners_album = LiveAlbum() diff --git a/Runtime/Python/LICENSE b/Runtime/Python/LICENSE index 87d9fa15..f83ce144 100644 --- a/Runtime/Python/LICENSE +++ b/Runtime/Python/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2021 Korbinian Habereder - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Korbinian Habereder + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Runtime/Python/setup.cfg b/Runtime/Python/setup.cfg index 3c187869..62598de2 100644 --- a/Runtime/Python/setup.cfg +++ b/Runtime/Python/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = python_bebop -version = 1.2.1 +version = 1.3.1 author = Korbinian Habereder author_email = korbinian.habereder@gmail.com description = bebop runtime bindings for Python @@ -11,7 +11,7 @@ project_urls = Bug Tracker = https://github.com/liquidiert/bebop classifiers = Programming Language :: Python :: 3 - License :: OSI Approved :: MIT License + License :: OSI Approved :: Apache Software License Operating System :: OS Independent [options] diff --git a/Runtime/Python/src/python_bebop/bebop.py b/Runtime/Python/src/python_bebop/bebop.py index ffb3a5fd..5523dd9f 100644 --- a/Runtime/Python/src/python_bebop/bebop.py +++ b/Runtime/Python/src/python_bebop/bebop.py @@ -4,6 +4,7 @@ from enum import Enum from typing import Any, TypeVar +# constants ticksBetweenEpochs = 621355968000000000 dateMask = 0x3fffffffffffffff @@ -102,12 +103,14 @@ def read_string(self): length = self.read_uint32() if length == 0: return self._emptyString - v = self._buffer[self.index : self.index + length] + string_data = self._buffer[self.index : self.index + length] self.index += length - return "".join([chr(c) for c in v]) + return string_data.decode('utf-8') def read_guid(self) -> UUID: - g = UUID(bytes_le=bytes(self._buffer[self.index : self.index + 16])) + b = self._buffer[self.index : self.index + 16] + reordered = [b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]] + g = UUID(bytes=bytes(reordered)) self.index += 16 return g @@ -116,9 +119,6 @@ def read_date(self) -> datetime: ms = (ticks - ticksBetweenEpochs) / 10000000 return datetime.fromtimestamp(ms) - def read_enum(self, values: list): - return values[self.read_uint32()] - read_message_length = read_uint32 @@ -199,19 +199,18 @@ def write_string(self, val: str): if len(val) == 0: self.write_uint32(0) return - self.write_bytes(val.encode()) + self.write_bytes(val.encode("utf-8")) def write_guid(self, guid: UUID): - self.write_bytes(guid.bytes_le, write_msg_length=False) + b = guid.bytes + bebop_uid = [b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]] + self.write_bytes(bebop_uid, write_msg_length=False) def write_date(self, date: datetime): ms = int(date.timestamp()) ticks = ms * 10000000 + ticksBetweenEpochs self.write_uint64(ticks & dateMask) - def write_enum(self, val: Enum): - self.write_uint32(val.value) - def reserve_message_length(self): """ Reserve some space to write a message's length prefix, and return its index. From 119ee91292987f3e4d9ac581384ff9f317216483 Mon Sep 17 00:00:00 2001 From: liquidiert Date: Wed, 8 Nov 2023 09:17:52 +0100 Subject: [PATCH 5/6] chore: updated integration test to build python bebop locally --- .github/workflows/integration-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 5ed2f6a5..328d627b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -45,8 +45,12 @@ jobs: yarn test yarn build working-directory: ./Runtime/TypeScript - - name: Install python runtime - run: pip install python-bebop + - name: Build Python runtime + run: | + pip install build + python -m build + pip install dist/*.whl + working-directory: ./Runtime/Python - name: yarn install run: yarn install working-directory: ${{env.TEST_ROOT}} From dfc0eeda2413a32782c6c90b264899e42089c364 Mon Sep 17 00:00:00 2001 From: liquidiert Date: Wed, 8 Nov 2023 20:44:25 +0100 Subject: [PATCH 6/6] chore: added bytesarray cast to read string for ensuring encode works --- Runtime/Python/setup.cfg | 2 +- Runtime/Python/src/python_bebop/bebop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Python/setup.cfg b/Runtime/Python/setup.cfg index 62598de2..8a020579 100644 --- a/Runtime/Python/setup.cfg +++ b/Runtime/Python/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = python_bebop -version = 1.3.1 +version = 1.3.2 author = Korbinian Habereder author_email = korbinian.habereder@gmail.com description = bebop runtime bindings for Python diff --git a/Runtime/Python/src/python_bebop/bebop.py b/Runtime/Python/src/python_bebop/bebop.py index 5523dd9f..404a9488 100644 --- a/Runtime/Python/src/python_bebop/bebop.py +++ b/Runtime/Python/src/python_bebop/bebop.py @@ -105,7 +105,7 @@ def read_string(self): return self._emptyString string_data = self._buffer[self.index : self.index + length] self.index += length - return string_data.decode('utf-8') + return bytearray(string_data).decode('utf-8') def read_guid(self) -> UUID: b = self._buffer[self.index : self.index + 16]