Skip to content

Commit cec1d69

Browse files
committed
add optional metadata generation for embedded
1 parent 7d13889 commit cec1d69

11 files changed

Lines changed: 1960 additions & 6 deletions

avlos/generators/filters.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22
from copy import copy
33
from typing import List
44

5+
from avlos.datatypes import DataType
6+
7+
# Avlos_Dtype enum names for metadata (reduced set for UART/ASCII parsing)
8+
_AVLOS_DTYPE_MAP = {
9+
DataType.VOID: "AVLOS_DTYPE_VOID",
10+
DataType.BOOL: "AVLOS_DTYPE_BOOL",
11+
DataType.UINT8: "AVLOS_DTYPE_UINT8",
12+
DataType.INT8: "AVLOS_DTYPE_UINT8",
13+
DataType.UINT16: "AVLOS_DTYPE_UINT32",
14+
DataType.INT16: "AVLOS_DTYPE_INT32",
15+
DataType.UINT32: "AVLOS_DTYPE_UINT32",
16+
DataType.INT32: "AVLOS_DTYPE_INT32",
17+
DataType.UINT64: "AVLOS_DTYPE_UINT32",
18+
DataType.INT64: "AVLOS_DTYPE_INT32",
19+
DataType.FLOAT: "AVLOS_DTYPE_FLOAT",
20+
DataType.DOUBLE: "AVLOS_DTYPE_FLOAT",
21+
DataType.STR: "AVLOS_DTYPE_STRING",
22+
}
23+
524

625
def avlos_endpoints(input) -> List:
726
"""
@@ -101,3 +120,50 @@ def capitalize_first(input: str) -> str:
101120
String with first character capitalized
102121
"""
103122
return input[0].upper() + input[1:]
123+
124+
125+
def avlos_ep_kind(ep) -> str:
126+
"""
127+
Return the Avlos_EndpointKind enum name for an endpoint (for metadata generation).
128+
129+
Args:
130+
ep: Endpoint object (RemoteAttribute, RemoteFunction, RemoteEnum, or RemoteBitmask)
131+
132+
Returns:
133+
String like AVLOS_EP_KIND_READ_ONLY, AVLOS_EP_KIND_CALL_WITH_ARGS, etc.
134+
"""
135+
has_getter = getattr(ep, "getter_name", None) is not None
136+
has_setter = getattr(ep, "setter_name", None) is not None
137+
has_caller = getattr(ep, "caller_name", None) is not None
138+
if has_caller:
139+
num_args = len(getattr(ep, "arguments", None) or [])
140+
if num_args == 0:
141+
return "AVLOS_EP_KIND_CALL_NO_ARGS"
142+
return "AVLOS_EP_KIND_CALL_WITH_ARGS"
143+
if has_getter and has_setter:
144+
return "AVLOS_EP_KIND_READ_WRITE"
145+
if has_getter:
146+
return "AVLOS_EP_KIND_READ_ONLY"
147+
if has_setter:
148+
return "AVLOS_EP_KIND_WRITE_ONLY"
149+
return "AVLOS_EP_KIND_READ_ONLY" # fallback
150+
151+
152+
def avlos_metadata_dtype(value) -> str:
153+
"""
154+
Map a DataType or an object with .dtype (endpoint or argument) to Avlos_Dtype enum name.
155+
156+
Used for value_dtype and arg_dtypes in endpoint metadata. Narrowing (e.g. 64-bit to
157+
32-bit) is applied where the metadata enum set is smaller than DataType.
158+
159+
Args:
160+
value: Either a DataType enum member or an object with a .dtype attribute (endpoint, argument)
161+
162+
Returns:
163+
String like AVLOS_DTYPE_UINT32, AVLOS_DTYPE_FLOAT, etc.
164+
"""
165+
dtype = getattr(value, "dtype", value)
166+
try:
167+
return _AVLOS_DTYPE_MAP[dtype]
168+
except KeyError:
169+
return "AVLOS_DTYPE_UINT32" # safe fallback

avlos/generators/generator_c.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,60 @@
44
from jinja2 import Environment, PackageLoader, select_autoescape
55

66
from avlos.formatting import format_c_code, is_clang_format_available
7-
from avlos.generators.filters import as_include, avlos_bitmask_eps, avlos_endpoints, avlos_enum_eps
7+
from avlos.generators.filters import (
8+
as_include,
9+
avlos_bitmask_eps,
10+
avlos_endpoints,
11+
avlos_enum_eps,
12+
avlos_ep_kind,
13+
avlos_metadata_dtype,
14+
)
815
from avlos.validation import ValidationError, validate_all
916

1017
env = Environment(loader=PackageLoader("avlos"), autoescape=select_autoescape())
1118

1219

