a2l_parse/lib.rs
1//! ASAP2/A2L ECU description parser — PROJECT/MODULE/CHARACTERISTIC/
2//! MEASUREMENT/RECORD_LAYOUT/COMPU_METHOD, calibration data model.
3//!
4//! `a2l-parse` is the estate's **L1 substrate** for the description half
5//! of the calibration stack: it turns A2L (ASAM MCD-2 MC / ASAP2) ECU
6//! description files into a typed data model that pairs with `xcp-core`'s
7//! memory access (UPLOAD/DOWNLOAD to the addresses found here).
8//!
9//! # Skip-tolerant, total-ish parsing
10//!
11//! The grammar is keyword-driven and block-structured (`/begin NAME …
12//! /end NAME`). The parser consumes the required positional parameters of
13//! the six core blocks, captures the optional keywords the model needs
14//! (`ECU_ADDRESS`, `FNC_VALUES`, `COEFFS`, `COEFFS_LINEAR`,
15//! `COMPU_TAB_REF`), and **skips** everything else — unknown optional
16//! keywords, unknown `/begin` sub-blocks ([`AXIS_DESCR`-style vendor
17//! extensions]), `IF_DATA` bodies with braces. Real-world files with
18//! vendor extensions parse as long as the core blocks are intact. Malformed
19//! input resolves to a typed [`A2lError`] with a 1-based line number —
20//! never a panic (deny-level lints on the lib target, a cargo-fuzz target,
21//! miri).
22//!
23//! # Example
24//!
25//! ```
26//! use a2l_parse::{A2lProject, CharType, CompuMethod, ConversionType, DataType};
27//!
28//! let a2l = r#"
29//! /begin PROJECT demo "Demonstration project"
30//! /begin MODULE engine "V8 ECU"
31//! /begin CHARACTERISTIC eng_load "Engine load" VALUE 0x720000
32//! RL_VALUE 0 rack_lin 0.0 100.0
33//! /end CHARACTERISTIC
34//! /begin MEASUREMENT engine_speed "Engine speed" UWORD
35//! engine_speed_c 0.01 0.0 0.0 8000.0
36//! ECU_ADDRESS 0x320001
37//! /end MEASUREMENT
38//! /begin COMPU_METHOD rack_lin "Rack position" RAT_FUNC
39//! "%.4f" "%" COEFFS 0 0.00390625 0 0 0 1
40//! /end COMPU_METHOD
41//! /end MODULE
42//! /end PROJECT
43//! "#;
44//! let project = A2lProject::parse(a2l)?;
45//!
46//! assert_eq!(project.project, "demo");
47//! assert_eq!(project.modules.len(), 1);
48//!
49//! let module = project.module_by_name("engine").unwrap();
50//! let kfile = module.characteristic_by_name("eng_load").unwrap();
51//! assert_eq!(kfile.r#type, CharType::Value);
52//! assert_eq!(kfile.address, 0x720000);
53//! assert_eq!(kfile.deposit, "RL_VALUE");
54//! assert_eq!(kfile.lower_limit, 0.0);
55//! assert_eq!(kfile.upper_limit, 100.0);
56//!
57//! let meas = module.measurement_by_name("engine_speed").unwrap();
58//! assert_eq!(meas.datatype, DataType::Uword);
59//! assert_eq!(meas.address, 0x320001);
60//!
61//! // Raw → physical conversion of the RAT_FUNC method:
62//! let cm = module.compu_method_by_name("rack_lin").unwrap();
63//! assert_eq!(cm.conversion_type, ConversionType::RatFunc);
64//! let lin = cm.to_linear().unwrap();
65//! assert_eq!(lin.apply(256.0), 1.0);
66//! # Ok::<(), a2l_parse::A2lError>(())
67//! ```
68//!
69//! # `no_std`
70//!
71//! The crate is `no_std` + `alloc` with **zero runtime dependencies** —
72//! the scanner, parser, and error type are hand-rolled over `core` +
73//! `alloc`.
74
75#![no_std]
76extern crate alloc;
77
78mod error;
79mod lexer;
80mod model;
81mod parser;
82
83pub use error::A2lError;
84pub use model::{
85 A2lModule, A2lProject, CharType, Characteristic, Coeffs, CompuMethod, ConversionType, DataType,
86 FncValues, LinearFn, Measurement, RecordLayout,
87};