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
8 changes: 5 additions & 3 deletions bakelite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from pkg_resources import DistributionNotFound, get_distribution
"""Bakelite - Protocol code generator for embedded systems."""

from importlib.metadata import PackageNotFoundError, version

try:
__version__ = get_distribution("bakelite").version
except DistributionNotFound:
__version__ = version("bakelite")
except PackageNotFoundError:
__version__ = "(local)"
2 changes: 2 additions & 0 deletions bakelite/generator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
"""Bakelite protocol code generator."""

from .parser import *
from .types import *
17 changes: 7 additions & 10 deletions bakelite/generator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,19 @@ def cli() -> None:
@click.option("--input", "-i", required=True)
@click.option("--output", "-o", required=True)
def gen(language: str, input: str, output: str) -> None:
render_func = None
with open(input, "r", encoding="utf-8") as f:
proto = f.read()

proto_def = parse(proto)

if language == "python":
render_func = python.render
generated_file = python.render(*proto_def)
elif language == "cpptiny":
render_func = cpptiny.render
generated_file = cpptiny.render(*proto_def)
else:
print(f"Unkown language: {language}")
print(f"Unknown language: {language}")
sys.exit(1)

with open(input, "r", encoding="utf-8") as f:
proto = f.read()

proto_def = parse(proto)
generated_file = render_func(*proto_def)

with open(output, "w", encoding="utf-8") as f:
f.write(generated_file)

