Skip to content

Commit

Permalink
New NFA style more suited to DFA building
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
seanyoung committed Mar 4, 2024
1 parent 0912c86 commit c5641b0
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 963 deletions.
6 changes: 3 additions & 3 deletions irp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand All @@ -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"]);
});
}
Expand Down
253 changes: 80 additions & 173 deletions irp/src/build_dfa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vertex>,
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -192,61 +130,71 @@ 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 });
}
}

fn path_length(&self, path: &[Path]) -> Option<Rc<Expression>> {
let mut len: Option<Rc<Expression>> = 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<Action> {
fn path_actions(
&self,
path: &[Path],
flash: bool,
length: Option<Rc<Expression>>,
) -> Vec<Action> {
let mut res: Vec<Action> = 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
Expand All @@ -265,100 +213,59 @@ impl<'a> Builder<'a> {
res.push(p);
}

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<Path> {
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();
}
_ => (),
}
}
res
}

fn get_input_edges(&self, pos: usize, flash: bool) -> Vec<Path> {
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<Path> {
fn get_non_input_edges(&self, pos: usize) -> Vec<Path> {
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<Path>, res: &mut Vec<Vec<Path>>) {
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 {
Expand Down
Loading

0 comments on commit c5641b0

Please sign in to comment.