The Error Codes Generator defines structured module/submodule/error triplets in a YAML file and automatically generates a C header and string table. It can also emit JSON/CSV for downstream docs/tests, and builds a static library plus small utilities to inspect and use the codes directly.
- Manual or automatic ID assignment: modules and submodules may specify explicit IDs or be auto-allocated; error indices are assigned in declaration order per scope.
- Binary-search lookup:
ec_strerror()is O(log N) over a sorted table. - Deterministic encoding: identical YAML → identical numeric layout and header.
- Configurable bit layout (with padding): from YAML via top-level
configuration. Ifconfigurationis present, all fields must be provided:module,submodule,error_id,padding. Supported totals: 8, 16, 32. - Strict YAML validation: duplicate keys, overlapping IDs, invalid identifiers/widths are rejected.
- Introspection enum: generator emits an
enumwith symbolic names and negative decimal constants for debugger-friendly casting. - JSON/CSV emission: optional
--jsonand--csvoutputs with stable schema for specs, docs, and tests. - Success block: optional
successmapping to rename and describe the zero code (E_SUCCESSby default). - Reserved value:
(error_code_t)-1is unusable and excluded byEC_IS_VALID/EC_IS_ERROR.
make
Steps:
- Parse
error_codes.yml→ Generatebuild/inc/error_codes.handbuild/src/error_codes_str.c - Compile and archive into
build/lib/liberrorcodes.a - Link:
build/bin/examplebuild/bin/dbg_print_all_errors
- Copy public artifacts to
dist/
dist/
error_codes.h
liberrorcodes.a
gcc -Idist -Ldist your_app.c -o your_app -lerrorcodes
Example:
#include <stdio.h>
#include <inttypes.h>
#include "error_codes.h"
int main(void) {
error_code_t ec = E_READ_DEV;
printf("Error: 0x%04" PRIx16 " (%" PRId16 "): %s\n", (uint16_t)ec, ec, ec_strerror(ec));
return 0;
}Top-level:
configuration: # Optional; if present, all four fields are required
module: <int > 0>
submodule: <int > 0>
error_id: <int > 0>
padding: <int ≥ 0> # MSB padding; increases total width, does not affect M|S|E
success: # Optional
name: E_SUCCESS # Default: E_SUCCESS
description: "Success" # Default: "Success"
modules:
<ModuleName>:
id: <int optional>
errors:
<ERROR_NAME>: "<Description>"
...
submodules:
<SubmoduleName>:
id: <int optional, != 0>
errors:
<ERROR_NAME>: "<Description>"
...Constraints:
- If
configurationis present, it must define all four fields; otherwise built-in defaults are used. module + submodule + error_id + padding ∈ {8,16,32}.- Keys must be unique at every level (duplicate YAML keys are rejected).
- Error names must match
^[A-Z][A-Z0-9_]*$. - Submodule ID
0is reserved for module-level errors. - IDs must fit within their configured field width.
EC_BITS_* macros in the generated header:
EC_BITS_MODULE
EC_BITS_SUBMODULE
EC_BITS_ERROR
EC_BITS_PADDING
EC_BITS_TOTAL
ERROR_CODE_BITSIZE == EC_BITS_TOTAL, selecting:
- 8 →
int8_t - 16 →
int16_t - 32 →
int32_t
padding occupies the most significant bits and is ignored by the M|S|E grouping and extraction macros.
Examples:
configuration:
module: 5
submodule: 5
error_id: 6
padding: 0# 32-bit layout
configuration:
module: 8
submodule: 8
error_id: 16
padding: 0modules:
Power:
id: 1
errors:
E_START_FAIL: "Power failed to start"
E_SHUTDOWN: "Power failed to shut down"
submodules:
Sensor:
id: 3
errors:
E_TIMEOUT: "Sensor timeout"
Actuator: # automatic; next available ID
errors:
E_RANGE: "Actuator range exceeded"Rules:
- Explicit IDs preserved verbatim; missing IDs auto-assigned to the lowest unused integer (modules from 0; submodules from 1).
- Error indices start at 0 in each
(module, submodule)scope, in declaration order. - Violations (duplicates, out-of-range) are hard errors.
success lets you rename or describe the zero code:
success:
name: E_SUCCESS
description: "Success"If omitted: E_SUCCESS with description "Success" is generated.
(error_code_t)-1 is reserved and never emitted. Macros:
EC_RESERVED_NEG1
EC_IS_VALID(code) // true for 0 and all negative codes except -1
EC_IS_OK(code) // code == 0
EC_IS_ERROR(code) // negative codes except -1
The header exposes a debugger-friendly enum with negative decimal values:
enum ec_error_code_sym {
E_SUCCESS = 0,
/* ... */
/* E_OPEN_DEV = -2048, etc. */
};Public API:
typedef /* int8_t|int16_t|int32_t */ error_code_t;
struct error_desc {
error_code_t code;
const char *description;
};
const char *ec_strerror(error_code_t code);
extern const struct error_desc error_desc_array[];
extern const size_t error_desc_count;Extraction and iteration helpers:
EC_EXTRACT_MODULE(code)
EC_EXTRACT_SUBMODULE(code)
EC_EXTRACT_ERROR(code)
EC_FOR_EACH_ERROR(ptr)
EC_FOR_EACH_ERROR_REVERSE(ptr)
EC_FOR_EACH_ERROR_IDX(i)
EC_FOR_EACH_ERROR_IDX_REVERSE(i)
EC_ERROR_AT(i)
parser.py supports additional artifacts:
parser.py error_codes.yml \
--header build/inc/error_codes.h \
--cfile build/src/error_codes_str.c \
--json build/error_codes.json \
--csv build/error_codes.csv
--headerPath to generated header (C).--cfilePath to generated string table (C).--jsonOptional path; emits a UTF-8 JSON document with stable key order and a trailing newline.--csvOptional path; emits CSV with stable columns.
{
"configuration": {
"module": 5,
"submodule": 5,
"error_id": 6,
"padding": 0,
"total": 16
},
"error_codes": [
{
"name": "E_OPEN_DEV",
"code": -2048,
"module": 1,
"submodule": 0,
"error": 0,
"description": "Error opening device"
}
]
}- Encoding: UTF-8 (
ensure_ascii=false) - Pretty:
indent=2 - Stable:
sort_keys=true - Strict numbers:
allow_nan=false
name,code,module,submodule,error,description
E_OPEN_DEV,-2048,1,0,0,"Error opening device"
IDX CODE(dec) CODE(hex) CODE(bin M|S|E) MODULE SUBMODULE ERROR DESCRIPTION
----------------------------------------------------------------------------------------------------
0 -2048 0xf800 00001|00000|000000 1 0 0 Error opening device
1 -2049 0xf7ff 00001|00000|000001 1 0 1 Error closing device
2 -2112 0xf7c0 00001|00001|000000 1 1 0 Error foo
3 -2113 0xf7bf 00001|00001|000001 1 1 1 Error bar
...
make clean # remove build/
make distclean # remove build/ and dist/
MIT — see LICENSE.