From d0c114667c5918ee62da76d80e6eeaf53387fb40 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sun, 3 Mar 2024 09:52:06 +0000 Subject: [PATCH] New NFA style more suited to DFA building In this new style, edges can have both flash/gap conditionals and also arithmetic conditionals. A conditional branch (i.e. two way) now exists as two edges. This makes it much simpler to build the DFA, as we simply collect all the actions along a particular path and need no special consideration for conditional code. Signed-off-by: Sean Young --- .github/workflows/tests.yml | 4 +- irp/README.md | 6 +- irp/src/build_dfa.rs | 265 +++++---------- irp/src/build_nfa.rs | 184 ++++++----- irp/src/{decoder_nfa.rs => decoder.rs} | 376 ++++++++++----------- irp/src/decoder_dfa.rs | 434 ------------------------- irp/src/graphviz.rs | 110 ++----- irp/src/lib.rs | 10 +- irp/tests/tests.rs | 8 +- src/bin/commands/decode.rs | 8 +- src/lircd_conf/decode.rs | 8 +- 11 files changed, 445 insertions(+), 968 deletions(-) rename irp/src/{decoder_nfa.rs => decoder.rs} (53%) delete mode 100644 irp/src/decoder_dfa.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c4c5baf4..f361859c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,11 +6,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'adopt' diff --git a/irp/README.md b/irp/README.md index ab974580..432f189f 100644 --- a/irp/README.md +++ b/irp/README.md @@ -187,7 +187,7 @@ we compile the DFA state machine, for decoding. Then we create a decoder, which needs some matching parameters, and then we can feed it input. ```rust -use irp::{Irp, InfraredData, DFADecoder}; +use irp::{Irp, InfraredData, Decoder}; fn main() { let irp = Irp::parse(r#" @@ -197,11 +197,11 @@ fn main() { let dfa = irp.compile().expect("build dfa should succeed"); // Create a decoder with 100 microsecond tolerance, 30% relative tolerance, // and 20000 microseconds maximum gap. - let mut decoder = DFADecoder::new(100, 30, 20000); + let mut decoder = Decoder::new(100, 30, 20000); for ir in InfraredData::from_rawir( "+940 -860 +1790 -1750 +880 -880 +900 -890 +870 -900 +1750 -900 +890 -910 +840 -920 +870 -920 +840 -920 +870 -1810 +840 -125000").unwrap() { - decoder.input(ir, &dfa, |event, vars| { + decoder.dfa_input(ir, &dfa, |event, vars| { println!("decoded: {} F={} D={} T={}", event, vars["F"], vars["D"], vars["T"]); }); } diff --git a/irp/src/build_dfa.rs b/irp/src/build_dfa.rs index 0fb24ba3..8ac3bca1 100644 --- a/irp/src/build_dfa.rs +++ b/irp/src/build_dfa.rs @@ -6,6 +6,7 @@ use std::{collections::HashMap, hash::Hash, rc::Rc}; /// Deterministic finite automation for decoding IR. Using this we can match IR. #[derive(Debug, Default)] +#[allow(clippy::upper_case_acronyms)] pub struct DFA { pub(crate) verts: Vec, } @@ -72,37 +73,19 @@ impl<'a> Builder<'a> { fn build(&mut self) { assert_eq!(self.add_vertex(), 0); - self.verts[0].actions = self.nfa.verts[0].actions.clone(); + self.verts[0].entry = self.nfa.verts[0].entry.clone(); self.nfa_to_dfa.insert(0, 0); self.add_path(true, 0); } - /// Recursively add a new path + // Recursively add a new path fn add_path(&mut self, mut flash: bool, pos: usize) { self.visited.push(pos); let mut paths = Vec::new(); - self.conditional_closure(pos, Vec::new(), &mut paths); - - let mut next = Vec::new(); - - for path in &paths { - self.add_conditional_path_to_dfa(path); - let last = path.last().unwrap(); - next.push(last.to); - } - - for next in next { - if !self.visited.contains(&next) { - self.add_path(flash, next); - } - } - - let mut paths = Vec::new(); - self.input_closure(pos, flash, Vec::new(), &mut paths); let mut next = Vec::new(); @@ -122,51 +105,6 @@ impl<'a> Builder<'a> { } } - fn add_conditional_path_to_dfa(&mut self, path: &[Path]) { - for path in path { - let from = self.nfa_to_dfa[&path.from]; - let to = self.copy_vert(path.to); - - for edge in &self.nfa.verts[path.from].edges { - match edge { - Edge::Branch(dest) if *dest == path.to => { - let edge = Edge::Branch(to); - - if !self.verts[from].edges.contains(&edge) { - self.verts[from].edges.push(edge); - } - } - Edge::BranchCond { expr, yes, no } => { - let yes = self.copy_vert(*yes); - let no = self.copy_vert(*no); - let expr = expr.clone(); - - let edge = Edge::BranchCond { expr, yes, no }; - - if !self.verts[from].edges.contains(&edge) { - self.verts[from].edges.push(edge); - } - } - _ => (), - } - } - } - } - - fn copy_vert(&mut self, original: usize) -> usize { - if let Some(vert_no) = self.nfa_to_dfa.get(&original) { - *vert_no - } else { - let vert_no = self.add_vertex(); - - self.nfa_to_dfa.insert(original, vert_no); - - self.verts[vert_no].actions = self.nfa.verts[original].actions.clone(); - - vert_no - } - } - fn add_input_path_to_dfa(&mut self, flash: bool, path: &[Path]) { let from = self.nfa_to_dfa[&path[0].from]; let nfa_to = path[path.len() - 1].to; @@ -192,27 +130,9 @@ impl<'a> Builder<'a> { self.edges.insert(dfa_edge, to); - self.verts[from].edges.push(if let Some(length) = length { - if flash { - Edge::Flash { - length, - complete: true, - dest: to, - } - } else { - Edge::Gap { - length, - complete: true, - dest: to, - } - } - } else { - Edge::Branch(to) - }); - - let actions = self.path_actions(path); + let actions = self.path_actions(path, flash, length); - self.verts[to].actions.extend(actions); + self.verts[from].edges.push(Edge { dest: to, actions }); } } @@ -220,33 +140,61 @@ impl<'a> Builder<'a> { let mut len: Option> = None; for elem in path { - match &self.nfa.verts[elem.from].edges[elem.edge_no] { - Edge::Gap { length, .. } | Edge::Flash { length, .. } => { - if let Some(prev) = len { - if let (Expression::Number(left), Expression::Number(right)) = - (length.as_ref(), prev.as_ref()) - { - // TODO: proper const folding - len = Some(Rc::new(Expression::Number(left + right))); + for action in &self.nfa.verts[elem.from].edges[elem.edge_no].actions { + match action { + Action::Gap { length, .. } | Action::Flash { length, .. } => { + if let Some(prev) = len { + if let (Expression::Number(left), Expression::Number(right)) = + (length.as_ref(), prev.as_ref()) + { + // TODO: proper const folding + len = Some(Rc::new(Expression::Number(left + right))); + } else { + len = Some(Rc::new(Expression::Add(length.clone(), prev))); + } } else { - len = Some(Rc::new(Expression::Add(length.clone(), prev))); + len = Some(length.clone()); } - } else { - len = Some(length.clone()); } + _ => (), } - _ => (), } } len } - fn path_actions(&self, path: &[Path]) -> Vec { + fn path_actions( + &self, + path: &[Path], + flash: bool, + length: Option>, + ) -> Vec { let mut res: Vec = Vec::new(); + if let Some(length) = length { + res.push(if flash { + Action::Flash { + length, + complete: true, + } + } else { + Action::Gap { + length, + complete: true, + } + }); + } + for elem in path { - res.extend(self.nfa.verts[elem.to].actions.iter().cloned()); + res.extend(self.nfa.verts[elem.to].entry.iter().cloned()); + res.extend( + self.nfa.verts[elem.from].edges[elem.edge_no] + .actions + .iter() + .filter(|action| !matches!(action, Action::Flash { .. } | Action::Gap { .. })) + .cloned(), + ); } res @@ -261,104 +209,69 @@ impl<'a> Builder<'a> { ) { for path in self.get_input_edges(pos, flash) { let mut p = current_path.clone(); - p.push(path); - res.push(p); + let pos = path.to; + if current_path.iter().all(|p| p.from != pos) { + p.push(path); + res.push(p.clone()); + self.input_closure(pos, flash, p, res); + } } - for path in self.get_unconditional_edges(pos) { + for path in self.get_non_input_edges(pos) { let mut p = current_path.clone(); let pos = path.to; - p.push(path); - self.input_closure(pos, flash, p, res); - } - } - - fn get_unconditional_edges(&self, pos: usize) -> Vec { - let mut res = Vec::new(); - for (i, edge) in self.nfa.verts[pos].edges.iter().enumerate() { - match edge { - Edge::Branch(dest) => { - res.push(Path { - to: *dest, - from: pos, - edge_no: i, - }); - } - Edge::MayBranchCond { .. } | Edge::BranchCond { .. } => { - return Vec::new(); - } - _ => (), + if current_path.iter().all(|p| p.from != pos) { + p.push(path); + self.input_closure(pos, flash, p, res); } } - res } fn get_input_edges(&self, pos: usize, flash: bool) -> Vec { let mut res = Vec::new(); - for (i, edge) in self.nfa.verts[pos].edges.iter().enumerate() { - match edge { - Edge::Flash { dest, .. } if flash => { - res.push(Path { - from: pos, - to: *dest, - edge_no: i, - }); - } - Edge::Gap { dest, .. } if !flash => { - res.push(Path { - from: pos, - to: *dest, - edge_no: i, - }); + for (edge_no, edge) in self.nfa.verts[pos].edges.iter().enumerate() { + for action in &edge.actions { + match action { + Action::Flash { .. } if flash => { + res.push(Path { + from: pos, + to: edge.dest, + edge_no, + }); + break; + } + Action::Gap { .. } if !flash => { + res.push(Path { + from: pos, + to: edge.dest, + edge_no, + }); + break; + } + _ => (), } - _ => (), } } res } - fn get_conditional_edges(&self, pos: usize) -> Vec { + fn get_non_input_edges(&self, pos: usize) -> Vec { let mut res = Vec::new(); for (i, edge) in self.nfa.verts[pos].edges.iter().enumerate() { - match edge { - Edge::MayBranchCond { dest, .. } => { - res.push(Path { - from: pos, - to: *dest, - edge_no: i, - }); - } - Edge::BranchCond { yes, no, .. } => { - res.push(Path { - from: pos, - to: *yes, - edge_no: i, - }); - res.push(Path { - from: pos, - to: *no, - edge_no: i, - }); - } - _ => (), + if !edge + .actions + .iter() + .any(|action| matches!(action, Action::Flash { .. } | Action::Gap { .. })) + { + res.push(Path { + from: pos, + to: edge.dest, + edge_no: i, + }); } } - res - } - - fn conditional_closure(&self, pos: usize, current_path: Vec, res: &mut Vec>) { - for path in self.get_conditional_edges(pos) { - let mut p = current_path.clone(); - p.push(path); - res.push(p); - } - for path in self.get_unconditional_edges(pos) { - let mut p = current_path.clone(); - let pos = path.to; - p.push(path); - self.conditional_closure(pos, p, res); - } + res } fn add_vertex(&mut self) -> usize { diff --git a/irp/src/build_nfa.rs b/irp/src/build_nfa.rs index f80c694e..a35e5c95 100644 --- a/irp/src/build_nfa.rs +++ b/irp/src/build_nfa.rs @@ -13,31 +13,21 @@ use std::{ */ #[derive(PartialEq, Debug, Clone)] -pub(crate) enum Edge { +pub(crate) struct Edge { + pub dest: usize, + pub actions: Vec, +} + +#[derive(PartialEq, Debug, Clone)] +pub(crate) enum Action { Flash { length: Rc, complete: bool, - dest: usize, }, Gap { length: Rc, complete: bool, - dest: usize, - }, - BranchCond { - expr: Rc, - yes: usize, - no: usize, }, - MayBranchCond { - expr: Rc, - dest: usize, - }, - Branch(usize), -} - -#[derive(PartialEq, Debug, Clone)] -pub(crate) enum Action { Set { var: String, expr: Rc, @@ -51,7 +41,7 @@ pub(crate) enum Action { #[derive(PartialEq, Default, Clone, Debug)] pub(crate) struct Vertex { - pub actions: Vec, + pub entry: Vec, pub edges: Vec, } @@ -183,7 +173,7 @@ impl<'a> Builder<'a> { }) .collect(); - self.add_action(Action::Done(event, res)); + self.add_entry_action(Action::Done(event, res)); self.mask_results()?; self.cur.seen_edges = false; Ok(true) @@ -204,8 +194,8 @@ impl<'a> Builder<'a> { self.cur.head = head; } - fn add_action(&mut self, action: Action) { - self.verts[self.cur.head].actions.push(action); + fn add_entry_action(&mut self, action: Action) { + self.verts[self.cur.head].entry.push(action); } fn add_edge(&mut self, edge: Edge) { @@ -213,7 +203,7 @@ impl<'a> Builder<'a> { } fn add_action_at_node(&mut self, node: usize, action: Action) { - self.verts[node].actions.push(action); + self.verts[node].entry.push(action); } fn add_edge_at_node(&mut self, node: usize, edge: Edge) { @@ -295,7 +285,7 @@ impl<'a> Builder<'a> { if self.expression_available(expr, false).is_ok() { // just set an initial value - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr: self.const_folding(expr), }); @@ -438,7 +428,7 @@ impl<'a> Builder<'a> { }; if let Expression::Identifier(name) = value.as_ref() { - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr: self.const_folding(&Rc::new(bits)), }); @@ -548,7 +538,9 @@ impl<'a> Builder<'a> { } Err(name) => match self.inverse(bits, value.clone(), &name) { Some((bits, actions, _)) => { - actions.into_iter().for_each(|act| self.add_action(act)); + actions + .into_iter() + .for_each(|act| self.add_entry_action(act)); self.use_decode_bits(&name, bits, mask, &mut delayed)?; } @@ -573,9 +565,9 @@ impl<'a> Builder<'a> { Action::Set { expr, .. } => { self.have_definitions(expr)?; } - Action::Done(..) => (), + _ => (), } - self.add_action(action); + self.add_entry_action(action); } pos += expr_count; @@ -633,7 +625,7 @@ impl<'a> Builder<'a> { bits.clone() }; - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr: self.const_folding(&expr), }); @@ -657,7 +649,7 @@ impl<'a> Builder<'a> { }; if self.have_definitions(&def).is_ok() { - self.add_action(action); + self.add_entry_action(action); } else { delayed.push(action); } @@ -667,7 +659,7 @@ impl<'a> Builder<'a> { Rc::new(Expression::Number(mask)), ))); - self.add_action(Action::AssertEq { + self.add_entry_action(Action::AssertEq { left, right: self.const_folding(&bits), }); @@ -683,7 +675,7 @@ impl<'a> Builder<'a> { self.set(name, mask); - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr: self.const_folding(&expr), }); @@ -703,7 +695,7 @@ impl<'a> Builder<'a> { value, Rc::new(Expression::Number(mask)), ))); - self.add_action(Action::AssertEq { left, right }); + self.add_entry_action(Action::AssertEq { left, right }); Ok(()) } @@ -719,7 +711,7 @@ impl<'a> Builder<'a> { Ok(_) => { trace!("found definition {} = {}", name, def); - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr: self.const_folding(&def), }); @@ -768,14 +760,16 @@ impl<'a> Builder<'a> { trace!("found definition {} = {}", name, expr); - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: name.to_owned(), expr, }); self.set(name, mask.unwrap_or(!0)); - actions.into_iter().for_each(|act| self.add_action(act)); + actions + .into_iter() + .for_each(|act| self.add_entry_action(act)); found = true; @@ -836,12 +830,15 @@ impl<'a> Builder<'a> { self.expression(e, &bit_spec[1..], last)?; - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: String::from("$bits"), expr: Rc::new(Expression::Number(bit as i64)), }); - self.add_edge(Edge::Branch(next)); + self.add_edge(Edge { + dest: next, + actions: vec![], + }); self.pop_location(); } @@ -859,7 +856,10 @@ impl<'a> Builder<'a> { let done = self.add_vertex(); - self.add_edge(Edge::Branch(entry)); + self.add_edge(Edge { + dest: entry, + actions: vec![], + }); self.set_head(entry); @@ -874,12 +874,15 @@ impl<'a> Builder<'a> { self.expression(e, &bit_spec[1..], last)?; - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: String::from("$v"), expr: Rc::new(Expression::Number(bit as i64)), }); - self.add_edge(Edge::Branch(next)); + self.add_edge(Edge { + dest: next, + actions: vec![], + }); self.pop_location(); } @@ -943,25 +946,44 @@ impl<'a> Builder<'a> { self.add_edge_at_node( next, - Edge::BranchCond { - expr: Rc::new(Expression::Less( - Rc::new(Expression::Identifier(length.to_owned())), - Rc::new(Expression::Number(max)), - )), - yes: entry, - no: done, + Edge { + dest: entry, + actions: vec![Action::AssertEq { + left: Rc::new(Expression::Less( + Rc::new(Expression::Identifier(length.to_owned())), + Rc::new(Expression::Number(max)), + )), + right: Rc::new(Expression::Number(1)), + }], + }, + ); + + self.add_edge_at_node( + next, + Edge { + dest: done, + actions: vec![Action::AssertEq { + left: Rc::new(Expression::Less( + Rc::new(Expression::Identifier(length.to_owned())), + Rc::new(Expression::Number(max)), + )), + right: Rc::new(Expression::Number(0)), + }], }, ); if let Some(min) = min { self.add_edge_at_node( next, - Edge::MayBranchCond { - expr: Rc::new(Expression::GreaterEqual( - Rc::new(Expression::Identifier(length)), - Rc::new(Expression::Number(min)), - )), + Edge { dest: done, + actions: vec![Action::AssertEq { + left: Rc::new(Expression::GreaterEqual( + Rc::new(Expression::Identifier(length)), + Rc::new(Expression::Number(min)), + )), + right: Rc::new(Expression::Number(1)), + }], }, ); } @@ -985,7 +1007,10 @@ impl<'a> Builder<'a> { let node = self.add_vertex(); - self.add_edge(Edge::Branch(node)); + self.add_edge(Edge { + dest: node, + actions: vec![], + }); self.set_head(node); @@ -997,7 +1022,10 @@ impl<'a> Builder<'a> { if self.cur.seen_edges { self.add_done(event)?; - self.add_edge(Edge::Branch(0)); + self.add_edge(Edge { + dest: 0, + actions: vec![], + }); self.set_head(0); self.cur.seen_edges = false; @@ -1008,7 +1036,7 @@ impl<'a> Builder<'a> { fn next_extent(&mut self) { if let Some(v) = self.extents.pop() { - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: "$extent".to_owned(), expr: Rc::new(Expression::Number(v)), }); @@ -1053,10 +1081,12 @@ impl<'a> Builder<'a> { let node = self.add_vertex(); - self.add_edge(Edge::Flash { - length: Rc::new(Expression::Number(len)), - complete: last, + self.add_edge(Edge { dest: node, + actions: vec![Action::Flash { + length: Rc::new(Expression::Number(len)), + complete: last, + }], }); self.set_head(node); @@ -1070,10 +1100,12 @@ impl<'a> Builder<'a> { let node = self.add_vertex(); - self.add_edge(Edge::Gap { - length: Rc::new(Expression::Number(len)), - complete: last, + self.add_edge(Edge { dest: node, + actions: vec![Action::Gap { + length: Rc::new(Expression::Number(len)), + complete: last, + }], }); self.set_head(node); @@ -1098,10 +1130,12 @@ impl<'a> Builder<'a> { let node = self.add_vertex(); - self.add_edge(Edge::Flash { - length: expr, - complete: last, + self.add_edge(Edge { dest: node, + actions: vec![Action::Flash { + length: expr, + complete: last, + }], }); self.set_head(node); @@ -1135,10 +1169,12 @@ impl<'a> Builder<'a> { )); } - self.add_edge(Edge::Gap { - length: expr, - complete: last, + self.add_edge(Edge { dest: node, + actions: vec![Action::Gap { + length: expr, + complete: last, + }], }); self.set_head(node); @@ -1164,10 +1200,12 @@ impl<'a> Builder<'a> { let node = self.add_vertex(); - self.add_edge(Edge::Gap { - length: Rc::new(Expression::Identifier("$extent".to_owned())), - complete: last, + self.add_edge(Edge { dest: node, + actions: vec![Action::Gap { + length: Rc::new(Expression::Identifier("$extent".into())), + complete: last, + }], }); self.next_extent(); @@ -1181,7 +1219,7 @@ impl<'a> Builder<'a> { self.have_definitions(expr)?; - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: var.to_owned(), expr: self.const_folding(expr), }) @@ -1356,7 +1394,7 @@ impl<'a> Builder<'a> { if let Some(fields) = self.cur.vars.get(¶m.name) { if (fields & !mask) != 0 { - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: param.name.to_owned(), expr: Rc::new(Expression::BitwiseAnd( Rc::new(Expression::Identifier(param.name.to_owned())), @@ -1378,7 +1416,7 @@ impl<'a> Builder<'a> { expr, )); - self.add_action(Action::Set { + self.add_entry_action(Action::Set { var: "$extent".to_owned(), expr, }); diff --git a/irp/src/decoder_nfa.rs b/irp/src/decoder.rs similarity index 53% rename from irp/src/decoder_nfa.rs rename to irp/src/decoder.rs index 81f2af1b..36ca20f7 100644 --- a/irp/src/decoder_nfa.rs +++ b/irp/src/decoder.rs @@ -1,26 +1,29 @@ use super::{ - build_nfa::{Action, Edge, NFA}, + build_dfa::DFA, + build_nfa::{Action, NFA}, InfraredData, Vartable, }; -use crate::{Event, Message}; +use crate::{build_nfa::Vertex, Event, Message}; use log::trace; use std::{collections::HashMap, fmt, fmt::Write}; /// NFA Decoder state #[derive(Debug)] -pub struct NFADecoder<'a> { +pub struct Decoder<'a> { pos: Vec<(usize, Vartable<'a>)>, + dfa: bool, abs_tolerance: u32, rel_tolerance: u32, max_gap: u32, } -impl<'a> NFADecoder<'a> { +impl<'a> Decoder<'a> { /// Create a decoder with parameters. abs_tolerance is microseconds, rel_tolerance is in percentage, /// and trailing gap is the minimum gap in microseconds which must follow. - pub fn new(abs_tolerance: u32, rel_tolerance: u32, max_gap: u32) -> NFADecoder<'a> { - NFADecoder { + pub fn new(abs_tolerance: u32, rel_tolerance: u32, max_gap: u32) -> Decoder<'a> { + Decoder { pos: Vec::new(), + dfa: false, abs_tolerance, rel_tolerance, max_gap, @@ -58,15 +61,6 @@ impl InfraredData { }) .collect()) } - - #[must_use] - pub(crate) fn consume(&self, v: u32) -> Self { - match self { - InfraredData::Flash(dur) => InfraredData::Flash(*dur - v), - InfraredData::Gap(dur) => InfraredData::Gap(*dur - v), - _ => unreachable!(), - } - } } impl fmt::Display for InfraredData { @@ -94,12 +88,27 @@ impl<'a> fmt::Display for Vartable<'a> { } } -impl<'a> NFADecoder<'a> { +enum ActionResult<'v> { + Fail, + Retry(Vartable<'v>), + Match(Option, Vartable<'v>), +} + +impl<'a> Decoder<'a> { /// Reset decoder state pub fn reset(&mut self) { self.pos.truncate(0); } + pub fn add_pos(&mut self, pos: usize, vartab: Vartable<'a>) { + let entry = (pos, vartab); + if self.dfa { + self.pos = vec![entry]; + } else if !self.pos.contains(&entry) { + self.pos.push(entry); + } + } + fn tolerance_eq(&self, expected: u32, received: u32) -> bool { let diff = expected.abs_diff(received); @@ -111,8 +120,84 @@ impl<'a> NFADecoder<'a> { } } + pub(crate) fn consume_flash( + &self, + ir: &mut Option, + expected: i64, + complete: bool, + ) -> bool { + match ir { + Some(InfraredData::Flash(received)) => { + if self.tolerance_eq(expected as u32, *received) { + trace!("matched flash {} (expected {})", received, expected); + *ir = None; + true + } else if !complete && *received as i64 > expected { + trace!( + "matched flash {} (expected {}) (incomplete consume)", + received, + expected, + ); + *ir = Some(InfraredData::Flash(*received - expected as u32)); + true + } else { + false + } + } + _ => false, + } + } + + pub(crate) fn consume_gap( + &self, + ir: &mut Option, + expected: i64, + complete: bool, + ) -> bool { + match ir { + Some(InfraredData::Gap(received)) => { + if expected > self.max_gap as i64 && *received >= self.max_gap { + trace!("large gap matched gap {} (expected {})", received, expected,); + *ir = None; + true + } else if self.tolerance_eq(expected as u32, *received) { + trace!("matched gap {} (expected {})", received, expected); + *ir = None; + true + } else if !complete && *received as i64 > expected { + trace!( + "matched gap {} (expected {}) (incomplete consume)", + received, + expected, + ); + *ir = Some(InfraredData::Gap(*received - expected as u32)); + true + } else { + false + } + } + _ => false, + } + } + + pub fn nfa_input(&mut self, ir: InfraredData, nfa: &NFA, callback: F) + where + F: FnMut(Event, HashMap), + { + self.dfa = false; + self.input(ir, &nfa.verts, callback) + } + + pub fn dfa_input(&mut self, ir: InfraredData, dfa: &DFA, callback: F) + where + F: FnMut(Event, HashMap), + { + self.dfa = true; + self.input(ir, &dfa.verts, callback) + } + /// Feed infrared data to the decoder - pub fn input(&mut self, ir: InfraredData, nfa: &NFA, mut callback: F) + fn input(&mut self, ir: InfraredData, verts: &[Vertex], mut callback: F) where F: FnMut(Event, HashMap), { @@ -122,194 +207,113 @@ impl<'a> NFADecoder<'a> { return; } - if self.pos.is_empty() { - let (success, mut vartab) = self.run_actions(0, &Vartable::new(), nfa, &mut callback); - - vartab.set("$down".into(), 0); + let ir = if self.pos.is_empty() { + let mut vartable = Vartable::new(); + vartable.set("$down".into(), 0); - assert!(success); - - self.pos.push((0, vartab)); - } + match self.run_actions(&verts[0].entry, &vartable, Some(ir), &mut callback) { + ActionResult::Match(ir, vartab) => { + self.add_pos(0, vartab); + ir + } + ActionResult::Retry(vartab) => { + self.add_pos(0, vartab); + Some(ir) + } + ActionResult::Fail => { + return; + } + } + } else { + Some(ir) + }; let mut work = Vec::new(); for (pos, vartab) in &self.pos { - work.push((Some(ir), *pos, vartab.clone())); + work.push((ir, *pos, vartab.clone())); } - let mut new_pos = Vec::new(); + self.pos.truncate(0); while let Some((ir, pos, vartab)) = work.pop() { - let edges = &nfa.verts[pos].edges; + let edges = &verts[pos].edges; trace!("pos:{} ir:{:?} vars:{}", pos, ir, vartab); - for edge in edges { + for (edge_no, edge) in edges.iter().enumerate() { //trace!(&format!("edge:{:?}", edge)); - match edge { - Edge::Flash { - length: expected, - complete, - dest, - } => { - let expected = expected.eval(&vartab).unwrap(); - - if let Some(ir @ InfraredData::Flash(received)) = ir { - if self.tolerance_eq(expected as u32, received) { - trace!( - "matched flash {} (expected {}) => {}", - received, - expected, - dest - ); - - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - if success { - work.push((None, *dest, vartab)); - } - } else if !complete && received > expected as u32 { - trace!( - "matched flash {} (expected {}) (incomplete consume) => {}", - received, - expected, - dest - ); - - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - if success { - work.push((Some(ir.consume(expected as u32)), *dest, vartab)); + match self.run_actions(&edge.actions, &vartab, ir, &mut callback) { + ActionResult::Match(ir, vartab) => { + match self.run_actions(&verts[edge.dest].entry, &vartab, ir, &mut callback) + { + ActionResult::Match(ir, vartab) => { + trace!("pos {pos}: edge: {edge_no} match"); + work.push((ir, edge.dest, vartab)); + if self.dfa { + break; } } - } else if ir.is_none() && new_pos.iter().all(|(n, _)| *n != pos) { - new_pos.push((pos, vartab.clone())); - } - } - Edge::Gap { - length: expected, - complete, - dest, - } => { - let expected = expected.eval(&vartab).unwrap(); - - if let Some(ir @ InfraredData::Gap(received)) = ir { - if expected >= self.max_gap as i64 { - if received >= self.max_gap { - trace!( - "large gap matched gap {} (expected {}) => {}", - received, - expected, - dest - ); - - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - if success { - work.push((None, *dest, vartab)); - } - } - } else if self.tolerance_eq(expected as u32, received) { - trace!( - "matched gap {} (expected {}) => {}", - received, - expected, - dest - ); - - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - if success { - work.push((None, *dest, vartab)); - } - } else if !complete && received > expected as u32 { - trace!( - "matched gap {} (expected {}) (incomplete consume) => {}", - received, - expected, - dest - ); - - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - if success { - work.push((Some(ir.consume(expected as u32)), *dest, vartab)); - } + ActionResult::Retry(..) => { + panic!("no flash/gap on entry actions allowed"); } - } else if ir.is_none() && new_pos.iter().all(|(n, _)| *n != pos) { - new_pos.push((pos, vartab.clone())); + ActionResult::Fail => (), } } - Edge::Branch(dest) => { - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - - if success { - work.push((ir, *dest, vartab)); - } + ActionResult::Retry(vartab) => { + self.add_pos(pos, vartab); } - Edge::BranchCond { expr, yes, no } => { - let cond = expr.eval(&vartab).unwrap(); - - let dest = if cond != 0 { *yes } else { *no }; - - let (success, vartab) = self.run_actions(dest, &vartab, nfa, &mut callback); - - if success { - trace!( - "conditional branch {}: {}: destination {}", - expr, - cond != 0, - dest - ); - - work.push((ir, dest, vartab)); - } - } - Edge::MayBranchCond { expr, dest } => { - let cond = expr.eval(&vartab).unwrap(); - - if cond != 0 { - let (success, vartab) = - self.run_actions(*dest, &vartab, nfa, &mut callback); - - if success { - let dest = *dest; - - trace!( - "conditional branch {}: {}: destination {}", - expr, - cond != 0, - dest - ); - - work.push((ir, dest, vartab)); - } - } + ActionResult::Fail => { + trace!("pos {pos}: edge: {edge_no} no match"); } } } } - - self.pos = new_pos; } fn run_actions<'v, F>( - &mut self, - pos: usize, + &self, + actions: &[Action], vartab: &Vartable<'v>, - nfa: &NFA, + mut ir: Option, callback: &mut F, - ) -> (bool, Vartable<'v>) + ) -> ActionResult<'v> where F: FnMut(Event, HashMap), { let mut vartable = vartab.clone(); - for a in &nfa.verts[pos].actions { + for a in actions { match a { + Action::Flash { + length: expected, + complete, + } => { + let expected = expected.eval(vartab).unwrap(); + + if ir.is_none() { + return ActionResult::Retry(vartable); + } else if self.consume_flash(&mut ir, expected, *complete) { + continue; + } + + return ActionResult::Fail; + } + Action::Gap { + length: expected, + complete, + } => { + let expected = expected.eval(vartab).unwrap(); + + if ir.is_none() { + return ActionResult::Retry(vartable); + } else if self.consume_gap(&mut ir, expected, *complete) { + continue; + } + + return ActionResult::Fail; + } Action::Set { var, expr } => { let val = expr.eval(&vartable).unwrap(); trace!("set {} = {} = {}", var, expr, val); @@ -327,7 +331,7 @@ impl<'a> NFADecoder<'a> { left_val, right_val ); - return (false, vartable); + return ActionResult::Fail; } else { trace!( "assert {} == {} ({} == {})", @@ -338,7 +342,7 @@ impl<'a> NFADecoder<'a> { ); } } else { - return (false, vartable); + return ActionResult::Fail; } } Action::Done(event, include) => { @@ -357,18 +361,22 @@ impl<'a> NFADecoder<'a> { } } - (true, vartable) + ActionResult::Match(ir, vartable) } /// Generate a GraphViz dot file and write to the given path - pub fn dotgraphviz(&self, path: &str, nfa: &NFA) { + pub fn nfa_dotgraphviz(&self, path: &str, nfa: &NFA) { crate::graphviz::graphviz(&nfa.verts, "NFA", &self.pos, path); } + + pub fn dfa_dotgraphviz(&self, path: &str, dfa: &DFA) { + crate::graphviz::graphviz(&dfa.verts, "DFA", &self.pos, path); + } } #[cfg(test)] mod test { - use super::{InfraredData, NFADecoder}; + use super::{Decoder, InfraredData}; use crate::{Event, Irp}; use std::collections::HashMap; @@ -381,11 +389,11 @@ mod test { let mut res: Vec<(Event, HashMap)> = Vec::new(); - let mut matcher = NFADecoder::new(100, 3, 20000); + let mut matcher = Decoder::new(100, 3, 20000); for ir in InfraredData::from_rawir( "+2400 -600 +600 -600 +600 -600 +1200 -600 +600 -600 +600 -600 +600 -600 +1200 -600 +1200 -31200").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } assert_eq!(res.len(), 1); @@ -404,12 +412,12 @@ mod test { let mut res: Vec<(Event, HashMap)> = Vec::new(); - let mut matcher = NFADecoder::new(100, 3, 20000); + let mut matcher = Decoder::new(100, 3, 20000); for ir in InfraredData::from_rawir( "+9024 -4512 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -564 +564 -39756").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } assert_eq!(res.len(), 1); @@ -422,7 +430,7 @@ mod test { assert_eq!(vars["S"], 191); for ir in InfraredData::from_rawir("+9024 -2256 +564 -96156").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } assert_eq!(res.len(), 2); @@ -435,7 +443,7 @@ mod test { assert_eq!(vars["S"], 191); for ir in InfraredData::from_rawir("+9024 -2256 +564 -96156").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } assert_eq!(res.len(), 3); @@ -450,7 +458,7 @@ mod test { for ir in InfraredData::from_rawir( "+9024 -4512 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -39756").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } assert_eq!(res.len(), 4); @@ -473,12 +481,12 @@ mod test { let mut res: Vec<(Event, HashMap)> = Vec::new(); - let mut matcher = NFADecoder::new(100, 3, 20000); + let mut matcher = Decoder::new(100, 3, 20000); for ir in InfraredData::from_rawir( "+889 -889 +1778 -1778 +889 -889 +889 -889 +889 -889 +1778 -889 +889 -889 +889 -889 +889 -889 +889 -889 +889 -1778 +889 -89997").unwrap() { - matcher.input(ir, &nfa, |ev, vars| res.push((ev, vars))); + matcher.nfa_input(ir, &nfa, |ev, vars| res.push((ev, vars))); } let (event, vars) = &res[0]; diff --git a/irp/src/decoder_dfa.rs b/irp/src/decoder_dfa.rs deleted file mode 100644 index da1df96d..00000000 --- a/irp/src/decoder_dfa.rs +++ /dev/null @@ -1,434 +0,0 @@ -use super::{ - build_dfa::DFA, - build_nfa::{Action, Edge}, - InfraredData, Vartable, -}; -use crate::Event; -use log::trace; -use std::collections::HashMap; - -/// NFA Decoder state -#[derive(Debug)] -pub struct DFADecoder<'a> { - pos: usize, - vartab: Vartable<'a>, - abs_tolerance: u32, - rel_tolerance: u32, - max_gap: u32, -} - -impl<'a> DFADecoder<'a> { - /// Create a decoder with parameters. abs_tolerance is microseconds, rel_tolerance is in percentage, - /// and trailing gap is the minimum gap in microseconds which must follow. - pub fn new(abs_tolerance: u32, rel_tolerance: u32, max_gap: u32) -> DFADecoder<'a> { - DFADecoder { - pos: 0, - vartab: Vartable::new(), - abs_tolerance, - rel_tolerance, - max_gap, - } - } -} - -impl<'a> DFADecoder<'a> { - /// Reset decoder state - pub fn reset(&mut self) { - self.pos = 0; - } - - fn tolerance_eq(&self, expected: u32, received: u32) -> bool { - let diff = expected.abs_diff(received); - - if diff <= self.abs_tolerance { - true - } else { - // upcast to u64 since diff * 100 may overflow - ((diff as u64 * 100) / expected as u64) <= self.rel_tolerance as u64 - } - } - - /// Feed infrared data to the decoder - pub fn input(&mut self, ir: InfraredData, dfa: &DFA, mut callback: F) - where - F: FnMut(Event, HashMap), - { - if ir == InfraredData::Reset { - trace!("decoder reset"); - self.reset(); - return; - } - - if self.pos == 0 { - let success = self.run_actions(0, dfa, &mut callback); - - self.vartab.set("$down".into(), 0); - - assert!(success); - } - - let mut input = Some(ir); - - loop { - let mut stuff_to_do = false; - let edges = &dfa.verts[self.pos].edges; - - trace!("pos:{} ir:{:?} vars:{}", self.pos, input, self.vartab); - - for edge in edges { - //trace!("edge:{:?}", edge); - - match edge { - Edge::Flash { - length: expected, - complete, - dest, - } => { - let expected = expected.eval(&self.vartab).unwrap(); - - if let Some(ir @ InfraredData::Flash(received)) = input { - if self.tolerance_eq(expected as u32, received) { - trace!( - "matched flash {} (expected {}) => {}", - received, - expected, - dest - ); - - let success = self.run_actions(*dest, dfa, &mut callback); - if success { - self.pos = *dest; - input = None; - stuff_to_do = true; - } else { - self.reset(); - } - } else if !complete && received > expected as u32 { - trace!( - "matched flash {} (expected {}) (incomplete consume) => {}", - received, - expected, - dest - ); - - let success = self.run_actions(*dest, dfa, &mut callback); - if success { - self.pos = *dest; - input = Some(ir.consume(expected as u32)); - stuff_to_do = true; - } else { - self.reset(); - } - } - } - } - Edge::Gap { - length: expected, - complete, - dest, - } => { - let expected = expected.eval(&self.vartab).unwrap(); - - if let Some(ir @ InfraredData::Gap(received)) = input { - if expected >= self.max_gap as i64 { - if received >= self.max_gap { - trace!( - "large gap matched gap {} (expected {}) => {}", - received, - expected, - dest - ); - - let success = self.run_actions(*dest, dfa, &mut callback); - if success { - self.pos = *dest; - stuff_to_do = true; - } else { - self.reset(); - } - } - } else if self.tolerance_eq(expected as u32, received) { - trace!( - "matched gap {} (expected {}) => {}", - received, - expected, - dest - ); - - let success = self.run_actions(*dest, dfa, &mut callback); - if success { - self.pos = *dest; - stuff_to_do = true; - input = None; - } else { - self.reset(); - } - } else if !complete && received > expected as u32 { - trace!( - "matched gap {} (expected {}) (incomplete consume) => {}", - received, - expected, - dest - ); - - let success = self.run_actions(*dest, dfa, &mut callback); - if success { - self.pos = *dest; - stuff_to_do = true; - input = Some(ir.consume(expected as u32)); - } else { - self.reset(); - } - } - } - } - Edge::Branch(dest) => { - let success = self.run_actions(*dest, dfa, &mut callback); - - if success { - self.pos = *dest; - stuff_to_do = true; - } else { - self.reset(); - } - } - Edge::BranchCond { expr, yes, no } => { - let cond = expr.eval(&self.vartab).unwrap(); - - let dest = if cond != 0 { *yes } else { *no }; - - let success = self.run_actions(dest, dfa, &mut callback); - - if success { - trace!( - "conditional branch {}: {}: destination {}", - expr, - cond != 0, - dest - ); - - self.pos = dest; - stuff_to_do = true; - } else { - self.reset(); - } - } - Edge::MayBranchCond { expr, dest } => { - let cond = expr.eval(&self.vartab).unwrap(); - - if cond != 0 { - let success = self.run_actions(*dest, dfa, &mut callback); - - if success { - self.pos = *dest; - - trace!( - "conditional branch {}: {}: destination {}", - expr, - cond != 0, - dest - ); - } - stuff_to_do = true; - } else { - self.reset(); - } - } - } - } - if !stuff_to_do { - break; - } - } - } - - fn run_actions(&mut self, pos: usize, dfa: &DFA, callback: &mut F) -> bool - where - F: FnMut(Event, HashMap), - { - for a in &dfa.verts[pos].actions { - match a { - Action::Set { var, expr } => { - let val = expr.eval(&self.vartab).unwrap(); - trace!("set {} = {} = {}", var, expr, val); - self.vartab.vars.insert(var.to_string(), (val, None)); - } - Action::AssertEq { left, right } => { - if let (Ok(left_val), Ok(right_val)) = - (left.eval(&self.vartab), right.eval(&self.vartab)) - { - if left_val != right_val { - trace!( - "assert FAIL {} != {} ({} != {})", - left, - right, - left_val, - right_val - ); - return false; - } else { - trace!( - "assert {} == {} ({} == {})", - left, - right, - left_val, - right_val - ); - } - } else { - return false; - } - } - Action::Done(event, include) => { - let mut res: HashMap = HashMap::new(); - - for (name, (val, _)) in &self.vartab.vars { - if include.contains(name) { - trace!("done {}", event); - - res.insert(name.to_owned(), *val); - } - } - - (callback)(*event, res); - } - } - } - - true - } - - /// Generate a GraphViz dot file and write to the given path - pub fn dotgraphviz(&self, path: &str, dfa: &DFA) { - crate::graphviz::graphviz(&dfa.verts, "DFA", &[(self.pos, self.vartab.clone())], path); - } -} - -#[cfg(test)] -mod test { - use super::{DFADecoder, InfraredData}; - use crate::{Event, Irp}; - use std::collections::HashMap; - - #[test] - #[ignore] - fn sony8() { - // sony 8 - let irp = Irp::parse("{40k,600}<1,-1|2,-1>(4,-1,F:8,^45m)[F:0..255]").unwrap(); - - let nfa = irp.build_nfa().unwrap(); - let dfa = nfa.build_dfa(); - - let mut res: Vec<(Event, HashMap)> = Vec::new(); - - let mut matcher = DFADecoder::new(100, 3, 20000); - - for ir in InfraredData::from_rawir( - "+2400 -600 +600 -600 +600 -600 +1200 -600 +600 -600 +600 -600 +600 -600 +1200 -600 +1200 -31200").unwrap() { - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - assert_eq!(res.len(), 1); - - let (event, res) = &res[0]; - - assert_eq!(*event, Event::Down); - assert_eq!(res["F"], 196); - } - - #[test] - fn nec() { - let irp = Irp::parse("{38.4k,564}<1,-1|1,-3>(16,-8,D:8,S:8,F:8,~F:8,1,^108m,(16,-4,1,^108m)*) [D:0..255,S:0..255=255-D,F:0..255]").unwrap(); - - let nfa = irp.build_nfa().unwrap(); - let dfa = nfa.build_dfa(); - - let mut res: Vec<(Event, HashMap)> = Vec::new(); - - let mut matcher = DFADecoder::new(100, 3, 20000); - - for ir in InfraredData::from_rawir( - "+9024 -4512 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -564 +564 -39756").unwrap() { - - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - assert_eq!(res.len(), 1); - - let (event, vars) = &res[0]; - - assert_eq!(*event, Event::Down); - assert_eq!(vars["F"], 196); - assert_eq!(vars["D"], 64); - assert_eq!(vars["S"], 191); - - for ir in InfraredData::from_rawir("+9024 -2256 +564 -96156").unwrap() { - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - assert_eq!(res.len(), 2); - - let (event, vars) = &res[1]; - - assert_eq!(*event, Event::Repeat); - assert_eq!(vars["F"], 196); - assert_eq!(vars["D"], 64); - assert_eq!(vars["S"], 191); - - for ir in InfraredData::from_rawir("+9024 -2256 +564 -96156").unwrap() { - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - assert_eq!(res.len(), 3); - - let (event, vars) = &res[2]; - - assert_eq!(*event, Event::Repeat); - assert_eq!(vars["F"], 196); - assert_eq!(vars["D"], 64); - assert_eq!(vars["S"], 191); - - for ir in InfraredData::from_rawir( - "+9024 -4512 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -1692 +564 -564 +564 -1692 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -564 +564 -1692 +564 -564 +564 -39756").unwrap() { - - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - assert_eq!(res.len(), 4); - - let (event, vars) = &res[3]; - - assert_eq!(*event, Event::Down); - // not quite - assert_eq!(vars["F"], 191); - assert_eq!(vars["D"], 59); - assert_eq!(vars["S"], 196); - } - - #[test] - #[ignore] - fn rc5() { - // RC5 - let irp = Irp::parse("{36k,msb,889}<1,-1|-1,1>((1,~F:1:6,T:1,D:5,F:6,^114m)*,T=1-T)[D:0..31,F:0..127,T@:0..1=0]").unwrap(); - - let nfa = irp.build_nfa().unwrap(); - let dfa = nfa.build_dfa(); - - let mut res: Vec<(Event, HashMap)> = Vec::new(); - - let mut matcher = DFADecoder::new(100, 3, 20000); - - for ir in InfraredData::from_rawir( - "+889 -889 +1778 -1778 +889 -889 +889 -889 +889 -889 +1778 -889 +889 -889 +889 -889 +889 -889 +889 -889 +889 -1778 +889 -89997").unwrap() { - - matcher.input(ir, &dfa, |ev, vars| res.push((ev, vars))); - } - - let (event, vars) = &res[0]; - - assert_eq!(*event, Event::Repeat); - assert_eq!(vars["F"], 1); - assert_eq!(vars["D"], 30); - assert_eq!(vars["T"], 0); - } -} diff --git a/irp/src/graphviz.rs b/irp/src/graphviz.rs index 79173e99..6fffa8d2 100644 --- a/irp/src/graphviz.rs +++ b/irp/src/graphviz.rs @@ -1,5 +1,5 @@ use super::{ - build_nfa::{Action, Edge, Vertex}, + build_nfa::{Action, Vertex}, Vartable, }; use itertools::Itertools; @@ -15,37 +15,13 @@ pub(crate) fn graphviz(verts: &[Vertex], name: &str, states: &[(usize, Vartable) let mut vert_names = Vec::new(); for (no, v) in verts.iter().enumerate() { - let name = if v.actions.iter().any(|a| matches!(a, Action::Done(..))) { + let name = if v.entry.iter().any(|a| matches!(a, Action::Done(..))) { format!("done ({no})") } else { format!("{} ({})", no_to_name(vert_names.len()), no) }; - let mut labels: Vec = v - .actions - .iter() - .map(|a| match a { - Action::Set { var, expr } => format!("{var} = {expr}"), - Action::AssertEq { left, right } => format!("assert {left} = {right}",), - Action::Done(event, res) => format!("{} ({})", event, res.iter().join(", ")), - }) - .collect::>(); - - if let Some(Edge::BranchCond { expr, .. }) = v - .edges - .iter() - .find(|e| matches!(e, Edge::BranchCond { .. })) - { - labels.push(format!("cond: {expr}")); - } - - if let Some(Edge::MayBranchCond { expr, .. }) = v - .edges - .iter() - .find(|e| matches!(e, Edge::MayBranchCond { .. })) - { - labels.push(format!("may cond: {expr}")); - } + let mut labels = actions(&v.entry); let color = if let Some((_, vars)) = states.iter().find(|(node, _)| *node == no) { let values = vars @@ -80,63 +56,24 @@ pub(crate) fn graphviz(verts: &[Vertex], name: &str, states: &[(usize, Vartable) for (i, v) in verts.iter().enumerate() { for edge in &v.edges { - match edge { - Edge::Flash { - length, - complete, - dest, - } => writeln!( - &mut file, - "\t\"{}\" -> \"{}\" [label=\"flash {} {}\"]", - vert_names[i], - vert_names[*dest], - length, - if *complete { " complete" } else { "" } - ) - .unwrap(), - Edge::Gap { - length, - complete, - dest, - } => writeln!( + let labels = actions(&edge.actions); + + if !labels.is_empty() { + writeln!( &mut file, - "\t\"{}\" -> \"{}\" [label=\"gap {} {}\"]", + "\t\"{}\" -> \"{}\" [label=\"{}\"]", vert_names[i], - vert_names[*dest], - length, - if *complete { " complete" } else { "" } + vert_names[edge.dest], + labels.join("\\n"), ) - .unwrap(), - Edge::BranchCond { yes, no, .. } => { - writeln!( - &mut file, - "\t\"{}\" -> \"{}\" [label=\"cond: true\"]", - vert_names[i], vert_names[*yes] - ) - .unwrap(); - // - - writeln!( - &mut file, - "\t\"{}\" -> \"{}\" [label=\"cond: false\"]", - vert_names[i], vert_names[*no] - ) - .unwrap(); - } - Edge::MayBranchCond { dest, .. } => { - writeln!( - &mut file, - "\t\"{}\" -> \"{}\" [label=\"may branch\"]", - vert_names[i], vert_names[*dest] - ) - .unwrap(); - } - Edge::Branch(dest) => writeln!( + .unwrap(); + } else { + writeln!( &mut file, "\t\"{}\" -> \"{}\"", - vert_names[i], vert_names[*dest] + vert_names[i], vert_names[edge.dest] ) - .unwrap(), + .unwrap(); } } } @@ -159,3 +96,20 @@ fn no_to_name(no: usize) -> String { } } } + +fn actions(actions: &[Action]) -> Vec { + actions + .iter() + .map(|a| match a { + Action::Flash { length, complete } => { + format!("flash {length} {}", if *complete { "complete" } else { "" }) + } + Action::Gap { length, complete } => { + format!("gap {length} {}", if *complete { "complete" } else { "" }) + } + Action::Set { var, expr } => format!("{var} = {expr}"), + Action::AssertEq { left, right } => format!("assert {left} = {right}",), + Action::Done(event, res) => format!("{} ({})", event, res.iter().join(", ")), + }) + .collect::>() +} diff --git a/irp/src/lib.rs b/irp/src/lib.rs index 566cbb2b..60d6811a 100644 --- a/irp/src/lib.rs +++ b/irp/src/lib.rs @@ -3,8 +3,8 @@ mod build_dfa; mod build_nfa; -mod decoder_dfa; -mod decoder_nfa; +//mod decoder_dfa; +mod decoder; mod encode; mod expression; mod graphviz; @@ -183,7 +183,7 @@ impl ParameterSpec { } /// During IRP evaluation, variables may change their value -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, PartialEq)] pub struct Vartable<'a> { vars: HashMap)>, } @@ -214,8 +214,6 @@ impl fmt::Display for Event { } } -pub use build_dfa::DFA; pub use build_nfa::NFA; -pub use decoder_dfa::DFADecoder; -pub use decoder_nfa::NFADecoder; +pub use decoder::Decoder; use num_rational::Rational64; diff --git a/irp/tests/tests.rs b/irp/tests/tests.rs index bcd139e5..5c22082e 100644 --- a/irp/tests/tests.rs +++ b/irp/tests/tests.rs @@ -1,6 +1,6 @@ use irp::{ protocols::{parse, Protocol}, - Event, InfraredData, Irp, Message, NFADecoder, Vartable, + Decoder, Event, InfraredData, Irp, Message, Vartable, }; use irptransmogrifier::{create_jvm, IrpTransmogrifierRender}; use itertools::Itertools; @@ -456,14 +456,14 @@ fn decode_all() { 20000 }; - let mut decoder = NFADecoder::new(10, 3, max_gap); + let mut decoder = Decoder::new(10, 3, max_gap); let first = if irp.has_ending() { 1 } else { 0 }; for n in first..10 { let repeats = if n < 3 { n } else { rng.gen_range(n..n + 20) }; - decoder.input(InfraredData::Reset, &nfa, |_, _| {}); + decoder.nfa_input(InfraredData::Reset, &nfa, |_, _| {}); let mut vars = Vartable::new(); let mut params = HashMap::new(); @@ -487,7 +487,7 @@ fn decode_all() { let mut decodes = Vec::new(); for data in InfraredData::from_u32_slice(&msg.raw) { - decoder.input(data, &nfa, |ev, res| decodes.push((ev, res))); + decoder.nfa_input(data, &nfa, |ev, res| decodes.push((ev, res))); } let mut ok = false; diff --git a/src/bin/commands/decode.rs b/src/bin/commands/decode.rs index 10b708a5..184804cf 100644 --- a/src/bin/commands/decode.rs +++ b/src/bin/commands/decode.rs @@ -1,6 +1,6 @@ use super::{find_devices, Purpose}; use cir::{lirc, lircd_conf::parse}; -use irp::{DFADecoder, InfraredData, Irp, Message}; +use irp::{Decoder, InfraredData, Irp, Message}; use itertools::Itertools; use log::{error, info, trace}; use std::{ @@ -141,11 +141,11 @@ fn decode_irp(matches: &clap::ArgMatches) { None }; - let mut decoder = DFADecoder::new(abs_tolerance, rel_tolerance, max_gap); + let mut decoder = Decoder::new(abs_tolerance, rel_tolerance, max_gap); let mut feed_decoder = |raw: &[InfraredData]| { for (index, ir) in raw.iter().enumerate() { - decoder.input(*ir, &dfa, |event, var| { + decoder.dfa_input(*ir, &dfa, |event, var| { let mut var: Vec<(String, i64)> = var.into_iter().collect(); var.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); println!( @@ -162,7 +162,7 @@ fn decode_irp(matches: &clap::ArgMatches) { info!("saving nfa at step {} as {}", index, filename); - decoder.dotgraphviz(&filename, &dfa); + decoder.dfa_dotgraphviz(&filename, &dfa); } } }; diff --git a/src/lircd_conf/decode.rs b/src/lircd_conf/decode.rs index c76c2b30..5e45c640 100644 --- a/src/lircd_conf/decode.rs +++ b/src/lircd_conf/decode.rs @@ -1,11 +1,11 @@ use super::{Code, Remote}; -use irp::{InfraredData, Irp, NFADecoder, NFA}; +use irp::{Decoder, InfraredData, Irp, NFA}; use log::debug; pub struct LircDecoder<'a> { pub remote: &'a Remote, pub nfa: NFA, - pub decoder: NFADecoder<'a>, + pub decoder: Decoder<'a>, } impl Remote { @@ -19,7 +19,7 @@ impl Remote { let nfa = irp.build_nfa().unwrap(); - let decoder = NFADecoder::new( + let decoder = Decoder::new( abs_tolerance.max(self.aeps as u32), rel_tolerance.max(self.eps as u32), max_gap, @@ -38,7 +38,7 @@ impl<'a> LircDecoder<'a> { where F: FnMut(u64, Option<&'a Code>), { - self.decoder.input(ir, &self.nfa, |_, vars| { + self.decoder.nfa_input(ir, &self.nfa, |_, vars| { let decoded = vars["CODE"] as u64; callback(