Expand Down
65 changes: 32 additions & 33 deletions bakelite/generator/cpptiny.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""C++ (tiny) code generator for bakelite protocols."""

import os
from copy import copy
from typing import List

from jinja2 import Environment, PackageLoader

from .types import *
from .types import Protocol, ProtoEnum, ProtoStruct, ProtoStructMember, ProtoType

env = Environment(
loader=PackageLoader("bakelite.generator", "templates"),
Expand All @@ -17,7 +18,7 @@

template = env.get_template("cpptiny.h.j2")

prim_types = {
PRIMITIVE_TYPE_MAP = {
"bool": "bool",
"int8": "int8_t",
"int16": "int16_t",
Expand All @@ -35,26 +36,21 @@


def _map_type(t: ProtoType) -> str:
if t.name in prim_types:
type_name = prim_types[t.name]
else:
type_name = t.name

return type_name
return PRIMITIVE_TYPE_MAP.get(t.name, t.name)


def _map_type_member(member: ProtoStructMember) -> str:
type_name = _map_type(member.type)

if member.type.name == "bytes" and not member.type.size and member.arraySize == 0:
if member.type.name == "bytes" and not member.type.size and member.array_size == 0:
return f"Bakelite::SizedArray<Bakelite::SizedArray<{type_name}> >"
if member.type.name == "bytes" and not member.type.size:
return f"Bakelite::SizedArray<{type_name}>"
if member.type.name == "string" and (not member.type.size) and member.arraySize == 0:
if member.type.name == "string" and (not member.type.size) and member.array_size == 0:
return f"Bakelite::SizedArray<{type_name}*>"
if member.type.name == "string" and (not member.type.size):
return f"{type_name}*"
if member.arraySize == 0:
if member.array_size == 0:
return f"Bakelite::SizedArray<{type_name}>"
return type_name

Expand All @@ -68,31 +64,32 @@ def _size_postfix(member: ProtoStructMember) -> str:


def _array_postfix(member: ProtoStructMember) -> str:
if member.arraySize is None or member.arraySize == 0:
if member.array_size is None or member.array_size == 0:
return ""
return f"[{member.arraySize}]"
return f"[{member.array_size}]"


def overhead(size: int, crc_size: int) -> int:
"""Calculate COBS overhead for a message size."""
cobs_overhead = int((size + 253) / 254)
return cobs_overhead + crc_size + 1


def render(
enums: List[ProtoEnum],
structs: List[ProtoStruct],
proto: Optional[Protocol],
comments: List[str],
enums: list[ProtoEnum],
structs: list[ProtoStruct],
proto: Protocol | None,
comments: list[str],
) -> str:

"""Render a protocol definition to C++ source code."""
enums_types = {enum.name: enum for enum in enums}
structs_types = {struct.name: struct for struct in structs}

def _write_type(member: ProtoStructMember) -> str:
if member.arraySize is not None:
size_arg = f", {member.arraySize}" if member.arraySize > 0 else ""
if member.array_size is not None:
size_arg = f", {member.array_size}" if member.array_size > 0 else ""
tmp_member = copy(member)
tmp_member.arraySize = None
tmp_member.array_size = None
tmp_member.name = "val"
return f"""writeArray(stream, {member.name}{size_arg}, [](T &stream, const auto &val) {{
return {_write_type(tmp_member)}
Expand All @@ -102,7 +99,7 @@ def _write_type(member: ProtoStructMember) -> str:
return f"write(stream, ({underlying_type}){member.name});"
if member.type.name in structs_types:
return f"{member.name}.pack(stream);"
if member.type.name in prim_types and member.type.name not in {"bytes", "string"}:
if member.type.name in PRIMITIVE_TYPE_MAP and member.type.name not in {"bytes", "string"}:
return f"write(stream, {member.name});"
if member.type.name == "bytes":
if member.type.size:
Expand All @@ -112,13 +109,13 @@ def _write_type(member: ProtoStructMember) -> str:
if member.type.size:
return f"writeString(stream, {member.name}, {member.type.size});"
return f"writeString(stream, {member.name});"
raise RuntimeError(f"Unkown type {member.type.name}")
raise RuntimeError(f"Unknown type {member.type.name}")

def _read_type(member: ProtoStructMember) -> str:
if member.arraySize is not None:
size_arg = f", {member.arraySize}" if member.arraySize > 0 else ""
if member.array_size is not None:
size_arg = f", {member.array_size}" if member.array_size > 0 else ""
tmp_member = copy(member)
tmp_member.arraySize = None
tmp_member.array_size = None
tmp_member.name = "val"
return f"""readArray(stream, {member.name}{size_arg}, [](T &stream, auto &val) {{
return {_read_type(tmp_member)}
Expand All @@ -128,7 +125,7 @@ def _read_type(member: ProtoStructMember) -> str:
return f"read(stream, ({underlying_type}&){member.name});"
if member.type.name in structs_types:
return f"{member.name}.unpack(stream);"
if member.type.name in prim_types and member.type.name not in {"bytes", "string"}:
if member.type.name in PRIMITIVE_TYPE_MAP and member.type.name not in {"bytes", "string"}:
return f"read(stream, {member.name});"
if member.type.name == "bytes":
if member.type.size:
Expand All @@ -138,17 +135,17 @@ def _read_type(member: ProtoStructMember) -> str:
if member.type.size:
return f"readString(stream, {member.name}, {member.type.size});"
return f"readString(stream, {member.name});"
raise RuntimeError(f"Unkown type {member.type.name}")
raise RuntimeError(f"Unknown type {member.type.name}")

message_ids = []
message_ids: list[tuple[str, int]] = []
framer = ""

if proto is not None:
message_ids = [(msg.name, msg.number) for msg in proto.message_ids]
options = {option.name: option.value for option in proto.options}
crc = options.get("crc", "none").lower()
framing = options.get("framing", "").lower()
max_length = options.get("maxLength", None)
max_length = options.get("maxLength")

if framing == "":
raise RuntimeError("A frame type must be specified")
Expand All @@ -169,15 +166,15 @@ def _read_type(member: ProtoStructMember) -> str:
crc_type = "Crc32"
crc_size = 4
else:
raise RuntimeError(f"Unkown CRC type {crc}")
raise RuntimeError(f"Unknown CRC type {crc}")

max_length = int(max_length)
max_length += overhead(int(max_length), crc_size)

if framing == "cobs":
framer = f"Bakelite::CobsFramer<Bakelite::{crc_type}, {max_length}>"
else:
raise RuntimeError(f"Unkown CRC type {crc}")
raise RuntimeError(f"Unknown framing type {framing}")

return template.render(
enums=enums,
Expand All @@ -196,6 +193,8 @@ def _read_type(member: ProtoStructMember) -> str:


def runtime() -> str:
"""Generate the C++ runtime support code."""

def include(filename: str) -> str:
with open(
os.path.join(os.path.dirname(__file__), "runtimes", "cpptiny", filename),
Expand Down
Loading
Loading