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
80 changes: 64 additions & 16 deletions tools/j2735_c_generator_data_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,91 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Yogev Neumann
"""
TODO
J2735 Data Element C Code Generator.

Generates zero-copy C header files for J2735 Data Element types (BIT STRING,
INTEGER, ENUMERATED, etc.). Data Elements use the DE_ prefix in J2735 as they
represent atomic/primitive types.

Example usage:
from tools.j2735_c_generator_data_element import generate_data_element
from tools.j2735_spec_parser import parse_spec_file

spec = parse_spec_file("J2735_202409_pdf_content.txt")

# BIT STRING types
code = generate_data_element("VehicleEventFlags", spec)
"""

from .j2735_c_generator_jinja import create_jinja_env, get_template
from .j2735_spec_constraints import BitStringConstraint
from .j2735_spec_parser import J2735Specification
from .j2735_spec_parser import ASN1TypeClass, ASN1TypeDefinition, J2735Specification

_TEMPLATE_NAME = "assemble_de.j2"
_BITSTRING_TEMPLATE_NAME = "assemble_de_bitstring.j2"


def generate_data_element(type_name: str, spec: J2735Specification) -> str:
"""Generate a complete C header file for a J2735 Data Element.
"""Generate complete C header file for a Data Element.

This is the main entry point for Data Element code generation. Dispatches
to the appropriate internal generator based on the ASN.1 type class.

Supported types:
- BIT STRING: Fixed-size bit fields with named bits

Args:
type_name: The ASN.1 type name (e.g., "VehicleEventFlags").
type_name: Name of the type (e.g., "VehicleEventFlags").
spec: The parsed J2735 specification.

Returns:
Complete C header file content as a string.

Raises:
TypeError: If the type is not a supported constraint type.
ValueError: If type_name is not found.
ValueError: If type is not a supported Data Element type.
"""
typedef = spec.lookup_type(type_name)
if typedef is None:
raise ValueError(f"Type '{type_name}' not found in specification")
if not isinstance(typedef.constraint, BitStringConstraint):

if typedef.type_class == ASN1TypeClass.BIT_STRING:
return _generate_bitstring(typedef)
raise ValueError(
f"Type '{type_name}' is {typedef.type_class.name}, which is not a supported "
f"Data Element type. Supported types: BIT_STRING"
)


def _generate_bitstring(typedef: ASN1TypeDefinition) -> str:
"""Generate C code for a BIT STRING type.

Internal helper for generate_data_element().

Args:
typedef: The ASN.1 type definition for the BIT STRING.

Returns:
Complete C header file content as a string.

Raises:
TypeError: If constraint is not BitStringConstraint.

