diff --git a/tools/kicad/kicad_functions/Cargo.toml b/tools/kicad/kicad_functions/Cargo.toml index 3873845..a1e71ba 100644 --- a/tools/kicad/kicad_functions/Cargo.toml +++ b/tools/kicad/kicad_functions/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" [dependencies] evalexpr = "6.1" +regex = "1.5" resistor-calc = { git = "https://github.com/racklet/resistor-calc.git", branch = "fix-no-expr_builder-compile", default-features = false } diff --git a/tools/kicad/kicad_functions/src/lib.rs b/tools/kicad/kicad_functions/src/lib.rs index 1f9e37f..136bbb6 100644 --- a/tools/kicad/kicad_functions/src/lib.rs +++ b/tools/kicad/kicad_functions/src/lib.rs @@ -4,7 +4,7 @@ mod vdiv; use evalexpr::{EvalexprError, EvalexprResult, Value}; -// Match function for all custom functions available in the kicad_rs evaluator +/// Matching function for all custom functions provided by the kicad_rs evaluator pub fn call_function(identifier: &str, argument: &Value) -> EvalexprResult { match identifier { "idx" => idx::index(argument), diff --git a/tools/kicad/kicad_functions/src/vdiv.rs b/tools/kicad/kicad_functions/src/vdiv.rs index 8c59931..3feb7f8 100644 --- a/tools/kicad/kicad_functions/src/vdiv.rs +++ b/tools/kicad/kicad_functions/src/vdiv.rs @@ -2,8 +2,11 @@ use crate::util::err; use evalexpr::{ ContextWithMutableVariables, EvalexprError, EvalexprResult, HashMapContext, Node, Value, }; +use regex::Regex; use resistor_calc::{RCalc, RRes, RSeries}; +use std::cell::RefCell; use std::collections::HashSet; +use std::ops::Deref; use std::panic::panic_any; fn parse_series(str: &str) -> EvalexprResult<&'static RSeries> { @@ -18,15 +21,26 @@ fn parse_series(str: &str) -> EvalexprResult<&'static RSeries> { } } -// evalexpr is not smart enough to distinguish identifiers on its own -fn unique_identifiers(e: &Node) -> usize { +fn resistor_identifiers(e: &Node) -> usize { + let re = Regex::new(r"^R[1-9][0-9]*$").unwrap(); let mut set = HashSet::new(); - e.iter_variable_identifiers().for_each(|i| { - set.insert(i); - }); + e.iter_variable_identifiers() + .filter(|i| re.is_match(i)) // Match only R? identifiers + .for_each(|i| { + set.insert(i); + }); set.len() } +// Helper for parsing of potentially single-element tuples +fn parse_tuple(v: &Value) -> Vec { + if let Ok(t) = v.as_tuple() { + return t; + } + + vec![v.clone()] +} + struct VoltageDividerConfig { target: f64, expression: Node, @@ -34,19 +48,30 @@ struct VoltageDividerConfig { series: &'static RSeries, resistance_min: Option, resistance_max: Option, + extra_parameters: Option>, } impl VoltageDividerConfig { fn parse(v: &Value) -> EvalexprResult { let tuple = v.as_tuple()?; - let resistance_min = tuple.get(3).map(|v| v.as_number()).transpose()?; - let resistance_max = tuple.get(4).map(|v| v.as_number()).transpose()?; + let resistance = tuple.get(3).map(|v| v.as_tuple()).transpose()?; + let resistance_min = resistance + .as_ref() + .map(|r| r.get(0).map(|v| v.as_number())) + .flatten() + .transpose()?; + let resistance_max = resistance + .as_ref() + .map(|r| r.get(1).map(|v| v.as_number())) + .flatten() + .transpose()?; + + let extra_parameters = tuple.get(4).map(|v| parse_tuple(v)); - // TODO: Support external constants if let [target, expression, series] = &tuple[..3] { let expression = evalexpr::build_operator_tree(&expression.as_string()?)?; - let count = unique_identifiers(&expression); + let count = resistor_identifiers(&expression); Ok(Self { target: target.as_number()?, @@ -55,6 +80,7 @@ impl VoltageDividerConfig { series: parse_series(&series.as_string()?)?, resistance_min, resistance_max, + extra_parameters, }) } else { err(&format!("unsupported argument count: {}", tuple.len())) @@ -65,6 +91,16 @@ impl VoltageDividerConfig { fn calculate(config: &VoltageDividerConfig) -> Option { let calc = RCalc::new(vec![config.series; config.count]); + let mut context = HashMapContext::new(); + if let Some(v) = &config.extra_parameters { + for (i, p) in v.iter().enumerate() { + context + .set_value(format!("E{}", i + 1).into(), p.clone()) + .unwrap(); + } + } + + let context_rc = RefCell::new(context); calc.calc(|set| { if let Some(true) = config.resistance_min.map(|r| set.sum() < r) { return None; // Sum of resistance less than minimum @@ -74,16 +110,17 @@ fn calculate(config: &VoltageDividerConfig) -> Option { return None; // Sum of resistance larger than maximum } - // TODO: Storing this externally and using interior mutability - // could be helpful to avoid reallocating on every invocation. - let mut context = HashMapContext::new(); for i in 1..=config.count { - context + context_rc + .borrow_mut() .set_value(format!("R{}", i).into(), Value::Float(set.r(i))) .unwrap(); } - match config.expression.eval_with_context(&context) { + match config + .expression + .eval_with_context(context_rc.borrow().deref()) + { Ok(v) => Some((config.target - v.as_number().unwrap()).abs()), Err(e) => match &e { EvalexprError::DivisionError { divisor: d, .. } => { @@ -96,22 +133,24 @@ fn calculate(config: &VoltageDividerConfig) -> Option { return None; } } - panic_any(e) // No graceful way to handle this from the closure + panic_any(e.to_string()) // No graceful way to handle this from the closure } - _ => panic_any(e), + _ => panic_any(e.to_string()), }, } }) } /// `voltage_divider` computes values for resistor-based voltage dividers. -/// - Usage: vdiv(, , , (min resistance), (max resistance)) -/// - Example: vdiv(5.1, "(R1+R2)/R2*0.8", "E96", 500e3, 700e3) -/// - Output: (, , , ...) +/// - Usage: vdiv(\, \, \, +/// {(\, \)}, ({extra 1}, {extra 2}, ...)) +/// - Example: vdiv(5.1, "(R1+R2)/R2*E1", "E96", (500e3, 700e3), (0.8)) +/// - Output: (\, \, \, ...) /// There can be arbitrary many resistors in the divider, but they must be named "R1", "R2", etc. /// The computed optimal resistance values are also presented in this order. The minimal and maximal -/// resistance limits are optional parameters, and only consider the sum of the resistances of all -/// resistors defined in the expression. +/// resistance pair is an optional parameter, and the limits only consider the sum of resistance of +/// all resistors defined in the expression. The "extra" parameters are optional external inputs for +/// the divider expression, and will be made available as "E1", "E2", etc. in order. pub(crate) fn voltage_divider(argument: &Value) -> EvalexprResult { let config = VoltageDividerConfig::parse(argument)?; if let Some(res) = calculate(&config) {