20+
def _generate_metadata_if_requested(instance, config):
21+
"""Generate endpoint metadata .h/.c when both paths are in config. Returns extra paths or []."""
22+
paths = config["paths"]
23+
if "output_metadata_header" not in paths or "output_metadata_impl" not in paths:
24+
return []
25+
meta_header_path = paths["output_metadata_header"]
26+
meta_impl_path = paths["output_metadata_impl"]
27+
metadata_header_basename = os.path.basename(meta_header_path)
28+
os.makedirs(os.path.dirname(meta_header_path), exist_ok=True)
29+
template_meta_h = env.get_template("avlos_endpoint_metadata.h.jinja")
30+
with open(meta_header_path, "w") as f:
31+
print(template_meta_h.render(), file=f)
32+
os.makedirs(os.path.dirname(meta_impl_path), exist_ok=True)
33+
template_meta_c = env.get_template("avlos_endpoint_metadata.c.jinja")
34+
with open(meta_impl_path, "w") as f:
35+
print(
36+
template_meta_c.render(
37+
instance=instance,
38+
metadata_header_basename=metadata_header_basename,
39+
),
40+
file=f,
41+
)
42+
return [meta_header_path, meta_impl_path]
43+
44+
1345
def process(instance, config):
46+
# Validate config has required paths
47+
required_paths = ["output_enums", "output_header", "output_impl"]
48+
if "paths" not in config:
49+
raise ValidationError(
50+
"Config validation failed: Missing 'paths' section in avlos config.\n"
51+
"Please add a 'paths' section with: output_enums, output_header, output_impl"
52+
)
53+
54+
missing_paths = [p for p in required_paths if p not in config["paths"]]
55+
if missing_paths:
56+
raise ValidationError(
57+
f"Config validation failed: Missing required paths in avlos config: {', '.join(missing_paths)}\n"
58+
f"Please add these paths to the 'paths' section of your avlos config file."
59+
)
60+
1461
# Validate before generation
1562
validation_errors = validate_all(instance)
1663
if validation_errors:
@@ -21,6 +68,8 @@ def process(instance, config):
2168
env.filters["enum_eps"] = avlos_enum_eps
2269
env.filters["bitmask_eps"] = avlos_bitmask_eps
2370
env.filters["as_include"] = as_include
71+
env.filters["avlos_ep_kind"] = avlos_ep_kind
72+
env.filters["avlos_metadata_dtype"] = avlos_metadata_dtype
2473

2574
template = env.get_template("tm_enums.h.jinja")
2675
os.makedirs(os.path.dirname(config["paths"]["output_enums"]), exist_ok=True)
@@ -54,17 +103,16 @@ def process(instance, config):
54103
file=output_file,
55104
)
56105