Examples:
>>> from tools.tests.conftest import SPEC_FILE_PATH
>>> from tools.j2735_spec_parser import parse_spec_file
>>> spec = parse_spec_file(SPEC_FILE_PATH)
>>> typedef = spec.lookup_type("VehicleEventFlags")
>>> code = _generate_bitstring(typedef)
>>> "J2735_VEHICLE_EVENT_FLAGS_GET_EVENT_HAZARD_LIGHTS" in code
True
"""
# Type narrowing for mypy - validate constraint type
bitstring = typedef.constraint
if not isinstance(bitstring, BitStringConstraint):
raise TypeError(
f"generate_data_element currently only supports BitStringConstraint, "
f"got {type(typedef.constraint).__name__}"
f"Expected BitStringConstraint for BIT STRING type, got {type(bitstring).__name__}"
)

# Create Jinja environment and add pow() global for mask calculations
env = create_jinja_env()
template = get_template(env, _TEMPLATE_NAME)
template = get_template(create_jinja_env(), _BITSTRING_TEMPLATE_NAME)

return template.render(
data_type=typedef.type_class.name.lower(),
typedef=typedef,
)
return template.render(typedef=typedef)
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,86 @@
SPDX-FileCopyrightText: 2026 Yogev Neumann
-#}
{#-
Template: assemble_de.j2
Purpose: Generate C header file for a data element (DE).
Template: assemble_de_bitstring.j2
Purpose: Generate C header file for a BIT STRING data element (DE).

Template Context (from generator):
- typedef: The full typedef object containing the metadata
-#}
{%- set data_type = typedef.type_class.name | lower -%}
{%- set is_extensible = typedef.constraint.is_extensible -%}
{%- set named_bits = typedef.constraint.named_bits -%}
{%- set typedef_name = typedef.name -%}
{%- set typedef_name_upper = typedef_name | screaming_snake -%}
{#- Banner line width: 100 chars total, content area is 92 chars -#}
{%- set W = 92 -%}
{% include 'license.j2' %}
{% include 'file_header_de.j2' %}
{#- Compute derived values for the header -#}
{%- set ext_bits = typedef.constraint.ext_bits -%}
{%- set read_bits = typedef.constraint.read_bits -%}
{%- set root_size = typedef.constraint.root_size -%}
{%- set root_wire_size = 1 + root_size -%}
{%- set garbage_bits = read_bits - 1 - root_size -%}
{%- set ext_bit_pos = read_bits - 1 -%}
{#- Box column widths -#}
{%- set COL1 = 5 -%}
{%- set COL2_NONEXT = 52 -%}
{%- set COL2_EXT = 18 -%}
{%- set COL3_EXT = 31 -%}
{% include 'license.j2' %}
/**
* @file
* @author Yogev Neumann
* @brief J2735 {{ typedef_name }} Definition and Access Macros.
*
* {{ typedef_name }} ::= BIT STRING {
{% for name, index in typedef.constraint.named_bits.items() | sort(attribute='1') %}
* {{ name }} ({{ index }}){{ "," if not loop.last else "" }}
{% endfor %}
* } (SIZE ({{ root_size }}{{ ", ..." if is_extensible else "" }}{{ ", " ~ ext_bits if is_extensible and ext_bits != root_size else "" }}))
*
{% if is_extensible %}
* Extensible BIT STRING with root size {{ root_size }} and known extension size {{ ext_bits }}.
*
* Wire Format (non-extended, {{ root_wire_size }} bits total):
* ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_NONEXT + 2) }}┐
* │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_NONEXT, "Bits 1-" ~ root_size) }} │
* ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_NONEXT + 2) }}┤
* │ {{ "%-*s"|format(COL1, "Ext=0") }} │ {{ "%-*s"|format(COL2_NONEXT, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │
* └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_NONEXT + 2) }}┘
*
* Wire Format (extended, {{ read_bits }} bits total):
* ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_EXT + 2) }}┬{{ "─" * (COL3_EXT + 2) }}┐
* │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_EXT, "Bits 1-7") }} │ {{ "%-*s"|format(COL3_EXT, "Bits 8-" ~ (read_bits - 1)) }} │
* ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_EXT + 2) }}┼{{ "─" * (COL3_EXT + 2) }}┤
* │ {{ "%-*s"|format(COL1, "Ext=1") }} │ {{ "%-*s"|format(COL2_EXT, "nsnnwn=" ~ ext_bits ~ " (7 bits)") }} │ {{ "%-*s"|format(COL3_EXT, "flags[0.." ~ (ext_bits - 1) ~ "] (" ~ ext_bits ~ " bits)") }} │
* └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_EXT + 2) }}┴{{ "─" * (COL3_EXT + 2) }}┘
{% else %}
{%- set COL_FIXED = 60 %}
* Fixed BIT STRING with size {{ root_size }}.
*
* Wire Format ({{ root_wire_size }} bits total):
* ┌{{ "─" * (COL_FIXED + 2) }}┐
* │ {{ "%-*s"|format(COL_FIXED, "Bits 0-" ~ (root_size - 1)) }} │
* ├{{ "─" * (COL_FIXED + 2) }}┤
* │ {{ "%-*s"|format(COL_FIXED, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │
* └{{ "─" * (COL_FIXED + 2) }}┘
{% endif %}
*
* Optimization: Single-Read Strategy
* ──────────────────────────────────────────────────────────────────────────────────────────
* Max wire size = {{ read_bits }} bits ≤ 56-bit READ_BITS limit.
* We read all {{ read_bits }} bits in ONE call, then use bit arithmetic to extract:
* - Extension bit at position {{ ext_bit_pos }} (MSB of {{ read_bits }}-bit value)
* - Flags at positions 0-{{ ext_bits - 1 }} (extended) or shifted for non-extended
*
* {{ read_bits }}-bit read layout (left-justified from bit 0):
* Non-extended: [Ext=0][F0..F{{ root_size - 1 }}][{{ garbage_bits }} garbage bits]
* bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ garbage_bits }} {{ garbage_bits - 1 }}..0
* Extended: [Ext=1][nsnnwn:7][F0..F{{ ext_bits - 1 }}]
* bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ ext_bits }} {{ ext_bits - 1 }}..0
*
* @todo Update the Doxygen to indicate [in] and [out] parameters
*/
#ifndef J2735_INTERNAL_DE_{{ typedef_name | upper }}_H
#define J2735_INTERNAL_DE_{{ typedef_name | upper }}_H

Expand Down
92 changes: 0 additions & 92 deletions tools/templates/file_header_de.j2

This file was deleted.

12 changes: 0 additions & 12 deletions tools/tests/c_generator/test_bitstring_internal_bit_pos.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,3 @@ def test_small_type_lane_direction(self) -> None:
# Should have bits 0 and 1
self.assertIn("0U", code)
self.assertIn("1U", code)

def test_invalid_type_raises_value_error(self) -> None:
"""Non-existent type should raise ValueError."""
with self.assertRaises(ValueError) as ctx:
_ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType")
self.assertIn("not found", str(ctx.exception))

def test_non_bitstring_type_raises_value_error(self) -> None:
"""INTEGER type should raise ValueError."""
with self.assertRaises(ValueError) as ctx:
_ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount")
self.assertIn("not a BIT STRING", str(ctx.exception))
53 changes: 53 additions & 0 deletions tools/tests/c_generator/test_bitstring_internal_ext_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2026 Yogev Neumann
#
# 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.
#
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Yogev Neumann
"""Tests for BIT STRING internal extended size constants generator.

