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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/struct_frame/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .base import version, NamingStyleC, NamingStyleCpp, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values
from .base import version, NamingStyleC, NamingStyleCpp, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values

from .c_gen import FileCGen, TestCGen
from .ts_gen import FileTsGen, TestTsGen
Expand All @@ -12,4 +12,4 @@
from .generate import main

__all__ = ["main", "FileCGen", "TestCGen", "FileTsGen", "TestTsGen", "FileJsGen", "TestJsGen", "FilePyGen", "TestPyGen", "FileGqlGen", "FileCppGen", "TestCppGen", "FileCSharpGen", "TestCSharpGen", "FileRustGen", "TestRustGen", "version",
"NamingStyleC", "NamingStyleCpp", "camel_to_snake_case", "pascal_case", "build_enum_leading_comments", "build_enum_values"]
"NamingStyleC", "NamingStyleCpp", "camel_to_snake_case", "pascal_case", "build_enum_leading_comments", "build_enum_values", "get_discriminator_enum_name", "build_discriminator_enum_values"]
31 changes: 31 additions & 0 deletions src/struct_frame/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,34 @@ def build_enum_values(field, naming_style, value_format=None, comment_formatter=
enum_values.append(enum_value)

return enum_values


def get_discriminator_enum_name(oneof, msg_name):
"""Return the standard discriminator enum type name used by most generators.

Produces ``{msg_name}{PascalCase(oneof.name)}Field``, which is the shared
convention for C, C#, JavaScript, Python, and TypeScript generators.
(C++ uses a scoped name without the message prefix and calls its own helper.)
"""
return f'{msg_name}{pascal_case(oneof.name)}Field'


def build_discriminator_enum_values(oneof, none_entry_fn, field_entry_fn):
"""Build the ``None`` + per-field entries for a field_order discriminator enum.

Args:
oneof: The oneof model object with ``.fields`` (ordered dict of field_name→field).
none_entry_fn: Callable() → str — returns the formatted ``None = 0`` line.
field_entry_fn: Callable(field_name, field_order, is_last) → str — returns one
formatted enum-value line for the given field.

Returns:
List of formatted enum-value lines (strings, without trailing newlines).
"""
lines = [none_entry_fn()]
items = list(oneof.fields.items())
for idx, (field_name, _field) in enumerate(items):
field_order = idx + 1
is_last = idx == len(items) - 1
lines.append(field_entry_fn(field_name, field_order, is_last))
return lines
2 changes: 2 additions & 0 deletions src/struct_frame/boilerplate/js/frame-base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Struct-frame boilerplate: frame parser base utilities
// NOTE: This file is the JavaScript twin of boilerplate/ts/frame-base.ts. Any algorithm
// fix to GenericFrameParser or related helpers MUST also be applied to the TypeScript source.


// Fletcher-16 checksum calculation
Expand Down
5 changes: 3 additions & 2 deletions src/struct_frame/boilerplate/ts/frame-base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// Struct-frame boilerplate: frame parser base utilities
// NOTE: This file is the TypeScript source of the GenericFrameParser and related helpers.
// The JavaScript twin is boilerplate/js/frame-base.js. Any algorithm fix MUST be applied
// to both files.

// Fletcher-16 checksum calculation
export function fletcherChecksum(buffer: Uint8Array | number[], start: number = 0, end?: number,
Expand Down Expand Up @@ -240,8 +243,6 @@ export class GenericFrameParser {
private getInitialState(): GenericParserState {
if (this.config.startBytes.length === 0) {
return GenericParserState.GETTING_MSG_ID;
} else if (this.config.startBytes.length === 1) {
return GenericParserState.LOOKING_FOR_START1;
} else {
return GenericParserState.LOOKING_FOR_START1;
}
Expand Down
35 changes: 18 additions & 17 deletions src/struct_frame/c_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
functions for binary compatibility across platforms.
"""

from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values
from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values
import time

_style_c = NamingStyleC()
Expand Down Expand Up @@ -199,27 +199,28 @@ def generate_discriminator_enum(oneof, msg_name, package=None):
"""Generate a discriminator enum for field_order oneofs in C."""
if not oneof.auto_discriminator or oneof.discriminator_type != "field_order":
return ''

result = ''
enum_name = f'{msg_name}{pascal_case(oneof.name)}Field'

result += f'/* Discriminator enum for {msg_name}::{oneof.name} oneof */\n'

enum_name = get_discriminator_enum_name(oneof, msg_name)
prefix = f'{camel_to_snake_case(msg_name).upper()}_{camel_to_snake_case(oneof.name).upper()}_FIELD'

def none_entry():
return f' {prefix}_NONE = 0,'

def field_entry(field_name, field_order, is_last):
value = f'{prefix}_{camel_to_snake_case(field_name).upper()}'
return f' {value} = {field_order},'

lines = build_discriminator_enum_values(oneof, none_entry, field_entry)
result = f'/* Discriminator enum for {msg_name}::{oneof.name} oneof */\n'
result += f'typedef enum {enum_name} {{\n'
result += f' {camel_to_snake_case(msg_name).upper()}_{camel_to_snake_case(oneof.name).upper()}_FIELD_NONE = 0,\n'

for idx, (field_name, field) in enumerate(oneof.fields.items()):
field_order = idx + 1
# Use SCREAMING_SNAKE_CASE for enum values with message prefix
enum_value = f'{camel_to_snake_case(msg_name).upper()}_{camel_to_snake_case(oneof.name).upper()}_FIELD_{camel_to_snake_case(field_name).upper()}'
result += f' {enum_value} = {field_order},\n'

result += '\n'.join(lines) + '\n'
result += f'}} {enum_name};\n\n'
return result

@staticmethod
def get_discriminator_enum_name(oneof, msg_name):
"""Get the enum type name for a field_order discriminator."""
return f'{msg_name}{pascal_case(oneof.name)}Field'
return get_discriminator_enum_name(oneof, msg_name)

@staticmethod
def generate(oneof, package=None, msg_name=None):
Expand Down Expand Up @@ -332,7 +333,7 @@ def generate(msg, package=None, equality=False):
result = ''
if leading_comment:
for c in msg.comments:
result = '%s\n' % c
result += '%s\n' % c

structName = '%s%s' % (pascal_case(msg.package), msg.name)
result += 'typedef struct %s {' % structName
Expand Down
29 changes: 15 additions & 14 deletions src/struct_frame/csharp_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
classes with manual Pack/Unpack methods for binary compatibility.
"""

from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values
from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values
import os
import time

Expand Down Expand Up @@ -124,27 +124,28 @@ def generate_discriminator_enum(oneof, msg_name):
"""Generate a discriminator enum for field_order oneofs in C#."""
if not oneof.auto_discriminator or oneof.discriminator_type != "field_order":
return ''

enum_name = f'{msg_name}{pascal_case(oneof.name)}Field'

enum_name = get_discriminator_enum_name(oneof, msg_name)

def none_entry():
return ' None = 0,'

def field_entry(field_name, field_order, is_last):
comma = '' if is_last else ','
return f' {pascal_case(field_name)} = {field_order}{comma}'

lines = build_discriminator_enum_values(oneof, none_entry, field_entry)
result = f' /// <summary>Discriminator enum for {msg_name}.{oneof.name} oneof</summary>\n'
result += f' public enum {enum_name} : byte\n'
result += f' {{\n'
result += f' None = 0,\n'

for idx, (field_name, field) in enumerate(oneof.fields.items()):
field_order = idx + 1
# Use PascalCase for C# enum values
enum_value = pascal_case(field_name)
comma = ',' if idx < len(oneof.fields) - 1 else ''
result += f' {enum_value} = {field_order}{comma}\n'

result += '\n'.join(lines) + '\n'
result += f' }}\n'
return result

@staticmethod
def get_discriminator_enum_name(oneof, msg_name):
"""Get the enum type name for a field_order discriminator."""
return f'{msg_name}{pascal_case(oneof.name)}Field'
return get_discriminator_enum_name(oneof, msg_name)


class FieldCSharpGen():
Expand Down
33 changes: 17 additions & 16 deletions src/struct_frame/js_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
but outputs JavaScript syntax (CommonJS) instead of TypeScript.
"""

from struct_frame import version, NamingStyleC, pascal_case, build_enum_leading_comments, build_enum_values
from struct_frame import version, NamingStyleC, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values
from struct_frame.ts_js_base import (
common_types,
common_typed_array_methods,
Expand Down Expand Up @@ -87,27 +87,28 @@ def generate_discriminator_enum(oneof, msg_name):
"""Generate a discriminator enum for field_order oneofs in JavaScript."""
if not oneof.auto_discriminator or oneof.discriminator_type != "field_order":
return ''

enum_name = f'{msg_name}{pascal_case(oneof.name)}Field'

enum_name = get_discriminator_enum_name(oneof, msg_name)

def none_entry():
return ' None: 0,'

def field_entry(field_name, field_order, is_last):
comma = '' if is_last else ','
return f' {pascal_case(field_name)}: {field_order}{comma}'

lines = build_discriminator_enum_values(oneof, none_entry, field_entry)
result = f'/** Discriminator enum for {msg_name}.{oneof.name} oneof */\n'
result += f'const {enum_name} = Object.freeze({{\n'
result += f' None: 0,\n'

for idx, (field_name, field) in enumerate(oneof.fields.items()):
field_order = idx + 1
# Use PascalCase for JavaScript enum values
enum_value = pascal_case(field_name)
comma = ',' if idx < len(oneof.fields) - 1 else ''
result += f' {enum_value}: {field_order}{comma}\n'

result += '\n'.join(lines) + '\n'
result += f'}});\n'
result += f'module.exports.{enum_name} = {enum_name};\n'
return result

@staticmethod
def get_discriminator_enum_name(oneof, msg_name):
"""Get the enum type name for a field_order discriminator."""
return f'{msg_name}{pascal_case(oneof.name)}Field'
return get_discriminator_enum_name(oneof, msg_name)


class FieldJsGen():
Expand All @@ -134,7 +135,7 @@ def generate(msg, package_name, package=None):
result = ''
if leading_comment:
for c in msg.comments:
result = '%s\n' % c
result += '%s\n' % c

package_msg_name = '%s%s' % (package_name, msg.name)

Expand Down Expand Up @@ -198,7 +199,7 @@ def generate(msg, package_name, package, packages, equality=False):
result = ''
if leading_comment:
for c in msg.comments:
result = '%s\n' % c
result += '%s\n' % c

package_msg_name = msg.name

Expand Down
29 changes: 15 additions & 14 deletions src/struct_frame/py_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
module for binary packing/unpacking with dataclass-style message definitions.
"""

from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values
from struct_frame import version, NamingStyleC, camel_to_snake_case, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values
import time

_style_c = NamingStyleC()
Expand Down Expand Up @@ -81,20 +81,21 @@ def generate_discriminator_enum(oneof, msg_name):
"""Generate a discriminator enum for field_order oneofs in Python."""
if not oneof.auto_discriminator or oneof.discriminator_type != "field_order":
return ''

enum_name = f'{msg_name}{pascal_case(oneof.name)}Field'

enum_name = get_discriminator_enum_name(oneof, msg_name)

def none_entry():
return ' NONE = 0'

def field_entry(field_name, field_order, is_last):
return f' {camel_to_snake_case(field_name).upper()} = {field_order}'

lines = build_discriminator_enum_values(oneof, none_entry, field_entry)
result = f'class {enum_name}(Enum):\n'
result += f' """Discriminator enum for {msg_name}.{oneof.name} oneof"""\n'
result += f' NONE = 0\n'

for idx, (field_name, field) in enumerate(oneof.fields.items()):
field_order = idx + 1
# Use SCREAMING_SNAKE_CASE for enum values
enum_value = camel_to_snake_case(field_name).upper()
result += f' {enum_value} = {field_order}\n'

result += '\n'.join(lines) + '\n'
return result

@staticmethod
def generate_nested(field):
"""Generate a nested enum class indented inside the message class body."""
Expand All @@ -112,7 +113,7 @@ def generate_nested(field):
@staticmethod
def get_discriminator_enum_name(oneof, msg_name):
"""Get the enum type name for a field_order discriminator."""
return f'{msg_name}{pascal_case(oneof.name)}Field'
return get_discriminator_enum_name(oneof, msg_name)


class FieldPyGen():
Expand Down Expand Up @@ -600,7 +601,7 @@ def generate(msg, equality=False):
result = ''
if leading_comment:
for c in msg.comments:
result = '#%s\n' % c
result += '#%s\n' % c

structName = msg.name
result += 'class %s:\n' % structName
Expand Down
33 changes: 17 additions & 16 deletions src/struct_frame/ts_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
ES6 module syntax (import/export).
"""

from struct_frame import version, NamingStyleC, pascal_case, build_enum_leading_comments, build_enum_values
from struct_frame import version, NamingStyleC, pascal_case, build_enum_leading_comments, build_enum_values, get_discriminator_enum_name, build_discriminator_enum_values
from struct_frame.ts_js_base import (
common_types,
common_typed_array_methods,
Expand Down Expand Up @@ -83,26 +83,27 @@ def generate_discriminator_enum(oneof, msg_name):
"""Generate a discriminator enum for field_order oneofs in TypeScript."""
if not oneof.auto_discriminator or oneof.discriminator_type != "field_order":
return ''

enum_name = f'{msg_name}{pascal_case(oneof.name)}Field'

enum_name = get_discriminator_enum_name(oneof, msg_name)

def none_entry():
return ' None = 0,'

def field_entry(field_name, field_order, is_last):
comma = '' if is_last else ','
return f' {pascal_case(field_name)} = {field_order}{comma}'

lines = build_discriminator_enum_values(oneof, none_entry, field_entry)
result = f'/** Discriminator enum for {msg_name}.{oneof.name} oneof */\n'
result += f'export enum {enum_name} {{\n'
result += f' None = 0,\n'

for idx, (field_name, field) in enumerate(oneof.fields.items()):
field_order = idx + 1
# Use PascalCase for TypeScript enum values
enum_value = pascal_case(field_name)
comma = ',' if idx < len(oneof.fields) - 1 else ''
result += f' {enum_value} = {field_order}{comma}\n'

result += '\n'.join(lines) + '\n'
result += f'}}\n'
return result

@staticmethod
def get_discriminator_enum_name(oneof, msg_name):
"""Get the enum type name for a field_order discriminator."""
return f'{msg_name}{pascal_case(oneof.name)}Field'
return get_discriminator_enum_name(oneof, msg_name)


class FieldTsGen():
Expand All @@ -129,7 +130,7 @@ def generate(msg, package_name, package=None):
result = ''
if leading_comment:
for c in msg.comments:
result = '%s\n' % c
result += '%s\n' % c

package_msg_name = msg.name

Expand Down Expand Up @@ -195,7 +196,7 @@ def generate(msg, package_name, package, packages, equality=False):
result = ''
if leading_comment:
for c in msg.comments:
result = '%s\n' % c
result += '%s\n' % c

package_msg_name = msg.name

Expand Down
4 changes: 2 additions & 2 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ def run_roundtrip_tests(self) -> bool:

# ---- C++ ----
cpp = self.languages.get("cpp")
if cpp and self.results["compilation"].get("cpp", True):
if cpp and "cpp" not in self.skipped_languages and self.results["compilation"].get("cpp", False):
gen_dir = self.project_root / cpp.gen_output_dir
sources = sorted(gen_dir.glob("test_roundtrip_*.cpp"))
if not sources:
Expand Down Expand Up @@ -2013,7 +2013,7 @@ def run_roundtrip_tests(self) -> bool:

# ---- C ----
c_lang = self.languages.get("c")
if c_lang and "c" not in self.skipped_languages and self.results["compilation"].get("c", True):
if c_lang and "c" not in self.skipped_languages and self.results["compilation"].get("c", False):
gen_dir = self.project_root / c_lang.gen_output_dir
sources = sorted(gen_dir.glob("test_roundtrip_*.c"))
if not sources:
Expand Down
Loading
Loading