ARB
saiop.cxx
Go to the documentation of this file.
1 // ========================================================= //
2 // //
3 // File : saiop.cxx //
4 // Purpose : operations on SAI //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in Oct 19 //
7 // http://www.arb-home.de/ //
8 // //
9 // ========================================================= //
10 
11 #include "saiop.h"
12 
13 #include <arb_msg.h>
14 #include <ConfigMapping.h>
15 #include <gb_aci.h>
16 
17 using namespace std;
18 
19 // --------------------
20 // SaiCalcEnv
21 
22 GB_ERROR SaiCalcEnv::check_lengths_equal(size_t& len) const { // @@@ also use in SaiAciApplicator
23  if (input.empty()) {
24  return "missing input data";
25  }
26  len = strlen(input[0]);
27  for (unsigned i = 1; i<input.size(); ++i) {
28  size_t otherLen = strlen(input[i]);
29  if (otherLen != len) {
30  return GBS_global_string("length mismatch in input data (%zu <> %zu)", len, otherLen);
31  }
32  }
33  return NULp;
34 }
35 
36 // ---------------------
37 // SaiOperator
38 
39 const char *SaiOperator::typeName[] = {
40  "ACI",
41  "Translator",
42  "Matrix",
43  "Bool chain",
44 };
45 
47  switch (type) {
48  case SOP_TRANSLATE: return SaiTranslator::make(config);
49  case SOP_MATRIX: return SaiMatrixTranslator::make(config);
50  case SOP_BOOLCHAIN: return SaiBoolchainOperator::make(config);
51  case SOP_ACI: return SaiAciApplicator::make(config);
52  }
53  return ErrorOrSaiOperatorPtr("can't make SaiOperator of that type (yet)", SaiOperatorPtr());
54 }
55 
57  string desc = type_name(get_type());
58 
59  desc += ": ";
60  desc += get_config(); // append config string
61 
62  return desc;
63 }
64 
65 // -----------------------
66 // SaiTranslator
67 
68 void SaiTranslator::addTranslation(const char *from, char to) {
69  for (size_t o = 0; from[o]; ++o) {
70  transtab[safeCharIndex(from[o])] = to;
71  }
72 }
73 
74 ErrorOrString SaiTranslator::apply(const SaiCalcEnv& calcEnv) const {
76  string result;
77 
78  const CharPtrArray& input = calcEnv.get_input();
79  if (input.size() != 1) {
80  error = GBS_global_string("translator applies to single SAI only (have: %zu)", input.size());
81  }
82  else {
83  const char *in = input[0];
84  size_t length = strlen(in);
85 
86  result.resize(length);
87  for (size_t o = 0; o<length; ++o) {
88  result[o] = transtab[safeCharIndex(in[o])];
89  }
90  }
91  return ErrorOrString(error, result);
92 }
93 
94 void SaiTranslator::deduceTranslations(class ConfigMapping& mapping) const {
95  // count occurrences of target characters in 'transtab'
96  uint8_t count[256];
97  for (int i = 0; i<256; ++i) count[i] = 0;
98  for (int i = 1; i<256; ++i) ++count[safeCharIndex(transtab[i])];
99 
100  // detect default translation (=max used)
101  int maxCount = 0;
102  unsigned char defaultTranslation = 0;
103  for (int i = 1; i<256; ++i) {
104  if (count[i]>maxCount) {
105  maxCount = count[i];
106  defaultTranslation = (unsigned char)i;
107  }
108  }
109 
110  mapping.set_entry("default", GBS_global_string("%c", defaultTranslation));
111 
112  int transCount = 0;
113  for (int i = 1; i<256; ++i) {
114  if (count[i]>0 && i != defaultTranslation) {
115  unsigned char transTo = (unsigned char)i;
116  string transFrom(1, transTo); // first character is target-char; rest are source-chars
117 
118  for (int j = 1; j<256; ++j) {
119  if (transtab[j] == transTo) {
120  transFrom += (unsigned char)j;
121  }
122  }
123 
124  sai_assert(transFrom.length()>0);
125  mapping.set_entry(GBS_global_string("trans%i", ++transCount), transFrom);
126  }
127  }
128 }
129 
130 string SaiTranslator::get_config() const {
131  ConfigMapping cfgmap;
132  deduceTranslations(cfgmap);
133  return cfgmap.config_string();
134 }
135 
136 ErrorOrSaiOperatorPtr SaiTranslator::make(const char *config) {
138  ConfigMapping cfgmap;
139  ARB_ERROR error = cfgmap.parseFrom(config);
140 
141  if (!error) {
142  const char *defTrans = cfgmap.get_entry("default");
143  if (defTrans) {
144  if (defTrans[0] && !defTrans[1]) { // expect exactly 1 char
145  SaiTranslator *translator = new SaiTranslator(defTrans[0]);
146 
147  int transCount = 0;
148  while (!error) {
149  const char *entry = GBS_global_string("trans%i", ++transCount);
150  const char *trans = cfgmap.get_entry(entry);
151  if (!trans) break;
152 
153  if (trans[0] && trans[1]) {
154  translator->addTranslation(trans+1, trans[0]);
155  }
156  else {
157  error = GBS_global_string("invalid content '%s' in config entry '%s'", trans, entry);
158  }
159  }
160 
161  if (error) {
162  delete translator;
163  }
164  else {
165  result = translator;
166  }
167  }
168  else {
169  error = GBS_global_string("invalid content '%s' in config entry 'default'", defTrans);
170  }
171  }
172  else {
173  error = "missing 'default' entry";
174  }
175  }
176 
177  return ErrorOrSaiOperatorPtr(error, result);
178 }
179 
180 // -----------------------------
181 // SaiMatrixTranslator
182 
183 std::string SaiMatrixTranslator::get_config() const {
184  ConfigMapping cfgmap;
185  cfgmap.set_entry("first", firstToIndexChar->get_config());
186  cfgmap.set_entry("columns", GBS_global_string("%zu", secondToResult.size()));
187  for (size_t s = 0; s<secondToResult.size(); ++s) {
188  string key = GBS_global_string("col%zu", s);
189  cfgmap.set_entry(key, secondToResult[s]->get_config());
190  }
191  return cfgmap.config_string();
192 }
193 
194 ErrorOrString SaiMatrixTranslator::apply(const SaiCalcEnv& calcEnv) const {
196  string result;
197 
198  const CharPtrArray& input = calcEnv.get_input();
199  if (input.size() != 2) {
200  error = GBS_global_string("matrix translator applies to a pair of SAIs only (have: %zu)", input.size());
201  }
202  else {
203  size_t length;
204  error = calcEnv.check_lengths_equal(length); // fails if both SAIs do not match in length
205  if (!error) {
206  result.resize(length);
207 
208  const CharPtrArray& sai = calcEnv.get_input();
209 
210  // translate 1st sai using firstToIndexChar:
211  ConstStrArray sai1;
212  sai1.put(sai[0]);
213 
214  SaiCalcEnv env1(sai1, calcEnv.get_gbmain());
215 
216  ErrorOrString trans1 = firstToIndexChar->apply(env1);
217  if (trans1.hasError()) {
218  error = trans1.getError();
219  }
220  else {
221  string tindex = trans1.getValue(); // index into 'secondToResult' (to select translator defined by matrix column)
222  const size_t idxCount = secondToResult.size();
223 
224  vector<string> trans2;
225  trans2.reserve(idxCount);
226 
227  // foreach entry in secondToResult -> translate 2nd sai using that entry (+store in array):
228  {
229  ConstStrArray sai2;
230  sai2.put(sai[1]);
231 
232  SaiCalcEnv env2(sai2, calcEnv.get_gbmain());
233 
234  for (size_t i = 0; i<idxCount && !error; ++i) {
235  ErrorOrString t = secondToResult[i]->apply(env2);
236  if (t.hasError()) {
237  error = t.getError();
238  }
239  else {
240  trans2.push_back(t.getValue());
241  }
242  }
243  }
244 
245  if (!error) {
246  sai_assert(trans2.size() == idxCount);
247 
248  // iterate over sai positions and read result from translation "matrix":
249  for (size_t o = 0; o<length; ++o) {
250  int idx = safeCharIndex(tindex[o]) - DEFAULT_INDEX_CHAR;
251  sai_assert(idx>=0 && size_t(idx)<idxCount);
252  result[o] = trans2[idx][o];
253  }
254  }
255  }
256  }
257  }
258  return ErrorOrString(error, result);
259 }
260 
261 void SaiMatrixTranslator::addOperator(const char *from, SaiOperatorPtr to) {
262  size_t meta = secondToResult.size()+DEFAULT_INDEX_CHAR;
263  sai_assert(meta < 256);
264  dynamic_cast<SaiTranslator*>(&*firstToIndexChar)->addTranslation(from, char(meta));
265  secondToResult.push_back(to);
266 }
267 
268 ErrorOrSaiOperatorPtr SaiMatrixTranslator::make(const char *config) {
270  ConfigMapping cfgmap;
271  ARB_ERROR error = cfgmap.parseFrom(config);
272 
273  if (!error) {
274  const char *first = cfgmap.get_entry("first");
275  if (first) {
277  if (product.hasValue()) {
278  SaiMatrixTranslator *smt = new SaiMatrixTranslator;
279  smt->firstToIndexChar = product.getValue(); // overwrite first translator
280 
281  const char *columnsStr = cfgmap.get_entry("columns");
282  if (columnsStr) {
283  int columns = atoi(columnsStr);
284  if (columns>0) {
285  sai_assert(smt->secondToResult.size() == 0);
286 
287  for (int c = 0; c<columns && !error; ++c) {
288  string key = GBS_global_string("col%i", c);
289  const char *col = cfgmap.get_entry(key.c_str());
290  if (col) {
292  if (colProduct.hasValue()) {
293  smt->secondToResult.push_back(colProduct.getValue()); // (compare: addOperator)
294  }
295  else {
296  error = GBS_global_string("%s (in '%s'; entry '%s')", colProduct.getError().deliver(), col, key.c_str());
297  }
298  }
299  else {
300  error = GBS_global_string("missing '%s' entry", key.c_str());
301  }
302  }
303  }
304  else {
305  error = GBS_global_string("entry 'columns' has to be 1 or higher (have: '%s')", columnsStr);
306  }
307  }
308  else {
309  error = "missing 'columns' entry";
310  }
311 
312  result = smt;
313  }
314  else {
315  error = GBS_global_string("%s (in '%s'; entry 'first')", product.getError().deliver(), first);
316  }
317  }
318  else {
319  error = "missing 'first' entry";
320  }
321  }
322  return ErrorOrSaiOperatorPtr(error, result);
323 }
324 
325 // ---------------------
326 // SaiBoolRule
327 
328 void SaiBoolRule::apply(char *inout, const char *in, size_t len) const {
329  for (size_t i = 0; i<len; ++i) {
330  bool a = inout[i]-'0';
331  bool b = in[i]-'0';
332  bool c = false;
333 
334  switch (op) {
335  case SBO_FIRST: sai_assert(0); break; // cannot be applied
336  case SBO_AND: c = a && b; break;
337  case SBO_OR: c = a || b; break;
338  case SBO_XOR: c = a ^ b; break;
339  case SBO_NAND: c = !(a && b); break;
340  case SBO_NOR: c = !(a || b); break;
341  case SBO_XNOR: c = !(a ^ b); break;
342  }
343 
344  inout[i] = c ? '1' : '0';
345  }
346 }
347 
348 static const char *opname[] = { // @@@ rename variable
349  "-->",
350  "AND",
351  "OR",
352  "XOR",
353  "NAND",
354  "NOR",
355  "XNOR",
356  NULp
357 };
358 
359 string SaiBoolRule::to_string() const {
360  // used to save into config AND used for display in rule selection list
361  string result = opname[op];
362  const size_t MAXLEN = 4;
363 
364 #if defined(ASSERTION_USED)
365  const size_t rlen = result.length();
366 #endif
367 
368  sai_assert(rlen<=MAXLEN);
369 
370  // align charset definitions at same column (to beautify list display)
371  for (size_t p = result.length(); p<(MAXLEN+1); ++p) {
372  result += ' ';
373  }
374 
375  result += specifyTrueChars ? '[' : ']';
376  result += chars;
377  result += specifyTrueChars ? ']' : '[';
378 
379  return result;
380 }
381 
382 ErrorOrSaiBoolRulePtr SaiBoolRule::make(const char *fromString) {
383  // convert 'fromString' created by to_string() back to SaiBoolRule
384  SaiBoolRulePtr product;
386 
387  if (!fromString) {
388  error = "no input string";
389  }
390  else {
391  const char *space1 = strchr(fromString, ' ');
392  bool appendFrom = true;
393 
394  if (!space1) {
395  error = "expected at least one space character";
396  }
397  else {
398  int tok1len = space1-fromString;
399 
400  int op;
401  for (op = 0; opname[op]; ++op) {
402  if (strncmp(opname[op], fromString, tok1len) == 0) {
403  break;
404  }
405  }
406 
407  if (!opname[op]) {
408  char *tok = ARB_strpartdup(fromString, space1-1);
409  error = GBS_global_string("unknown operator token '%s'", tok);
410  free(tok);
411  }
412  else {
413  const char *rest = space1;
414  while (rest[0] == ' ') ++rest; // eat all spaces
415 
416  if (!rest[0]) {
417  error = "truncated input string";
418  }
419  else {
420  bool specTrue = rest[0] == '[';
421  if (!specTrue && rest[0] != ']') {
422  error = GBS_global_string("Expected '[' or ']', found '%c'", rest[0]);
423  }
424  else {
425  char *chars = strdup(rest+1);
426  int lastPos = strlen(chars)-1;
427 
428  if (lastPos<0) {
429  error = "character specification too short";
430  }
431  else {
432  char lastExpected = rest[0] == '[' ? ']' : '[';
433 
434  if (chars[lastPos] != lastExpected) {
435  error = GBS_global_string("expected character specification to be terminated by '%c' (seen '%c')", lastExpected, chars[lastPos]);
436  }
437  else {
438  chars[lastPos] = 0; // truncate last char
439  product = new SaiBoolRule(SaiBoolOp(op), specTrue, chars);
440  }
441  }
442  free(chars);
443  }
444  }
445  }
446  }
447 
448  if (error && appendFrom) {
449  error = GBS_global_string("%s in '%s'", error.deliver(), fromString);
450  }
451  }
452 
453  return ErrorOrSaiBoolRulePtr(error, product);
454 }
455 
456 static ErrorOrSaiBoolRulePtr makeFromConfigRule(const ConfigMapping& cfgmap, int ruleNr) {
457  const char *rulename = GBS_global_string("rule%i", ruleNr);
458  const char *rule = cfgmap.get_entry(rulename);
459 
460  SaiBoolRulePtr noResult;
461  if (!rule) {
462  ARB_ERROR error = GBS_global_string("expected entry '%s' is missing", rulename);
463  return ErrorOrSaiBoolRulePtr(error, noResult);
464  }
465 
467  if (result.hasError()) {
468  ARB_ERROR error = GBS_global_string("%s (during production of 'rule%i')", result.getError().deliver(), ruleNr);
469  return ErrorOrSaiBoolRulePtr(error, noResult);
470  }
471 
472  return result;
473 }
474 
475 // ------------------------------
476 // SaiBoolchainOperator
477 
479  ConfigMapping cfgmap;
480  cfgmap.set_entry("out", GBS_global_string("%c%c", outTrans[0], outTrans[1]));
481  cfgmap.set_entry("rules", GBS_global_string("%zu", rule.size()));
482  for (size_t r = 0; r<rule.size(); ++r) {
483  string key = GBS_global_string("rule%zu", r);
484  cfgmap.set_entry(key, rule[r].to_string());
485  }
486  return cfgmap.config_string();
487 }
488 
490  string result;
491  size_t sailen;
492  ARB_ERROR error = calcEnv.check_lengths_equal(sailen);
493 
494  if (!error) {
495  const CharPtrArray& input = calcEnv.get_input();
496 
497  const size_t saiCount = input.size();
498  const size_t ruleCount = rule.size();
499 
500  if (ruleCount<1) {
501  error = "need at least one rule in chain";
502  }
503  else if (saiCount != ruleCount) {
504  error = GBS_global_string("number of input SAIs has to match number of rules (%zu <> %zu)", saiCount, ruleCount);
505  }
506  else {
507  char buffer[sailen+1];
508  rule[0].prepare_input_data(input[0], sailen, buffer);
509 
510  for (size_t r = 1; r<ruleCount; ++r) {
511  char othBuf[sailen+1];
512  rule[r].prepare_input_data(input[r], sailen, othBuf);
513  rule[r].apply(buffer, othBuf, sailen);
514  }
515 
516  // translate 01 into wanted output characters:
517  for (size_t i = 0; i<sailen; ++i) {
518  buffer[i] = outTrans[buffer[i]-'0'];
519  }
520 
521  result = buffer;
522  }
523  }
524 
525  return ErrorOrString(error, result);
526 }
527 
530  ConfigMapping cfgmap;
531  ARB_ERROR error = cfgmap.parseFrom(config);
532 
533  if (!error) {
534  const char *rulesStr = cfgmap.get_entry("rules");
535  const char *out = cfgmap.get_entry("out");
536 
537  if (!rulesStr) error = "expected 'rules' entry missing";
538  else if (!out) error = "expected 'out' entry missing";
539  else {
540  int rules = atoi(rulesStr);
541  char out0 = out[0] ? out[0] : '-';
542  char out1 = out[0] && out[1] ? out[1] : 'x';
543 
544  if (rules<1) { // no rule in config -> create default op
545  result = new SaiBoolchainOperator(out0, out1);
546  }
547  else {
548  ErrorOrSaiBoolRulePtr first = makeFromConfigRule(cfgmap, 0); // uses 'rule0'
549 
550  if (first.hasError()) {
551  error = first.getError();
552  }
553  else {
554  SaiBoolRulePtr rule = first.getValue();
555  if (rule->get_op() != SBO_FIRST) {
556  error = GBS_global_string("wrong type in 'rule0' (expected: '%s'; got: '%s')", opname[SBO_FIRST], opname[rule->get_op()]);
557  }
558  else {
559  SaiBoolchainOperator *sbo = new SaiBoolchainOperator(out0, out1);
560  sbo->addRule(*rule); // add 1st rule
561 
562  // add other rules:
563  for (int r = 1; r<rules && !error; ++r) {
564  ErrorOrSaiBoolRulePtr next = makeFromConfigRule(cfgmap, r); // uses 'rule1' and following
565  if (next.hasError()) {
566  error = next.getError();
567  }
568  else {
569  rule = next.getValue();
570  if (rule->get_op() == SBO_FIRST) {
571  error = GBS_global_string("wrong type in 'rule%i' (may not be '%s')", r, opname[SBO_FIRST]);
572  }
573  else {
574  sbo->addRule(*rule);
575  }
576  }
577  }
578 
579  result = sbo;
580  }
581  }
582  }
583  }
584  }
585 
586  return ErrorOrSaiOperatorPtr(error, result);
587 }
588 
589 // --------------------------
590 // SaiAciApplicator
591 
593  ConfigMapping cfgmap;
594  cfgmap.set_entry("aci", aci);
595  return cfgmap.config_string();
596 }
597 
600  ConfigMapping cfgmap;
601  ARB_ERROR error = cfgmap.parseFrom(config);
602 
603  if (!error) {
604  const char *defAci = cfgmap.get_entry("aci");
605  if (defAci) {
606  SaiAciApplicator *aciOp = new SaiAciApplicator(defAci);
607  result = aciOp;
608  }
609  else {
610  error = "missing 'aci' entry";
611  }
612  }
613 
614  return ErrorOrSaiOperatorPtr(error, result);
615 }
616 
619  string result;
620 
621  const CharPtrArray& input = calcEnv.get_input();
622 
623  size_t len[input.size()];
624  size_t maxlen = 0;
625 
626  for (int i = 0; input[i]; ++i) {
627  len[i] = strlen(input[i]);
628  maxlen = std::max(len[i], maxlen);
629  }
630 
631  if (maxlen == 0) {
632  error = "no input data";
633  }
634  else {
635  GBL_maybe_itemless_call_env callEnv(calcEnv.get_gbmain(), NULp);
636 
637  for (size_t p = 0; p<maxlen && !error; ++p) {
638  string toAci;
639  for (int i = 0; input[i]; ++i) {
640  if (p<len[i]) {
641  toAci += input[i][p];
642  }
643  }
644 
645  char *fromAci = GB_command_interpreter_in_env(toAci.c_str(), aci.c_str(), callEnv);
646  if (fromAci) {
647  size_t fromAciLen = strlen(fromAci);
648  if (fromAciLen == 1) {
649  result += fromAci;
650  }
651  else {
652  error = GBS_global_string("Expected single character result from ACI (but received %zu chars; while '%s' -> '%s')",
653  fromAciLen, toAci.c_str(), fromAci);
654  }
655  free(fromAci);
656  }
657  else {
658  error = GB_await_error();
659  }
660  }
661  }
662 
663  return ErrorOrString(error, result);
664 }
665 
666 // --------------------------------------------------------------------------------
667 
668 #ifdef UNIT_TESTS
669 #ifndef TEST_UNIT_H
670 #include <test_unit.h>
671 #endif
672 
673 #define TEST_EXPECT_APPLY_RESULT(op,env,expected) do{ \
674  ErrorOrString result = (op)->apply(env); \
675  TEST_EXPECT(result.hasValue()); \
676  string output = result.getValue(); \
677  TEST_EXPECT_EQUAL(output, expected); \
678  TEST_EXPECT_EQUAL(output.length(), strlen((env).get_input()[0])); \
679  }while(0)
680 
681 
682 #define TEST_EXPECT_APPLY_FAILURE(op,env,errorPart) do{ \
683  ErrorOrString result = (op)->apply(env); \
684  TEST_EXPECT(result.hasError()); \
685  TEST_EXPECT_CONTAINS(result.getError().deliver(), errorPart); \
686  }while(0)
687 
688 #define TEST_OPERATOR_PRODUCTION_FAILS(optype,cfg,errorPart) do{ \
689  ErrorOrSaiOperatorPtr product = SaiOperator::make(optype, cfg); \
690  TEST_EXPECT(product.hasError()); \
691  TEST_EXPECT_CONTAINS(product.getError().deliver(), errorPart); \
692  }while(0)
693 
694 
695 // TEST_OP_CFG_CONV_BIJECTIVE does
696 // - create + test config from 'op'
697 // - ask factory to re-create operator from config
698 // - test reloaded operator (has same type, produces same config and operates the same result when applied to 'env')
699 // (Note: TEST_OP_CFG_CONV_BIJECTIVE_SIMPLE doesn't apply)
700 
701 #define TEST_OCCB_COMMONCODE(op,cfgExpected) \
702  const string cfg = (op)->get_config(); \
703  TEST_EXPECT_EQUAL(cfg, cfgExpected); \
704  ErrorOrSaiOperatorPtr product = SaiOperator::make((op)->get_type(), cfg.c_str()); \
705  TEST_EXPECT(product.hasValue()); \
706  SaiOperatorPtr op_reloaded = product.getValue(); \
707  TEST_EXPECT_EQUAL(op_reloaded->get_type(), (op)->get_type()); \
708  const string cfg_reloaded = op_reloaded->get_config(); \
709  TEST_EXPECT_EQUAL(cfg_reloaded, cfg)
710 
711 #define TEST_OP_CFG_CONV_BIJECTIVE_SIMPLE(op,cfgExpected) do{ \
712  TEST_OCCB_COMMONCODE(op,cfgExpected); \
713  }while(0)
714 
715 #define TEST_OP_CFG_CONV_BIJECTIVE(op,cfgExpected,env) do{ \
716  TEST_OCCB_COMMONCODE(op,cfgExpected); \
717  ErrorOrString result = (op)->apply(env); \
718  ErrorOrString result_reloaded = op_reloaded->apply(env); \
719  TEST_EXPECT_EQUAL(result.hasValue(), result_reloaded.hasValue()); \
720  if (result.hasValue()) TEST_EXPECT_EQUAL(result.getValue(), result_reloaded.getValue()); \
721  else TEST_EXPECT_EQUAL(result.getError().deliver(), result_reloaded.getError().deliver()); \
722  }while(0)
723 
724 void TEST_SaiTranslator() {
725  SaiOperatorPtr op;
726 
727  // test simple translator
728  SaiTranslator *st1 = new SaiTranslator('-');
729  op = st1;
730 
732 
733  ConstStrArray input;
734  input.put("[[..]]]]....]>>>>>].]>>>>].[<<[..[<<[....]>>]");
735 
736  SaiCalcEnv env(input, NULp); // we can fake gb_main here
737 
738  // apply operator + test results:
739  TEST_EXPECT_APPLY_RESULT(op, env, "---------------------------------------------");
740 
741  // test some real translation:
742  st1->addTranslation("]>", ')');
743  st1->addTranslation("<[", '(');
744 
745  // ------------------------------ "[[..]]]]....]>>>>>].]>>>>].[<<[..[<<[....]>>]"
746  TEST_EXPECT_APPLY_RESULT(op, env, "((--))))----)))))))-))))))-((((--((((----))))");
747 
748  // provoke apply-failure:
749  input.put("whatever"); // provokes: too many input streams for translator
750  TEST_EXPECT_APPLY_FAILURE(op, env, "translator applies to single SAI only (have: 2)"); // @@@ clumsy message; test for no SAI is in calculator.cxx@CLUMSYMSG
751  input.remove(1); // again remove 'whatever'
752 
753  // test conversion op->config->op + test both ops operate identical:
754  TEST_OP_CFG_CONV_BIJECTIVE(op, "default='-';trans1='(<[';trans2=')>]'", env);
755 
756  // try to reload some invalid configs (test error cases):
757  TEST_OPERATOR_PRODUCTION_FAILS(SOP_TRANSLATE, "", "missing 'default' entry");
758  TEST_OPERATOR_PRODUCTION_FAILS(SOP_TRANSLATE, "default='xxx'", "invalid content 'xxx' in config entry 'default'");
759  TEST_OPERATOR_PRODUCTION_FAILS(SOP_TRANSLATE, "default='x';trans1='z'", "invalid content 'z' in config entry 'trans1'");
760 }
761 
762 void TEST_SaiMatrixTranslator() {
763  SaiOperatorPtr op;
764 
765  // test simple matrix translator
766  SaiTranslator *st1 = new SaiTranslator('.');
767  SaiMatrixTranslator *smt1 = new SaiMatrixTranslator(st1);
768  op = smt1;
769 
771 
772  // create + restore default config:
773  TEST_OP_CFG_CONV_BIJECTIVE_SIMPLE(op, "col0='default=\\'.\\'';columns='1';first='default=\\'A\\''");
774 
775  // helix numbers: 1 2 2 3 3 4 5 5 4 1
776  const char *h1235 = "..[.<<<...<<<<<[......[<<[...]>>]......[<<<<..<...<<[....]>>....>.>>>].........[<<[....]>>>]..........]>>>>.>>>>].";
777  const char *h1345 = "..[.<<<...<<<<[........................[<.<<..<<<<<<[....]>>>>>>>.>>.]....[<[..[<<<[...]>>]...]>]......]>>>>.>>>].";
778  const char *res01 = "..[.<<<...<<<<<[......[<<[...]>>]......[<<<<..<<<<<<[....]>>>>>>>.>>>]....[<[..[<<<[...]>>>]..]>].....]>>>>>>>>>].";
779 
780  ConstStrArray input;
781  input.put(h1235);
782 
783  SaiCalcEnv env(input, NULp); // we can fake gb_main here
784 
785  // matrix translator expects two SAIs:
786  TEST_EXPECT_APPLY_FAILURE(op, env, "matrix translator applies to a pair of SAIs only (have: 1)");
787 
788  input.put("hello"); // now add second SAI string which is too short
789 
790  // matrix translator expects SAIs with same length:
791  TEST_EXPECT_APPLY_FAILURE(op, env, "length mismatch in input data (114 <> 5)");
792 
793  input.remove(1); // drop "too short" sai again
794  input.put(h1345); // now add second SAI string
795 
796  struct {
797  const char *from;
798  const char *cfg;
799  } columnTranslatorConfig[] = {
800  { ".-=", "default='?';trans1='..-=';trans2='[[';trans3=']]';trans4='<<';trans5='>>'" },
801  { "[", "default='?';trans1='[[.-=';trans2='<<'" },
802  { "]", "default='?';trans1=']].-=';trans2='>>'" },
803  { "<", "default='?';trans1='<<.-=['" },
804  { ">", "default='?';trans1='>>.-=]'" },
805  { NULp, NULp }
806  };
807 
808  for (int t = 0; columnTranslatorConfig[t].from; ++t) {
809  ErrorOrSaiOperatorPtr generated = SaiTranslator::make(columnTranslatorConfig[t].cfg);
810  TEST_REJECT(generated.hasError());
811 
812  SaiOperatorPtr translator = generated.getValue();
813  smt1->addOperator(columnTranslatorConfig[t].from, translator);
814  }
815 
816  // apply operator + test results:
817  TEST_EXPECT_APPLY_RESULT(op, env, res01);
818 
819  // create + test config
820  TEST_OP_CFG_CONV_BIJECTIVE(op,
821  "col0='default=\\'.\\'';"
822  "col1='default=\\'?\\';trans1=\\'.-.=\\';trans2=\\'<<\\';trans3=\\'>>\\';trans4=\\'[[\\';trans5=\\']]\\'';"
823  "col2='default=\\'?\\';trans1=\\'<<\\';trans2=\\'[-.=[\\'';"
824  "col3='default=\\'?\\';trans1=\\'>>\\';trans2=\\']-.=]\\'';"
825  "col4='default=\\'?\\';trans1=\\'<-.<=[\\'';"
826  "col5='default=\\'?\\';trans1=\\'>-.=>]\\'';"
827  "columns='6';"
828  "first='default=\\'A\\';trans1=\\'B-.=\\';trans2=\\'C[\\';trans3=\\'D]\\';trans4=\\'E<\\';trans5=\\'F>\\''",
829  env);
830 
831  // try to reload some invalid configs (test error cases):
832  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "", "missing 'first' entry");
833  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first=''", "missing 'default' entry (in ''; entry 'first')");
834  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'\\''", "invalid content '' in config entry 'default' (in 'default='''; entry 'first')");
835  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\''", "missing 'columns' entry");
836  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\'';columns='0'", "entry 'columns' has to be 1 or higher (have: '0')");
837  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\'';columns='1'", "missing 'col0' entry");
838  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\'';columns='1';col0=''", "missing 'default' entry (in ''; entry 'col0')");
839  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\'';columns='2';col0='default=\\'\\''", "invalid content '' in config entry 'default' (in 'default='''; entry 'col0')");
840  TEST_OPERATOR_PRODUCTION_FAILS(SOP_MATRIX, "first='default=\\'x\\'';columns='2';col0='default=\\'y\\''", "missing 'col1' entry");
841 }
842 
843 void TEST_SaiBoolchainOperator() {
844  const char *inp1 = "--xX";
845  const char *inp2 = "-x=x";
846 
847  ConstStrArray input;
848  SaiCalcEnv env(input, NULp); // we can fake gb_main here
849 
850  // test simple boolchain operator
851  SaiBoolchainOperator *sbco1 = new SaiBoolchainOperator('-', 'x');
852  SaiOperatorPtr op = sbco1;
854 
855  TEST_EXPECT_APPLY_FAILURE(op, env, "missing input data"); // applying operator to no data does fail
856 
857  input.put(inp1);
858 
859  TEST_EXPECT_APPLY_FAILURE(op, env, "need at least one rule in chain"); // applying operator w/o rules does fail
860  TEST_OP_CFG_CONV_BIJECTIVE(op, "out='-x';rules='0'", env); // create + restore config w/o rules
861 
862  sbco1->addRule(SaiBoolRule(SBO_FIRST, true, "xX"));
863 
864  TEST_EXPECT_APPLY_RESULT(op, env, "--xx"); // apply single link boolchain to single SAI (=plain bool translation)
865  TEST_OP_CFG_CONV_BIJECTIVE(op, "out='-x';rule0='--> [xX]';rules='1'", env); // test create + restore config
866 
867  input.put(inp2);
868 
869  TEST_EXPECT_APPLY_FAILURE(op, env, "number of input SAIs has to match number of rules (2 <> 1)"); // apply single link boolchain to 2 SAIs and test failure
870 
871  struct {
872  SaiBoolOp op;
873  const char *expected;
874  } testdata[] = {
875  // Input: "--xx"
876  // "-x-x"
877  { SBO_AND, "---x" },
878  { SBO_OR, "-xxx" },
879  { SBO_XOR, "-xx-" },
880  { SBO_NAND, "xxx-" },
881  { SBO_NOR, "x---" },
882  { SBO_XNOR, "x--x" },
883  { SBO_XOR, "xx--" }, // [6] simulate a NOT (performs XOR "xxxx"; tests empty charset, see below)
884  { SBO_FIRST, NULp },
885  };
886 
887  for (int d = 0; testdata[d].expected; ++d) {
888  TEST_ANNOTATE(GBS_global_string("d=%i", d));
889  sbco1->addRule(SaiBoolRule(testdata[d].op, false, d == 6 ? "" : "-="));
890  TEST_EXPECT_APPLY_RESULT(op, env, testdata[d].expected);
891 
892  if (d == 3) {
893  TEST_OP_CFG_CONV_BIJECTIVE(op, "out='-x';rule0='--> [xX]';rule1='NAND ]-=[';rules='2'", env);
894  }
895 
896  sbco1->dropRule();
897  }
898 
899  // test bool chains with 3 links:
900  input.clear();
901 
902  input.put("-x-x-x-x");
903  input.put("xx--xx--");
904  input.put("xxxx----");
905 
906  struct {
907  SaiBoolOp op1, op2;
908  const char *expected;
909  } testdata2[] = {
910  // Input: "-x-x-x-x"
911  // "xx--xx--"
912  // "xxxx----"
913  { SBO_AND, SBO_AND, "-x------" },
914  { SBO_AND, SBO_OR, "xxxx-x--" },
915  { SBO_OR, SBO_AND, "xx-x----" },
916  { SBO_OR, SBO_OR, "xxxxxx-x" },
917  { SBO_NOR, SBO_XNOR, "--x-xx-x" },
918  { SBO_XNOR, SBO_NAND, "x--xxxxx" },
919  { SBO_NAND, SBO_XOR, "-x--x-xx" },
920  { SBO_XOR, SBO_NOR, "-----xx-" },
921  { SBO_FIRST, SBO_FIRST, NULp },
922  };
923 
924  for (int d = 0; testdata2[d].expected; ++d) {
925  TEST_ANNOTATE(GBS_global_string("d=%i", d));
926  sbco1->addRule(SaiBoolRule(testdata2[d].op1, false, "-="));
927  sbco1->addRule(SaiBoolRule(testdata2[d].op2, false, "-="));
928  TEST_EXPECT_APPLY_RESULT(op, env, testdata2[d].expected);
929 
930  if (d == 6) {
931  TEST_OP_CFG_CONV_BIJECTIVE(op, "out='-x';rule0='--> [xX]';rule1='NAND ]-=[';rule2='XOR ]-=[';rules='3'", env);
932  }
933 
934  sbco1->dropRule();
935  sbco1->dropRule();
936  }
937  TEST_ANNOTATE(NULp);
938 
939  // try to reload some invalid configs (test error cases):
940  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "", "expected 'rules' entry missing");
941  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='0'", "expected 'out' entry missing");
942  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +'", "expected entry 'rule0' is missing");
943 
944  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='123'", "expected at least one space character in '123' (during production of 'rule0')");
945  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='1 3'", "unknown operator token '1' in '1 3' (during production of 'rule0')");
946 
947  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='XOR '", "truncated input string in 'XOR ' (during production of 'rule0')");
948  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='OR 3'", "Expected '[' or ']', found '3' in 'OR 3' (during production of 'rule0')");
949  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='AND ['", "character specification too short in 'AND [' (during production of 'rule0')");
950  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='XNOR []'", "wrong type in 'rule0' (expected: '-->'; got: 'XNOR')"); // accepts empty charspec (can be used as NOT operator)
951  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='OR ]x '", "expected character specification to be terminated by '[' (seen ' ') in 'OR ]x ' (during production of 'rule0')");
952 
953  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='NOR [x]'", "wrong type in 'rule0' (expected: '-->'; got: 'NOR')");
954  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='1';out=' +';rule0='NAND ]x['", "wrong type in 'rule0' (expected: '-->'; got: 'NAND')");
955  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='2';out=' +';rule0='--> ]x['", "expected entry 'rule1' is missing");
956  TEST_OPERATOR_PRODUCTION_FAILS(SOP_BOOLCHAIN, "rules='2';out=' +';rule0='--> ]x[';rule1='--> ]x['", "wrong type in 'rule1' (may not be '-->')");
957 }
958 
959 void TEST_SaiAciApplicator() {
960  GB_shell shell;
961  GBDATA *gb_main = GB_open("no.arb", "c");
962 
963  SaiOperatorPtr op;
964 
965  // test aci applicator
966  const char *aci = "minus(1)|head(1)";
967  SaiAciApplicator *saa1 = new SaiAciApplicator(aci);
968  op = saa1;
969 
971 
972  ConstStrArray input;
973  input.put("....664--2662440-44-61662664462-----4--4------662440...."); // some PVP
974 
975  SaiCalcEnv env(input, gb_main);
976 
977  {
978  SaiAciApplicator sub1("minus(1)");
979  TEST_EXPECT_APPLY_FAILURE(&sub1, env, "Expected single character result from ACI (but received 2 chars; while '.' -> '-1')");
980  }
981 
982  // ------------------------------ "....664--2662440-44-61662664462-----4--4------662440...."
983  TEST_EXPECT_APPLY_RESULT(op, env, "----553--155133--33-50551553351-----3--3------55133-----");
984 
985  TEST_OP_CFG_CONV_BIJECTIVE(op, "aci='minus(1)|head(1)'", env);
986 
987  // @@@ test multiple input strings
988  // @@@ test input strings with varying lengths (how to handle?)
989  // @@@ test empty input data -> error
990 
991  TEST_OPERATOR_PRODUCTION_FAILS(SOP_ACI, "", "missing 'aci' entry");
992 
993  GB_close(gb_main);
994 }
995 
996 #endif // UNIT_TESTS
997 
998 // --------------------------------------------------------------------------------
static ErrorOrSaiOperatorPtr make(const char *config)
Definition: saiop.cxx:528
string result
GBDATA * GB_open(const char *path, const char *opent)
Definition: ad_load.cxx:1363
GB_TYPES type
void put(const char *elem)
Definition: arb_strarray.h:188
size_t size() const
Definition: arb_strarray.h:85
return string(buffer, length)
CONSTEXPR_INLINE unsigned char safeCharIndex(char c)
Definition: dupstr.h:73
#define MAXLEN
Definition: readcfg.c:8
std::string get_description() const
Definition: saiop.cxx:56
bool hasError() const
Definition: ErrorOrType.h:50
void apply(char *inout, const char *in, size_t len) const
Definition: saiop.cxx:328
ErrorOr< std::string > ErrorOrString
Definition: saiop.h:36
static ErrorOrSaiBoolRulePtr makeFromConfigRule(const ConfigMapping &cfgmap, int ruleNr)
Definition: saiop.cxx:456
TYPE getValue() const
Definition: ErrorOrType.h:58
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
static const char * opname[]
Definition: saiop.cxx:348
STL namespace.
char * ARB_strpartdup(const char *start, const char *end)
Definition: arb_string.h:51
char buffer[MESSAGE_BUFFERSIZE]
Definition: seq_search.cxx:34
GB_ERROR check_lengths_equal(size_t &len) const
Definition: saiop.cxx:22
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:342
std::string config_string() const
Definition: ConfigMapping.h:55
GB_ERROR deliver() const
Definition: arb_error.h:116
static ErrorOrSaiOperatorPtr make(SaiOperatorType type, const char *config)
Definition: saiop.cxx:46
int chars
Definition: seq_search.cxx:38
ErrorOrString apply(const SaiCalcEnv &calcEnv) const OVERRIDE
Definition: saiop.cxx:617
Definition: saiop.h:137
Definition: saiop.h:134
#define TEST_REJECT(cond)
Definition: test_unit.h:1330
static void error(const char *msg)
Definition: mkptypes.cxx:96
void remove(int i)
const CharPtrArray & get_input() const
Definition: saiop.h:59
std::string get_config() const OVERRIDE
Definition: saiop.cxx:592
SaiOperatorType
Definition: saiop.h:39
SaiOperatorType get_type() const
Definition: saiop.h:78
GBDATA * get_gbmain() const
Definition: saiop.h:60
std::string get_config() const OVERRIDE
Definition: saiop.cxx:478
TYPE get_type() const
Definition: probe_tree.h:64
void set_entry(const std::string &entry, const std::string &value)
Definition: ConfigMapping.h:44
Definition: saiop.h:40
bool hasValue() const
Definition: ErrorOrType.h:51
void addRule(const SaiBoolRule &r)
Definition: saiop.h:194
GB_ERROR parseFrom(const std::string &configString)
ErrorOrString apply(const SaiCalcEnv &calcEnv) const OVERRIDE
Definition: saiop.cxx:489
std::string to_string() const
Definition: saiop.cxx:359
const char * get_entry(const char *entry) const
Definition: ConfigMapping.h:39
SmartPtr< SaiOperator > SaiOperatorPtr
Definition: saiop.h:32
#define sai_assert(cond)
#define NULp
Definition: cxxforward.h:116
SaiBoolOp
Definition: saiop.h:131
Definition: saiop.h:135
NOT4PERL char * GB_command_interpreter_in_env(const char *str, const char *commands, const GBL_call_env &callEnv)
Definition: gb_aci.cxx:361
ARB_ERROR getError() const
Definition: ErrorOrType.h:53
Definition: saiop.h:133
ErrorOr< SaiOperatorPtr > ErrorOrSaiOperatorPtr
Definition: saiop.h:37
GBDATA * gb_main
Definition: adname.cxx:32
size_t length
ErrorOr< SaiBoolRulePtr > ErrorOrSaiBoolRulePtr
Definition: saiop.h:142
static ErrorOrSaiOperatorPtr make(const char *config)
Definition: saiop.cxx:598
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1294
static ErrorOrSaiBoolRulePtr make(const char *fromString)
Definition: saiop.cxx:382
void GB_close(GBDATA *gbd)
Definition: arbdb.cxx:655
#define max(a, b)
Definition: f2c.h:154
GB_write_int const char s
Definition: AW_awar.cxx:154