57-
# Post-process with clang-format if available
58-
format_style = config.get("format_style", "LLVM")
59-
60-
generated_files = [
106+
base_files = [
61107
config["paths"]["output_enums"],
62108
config["paths"]["output_header"],
63109
config["paths"]["output_impl"],
64110
]
111+
generated_files = base_files + _generate_metadata_if_requested(instance, config)
65112

113+
# Post-process with clang-format if available
114+
format_style = config.get("format_style", "LLVM")
66115
for file_path in generated_files:
67116
success = format_c_code(file_path, format_style)
68117
if not success and is_clang_format_available():
69-
# Only warn if clang-format is installed but failed
70118
print(f"Warning: clang-format failed for {file_path}", file=sys.stderr)

avlos/generators/generator_cpp.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212

1313

1414
def process(instance, config):
15+
# Validate config has required paths
16+
required_paths = ["output_helpers", "output_header", "output_impl"]
17+
if "paths" not in config:
18+
raise ValidationError(
19+
"Config validation failed: Missing 'paths' section in avlos config.\n"
20+
"Please add a 'paths' section with: output_helpers, output_header, output_impl"
21+
)
22+
23+
missing_paths = [p for p in required_paths if p not in config["paths"]]
24+
if missing_paths:
25+
raise ValidationError(
26+
f"Config validation failed: Missing required paths in avlos config: {', '.join(missing_paths)}\n"
27+
f"Please add these paths to the 'paths' section of your avlos config file."
28+
)
29+
1530
# Validate before generation
1631
validation_errors = validate_all(instance)
1732
if validation_errors:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This file was automatically generated using Avlos.
3+
* https://github.com/tinymovr/avlos
4+
*
5+
* Any changes to this file will be overwritten when
6+
* content is regenerated.
7+
*/
8+
9+
#include "{{ metadata_header_basename }}"
10+
11+
{% set eps = instance | endpoints %}
12+
const Avlos_EndpointMeta avlos_endpoint_meta[] = {
13+
{% for ep in eps %}
14+
/* {{ ep.endpoint_function_name }} */
15+
[{{ ep.ep_id }}] = {
16+
.kind = {{ ep | avlos_ep_kind }},
17+
.value_dtype = {{ ep | avlos_metadata_dtype }},
18+
.num_args = {% if ep | avlos_ep_kind == "AVLOS_EP_KIND_CALL_WITH_ARGS" %}{{ ep.arguments | length }}{% else %}0{% endif %},
19+
.arg_dtypes = {
20+
{%- for i in range(4) %}
21+
{%- if ep | avlos_ep_kind == "AVLOS_EP_KIND_CALL_WITH_ARGS" and i < (ep.arguments | length) %}
22+
{{ ep.arguments[i] | avlos_metadata_dtype }}
23+
{%- else %}
24+
AVLOS_DTYPE_VOID
25+
{%- endif %}
26+
{%- if i < 3 %}, {% endif %}
27+
{%- endfor %}
28+
}
29+
}{% if not loop.last %},{% endif %}
30+
31+
{% endfor %}
32+
};
33+
34+
const uint8_t avlos_endpoint_meta_count = sizeof(avlos_endpoint_meta) / sizeof(avlos_endpoint_meta[0]);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* This file was automatically generated using Avlos.
3+
* https://github.com/tinymovr/avlos
4+
*
5+
* Any changes to this file will be overwritten when
6+
* content is regenerated.
7+
*
8+
* Endpoint type metadata for type-aware UART/ASCII parsing and formatting.
9+
* No dependency on UART; consumed by the firmware layer.
10+
*/
11+
12+
#pragma once
13+
#include <stdint.h>
14+
15+
typedef enum {
16+
AVLOS_DTYPE_UINT8,
17+
AVLOS_DTYPE_UINT32,
18+
AVLOS_DTYPE_INT32,
19+
AVLOS_DTYPE_FLOAT,
20+
AVLOS_DTYPE_BOOL,
21+
AVLOS_DTYPE_STRING,
22+
AVLOS_DTYPE_VOID
23+
} Avlos_Dtype;
24+
25+
typedef enum {
26+
AVLOS_EP_KIND_READ_ONLY,
27+
AVLOS_EP_KIND_WRITE_ONLY,
28+
AVLOS_EP_KIND_READ_WRITE,
29+
AVLOS_EP_KIND_CALL_NO_ARGS,
30+
AVLOS_EP_KIND_CALL_WITH_ARGS
31+
} Avlos_EndpointKind;
32+
33+
#define AVLOS_MAX_CALL_ARGS 4
34+
35+
typedef struct {
36+
Avlos_EndpointKind kind;
37+
Avlos_Dtype value_dtype;
38+
uint8_t num_args;
39+
Avlos_Dtype arg_dtypes[AVLOS_MAX_CALL_ARGS];
40+
} Avlos_EndpointMeta;
41+
42+
extern const Avlos_EndpointMeta avlos_endpoint_meta[];
43+
extern const uint8_t avlos_endpoint_meta_count;

docs/config.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ The output config defines the output modules that will be used and their options
7474
7575
There are three main generated files that are configured above: A header containing enums (`output_enums`), a header containing function declarations (`output_header`), and an implementation containing function definitions (`output_impl`).
7676

77+
Optional: Endpoint metadata (for type-aware UART/ASCII parsing)
78+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
79+
80+
If you want the C generator to emit an endpoint type metadata table (so firmware can parse and format ASCII without a hand-maintained table), add both of these optional paths under ``paths``:
81+
82+
- ``output_metadata_header``: Path for the generated metadata header (e.g. ``fw/avlos_endpoint_metadata.h``).
83+
- ``output_metadata_impl``: Path for the generated metadata implementation (e.g. ``fw/avlos_endpoint_metadata.c``).
84+
85+
When both are present, Avlos generates ``Avlos_EndpointMeta avlos_endpoint_meta[]`` and ``avlos_endpoint_meta_count`` in the same order as ``avlos_endpoints[]``. Each entry describes the endpoint kind (read-only, read-write, call with/without args), value type, and for callables the argument types. If either path is omitted, no metadata files are generated (backward compatible).
86+
7787
Of note is that no #include statements for the generated files are generated automatically. This is something that we decided in order to maximize compatibility to edge cases, but may be revised in future Avlos versions.
7888

7989
CLI Usage

example/avlos_config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ generators:
44
paths:
55
output_header: fw/protocol.h
66
output_impl: fw/protocol.c
7+
# Optional: emit endpoint type metadata for type-aware UART/ASCII (uncomment to enable)
8+
# output_metadata_header: fw/avlos_endpoint_metadata.h
9+
# output_metadata_impl: fw/avlos_endpoint_metadata.c
710
header_includes:
811
- header.h
912
impl_includes:

0 commit comments

Comments
 (0)