Tests cover bitstring_internal_ext_size.j2 which generates
J2735_INTERNAL_EXT_SIZE_{TYPE} constants for extended bit count.
"""

from tools.tests.conftest import SpecLoadingTestBase, generate_bitstring_code

_TEMPLATE_NAME = "bitstring/bitstring_internal_ext_size.j2"


class TestExtSizeGenerator(SpecLoadingTestBase):
"""Tests for bitstring_internal_ext_size template."""

def test_extensible_type_has_ext_size_14(self) -> None:
"""VehicleEventFlags should have ext_bits=14."""
code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "VehicleEventFlags")
self.assertIn("J2735_INTERNAL_EXT_SIZE_VEHICLE_EVENT_FLAGS", code)
self.assertIn("14U", code)

def test_non_extensible_8bit_type(self) -> None:
"""GNSSstatus (8-bit) should have ext_bits=8 (same as root)."""
code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "GNSSstatus")
self.assertIn("J2735_INTERNAL_EXT_SIZE_GNSS_STATUS", code)
self.assertIn("8U", code)

def test_non_extensible_12bit_type(self) -> None:
"""AllowedManeuvers (12-bit) should have ext_bits=12."""
code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "AllowedManeuvers")
self.assertIn("J2735_INTERNAL_EXT_SIZE_ALLOWED_MANEUVERS", code)
self.assertIn("12U", code)

def test_small_2bit_type(self) -> None:
"""LaneDirection (2-bit) should have ext_bits=2."""
code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection")
self.assertIn("J2735_INTERNAL_EXT_SIZE_LANE_DIRECTION", code)
self.assertIn("2U", code)
Loading