source: branches/stable/SL/SAICALC/saiop.cxx

Last change on this file was 18386, checked in by westram, 5 years ago
  • fix uninitialized/unused warning.
File size: 38.1 KB
Line 
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
17using namespace std;
18
19// --------------------
20//      SaiCalcEnv
21
22GB_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
39const char *SaiOperator::typeName[] = {
40    "ACI",
41    "Translator",
42    "Matrix",
43    "Bool chain",
44};
45
46ErrorOrSaiOperatorPtr SaiOperator::make(SaiOperatorType type, const char *config) {
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
56string SaiOperator::get_description() const {
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
68void 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
74ErrorOrString SaiTranslator::apply(const SaiCalcEnv& calcEnv) const {
75    ARB_ERROR error;
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
94void 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
130string SaiTranslator::get_config() const {
131    ConfigMapping cfgmap;
132    deduceTranslations(cfgmap);
133    return cfgmap.config_string();
134}
135
136ErrorOrSaiOperatorPtr SaiTranslator::make(const char *config) {
137    SaiOperatorPtr result;
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
183std::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
194ErrorOrString SaiMatrixTranslator::apply(const SaiCalcEnv& calcEnv) const {
195    ARB_ERROR error;
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
261void 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
268ErrorOrSaiOperatorPtr SaiMatrixTranslator::make(const char *config) {
269    SaiOperatorPtr result;
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) {
276            ErrorOrSaiOperatorPtr product = SaiOperator::make(SOP_TRANSLATE, 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) {
291                                ErrorOrSaiOperatorPtr colProduct = SaiOperator::make(SOP_TRANSLATE, 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
328void 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
348static const char *opname[] = { // @@@ rename variable
349    "-->",
350    "AND",
351    "OR",
352    "XOR",
353    "NAND",
354    "NOR",
355    "XNOR",
356    NULp
357};
358
359string 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
382ErrorOrSaiBoolRulePtr SaiBoolRule::make(const char *fromString) {
383    // convert 'fromString' created by to_string() back to SaiBoolRule
384    SaiBoolRulePtr product;
385    ARB_ERROR      error;
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
456static 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
466    ErrorOrSaiBoolRulePtr result = SaiBoolRule::make(rule);
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
478std::string SaiBoolchainOperator::get_config() const {
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
489ErrorOrString SaiBoolchainOperator::apply(const SaiCalcEnv& calcEnv) const {
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
528ErrorOrSaiOperatorPtr SaiBoolchainOperator::make(const char *config) {
529    SaiOperatorPtr result;
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
592std::string SaiAciApplicator::get_config() const {
593    ConfigMapping cfgmap;
594    cfgmap.set_entry("aci", aci);
595    return cfgmap.config_string();
596}
597
598ErrorOrSaiOperatorPtr SaiAciApplicator::make(const char *config) {
599    SaiOperatorPtr result;
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
617ErrorOrString SaiAciApplicator::apply(const SaiCalcEnv& calcEnv) const {
618    ARB_ERROR error;
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
724void TEST_SaiTranslator() {
725    SaiOperatorPtr op;
726
727    // test simple translator
728    SaiTranslator *st1 = new SaiTranslator('-');
729    op                 = st1;
730
731    TEST_EXPECT_EQUAL(op->get_type(), SOP_TRANSLATE);
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
762void 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
770    TEST_EXPECT_EQUAL(op->get_type(), SOP_MATRIX);
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
843void 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;
853    TEST_EXPECT_EQUAL(op->get_type(), SOP_BOOLCHAIN);
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
959void 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
970    TEST_EXPECT_EQUAL(op->get_type(), SOP_ACI);
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// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.