dsntk_recognizer/
builder.rs

1//! # Decision table builder
2
3use crate::errors::*;
4use crate::recognizer::Recognizer;
5use crate::{AnnotationEntry, DecisionRule, DecisionTable, HitPolicy, InputClause, InputEntry, OutputClause, OutputEntry, RuleAnnotationClause};
6use dsntk_common::Result;
7
8struct Size {
9  input_clauses_count: usize,
10  input_values_count: usize,
11  output_clauses_count: usize,
12  output_components_count: usize,
13  output_values_count: usize,
14  annotation_clauses_count: usize,
15  rule_count: usize,
16}
17
18fn size_err(details: &str) -> Result<Size> {
19  Err(err_invalid_size(details))
20}
21
22/// Validates the sizes of the individual components of the decision table in relation to each other.
23fn validate_size(recognizer: &Recognizer) -> Result<Size> {
24  // decision table may have zero or more input clauses
25  let input_clauses_count = recognizer.input_clause_count;
26  // number of input expressions must be equal to the number of input clauses
27  let input_expression_count = recognizer.input_expressions.len();
28  if input_expression_count != input_clauses_count {
29    return size_err(&format!(
30      "number of input expressions ({input_expression_count}) must be equal to the number of input clauses ({input_clauses_count})"
31    ));
32  }
33  // when input values are present, then the number of input values must be equal to the number input expressions
34  let input_values_count = recognizer.allowed_input_values.len();
35  if input_values_count > 0 && input_values_count != input_clauses_count {
36    return size_err(&format!(
37      "number of input values ({input_values_count}) must be equal to the number of input clauses ({input_clauses_count})"
38    ));
39  }
40  // decision table must have minimum one output clause
41  let output_clauses_count = recognizer.output_clause_count;
42  if output_clauses_count == 0 {
43    return size_err("decision table must have minimum one output clause");
44  }
45  // the number of the output components must be equal to the number of output clauses when more than one output clause is present
46  let output_components_count = recognizer.output_components.len();
47  if output_clauses_count > 1 {
48    if output_components_count != output_clauses_count {
49      return size_err(&format!(
50        "number of output components ({output_components_count}) must be equal to the number of output clauses ({output_clauses_count})"
51      ));
52    }
53  } else if output_components_count != 0 {
54    return size_err("number of output components must be zero");
55  }
56  // when output values are present, then the number of output values must be equal to the number of output clauses
57  let output_values_count = recognizer.allowed_output_values.len();
58  if output_values_count > 0 && output_values_count != output_clauses_count {
59    return size_err(&format!(
60      "number of output values ({output_values_count}) must be equal to the number of output clauses ({output_clauses_count})"
61    ));
62  }
63  // decision table must contain minimum one rule
64  let rule_count = recognizer.rule_count;
65  if rule_count == 0 {
66    return size_err("decision table must contain minimum one rule");
67  }
68  // number of rows of input entries must be equal to the number of rules
69  let input_entries_row_count = recognizer.input_entries.len();
70  if input_entries_row_count != rule_count {
71    return size_err(&format!(
72      "number of input entries ({input_entries_row_count}) must be equal to the number of rules ({rule_count})",
73    ));
74  }
75  // number of input entries in each row must be equal to the number of input clauses
76  for (row_index, row) in recognizer.input_entries.iter().enumerate() {
77    if row.len() != input_clauses_count {
78      return size_err(&format!(
79        "number of input entries ({}) must be equal to the number of input clauses ({input_clauses_count}) in row {row_index}",
80        row.len(),
81      ));
82    }
83  }
84  // number of rows of output entries must be equal to the number of rules
85  let output_entries_row_count = recognizer.output_entries.len();
86  if output_entries_row_count != rule_count {
87    return size_err(&format!(
88      "number of output entries ({output_entries_row_count}) must be equal to the number of rules ({rule_count})"
89    ));
90  }
91  // number of output entries in each row must be equal to the number of output clauses
92  for (row_index, row) in recognizer.output_entries.iter().enumerate() {
93    if row.len() != output_clauses_count {
94      return size_err(&format!(
95        "number of output entries ({}) must be equal to the number of output clauses ({output_clauses_count}) in row {row_index}",
96        row.len()
97      ));
98    }
99  }
100  // decision table may contain some annotations
101  let annotation_clauses_count = recognizer.annotation_clause_count;
102  if annotation_clauses_count > 0 {
103    // number of rows of annotation entries must be equal to the number of rules
104    let annotation_entries_row_count = recognizer.annotation_entries.len();
105    if annotation_entries_row_count != rule_count {
106      return size_err(&format!(
107        "number of annotation entries ({annotation_entries_row_count}) must be equal to the number of rules ({rule_count})"
108      ));
109    }
110    // number of annotation entries in each row must be equal to the number of annotation clauses
111    for (row_index, row) in recognizer.annotation_entries.iter().enumerate() {
112      if row.len() != annotation_clauses_count {
113        return size_err(&format!(
114          "number of annotation entries ({}) must be equal to the number of annotation clauses ({}) in row {}",
115          row.len(),
116          annotation_clauses_count,
117          row_index
118        ));
119      }
120    }
121  }
122  Ok(Size {
123    input_clauses_count,
124    input_values_count,
125    output_clauses_count,
126    output_components_count,
127    output_values_count,
128    annotation_clauses_count,
129    rule_count,
130  })
131}
132
133/// Recognizes a decision table defined as plain Unicode text.
134pub fn recognize(text: &str, trace: bool) -> Result<DecisionTable> {
135  // recognize the decision table
136  let recognizer = Recognizer::recognize(text, trace)?;
137
138  // validate the sizes of the individual parts of the decision table
139  let size = validate_size(&recognizer)?;
140
141  let information_item_name = recognizer.information_item_name.clone();
142  let hit_policy = recognizer.hit_policy;
143  let aggregation = if let HitPolicy::Collect(built_in_aggregator) = hit_policy {
144    Some(built_in_aggregator)
145  } else {
146    None
147  };
148  let preferred_orientation = recognizer.orientation;
149  let output_label = recognizer.output_label.clone();
150
151  let mut input_clauses = vec![];
152  for i in 0..size.input_clauses_count {
153    input_clauses.push(InputClause {
154      input_expression: recognizer.input_expressions[i].clone(),
155      allowed_input_values: if size.input_values_count > 0 {
156        recognizer.allowed_input_values[i].clone()
157      } else {
158        None
159      },
160    });
161  }
162
163  let mut output_clauses = vec![];
164  for i in 0..size.output_clauses_count {
165    output_clauses.push(OutputClause {
166      name: if size.output_components_count > 0 {
167        recognizer.output_components[i].clone()
168      } else {
169        None
170      },
171      allowed_output_values: if size.output_values_count > 0 {
172        recognizer.allowed_output_values[i].clone()
173      } else {
174        None
175      },
176      default_output_entry: None,
177    });
178  }
179
180  let mut annotations = vec![];
181  for i in 0..recognizer.annotation_clause_count {
182    annotations.push(RuleAnnotationClause {
183      name: recognizer.annotations[i].clone(),
184    });
185  }
186
187  let mut rules = vec![];
188  for rule_index in 0..size.rule_count {
189    let mut input_entries = vec![];
190    for column_index in 0..size.input_clauses_count {
191      let input_entry = InputEntry {
192        text: recognizer.input_entries[rule_index][column_index].clone(),
193      };
194      input_entries.push(input_entry);
195    }
196    let mut output_entries = vec![];
197    for column_index in 0..size.output_clauses_count {
198      let output_entry = OutputEntry {
199        text: recognizer.output_entries[rule_index][column_index].clone(),
200      };
201      output_entries.push(output_entry);
202    }
203    let mut annotation_entries = vec![];
204    for column_index in 0..size.annotation_clauses_count {
205      let annotation_entry = AnnotationEntry {
206        text: recognizer.annotation_entries[rule_index][column_index].clone(),
207      };
208      annotation_entries.push(annotation_entry);
209    }
210    rules.push(DecisionRule {
211      input_entries,
212      output_entries,
213      annotation_entries,
214    });
215  }
216
217  Ok(DecisionTable::new(
218    information_item_name,
219    input_clauses,
220    output_clauses,
221    annotations,
222    rules,
223    hit_policy,
224    aggregation,
225    preferred_orientation,
226    output_label,
227  ))
228}