mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 20:30:35 +00:00
Replace evalexpr (#3860)
This commit is contained in:
parent
07214ef80d
commit
87febcbb4f
@ -22,7 +22,7 @@ document-features = "0.2.11"
|
||||
# used by the `build` and `tui` feature
|
||||
serde = { version = "1.0.197", default-features = false, features = ["derive"], optional = true }
|
||||
serde_yaml = { version = "0.9", optional = true }
|
||||
evalexpr = { version = "12.0.2", optional = true }
|
||||
somni-expr = { version = "0.1.0", optional = true }
|
||||
esp-metadata = { version = "0.8.0", path = "../esp-metadata", features = ["clap"], optional = true }
|
||||
esp-metadata-generated = { version = "0.1.0", path = "../esp-metadata-generated", features = ["build-script"], optional = true }
|
||||
|
||||
@ -42,7 +42,7 @@ pretty_assertions = "1.4.1"
|
||||
|
||||
[features]
|
||||
## Enable the generation and parsing of a config
|
||||
build = ["dep:serde", "dep:serde_yaml", "dep:evalexpr", "dep:esp-metadata-generated"]
|
||||
build = ["dep:serde", "dep:serde_yaml", "dep:somni-expr", "dep:esp-metadata-generated"]
|
||||
|
||||
## The TUI
|
||||
tui = [
|
||||
|
@ -92,16 +92,16 @@ checks:
|
||||
- 'ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET >= 32768'
|
||||
```
|
||||
|
||||
`if` and `active` are [evalexpr](https://crates.io/crates/evalexpr) expressions returning a boolean.
|
||||
`if` and `active` are [somni-expr](https://crates.io/crates/somni-expr) expressions evaluating to a boolean.
|
||||
|
||||
The expression supports these custom functions:
|
||||
|Function|Description|
|
||||
|---|---|
|
||||
|feature(String)|`true` if the given chip feature is present|
|
||||
|cargo_feature(String)|`true` if the given Cargo feature is active|
|
||||
|ignore_feature_gates()|Usually `false` but tooling will set this to `true` to hint that the expression is evaluated by e.g. a TUI|
|
||||
|ignore_feature_gates|Usually `false` but tooling will set this to `true` to hint that the expression is evaluated by e.g. a TUI|
|
||||
|
||||
`ignore_feature_gates()` is useful to enable otherwise disabled functionality - e.g. to offer all possible options regardless of any active / non-active features.
|
||||
`ignore_feature_gates` is useful to enable otherwise disabled functionality - e.g. to offer all possible options regardless of any active / non-active features.
|
||||
|
||||
The `chip` variable is populated with the name of the targeted chip (if the crate is using chip specific features).
|
||||
|
||||
|
@ -1,163 +0,0 @@
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct I128NumericTypes;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct I128(pub i128);
|
||||
|
||||
impl FromStr for I128 {
|
||||
type Err = core::num::ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(I128(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for I128 {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
use core::str::FromStr;
|
||||
|
||||
use evalexpr::{EvalexprError, EvalexprResult};
|
||||
|
||||
impl<NumericTypes: evalexpr::EvalexprNumericTypes<Int = Self>> evalexpr::EvalexprInt<NumericTypes>
|
||||
for I128
|
||||
{
|
||||
const MIN: Self = I128(i128::MIN);
|
||||
|
||||
const MAX: Self = I128(i128::MAX);
|
||||
|
||||
fn from_usize(int: usize) -> EvalexprResult<Self, NumericTypes> {
|
||||
Ok(I128(int.try_into().map_err(|_| {
|
||||
EvalexprError::IntFromUsize { usize_int: int }
|
||||
})?))
|
||||
}
|
||||
|
||||
fn into_usize(&self) -> EvalexprResult<usize, NumericTypes> {
|
||||
if self.0 >= 0 {
|
||||
(self.0 as u64)
|
||||
.try_into()
|
||||
.map_err(|_| EvalexprError::IntIntoUsize { int: *self })
|
||||
} else {
|
||||
Err(EvalexprError::IntIntoUsize { int: *self })
|
||||
}
|
||||
}
|
||||
|
||||
fn from_hex_str(literal: &str) -> Result<Self, ()> {
|
||||
Ok(I128(i128::from_str_radix(literal, 16).map_err(|_| ())?))
|
||||
}
|
||||
|
||||
fn checked_add(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_add(rhs.0);
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::AdditionError {
|
||||
augend: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
addend: evalexpr::Value::<NumericTypes>::from_int(*rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_sub(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_sub(rhs.0);
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::SubtractionError {
|
||||
minuend: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
subtrahend: evalexpr::Value::<NumericTypes>::from_int(*rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_neg(&self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_neg();
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::NegationError {
|
||||
argument: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_mul(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_mul(rhs.0);
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::MultiplicationError {
|
||||
multiplicand: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
multiplier: evalexpr::Value::<NumericTypes>::from_int(*rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_div(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_div(rhs.0);
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::DivisionError {
|
||||
dividend: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
divisor: evalexpr::Value::<NumericTypes>::from_int(*rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_rem(&self, rhs: &Self) -> EvalexprResult<Self, NumericTypes> {
|
||||
let result = (self.0).checked_rem(rhs.0);
|
||||
if let Some(result) = result {
|
||||
Ok(I128(result))
|
||||
} else {
|
||||
Err(EvalexprError::ModulationError {
|
||||
dividend: evalexpr::Value::<NumericTypes>::from_int(*self),
|
||||
divisor: evalexpr::Value::<NumericTypes>::from_int(*rhs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn abs(&self) -> EvalexprResult<Self, NumericTypes> {
|
||||
Ok(I128(self.0.abs()))
|
||||
}
|
||||
|
||||
fn bitand(&self, rhs: &Self) -> Self {
|
||||
I128(std::ops::BitAnd::bitand(self.0, rhs.0))
|
||||
}
|
||||
|
||||
fn bitor(&self, rhs: &Self) -> Self {
|
||||
I128(std::ops::BitOr::bitor(self.0, rhs.0))
|
||||
}
|
||||
|
||||
fn bitxor(&self, rhs: &Self) -> Self {
|
||||
I128(std::ops::BitXor::bitxor(self.0, rhs.0))
|
||||
}
|
||||
|
||||
fn bitnot(&self) -> Self {
|
||||
I128(std::ops::Not::not(self.0))
|
||||
}
|
||||
|
||||
fn bit_shift_left(&self, rhs: &Self) -> Self {
|
||||
I128(std::ops::Shl::shl(self.0, rhs.0))
|
||||
}
|
||||
|
||||
fn bit_shift_right(&self, rhs: &Self) -> Self {
|
||||
I128(std::ops::Shr::shr(self.0, rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl evalexpr::EvalexprNumericTypes for I128NumericTypes {
|
||||
type Int = I128;
|
||||
type Float = f64;
|
||||
|
||||
fn int_as_float(int: &Self::Int) -> Self::Float {
|
||||
int.0 as Self::Float
|
||||
}
|
||||
|
||||
fn float_as_int(float: &Self::Float) -> Self::Int {
|
||||
I128(float.trunc() as i128)
|
||||
}
|
||||
}
|
@ -1,22 +1,17 @@
|
||||
use core::fmt::Display;
|
||||
use std::{collections::HashMap, env, fmt, fs, io::Write, path::PathBuf};
|
||||
|
||||
use evalexpr::{ContextWithMutableFunctions, ContextWithMutableVariables};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use somni_expr::TypeSet128;
|
||||
|
||||
use crate::generate::{
|
||||
evalexpr_extensions::{I128, I128NumericTypes},
|
||||
validator::Validator,
|
||||
value::Value,
|
||||
};
|
||||
use crate::generate::{validator::Validator, value::Value};
|
||||
|
||||
pub(crate) mod evalexpr_extensions;
|
||||
mod markdown;
|
||||
pub(crate) mod validator;
|
||||
pub(crate) mod value;
|
||||
|
||||
/// Configuration errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
/// Parse errors.
|
||||
Parse(String),
|
||||
@ -42,6 +37,12 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@ -51,6 +52,20 @@ impl fmt::Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn core::error::Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
|
||||
/// The root node of a configuration.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@ -156,23 +171,18 @@ pub fn generate_config_from_yaml_definition(
|
||||
/// Check the given actual values by applying checking the given checks
|
||||
pub fn do_checks(checks: Option<&Vec<String>>, cfg: &HashMap<String, Value>) -> Result<(), Error> {
|
||||
if let Some(checks) = checks {
|
||||
let mut eval_ctx = evalexpr::HashMapContext::<I128NumericTypes>::new();
|
||||
let mut eval_ctx = somni_expr::Context::<TypeSet128>::new_with_types();
|
||||
for (k, v) in cfg.iter() {
|
||||
eval_ctx
|
||||
.set_value(
|
||||
k.clone(),
|
||||
match v {
|
||||
Value::Bool(v) => evalexpr::Value::Boolean(*v),
|
||||
Value::Integer(v) => evalexpr::Value::Int(I128(*v)),
|
||||
Value::String(v) => evalexpr::Value::String(v.clone()),
|
||||
},
|
||||
)
|
||||
.map_err(|err| Error::Parse(format!("Error setting value for {k} ({err})")))?;
|
||||
match v {
|
||||
Value::Bool(v) => eval_ctx.add_variable(k, *v),
|
||||
Value::Integer(v) => eval_ctx.add_variable(k, *v),
|
||||
Value::String(v) => eval_ctx.add_variable::<&str>(k, v),
|
||||
}
|
||||
}
|
||||
for check in checks {
|
||||
if !evalexpr::eval_with_context(check, &eval_ctx)
|
||||
.and_then(|v| v.as_boolean())
|
||||
.map_err(|err| Error::Validation(format!("Validation error: '{check}' ({err})")))?
|
||||
if !eval_ctx
|
||||
.evaluate::<bool>(check)
|
||||
.map_err(|err| Error::Parse(format!("Validation error: {err:?}")))?
|
||||
{
|
||||
return Err(Error::Validation(format!("Validation error: '{check}'")));
|
||||
}
|
||||
@ -190,96 +200,27 @@ pub fn evaluate_yaml_config(
|
||||
) -> Result<(Config, Vec<ConfigOption>), Error> {
|
||||
let config: Config = serde_yaml::from_str(yaml).map_err(|err| Error::Parse(err.to_string()))?;
|
||||
let mut options = Vec::new();
|
||||
let mut eval_ctx = evalexpr::HashMapContext::<evalexpr::DefaultNumericTypes>::new();
|
||||
let mut eval_ctx = somni_expr::Context::new();
|
||||
if let Some(chip) = chip {
|
||||
eval_ctx
|
||||
.set_value(
|
||||
"chip".into(),
|
||||
evalexpr::Value::String(chip.name().to_string()),
|
||||
)
|
||||
.map_err(|err| Error::Parse(err.to_string()))?;
|
||||
|
||||
eval_ctx
|
||||
.set_function(
|
||||
"feature".into(),
|
||||
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
|
||||
if let evalexpr::Value::String(which) = arg {
|
||||
let res = chip.contains(which);
|
||||
Ok(evalexpr::Value::Boolean(res))
|
||||
} else {
|
||||
Err(evalexpr::EvalexprError::CustomMessage(format!(
|
||||
"Bad argument: {arg:?}"
|
||||
)))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map_err(|err| Error::Parse(err.to_string()))?;
|
||||
|
||||
eval_ctx
|
||||
.set_function(
|
||||
"cargo_feature".into(),
|
||||
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
|
||||
if let evalexpr::Value::String(which) = arg {
|
||||
let res = features.contains(&which.to_uppercase().replace("-", "_"));
|
||||
Ok(evalexpr::Value::Boolean(res))
|
||||
} else {
|
||||
Err(evalexpr::EvalexprError::CustomMessage(format!(
|
||||
"Bad argument: {arg:?}"
|
||||
)))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map_err(|err| Error::Parse(err.to_string()))?;
|
||||
|
||||
eval_ctx
|
||||
.set_function(
|
||||
"ignore_feature_gates".into(),
|
||||
evalexpr::Function::<evalexpr::DefaultNumericTypes>::new(move |arg| {
|
||||
if let evalexpr::Value::Empty = arg {
|
||||
Ok(evalexpr::Value::Boolean(ignore_feature_gates))
|
||||
} else {
|
||||
Err(evalexpr::EvalexprError::CustomMessage(format!(
|
||||
"Bad argument: {arg:?}"
|
||||
)))
|
||||
}
|
||||
}),
|
||||
)
|
||||
.map_err(|err| Error::Parse(err.to_string()))?;
|
||||
eval_ctx.add_variable("chip", chip.name());
|
||||
eval_ctx.add_variable("ignore_feature_gates", ignore_feature_gates);
|
||||
eval_ctx.add_function("feature", move |feature: &str| chip.contains(feature));
|
||||
eval_ctx.add_function("cargo_feature", |feature: &str| {
|
||||
features.contains(&feature.to_uppercase().replace("-", "_"))
|
||||
});
|
||||
}
|
||||
for option in config.options.clone() {
|
||||
let active = evalexpr::eval_with_context(&option.active, &eval_ctx)
|
||||
.map_err(|err| {
|
||||
Error::Parse(format!(
|
||||
"Error evaluating '{}', error = {:?}",
|
||||
option.active, err
|
||||
))
|
||||
})?
|
||||
.as_boolean()
|
||||
.map_err(|err| {
|
||||
Error::Parse(format!(
|
||||
"Error evaluating '{}', error = {:?}",
|
||||
option.active, err
|
||||
))
|
||||
})?;
|
||||
for option in &config.options {
|
||||
let active = eval_ctx
|
||||
.evaluate::<bool>(&option.active)
|
||||
.map_err(|err| Error::Parse(format!("{err:?}")))?;
|
||||
|
||||
let constraint = {
|
||||
let mut active_constraint = None;
|
||||
if let Some(constraints) = &option.constraints {
|
||||
for constraint in constraints {
|
||||
if evalexpr::eval_with_context(&constraint.if_, &eval_ctx)
|
||||
.map_err(|err| {
|
||||
Error::Parse(format!(
|
||||
"Error evaluating '{}', error = {err:?}",
|
||||
constraint.if_
|
||||
))
|
||||
})?
|
||||
.as_boolean()
|
||||
.map_err(|err| {
|
||||
Error::Parse(format!(
|
||||
"Error evaluating '{}', error = {:?}",
|
||||
constraint.if_, err
|
||||
))
|
||||
})?
|
||||
if eval_ctx
|
||||
.evaluate::<bool>(&constraint.if_)
|
||||
.map_err(|err| Error::Parse(format!("{err:?}")))?
|
||||
{
|
||||
active_constraint = Some(constraint.type_.clone());
|
||||
break;
|
||||
@ -299,14 +240,12 @@ pub fn evaluate_yaml_config(
|
||||
|
||||
let default_value = {
|
||||
let mut default_value = None;
|
||||
for value in option.default.clone() {
|
||||
if evalexpr::eval_with_context(&value.if_, &eval_ctx)
|
||||
.and_then(|v| v.as_boolean())
|
||||
.map_err(|err| {
|
||||
Error::Parse(format!("Error evaluating '{}', error = {err:?}", value.if_))
|
||||
})?
|
||||
for value in &option.default {
|
||||
if eval_ctx
|
||||
.evaluate::<bool>(&value.if_)
|
||||
.map_err(|err| Error::Parse(format!("{err:?}")))?
|
||||
{
|
||||
default_value = Some(value.value);
|
||||
default_value = Some(value.value.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -323,13 +262,12 @@ pub fn evaluate_yaml_config(
|
||||
|
||||
let option = ConfigOption {
|
||||
name: option.name.clone(),
|
||||
description: option.description,
|
||||
default_value: default_value.ok_or(Error::Parse(format!(
|
||||
"No default value found for {}",
|
||||
option.name
|
||||
)))?,
|
||||
description: option.description.clone(),
|
||||
default_value: default_value.ok_or_else(|| {
|
||||
Error::Parse(format!("No default value found for {}", option.name))
|
||||
})?,
|
||||
constraint,
|
||||
stability: option.stability,
|
||||
stability: option.stability.clone(),
|
||||
active,
|
||||
display_hint: option.display_hint.unwrap_or(DisplayHint::None),
|
||||
};
|
||||
|
@ -1,47 +1,49 @@
|
||||
crate: esp-hal-embassy
|
||||
|
||||
options:
|
||||
- name: low-power-wait
|
||||
description: "Enables the lower-power wait if no tasks are ready to run on the
|
||||
thread-mode executor. This allows the MCU to use less power if the workload allows.
|
||||
Recommended for battery-powered systems. May impact analog performance."
|
||||
default:
|
||||
- value: true
|
||||
- name: low-power-wait
|
||||
description:
|
||||
"Enables the lower-power wait if no tasks are ready to run on the
|
||||
thread-mode executor. This allows the MCU to use less power if the workload allows.
|
||||
Recommended for battery-powered systems. May impact analog performance."
|
||||
default:
|
||||
- value: true
|
||||
|
||||
- name: timer-queue
|
||||
description: "The flavour of the timer queue provided by this crate. Integrated
|
||||
queues require the `executors` feature to be enabled.</p><p>If you use
|
||||
embassy-executor, the `single-integrated` queue is recommended for ease of use,
|
||||
while the `multiple-integrated` queue is recommended for performance. The
|
||||
`multiple-integrated` option needs one timer per executor.</p><p>The `generic`
|
||||
queue allows using embassy-time without the embassy executors."
|
||||
default:
|
||||
- if: 'ignore_feature_gates()'
|
||||
value: '"single-integrated"'
|
||||
- if: 'cargo_feature("executors")'
|
||||
value: '"single-integrated"'
|
||||
- if: 'true'
|
||||
value: '"generic"'
|
||||
constraints:
|
||||
- if: 'cargo_feature("executors") || ignore_feature_gates()'
|
||||
type:
|
||||
validator: enumeration
|
||||
value:
|
||||
- 'generic'
|
||||
- 'single-integrated'
|
||||
- 'multiple-integrated'
|
||||
- if: 'true'
|
||||
type:
|
||||
validator: enumeration
|
||||
value:
|
||||
- 'generic'
|
||||
stability: Unstable
|
||||
active: 'cargo_feature("executors") || ignore_feature_gates()'
|
||||
- name: timer-queue
|
||||
description:
|
||||
"The flavour of the timer queue provided by this crate. Integrated
|
||||
queues require the `executors` feature to be enabled.</p><p>If you use
|
||||
embassy-executor, the `single-integrated` queue is recommended for ease of use,
|
||||
while the `multiple-integrated` queue is recommended for performance. The
|
||||
`multiple-integrated` option needs one timer per executor.</p><p>The `generic`
|
||||
queue allows using embassy-time without the embassy executors."
|
||||
default:
|
||||
- if: "ignore_feature_gates"
|
||||
value: '"single-integrated"'
|
||||
- if: 'cargo_feature("executors")'
|
||||
value: '"single-integrated"'
|
||||
- if: "true"
|
||||
value: '"generic"'
|
||||
constraints:
|
||||
- if: 'cargo_feature("executors") || ignore_feature_gates'
|
||||
type:
|
||||
validator: enumeration
|
||||
value:
|
||||
- "generic"
|
||||
- "single-integrated"
|
||||
- "multiple-integrated"
|
||||
- if: "true"
|
||||
type:
|
||||
validator: enumeration
|
||||
value:
|
||||
- "generic"
|
||||
stability: Unstable
|
||||
active: 'cargo_feature("executors") || ignore_feature_gates'
|
||||
|
||||
- name: generic-queue-size
|
||||
description: The capacity of the queue when the `generic` timer queue flavour is selected.
|
||||
default:
|
||||
- value: 64
|
||||
constraints:
|
||||
- type:
|
||||
validator: positive_integer
|
||||
- name: generic-queue-size
|
||||
description: The capacity of the queue when the `generic` timer queue flavour is selected.
|
||||
default:
|
||||
- value: 64
|
||||
constraints:
|
||||
- type:
|
||||
validator: positive_integer
|
||||
|
Loading…
x
Reference in New Issue
Block a user