source: tags/arb-7.0/ARBDB/gb_aci.cxx

Last change on this file was 18646, checked in by westram, 3 years ago
  • split TEST_GB_command_interpreter…() into smaller parts.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.5 KB
Line 
1// ============================================================ //
2//                                                              //
3//   File      : gb_aci.cxx                                     //
4//   Purpose   : ARB command interpreter (ACI)                  //
5//                                                              //
6//   http://www.arb-home.de/                                    //
7//                                                              //
8// ============================================================ //
9
10#include "gb_aci.h"
11#include "gb_aci_impl.h"
12
13#include <arb_strbuf.h>
14#include <arb_match.h>
15
16using namespace GBL_IMPL;
17
18GBL_command_lookup_table::GBL_command_lookup_table(const GBL_command_definition *table, unsigned size) {
19    /*! create table to lookup ACI commands
20     * @param table command-table (has to exist as long as GBL_command_lookup_table exists; needs sentinel)
21     * @param size  number of commands in 'table' ( = size of 'table' - 1)
22     */
23
24    for (unsigned i = 0; i<size; ++i) {
25        const GBL_command_definition& cmd = table[i];
26        gb_assert(cmd.is_defined());
27
28        defined[cmd.identifier] = cmd.function;
29    }
30    gb_assert(table[size].is_sentinel());
31}
32
33namespace GBL_IMPL {
34    static const char *search_matching_dquote(const char *str) {
35        int c;
36        for (; (c=*str); str++) {
37            if (c=='\\') {      // escaped characters
38                str++;
39                if (!(c=*str)) return NULp;
40                continue;
41            }
42            if (c=='"') return (char *)str;
43        }
44        return NULp;
45    }
46    inline char *search_matching_dquote(char *str) {
47        return const_cast<char*>(search_matching_dquote(const_cast<const char*>(str)));
48    }
49    const char *search_matching_parenthesis(const char *source) {
50        int c;
51        int deep = 0;
52        if (*source != '(') deep --;    // first bracket
53        for (; (c=*source); source++) {
54            if (c=='\\') {      // escaped characters
55                source++;
56                if (!*source) break;
57                continue;
58            }
59            if (c=='(') deep--;
60            else if (c==')') deep++;
61            if (!deep) return (char *)source;
62            if (c=='"') {       // search the second "
63                source = search_matching_dquote(source);
64                if (!source) return NULp;
65            }
66        }
67        if (!c) return NULp;
68        return source;
69    }
70    static const char *search_next_separator(const char *source, const char *seps) {
71        // search the next separator
72        static char tab[256];
73        static bool init = false;
74
75        if (!init) {
76            memset(tab, 0, 256);
77            init = true;
78        }
79
80        for (const char *p = seps; *p; ++p) tab[safeCharIndex(*p)] = 1;
81
82        tab['(']  = 2; // exclude () pairs
83        tab['"']  = 2; // exclude " pairs
84        tab['\\'] = 2; // exclude \-escaped chars
85
86        for (; *source; ++source) {
87            const char chType = tab[safeCharIndex(*source)];
88            if (chType == 0) continue; // accept char
89            if (chType == 1) break;    // found separator
90
91            if (*source == '\\') {
92                ++source;              // -> skip over next char
93                if (!source[0]) break; // abort if end of string seen
94            }
95            else if (*source == '(') {
96                source = search_matching_parenthesis(source);
97                if (!source) break;
98            }
99            else if (*source == '\"') {
100                source = search_matching_dquote(source+1);
101                if (!source) break;
102            }
103        }
104        for (const char *p = seps; *p; ++p) tab[safeCharIndex(*p)] = 0; // clear tab
105        return source && source[0] ? source : NULp;
106    }
107    inline char *search_next_separator(char *source, const char *seps) {
108        return const_cast<char*>(search_next_separator(const_cast<const char*>(source), seps));
109    }
110};
111
112static void dumpStreams(const char *name, const GBL_streams& streams) {
113    if (streams.empty()) {
114        print_trace(GBS_global_string("%s [none]\n", name));
115    }
116    else {
117        int   count  = streams.size();
118        char *header = GBS_global_string_copy("%s", name);
119
120        print_trace(GBS_global_string("%s [0]='%s'", header, streams.get(0)));
121        if (count>1) {
122            LocallyModify<int> inc(traceIndent, traceIndent+strlen(header)+1);
123            for (int c = 1; c<count; c++) {
124                if (c == 10 || c == 100 || c == 1000) --traceIndent; // dec indentation
125                print_trace(GBS_global_string("[%i]='%s'\n", c, streams.get(c)));
126            }
127        }
128        free(header);
129    }
130}
131
132static const char *shortenLongString(const char *str, size_t wanted_len) {
133    // shortens the string 'str' to 'wanted_len' (appends '[..]' if string was shortened)
134
135    const char *result;
136    size_t      len = strlen(str);
137
138    gb_assert(wanted_len>4);
139
140    if (len>wanted_len) {
141        static char   *shortened_str;
142        static size_t  short_len = 0;
143
144        if (short_len >= wanted_len) {
145            memcpy(shortened_str, str, wanted_len-4);
146        }
147        else {
148            freeset(shortened_str, ARB_strpartdup(str, str+wanted_len));
149            short_len = wanted_len;
150        }
151        strcpy(shortened_str+wanted_len-4, "[..]");
152        result = shortened_str;
153    }
154    else {
155        result = str;
156    }
157    return result;
158}
159
160static char *apply_ACI(const char *str, const char *commands, const GBL_call_env& callEnv) {
161    char *buffer = ARB_strdup(commands);
162
163    // ********************** remove all spaces and tabs *******************
164    {
165        const char *s1;
166        char *s2;
167        s1 = commands;
168        s2 = buffer;
169        {
170            int c;
171            for (; (c = *s1); s1++) {
172                if (c=='\\') {
173                    *(s2++) = c;
174                    if (!(c=*++s1)) { break; }
175                    *(s2++) = c;
176                    continue;
177                }
178
179                if (c=='"') {       // search the second "
180                    const char *hp = search_matching_dquote(s1+1);
181                    if (!hp) {
182                        GB_export_errorf("unbalanced '\"' in '%s'", commands);
183                        free(buffer);
184                        return NULp;
185                    }
186                    while (s1 <= hp) *(s2++) = *(s1++); // LOOP_VECTORIZED
187                    s1--;
188                    continue;
189                }
190                if (c!=' ' && c!='\t') *(s2++) = c;
191            }
192        }
193        *s2 = 0;
194    }
195
196    GBL_streams orig;
197
198    orig.insert(ARB_strdup(str));
199
200    GB_ERROR error = NULp;
201    GBL_streams out;
202    {
203        char *s1, *s2;
204        s1 = buffer;
205        if (*s1 == '|') s1++;
206
207        // ** loop over all commands **
208        for (; s1;  s1 = s2) {
209            int separator;
210            GBL_COMMAND command;
211            s2 = search_next_separator(s1, "|;,");
212            if (s2) {
213                separator = *(s2);
214                *(s2++) = 0;
215            }
216            else {
217                separator = 0;
218            }
219            // collect the parameters
220            GBL_streams argStreams;
221            if (*s1 == '"') {           // copy "text" to out
222                char *end = search_matching_dquote(s1+1);
223                if (!end) {
224                    UNCOVERED(); // seems unreachable (balancing is already ensured by search_next_separator)
225                    error = "Missing second '\"'";
226                    break;
227                }
228                *end = 0;
229
230                TRACE_ACI(GBS_global_string("copy    '%s'\n", s1+1));
231                out.insert(ARB_strdup(s1+1));
232            }
233            else {
234                char *bracket = strchr(s1, '(');
235                if (bracket) {      // I got the parameter list
236                    *(bracket++) = 0;
237                    int slen  = strlen(bracket);
238                    if (slen<1 || bracket[slen-1] != ')') {
239                        error = "Missing ')'";
240                    }
241                    else if (slen == 1) {
242                        error = "Invalid empty parameter list '()'. To pass an empty argument use '(\"\")'";
243                    }
244                    else {
245                        // go through the parameters
246                        char *p1, *p2;
247                        bracket[slen-1] = 0;
248                        for (p1 = bracket; p1;  p1 = p2) {
249                            p2 = search_next_separator(p1, ";,");
250                            if (p2) {
251                                *(p2++) = 0;
252                            }
253                            if (p1[0] == '"') { // remove "" pairs
254                                int len2;
255                                p1++;
256                                len2 = strlen(p1)-1;
257
258                                if (p1[len2] != '\"') {
259                                    error = GBS_global_string("Invalid parameter syntax for '%s' (needs '\"' at begin AND end of parameter)", p1-1);
260                                }
261                                else {
262                                    p1[len2] = 0;
263                                }
264                            }
265                            argStreams.insert(ARB_strdup(p1));
266                        }
267                    }
268                }
269                if (!error && (bracket || *s1)) {
270                    command = callEnv.get_env().lookup_command(s1);
271                    if (!command) {
272                        error = GBS_global_string("Unknown command '%s'", s1);
273                    }
274                    else {
275                        GBL_command_arguments args(callEnv, s1, orig, argStreams, out);
276
277                        TRACE_ACI(GBS_global_string("execute '%s':\n", args.get_cmdName()));
278                        {
279                            LocallyModify<int> inc(traceIndent, traceIndent+1);
280                            if (traceACI) {
281                                dumpStreams("ArgStreams", args.get_param_streams());
282                                dumpStreams("InpStreams", args.input);
283                            }
284
285                            error = command(&args); // execute the command
286
287                            if (!error && traceACI) dumpStreams("OutStreams", args.output);
288                        }
289
290                        if (error) {
291                            char *dup_error = ARB_strdup(error);
292
293#define MAX_PRINT_LEN 200
294
295                            char *paramlist = NULp;
296                            for (int j = 0; j<args.param_count(); ++j) {
297                                const char *param       = args.get_param(j);
298                                const char *param_short = shortenLongString(param, MAX_PRINT_LEN);
299
300                                if (!paramlist) paramlist = ARB_strdup(param_short);
301                                else freeset(paramlist, GBS_global_string_copy("%s,%s", paramlist, param_short));
302                            }
303                            char *inputstreams = NULp;
304                            for (int j = 0; j<args.input.size(); ++j) {
305                                const char *input       = args.input.get(j);
306                                const char *input_short = shortenLongString(input, MAX_PRINT_LEN);
307
308                                if (!inputstreams) inputstreams = ARB_strdup(input_short);
309                                else freeset(inputstreams, GBS_global_string_copy("%s;%s", inputstreams, input_short));
310                            }
311#undef MAX_PRINT_LEN
312                            if (paramlist) {
313                                error = GBS_global_string("while applying '%s(%s)'\nto '%s':\n%s", s1, paramlist, inputstreams, dup_error);
314                            }
315                            else {
316                                error = GBS_global_string("while applying '%s'\nto '%s':\n%s", s1, inputstreams, dup_error);
317                            }
318
319                            free(inputstreams);
320                            free(paramlist);
321                            free(dup_error);
322                        }
323                    }
324                }
325            }
326
327            if (error) break;
328
329            if (separator == '|') { // out -> in pipe; clear in
330                out.swap(orig);
331                out.erase();
332            }
333        }
334    }
335
336    {
337        char *s1 = out.concatenated();
338        free(buffer);
339        if (!error) return s1;
340        free(s1);
341    }
342
343    GB_export_errorf("Command '%s' failed:\nReason: %s", commands, error);
344    return NULp;
345}
346// --------------------------------------------------------------------------------
347
348char *GBL_streams::concatenated() const {
349    int count = size();
350    if (!count) return ARB_strdup("");
351    if (count == 1) return ARB_strdup(get(0));
352
353    GBS_strstruct *strstruct = GBS_stropen(1000);
354    for (int i=0; i<count; i++) {
355        const char *s = get(i);
356        if (s) GBS_strcat(strstruct, s);
357    }
358    return GBS_strclose(strstruct);
359}
360
361NOT4PERL char *GB_command_interpreter_in_env(const char *str, const char *commands, const GBL_call_env& callEnv) {
362    /* simple command interpreter returns NULp on error (which should be exported in that case)
363     * if first character is == ':' run string parser
364     * if first character is == '/' run regexpr
365     * else run ACI
366     */
367
368    // @@@   most code here and code in apply_ACI could be moved into GBL_call_env::interpret_subcommand
369
370    LocallyModify<int> localT(traceACI);    // localize effect of command 'trace'
371    SmartMallocPtr(char) heapstr;
372
373    if (!str) {
374        if (!callEnv.get_item_ref()) {
375            GB_export_error("ACI: no input streams found");
376            return NULp;
377        }
378
379        if (GB_read_type(callEnv.get_item_ref()) == GB_STRING) {
380            str = GB_read_char_pntr(callEnv.get_item_ref());
381        }
382        else {
383            char *asstr = GB_read_as_string(callEnv.get_item_ref());
384            if (!asstr) {
385                GB_export_error("Can't read this DB entry as string");
386                return NULp;
387            }
388
389            heapstr = asstr;
390            str     = &*heapstr;
391        }
392    }
393
394    if (traceACI) {
395        print_trace(GBS_global_string("CI: command '%s' apply to '%s'\n", commands, str));
396    }
397    modify_trace_indent(+1);
398
399    char *result = NULp;
400
401    if (!commands || !commands[0]) { // empty command -> do not modify string
402        result = ARB_strdup(str);
403    }
404    else if (commands[0] == ':') { // ':' -> string parser
405        result = GBS_string_eval_in_env(str, commands+1, callEnv);
406    }
407    else if (commands[0] == '/') { // regular expression
408        GB_ERROR err = NULp;
409        result       = GBS_regreplace(str, commands, &err);
410
411        if (!result) {
412            if (strcmp(err, "Missing '/' between search and replace string") == 0) {
413                // if GBS_regreplace didn't find a third '/' -> silently use GBS_regmatch:
414                size_t matchlen;
415                err = NULp;
416
417                const char *matched = GBS_regmatch(str, commands, &matchlen, &err);
418
419                if (matched) result   = ARB_strndup(matched, matchlen);
420                else if (!err) result = ARB_strdup("");
421            }
422
423            if (!result && err) GB_export_error(err);
424        }
425    }
426    else {
427        result = apply_ACI(str, commands, callEnv);
428    }
429
430    modify_trace_indent(-1);
431    if (traceACI) {
432        GBS_strstruct final_msg(1000);
433        if (result) {
434            final_msg.cat("CI: result ='");
435            final_msg.cat(result);
436        }
437        else {
438            final_msg.cat("CI: no result. error ='");
439            final_msg.cat(GB_get_error());
440        }
441        final_msg.put('\'');
442        final_msg.put('\n');
443        final_msg.nput('-', final_msg.get_position()-1);
444        final_msg.put('\n');
445
446        print_trace(final_msg.get_data());
447    }
448
449    gb_assert(contradicted(result, GB_have_error()));
450    return result;
451}
452
453char *GB_command_interpreter(const char *str, const char *commands, GBDATA *gb_main) {
454    //! @see GB_command_interpreter_in_env - this flavor runs in dummy environment
455    GBL_env      env(gb_main, NULp);
456    GBL_call_env callEnv(NULp, env);
457
458    return GB_command_interpreter_in_env(str, commands, callEnv);
459}
460
461void GBL_custom_command_lookup_table::warn_about_overwritten_commands(const GBL_command_definition *custom_table, unsigned custom_size) const {
462    int errcount = 0;
463    for (unsigned i = 0; i<custom_size; ++i) {
464        const GBL_command_definition& cdef = custom_table[i];
465        gb_assert(cdef.is_defined());
466
467        const char *cmd = cdef.identifier;
468        if (base_table.lookup(cmd)) {
469            fprintf(stderr, "Warning: ACI-command '%s' is substituted w/o permission\n", cmd);
470            ++errcount;
471        }
472    }
473    if (errcount>0) {
474        fprintf(stderr, "Warning: Detected probably unwanted substitution of %i ACI-commands\n", errcount);
475        gb_assert(0); // either use PERMIT_SUBSTITUTION or fix command names
476    }
477}
478
479// --------------------------------------------------------------------------------
480
481#ifdef UNIT_TESTS
482#include <test_unit.h>
483
484#include <arb_defs.h>
485
486#define TEST_CI__INTERNAL(input,cmd,expected,got,TEST_RESULT,callEnv) do {                                      \
487        char *result;                                                                                           \
488        TEST_EXPECT_RESULT__NOERROREXPORTED(result = GB_command_interpreter_in_env(input, cmd, callEnv));       \
489        TEST_RESULT(result,expected,got);                                                                       \
490        free(result);                                                                                           \
491    } while(0)
492
493#define TEST_CI(input,cmd,expected)                    TEST_CI__INTERNAL(input,    cmd, expected, narg, TEST_EXPECT_EQUAL__IGNARG, callEnv)
494#define TEST_CI_WITH_ENV(input,env,cmd,expected)       TEST_CI__INTERNAL(input,    cmd, expected, narg, TEST_EXPECT_EQUAL__IGNARG, env)
495#define TEST_CI__BROKEN(input,cmd,expected,regr)       TEST_CI__INTERNAL(input,    cmd, expected, regr, TEST_EXPECT_EQUAL__BROKEN, callEnv)
496#define TEST_CI_NOOP(inandout,cmd)                     TEST_CI__INTERNAL(inandout, cmd, inandout, narg, TEST_EXPECT_EQUAL__IGNARG, callEnv)
497#define TEST_CI_NOOP__BROKEN(inandout,regr,cmd)        TEST_CI__INTERNAL(inandout, cmd, inandout, regr, TEST_EXPECT_EQUAL__BROKEN, callEnv)
498
499#define TEST_CI_INVERSE(in,cmd,inv_cmd,out) do {        \
500        TEST_CI(in,  cmd,     out);                     \
501        TEST_CI(out, inv_cmd, in);                      \
502    } while(0)
503
504// @@@ rename errorpart_expected -> expected_errorpart
505
506#define TEST_CI_ERROR_CONTAINS(input,cmd,errorpart_expected)                                                            \
507    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_command_interpreter_in_env(input, cmd, callEnv), errorpart_expected)
508
509#define TEST_CI_ERROR_CONTAINS__BROKEN(input,cmd,errorpart_expected,unexpected_result) do{                              \
510        char         *result;                                                                                           \
511        TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS__BROKEN(result = GB_command_interpreter_in_env(input, cmd, callEnv), errorpart_expected); \
512        TEST_EXPECT_EQUAL(result, unexpected_result);                                                                   \
513        free(result);                                                                                                   \
514    }while(0)
515
516static GBDATA     *RCI_gb_main = NULp;
517static const char *RCI_input   = NULp;
518static const char *RCI_cmd     = NULp;
519static GBDATA     *RCI_gbd     = NULp;
520
521inline void run_ci() {
522    GBL_env      env(RCI_gb_main, NULp);
523    GBL_call_env callEnv(RCI_gbd, env);
524    GB_command_interpreter_in_env(RCI_input, RCI_cmd, callEnv);
525}
526
527#define TEST_CI_SEGFAULTS(input,cmd) do{        \
528        RCI_gb_main = gb_main;                  \
529        RCI_input   = input;                    \
530        RCI_cmd     = cmd;                      \
531        RCI_gbd     = gb_data;                  \
532        TEST_EXPECT_SEGFAULT(run_ci);           \
533    }while(0)
534
535#define TEST_CI_SEGFAULTS__UNWANTED(input,cmd) do{        \
536        RCI_gb_main = gb_main;                            \
537        RCI_input   = input;                              \
538        RCI_cmd     = cmd;                                \
539        RCI_gbd     = gb_data;                            \
540        TEST_EXPECT_SEGFAULT__UNWANTED(run_ci);           \
541    }while(0)
542
543#define ACI_SPLIT          "|split(\",\",0)"
544#define ACI_MERGE          "|merge(\",\")"
545#define WITH_SPLITTED(aci) ACI_SPLIT aci ACI_MERGE
546
547static GB_ERROR gbx_custom(GBL_command_arguments *args) {
548    EXPECT_NO_PARAM(args);
549    for (int i=0; i<args->input.size(); ++i) {
550        args->output.insert(strdup("4711"));
551    }
552    return NULp;
553}
554
555class ACI_test_env : virtual Noncopyable {
556    GB_shell            shell;
557    GBDATA             *gb_main;
558    LocallyModify<int>  traceMode;
559    GB_transaction      ta;
560    GBDATA             *gb_species;
561public:
562    ACI_test_env() :
563        gb_main(GB_open("TEST_aci.arb", "rw")),
564        traceMode(traceACI, 0), // set to 1 to trace all ACI tests
565        ta(gb_main)
566    {
567        gb_assert(gb_main);
568        gb_species = GBT_find_species(gb_main, "LcbReu40"); // ../UNIT_TESTER/run/TEST_aci.arb@LcbReu40
569    }
570    ~ACI_test_env() {
571        TEST_EXPECT_NO_ERROR(ta.close(NULp));
572        GB_close(gb_main);
573    }
574
575    GBDATA *gbmain() const { return gb_main; }
576    GBDATA *gbspecies() const { return gb_species; }
577};
578
579__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_1a() {
580    ACI_test_env E;
581    GBL_env      base_env(E.gbmain(), NULp);
582
583    // execute ACI on species container (=GB_DB) in this section ------------------------------
584    GBDATA * const gb_data = E.gbspecies();
585    GBL_call_env   callEnv(gb_data, base_env);
586
587    TEST_CI_NOOP("bla", "");
588
589    TEST_CI("bla", ":a=u", "blu"); // simple SRT
590
591    // GBS_REGREPLACE_TESTS:
592    TEST_CI("bla",    "/a/u/",      "blu");  // simple regExp replace
593
594    TEST_CI("test",   "/_[0-9]+//", "test"); // simple regExp replace (failing, ie. no match -> no replace)
595
596    TEST_CI("blabla", "/l.*b/",     "lab");  // simple regExp match
597    TEST_CI("blabla", "/b.b/",      "");     // simple regExp match (failing)
598
599    TEST_CI("tx_01_2", "/_[0-9]+//",  "tx");    // simple regExp replace (replace all occurrences)
600    TEST_CI("tx_01_2", "/_[0-9]+$//", "tx_01"); // simple regExp replace (replace one occurrence)
601
602    TEST_CI_ERROR_CONTAINS("xx_____",    "/_*//",   "regular expression '_*' matched an empty string"); // caused a deadlock until [16326]
603    TEST_CI("xx_____",   "/_//",                  "xx");      // working removal of multiple '_'
604    TEST_CI("xx_____yy", "/(_+)([^_]|$)/-=-\\2/", "xx-=-yy"); // replace multiple consecutive '_'
605    TEST_CI("xx_____",   "/(_+)([^_]|$)/-=-\\2/", "xx-=-");   // replace multiple consecutive '_'
606
607    TEST_CI("xx_____", "/_*$//", "xx"); // removal of multiple '_' from end of sequence
608    TEST_CI("xx",      "/_*$//", "xx"); // removal of no '_' from end of sequence
609    TEST_CI("_____yy", "/^_*//", "yy"); // removal of multiple '_' from start of sequence
610    TEST_CI("yy",      "/^_*//", "yy"); // removal of no '_' from start of sequence
611
612    TEST_CI("",    "/^$/ABC/",  "ABC"); // replacing a complete empty string should be possible
613    TEST_CI("XXX", "/^XXX$//",  "");    // erase whole known text
614
615    TEST_CI("xx/yy/zz",   "/\\//-/", "xx-yy-zz");   // search expression with an escaped slash
616    TEST_CI("xx-yy-zz",   "/-/\\//", "xx/yy/zz");   // reverse (escaped slash in replace expression)
617
618    TEST_CI_ERROR_CONTAINS("xx",    "\\///",   "Unknown command");
619    TEST_CI               ("x/x",   "/\\//",   "/");
620    TEST_CI_ERROR_CONTAINS("xx",    "//\\/",   "railing backslash"); // [Tt]railing (lib dependent?)
621
622    // escape / quote
623    TEST_CI_INVERSE("ac", "|quote",        "|unquote",          "\"ac\"");
624    TEST_CI_INVERSE("ac", "|escape",       "|unescape",         "ac");
625    TEST_CI_INVERSE("ac", "|escape|quote", "|unquote|unescape", "\"ac\"");
626    TEST_CI_INVERSE("ac", "|quote|escape", "|unescape|unquote", "\\\"ac\\\"");
627
628    TEST_CI_INVERSE("a\"b\\c", "|quote",        "|unquote",          "\"a\"b\\c\"");
629    TEST_CI_INVERSE("a\"b\\c", "|escape",       "|unescape",         "a\\\"b\\\\c");
630    TEST_CI_INVERSE("a\"b\\c", "|escape|quote", "|unquote|unescape", "\"a\\\"b\\\\c\"");
631    TEST_CI_INVERSE("a\"b\\c", "|quote|escape", "|unescape|unquote", "\\\"a\\\"b\\\\c\\\"");
632
633    TEST_CI_NOOP("ac", "|unquote");
634    TEST_CI_NOOP("\"ac", "|unquote");
635    TEST_CI_NOOP("ac\"", "|unquote");
636
637    TEST_CI               ("blabla", "|coUNT(ab)",         "4");                            // simple ACI
638    TEST_CI               ("l",      "|\"b\";dd;\"a\"|dd", "bla");                          // ACI with muliple streams
639    TEST_CI_ERROR_CONTAINS("bla",    "|count()",           "Invalid empty parameter list"); // no longer interpret '()' as "1 empty arg" (force use of explicit form; see next line)
640    TEST_CI               ("bla",    "|count(\"\")",       "0");                            // explicitly empty parameter (still strange, counts 0-byte before string-terminator; always 0)
641    TEST_CI               ("b a",    "|count(\" \")",      "1");                            // space in quotes
642    TEST_CI               ("b\\a",   "|count(\\a)",        "2");                            // count '\\' and 'a' (ok)
643    TEST_CI__BROKEN       ("b\\a",   "|count(\"\\a\")",    "1", "2");                       // should only count 'a' (which is escaped in param)
644    TEST_CI               ("b\\a",   "|count(\"\a\")",     "0");                            // does not contain '\a'
645    TEST_CI               ("b\a",    "|count(\"\a\")",     "1");                            // counts '\a'
646
647    // escaping (behavior is unexpected or weird and not documented very well)
648    TEST_CI("b\\a\a", "|count(\\a)",         "2"); // counts '\\' and 'a' (but not '\a')
649    TEST_CI("b\\a",   "|contains(\"\\\\\")", "0"); // searches for 2 backslashes (not in input)
650    TEST_CI("b\\a",   "|contains(\"\")",     "0"); // search for empty string never succeeds
651    TEST_CI("b\\a",   "|contains(\\)",       "2"); // finds backslash at position 1
652    TEST_CI("b\\\\a", "|contains(\"\\\\\")", "2"); // finds two backslashes at position 2
653
654    TEST_CI_ERROR_CONTAINS("b\\a",   "|contains(\"\\\")",   "ARB ERROR: unbalanced '\"' in '|contains(\"\\\")'"); // FIX: raises error (should search for 1 backslash)
655
656    // test binary ops
657    {
658        // LocallyModify<int> traceHere(traceACI, 1);
659        TEST_CI("", "\"5\";\"7\"|minus",                        "-2");
660        TEST_CI("", "\"5\"|minus(\"7\")",                       "-2");
661        // TEST_CI("", "minus(5,7)",               "-2"); // @@@ this fails (stating command '5' fails). fix how?
662        TEST_CI("", "minus(\"\"5\"\",\"\"7\"\")",               "-2"); // @@@ syntax needed here 'minus(""5"", ""7"")' should be documented more in-depth
663        TEST_CI("", "minus(\"\"5\";\"2\"|mult\",\"\"7\"\")",    "3"); // (5*2)-7
664        TEST_CI("", "minus(\"\"5\";\"2\\,\"|mult\",\"\"7\"\")", "3"); // comma has to be escaped
665
666        TEST_CI_ERROR_CONTAINS("", "minus(\"\"5\";\"2,\"|mult\",\"\"7\"\")", "Invalid parameter syntax for '\"\"5\";\"2'");
667    }
668
669    TEST_CI_NOOP("ab,bcb,abac", WITH_SPLITTED(""));
670    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|len"),                       "2,3,4");
671    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|count(a)"),                  "1,0,2");
672    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(len,count(a))"),       "1,3,2");
673    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(\"\"5\"\",count(a))"), "4,5,3");
674
675    // test other recursive uses of GB_command_interpreter
676    TEST_CI("one",   "|dd;\"two\";dd|command(\"dd;\"_\";dd;\"-\"\")",                          "one_one-two_two-one_one-");
677    TEST_CI("",      "|sequence|command(\"/^([\\\\.-]*)[A-Z].*/\\\\1/\")|len",                 "9"); // count gaps at start of sequence
678    TEST_CI("one",   "|dd;dd|eval(\"\"up\";\"per\"|merge\")",                                  "ONEONE");
679    TEST_CI("1,2,3", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "one,two,three");
680    TEST_CI_ERROR_CONTAINS("1,4", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "Illegal param number '4' (allowed [0..3])");
681
682    // test define and do
683    TEST_CI("ignored", "define(d4, \"dd;dd;dd;dd\")",        "");
684    TEST_CI("ignored", "define(d16, \"do(d4)|do(d4)\")",     "");
685    TEST_CI("ignored", "define(d64, \"do(d4)|do(d16)\")",    "");
686    TEST_CI("ignored", "define(d4096, \"do(d64)|do(d64)\")", "");
687
688    TEST_CI("x",  "do(d4)",           "xxxx");
689    TEST_CI("xy", "do(d4)",           "xyxyxyxy");
690    TEST_CI("x",  "do(d16)",          "xxxxxxxxxxxxxxxx");
691    TEST_CI("x",  "do(d64)|len",      "64");
692    TEST_CI("xy", "do(d4)|len",       "8");
693    TEST_CI("xy", "do(d4)|len(\"\")", "8");
694    TEST_CI("xy", "do(d4)|len(x)",    "4");
695    TEST_CI("x",  "do(d4096)|len",    "4096");
696
697    // create 4096 streams (disable trace; logs to much):
698    TEST_CI("x",  "trace(0)|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|streams", "4096");
699}
700
701__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_1b() {
702    ACI_test_env E;
703    GBL_env      base_env(E.gbmain(), NULp);
704
705    // execute ACI on species container (=GB_DB) in this section ------------------------------
706    GBDATA * const gb_data = E.gbspecies();
707    GBL_call_env   callEnv(gb_data, base_env);
708
709    // streams
710    TEST_CI("x", "dd;dd|streams",             "2");
711    TEST_CI("x", "dd;dd|dd;dd|streams",       "4");
712    TEST_CI("x", "dd;dd|dd;dd|dd;dd|streams", "8");
713    TEST_CI("x", "do(d4)|streams",            "1"); // stream is merged when do() returns
714
715    TEST_CI("", "ali_name", "ali_16s");  // ask for default-alignment name
716    TEST_CI("", "sequence_type", "rna"); // ask for sequence_type of default-alignment
717
718    // format
719    TEST_CI("acgt", "format", "          acgt");
720    TEST_CI("acgt", "format(firsttab=1)", " acgt");
721    TEST_CI("acgt", "format(firsttab=1, width=2)",
722            " ac\n"
723            "          gt");
724    TEST_CI("acgt", "format(firsttab=1,tab=1,width=2)",
725            " ac\n"
726            " gt");
727    TEST_CI("acgt", "format(firsttab=0,tab=0,width=2)",
728            "ac\n"
729            "gt");
730    TEST_CI("acgt", "format(firsttab=0,tab=0,width=1)",
731            "a\n"
732            "c\n"
733            "g\n"
734            "t");
735
736    TEST_CI_ERROR_CONTAINS("acgt", "format(gap=0)",   "Unknown Parameter 'gap=0' in command 'format'");
737    TEST_CI_ERROR_CONTAINS("acgt", "format(numleft)", "Unknown Parameter 'numleft' in command 'format'");
738
739    // format_sequence
740    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(numright=5, numleft)", "You may only specify 'numleft' OR 'numright',  not both");
741
742    TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numleft=1)",
743            "1    acgt\n"
744            "5    acgt\n"
745            "9    acgt\n"
746            "13   acg");
747
748    TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numright=9)", // test EMBL sequence formatting
749            "     acgt         4\n"
750            "     acgt         8\n"
751            "     acgt        12\n"
752            "     acg         15");
753
754    TEST_CI("acgtacgtacgtac", "format_sequence(firsttab=5,tab=5,width=4,gap=2,numright=-1)", // autodetect width for 'numright'
755            "     ac gt  4\n"
756            "     ac gt  8\n"
757            "     ac gt 12\n"
758            "     ac    14");
759
760    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,width=2,gap=1)",
761            "a c\n"
762            "g t");
763    TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=1)", "a c g t");
764    TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=2)", "ac gt");
765    TEST_CI("acgtacgt", "format_sequence(firsttab=0,width=10,gap=4)",      "acgt acgt");
766    TEST_CI("acgtacgt", "format_sequence(firsttab=1,width=10,gap=4)",      " acgt acgt");
767
768    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=0)",   "acgt");
769    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=-1)",  "acgt"); // no big alloc
770    TEST_CI("acgt", "format_sequence(firsttab=0,tab=-1,gap=-1)", "acgt"); // no big alloc
771    TEST_CI("acgt", "format(firsttab=0,tab=0,width=-1)",         "acgt"); // no big alloc for(!)format
772
773    TEST_CI("acgt", "format(firsttab=-1,tab=0)",           "acgt"); // did a 4Gb-alloc!
774    TEST_CI("acgt", "format(firsttab=-1,tab=-1)",          "acgt"); // did a 4Gb-alloc!
775    TEST_CI("acgt", "format(firsttab=-1,tab=-1,width=-1)", "acgt"); // did a 4Gb-alloc!
776
777    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=0,width=-1)",    "acgt"); // did a 4Gb-alloc!
778    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=0,gap=-1)",           "acgt"); // did a 4Gb-alloc!
779    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=-1,gap=-1)",          "acgt"); // did a 4Gb-alloc!
780    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=-1,gap=-1,width=-1)", "acgt"); // did a 4Gb-alloc!
781
782    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(nl=c)",     "Unknown Parameter 'nl=c' in command 'format_sequence'");
783    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(forcenl=)", "Unknown Parameter 'forcenl=' in command 'format_sequence'");
784
785    TEST_CI_ERROR_CONTAINS("acgt", "format(width=0)",          "Illegal zero width");
786    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(width=0)", "Illegal zero width");
787
788    // remove + keep
789    TEST_CI_NOOP("acgtacgt",         "remove(-.)");
790    TEST_CI     ("..acg--ta-cgt...", "remove(-.)", "acgtacgt");
791    TEST_CI     ("..acg--ta-cgt...", "remove(acgt)", "..---...");
792
793    TEST_CI_NOOP("acgtacgt",         "keep(acgt)");
794    TEST_CI     ("..acg--ta-cgt...", "keep(-.)", "..---...");
795    TEST_CI     ("..acg--ta-cgt...", "keep(acgt)", "acgtacgt");
796
797    // compare + icompare
798    TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|compare"),  "-1,0,1,1,1,-1");
799    TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|icompare"), "-1,0,1,-1,0,1");
800
801    TEST_CI("x,y,z", WITH_SPLITTED("|compare(\"y\")"), "-1,0,1");
802
803    // equals + iequals
804    TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|equals"),  "0,1,0");
805    TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|iequals"), "0,1,1");
806
807    // contains + icontains
808    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|contains(\"bc\")"),   "2,1,0");
809    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"bc\")"),  "2,1,1");
810    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"d\")"),   "0,3,3");
811
812    // partof + ipartof
813    TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|partof(\"abcdefg\")"),   "1,0,4,0");
814    TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|ipartof(\"abcdefg\")"),  "1,2,4,0");
815
816    TEST_CI(", ,  ,x", WITH_SPLITTED("|isempty"),             "1,0,0,0");
817    TEST_CI(", ,  ,x", WITH_SPLITTED("|crop(\" \")|isempty"), "1,1,1,0");
818
819    // translate
820    TEST_CI("abcdefgh", "translate(abc,cba)",     "cbadefgh");
821    TEST_CI("abcdefgh", "translate(cba,abc)",     "cbadefgh");
822    TEST_CI("abcdefgh", "translate(hcba,abch,-)", "hcb----a");
823    TEST_CI("abcdefgh", "translate(aceg,aceg,-)", "a-c-e-g-");
824    TEST_CI("abbaabba", "translate(ab,ba,-)",     "baabbaab");
825    TEST_CI("abbaabba", "translate(a,x,-)",       "x--xx--x");
826    TEST_CI("abbaabba", "translate(,,-)",         "--------");
827
828    // echo
829    TEST_CI("", "echo", "");
830    TEST_CI("", "echo(x,y,z)", "xyz");
831    TEST_CI("", "echo(x;y,z)", "xyz"); // check ';' as param-separator
832    TEST_CI("", "echo(x;y;z)", "xyz");
833    TEST_CI("", "echo(x,y,z)|streams", "3");
834
835    // upper, lower + caps
836    TEST_CI("the QUICK brOwn Fox", "lower", "the quick brown fox");
837    TEST_CI("the QUICK brOwn Fox", "upper", "THE QUICK BROWN FOX");
838    TEST_CI("the QUICK brOwn FoX", "caps",  "The Quick Brown Fox");
839}
840
841__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_2a() {
842    ACI_test_env E;
843    GBL_env      base_env(E.gbmain(), NULp);
844
845    // execute ACI on species container (=GB_DB) in this section ------------------------------
846    GBDATA * const gb_data = E.gbspecies();
847    GBL_call_env   callEnv(gb_data, base_env);
848
849    TEST_CI_ERROR_CONTAINS("a;b;c", "split(;)|merge(-)",     "Invalid separator (cannot be empty");
850    TEST_CI               ("a;b;c", "split(\";\")|merge(-)", "a-b-c");
851
852    // head, tail + mid/mid0
853    TEST_CI     ("1234567890", "head(3)", "123");
854    TEST_CI     ("1234567890", "head(9)", "123456789");
855    TEST_CI_NOOP("1234567890", "head(10)");
856    TEST_CI_NOOP("1234567890", "head(20)");
857
858    TEST_CI     ("1234567890", "tail(4)", "7890");
859    TEST_CI     ("1234567890", "tail(9)", "234567890");
860    TEST_CI_NOOP("1234567890", "tail(10)");
861    TEST_CI_NOOP("1234567890", "tail(20)");
862
863    TEST_CI("1234567890", "tail(0)", "");
864    TEST_CI("1234567890", "head(0)", "");
865    TEST_CI("1234567890", "tail(-2)", "");
866    TEST_CI("1234567890", "head(-2)", "");
867
868    TEST_CI("1234567890", "mid(3,5)", "345");
869    TEST_CI("1234567890", "mid(2,2)", "2");
870
871    TEST_CI("1234567890", "mid0(3,5)", "456");
872
873    TEST_CI("1234567890", "mid(9,20)", "90");
874    TEST_CI("1234567890", "mid(20,20)", "");
875
876    TEST_CI("1234567890", "tail(3)",     "890"); // example from ../HELP_SOURCE/oldhelp/commands.hlp@mid0
877    TEST_CI("1234567890", "mid(-2,0)",   "890");
878    TEST_CI("1234567890", "mid0(-3,-1)", "890");
879
880    // tab + pretab
881    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(2)"),    "x ,xx,xxx");
882    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(3)"),    "x  ,xx ,xxx");
883    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(4)"),    "x   ,xx  ,xxx ");
884    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(2)"), " x,xx,xxx");
885    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(3)"), "  x, xx,xxx");
886    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(4)"), "   x,  xx, xxx");
887
888    // crop
889    TEST_CI("   x  x  ",         "crop(\" \")",     "x  x");
890    TEST_CI("\n \t  x  x \n \t", "crop(\"\t\n \")", "x  x");
891
892    // cut, drop, dropempty and dropzero
893    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|cut(2,3,5)"),        "two,three,five");
894    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|drop(2,3,5)"),       "one,four,six");
895
896    TEST_CI_ERROR_CONTAINS("a", "drop(2)",   "Illegal stream number '2' (allowed [1..1])");
897    TEST_CI_ERROR_CONTAINS("a", "drop(0)",   "Illegal stream number '0' (allowed [1..1])");
898    TEST_CI_ERROR_CONTAINS("a", "drop",      "syntax: drop(streamnumber[,streamnumber]+)");
899    TEST_CI_ERROR_CONTAINS("a", "cut(2)",    "Illegal stream number '2' (allowed [1..1])");
900    TEST_CI_ERROR_CONTAINS("a", "cut(0)",    "Illegal stream number '0' (allowed [1..1])");
901    TEST_CI_ERROR_CONTAINS("a", "cut",       "syntax: cut(streamnumber[,streamnumber]+)");
902    TEST_CI_ERROR_CONTAINS("a", "cut()",     "Invalid empty parameter list '()'");
903    TEST_CI_ERROR_CONTAINS("a", "cut(\"\")", "Illegal stream number '0' (allowed [1..1])"); // still strange (atoi("")->0)
904
905    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|dropempty|streams"), "6");
906    TEST_CI("one,two,,,five,six",          WITH_SPLITTED("|dropempty|streams"), "4");
907    TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty"),         "");
908    TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty|streams"), "0");
909
910    TEST_CI("1,0,0,2,3,0",                 WITH_SPLITTED("|dropzero"),          "1,2,3");
911    TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero"),          "");
912    TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero|streams"),  "0");
913
914    TEST_CI("12345",        "|colsplit|streams",           "5");
915    TEST_CI("12345",        "|colsplit"    ACI_MERGE,      "1,2,3,4,5");
916    TEST_CI("12345",        "|colsplit(3)" ACI_MERGE,      "123,45");
917    TEST_CI("12345,678,90", WITH_SPLITTED("|colsplit(2)"), "12,34,5,67,8,90");
918    TEST_CI_NOOP("12345,678,90", WITH_SPLITTED("|colsplit(5)"));
919
920    // swap
921    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap"),                "1,2,3,four,six,five");
922    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)"),           "1,3,2,four,five,six");
923    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)|swap(4,3)"), "1,3,four,2,five,six");
924    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,3)"));
925    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(2,3)"));
926    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(3,1)|swap(2,1)|swap(1,3)"));
927
928    TEST_CI_ERROR_CONTAINS("a",   "swap",                        "need at least two input streams");
929    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(2,3)"),   "Illegal stream number '3' (allowed [1..2])");
930    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(3,2)"),   "Illegal stream number '3' (allowed [1..2])");
931    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1)"),     "syntax: swap[(streamnumber,streamnumber)]");
932    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1,2,3)"), "syntax: swap[(streamnumber,streamnumber)]");
933
934    // toback + tofront
935    TEST_CI     ("front,mid,back", WITH_SPLITTED("|toback(2)"),  "front,back,mid");
936    TEST_CI     ("front,mid,back", WITH_SPLITTED("|tofront(2)"), "mid,front,back");
937    TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|toback(3)"));
938    TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|tofront(1)"));
939    TEST_CI_NOOP("a",              WITH_SPLITTED("|tofront(1)"));
940    TEST_CI_NOOP("a",              WITH_SPLITTED("|toback(1)"));
941
942    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront(3)"),  "Illegal stream number '3' (allowed [1..2])");
943    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(3)"),   "Illegal stream number '3' (allowed [1..2])");
944    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront"),     "syntax: tofront(streamnumber)");
945    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(1,2)"), "syntax: toback(streamnumber)");
946    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|merge(1,2)"),  "syntax: merge[(\"separator\")]");
947
948    // split
949    TEST_CI               ("a\nb", "|split"        ACI_MERGE, "a,b");
950    TEST_CI               ("a-b",  "|split(-)"     ACI_MERGE, "a,b");
951    TEST_CI               ("a-b",  "|split(-,0)"   ACI_MERGE, "a,b");
952    TEST_CI               ("a-b",  "|split(-,1)"   ACI_MERGE, "a,-b");
953    TEST_CI               ("a-b",  "|split(-,2)"   ACI_MERGE, "a-,b");
954    TEST_CI_ERROR_CONTAINS("a-b",  "|split(-,3)"   ACI_MERGE, "Illegal split mode '3' (valid: 0..2)");
955    TEST_CI_ERROR_CONTAINS("a\nb", "|split(1,2,3)" ACI_MERGE, "syntax: split[(\"separator\"[,mode])]");
956
957#define C0_9 "0123456789"
958#define CA_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
959#define Ca_z "abcdefghijklmnopqrstuvwxyz"
960
961    // extract_words + extract_sequence
962    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" C0_9 "\",1)",            "1 2 3");
963    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 3)",           "five four six");
964    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" CA_Z "\", 3)",           "");                 // extract words works case sensitive
965    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 4)",           "five four");
966    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 5)",           "");
967    TEST_CI("7 3b 12A 1 767 111 1 77", "extract_words(\"" C0_9 CA_Z Ca_z "\", 1)", "1 1 111 12A 3b 7 767 77"); // does sort a list of helix numbers
968
969    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 1.0)",   "");
970    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.5)",   "");
971    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.0)",   "four five six");
972    TEST_CI     ("..acg--ta-cgt...",       "extract_sequence(\"acgtu\", 1.0)",   "");
973    TEST_CI_NOOP("..acg--ta-cgt...",       "extract_sequence(\"acgtu-.\", 1.0)");
974    TEST_CI_NOOP("..acg--ta-ygt...",       "extract_sequence(\"acgtu-.\", 0.7)");
975    TEST_CI     ("70 ..acg--ta-cgt... 70", "extract_sequence(\"acgtu-.\", 1.0)", "..acg--ta-cgt...");
976
977    // checksum + gcgchecksum
978    TEST_CI("", "sequence|checksum",      "4C549A5F");
979    TEST_CI("", "sequence | gcgchecksum", "4308");
980
981    // SRT
982    TEST_CI("The quick brown fox", "srt(\"quick=lazy:brown fox=dog\")", "The lazy dog"); // no need to escape spaces in quoted ACI parameter
983    TEST_CI("The quick brown fox", "srt(quick=lazy:brown\\ fox=dog)",   "The lazy dog"); // spaces need to be escaped in unquoted ACI parameter
984    TEST_CI_ERROR_CONTAINS("x", "srt(x=y,z)", "SRT ERROR: no '=' found in command");
985    TEST_CI_ERROR_CONTAINS("x", "srt",        "syntax: srt(expr[,expr]+)");
986
987    // REG-replace and -match
988    TEST_CI("stars*to*stripes", "/\\*/--/", "stars--to--stripes");
989
990    TEST_CI_ERROR_CONTAINS("xxx", "//--",    "Regular expression format is '/expr/' or '/expr/i', not '//--'");
991    TEST_CI_ERROR_CONTAINS("xxx", "/*/bla/",
992#if defined(DARWIN)
993                           // @@@ RESULT_MODIFIED_OSX: this test depends on library version
994                           // should either test for one-of-several results or just test for any error
995                           "repetition-operator operand invalid"
996#else // !DARWIN
997                           "Invalid preceding regular expression"
998#endif
999        );
1000
1001    TEST_CI("sImILaRWIllBE,GonEEASIly", WITH_SPLITTED("|command(/[A-Z]//)"),   "small,only");
1002    TEST_CI("sthBIGinside,FATnotCAP",   WITH_SPLITTED("|command(/([A-Z])+/)"), "BIG,FAT"); // does only do match
1003
1004    // command-queue vs. command-pipe (vs. both as sub-commands)
1005    TEST_CI("a,bb,ccc",           WITH_SPLITTED("|\"[\";len;\"]\""),    "[,1,2,3,]");   // queue
1006    TEST_CI("a,bb,ccc", WITH_SPLITTED("|command(\"\"[\";len;\"]\"\")"), "[1],[2],[3]"); // queue as sub-command
1007
1008    TEST_CI("a,bb,ccc",              WITH_SPLITTED("|len|minus(1)"),    "0,1,2"); // pipe
1009    TEST_CI("a,bb,ccc",    WITH_SPLITTED("|command(\"len|minus(1)\")"), "0,1,2"); // pipe as sub-command
1010
1011    TEST_CI(               "a,bb,ccc,dd",           WITH_SPLITTED("|len|minus"), "-1,1"); // pipe
1012    TEST_CI_ERROR_CONTAINS("a,bb,ccc,dd", WITH_SPLITTED("|command(\"len|minus\")"), "Expect an even number of input streams"); // pipe as sub-command FAILS
1013}
1014
1015__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_2b() {
1016    ACI_test_env E;
1017    GBL_env      base_env(E.gbmain(), NULp);
1018
1019    // execute ACI on species container (=GB_DB) in this section ------------------------------
1020    GBDATA * const gb_data = E.gbspecies();
1021    GBL_call_env   callEnv(gb_data, base_env);
1022
1023    // calculator
1024    TEST_CI("", "echo(9.9,3.9) |plus;fplus"   ACI_MERGE, "12,13.8");
1025    TEST_CI("", "echo(9.1,3.9) |minus;fminus" ACI_MERGE, "6,5.2");
1026    TEST_CI("", "echo(9,3.5)   |mult;fmult"   ACI_MERGE, "27,31.5");
1027    TEST_CI("", "echo(9,0.1)   |mult;fmult"   ACI_MERGE, "0,0.9");
1028    TEST_CI("", "echo(9,3)     |div;fdiv"     ACI_MERGE, "3,3");
1029    TEST_CI("", "echo(10,3)    |div;fdiv"     ACI_MERGE, "3,3.33333");
1030
1031    TEST_CI("", "echo(9,3)|rest", "0");
1032    TEST_CI("", "echo(9,5)|rest", "4");
1033
1034    TEST_CI("", "echo(9,3)  |per_cent;fper_cent" ACI_MERGE, "300,300");
1035    TEST_CI("", "echo(3,9)  |per_cent;fper_cent" ACI_MERGE, "33,33.3333");
1036    TEST_CI("", "echo(1,8)  |per_cent;fper_cent" ACI_MERGE, "12,12.5");
1037    TEST_CI("", "echo(15,16)|per_cent;fper_cent" ACI_MERGE, "93,93.75");
1038    TEST_CI("", "echo(1,8)  |fper_cent|round(0)", "13");
1039    TEST_CI("", "echo(15,16)|fper_cent|round(0);round(1)" ACI_MERGE, "94,93.8");
1040
1041    TEST_CI("", "echo(1,2,3)|plus(1)"     ACI_MERGE, "2,3,4");
1042    TEST_CI("", "echo(1,2,3)|minus(2)"    ACI_MERGE, "-1,0,1");
1043    TEST_CI("", "echo(1,2,3)|mult(42)"    ACI_MERGE, "42,84,126");
1044    TEST_CI("", "echo(1,2,3)|div(2)"      ACI_MERGE, "0,1,1");
1045    TEST_CI("", "echo(1,2,3)|rest(2)"     ACI_MERGE, "1,0,1");
1046    TEST_CI("", "echo(1,2,3)|per_cent(3)" ACI_MERGE, "33,66,100");
1047
1048    // rounding
1049#define ROUND_FLOATS(dig) "echo(0.3826,0.50849,12.58,77.2,700.099,0.9472e-4,0.175e+7)|round(" #dig ")" ACI_MERGE
1050
1051    TEST_CI("", ROUND_FLOATS(4),  "0.3826,0.5085,12.58,77.2,700.099,0.0001,1.75e+06");
1052    TEST_CI("", ROUND_FLOATS(3),  "0.383,0.508,12.58,77.2,700.099,0,1.75e+06");
1053    TEST_CI("", ROUND_FLOATS(2),  "0.38,0.51,12.58,77.2,700.1,0,1.75e+06");
1054    TEST_CI("", ROUND_FLOATS(1),  "0.4,0.5,12.6,77.2,700.1,0,1.75e+06");
1055    TEST_CI("", ROUND_FLOATS(0),  "0,1,13,77,700,0,1.75e+06");
1056    TEST_CI("", ROUND_FLOATS(-1), "0,0,10,80,700,0,1.75e+06");
1057    TEST_CI("", ROUND_FLOATS(-2), "0,0,0,100,700,0,1.75e+06");
1058    TEST_CI("", ROUND_FLOATS(-3), "0,0,0,0,1000,0,1.75e+06");
1059    TEST_CI("", ROUND_FLOATS(-5), "0,0,0,0,0,0,1.8e+06");
1060    TEST_CI("", ROUND_FLOATS(-6), "0,0,0,0,0,0,2e+06");
1061
1062
1063    // compare (integers)
1064    TEST_CI("", "echo(9,3)|isBelow;isAbove;isEqual", "010");
1065    TEST_CI("", "echo(3,9)|isBelow;isAbove;isEqual", "100");
1066    TEST_CI("", "echo(5,5)|isBelow;isAbove;isEqual", "001");
1067
1068    TEST_CI("", "echo(1,2,3)|isBelow(2)", "100");
1069    TEST_CI("", "echo(1,2,3)|isAbove(2)", "001");
1070    TEST_CI("", "echo(1,2,3)|isEqual(2)", "010");
1071
1072    TEST_CI("", "echo(1,2,3,4,5)|inRange(2,4)",        "01110");
1073    TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-2,-4)", "00000"); // empty range
1074    TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-4,-2)", "01110");
1075
1076    // compare (floats)
1077    TEST_CI("", "echo(1.7,1.4)  |isBelow;isAbove;isEqual", "010");
1078    TEST_CI("", "echo(-0.7,0.1) |isBelow;isAbove;isEqual", "100");
1079    TEST_CI("", "echo(5.10,5.1) |isBelow;isAbove;isEqual", "001");
1080    TEST_CI("", "echo(0.10,.11) |isBelow;isAbove;isEqual", "100");
1081    TEST_CI("", "echo(-7.1,-6.9)|isBelow;isAbove;isEqual", "100");
1082    TEST_CI("", "echo(1e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1083    TEST_CI("", "echo(2e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1084    TEST_CI("", "echo(2e+5,1e-6)|isBelow;isAbove;isEqual", "010");
1085    TEST_CI("", "echo(2e-5,1e+6)|isBelow;isAbove;isEqual", "100");
1086
1087    TEST_CI("", "echo(.1,.2,.3,.4,.5)   |inRange(.2,.4)",  "01110"); 
1088    TEST_CI("", "echo(.8,.9,1.0,1.1,1.2)|inRange(.9,1.1)", "01110");
1089    TEST_CI("", "echo(-.2,-.1,0.0,.1,.2)|inRange(-.1,.1)", "01110");
1090
1091    // boolean operators
1092    TEST_CI("0", "Not", "1");
1093    TEST_CI("1", "Not", "0");
1094
1095    TEST_CI("",     "Not", "1");
1096    TEST_CI("text", "Not", "1");
1097
1098    TEST_CI("", "echo(0,1)|Not", "10");
1099    TEST_CI("", "echo(0,0)|Or;And",  "00");
1100    TEST_CI("", "echo(0,1)|Or;And",  "10");
1101    TEST_CI("", "echo(1,0)|Or;And",  "10");
1102    TEST_CI("", "echo(1,1)|Or;And",  "11");
1103
1104    TEST_CI("", "command(echo(1\\,0)|Or);command(echo(0\\,1)|Or)|And",  "1");
1105
1106    // readdb
1107    TEST_CI("", "readdb(name)",     "LcbReu40");
1108    TEST_CI("", "readdb(acc)",      "X76328");
1109    TEST_CI("", "readdb(acc,name)", "X76328LcbReu40");
1110
1111    TEST_CI_ERROR_CONTAINS("", "readdb()",     "Invalid empty parameter list '()'");
1112    TEST_CI_ERROR_CONTAINS("", "readdb",       "syntax: readdb(fieldname[,fieldname]+)");
1113    TEST_CI               ("", "readdb(\"\")", ""); // still weird (want field error?)
1114
1115    // taxonomy
1116    TEST_CI("", "taxonomy(1)",           "No default tree");
1117    TEST_CI("", "taxonomy(tree_nuc, 1)", "group1");
1118    TEST_CI("", "taxonomy(tree_nuc, 5)", "lower-red/group1");
1119}
1120
1121__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_2c() {
1122    ACI_test_env E;
1123    GBL_env      base_env(E.gbmain(), NULp);
1124
1125    GBDATA * const gb_data = E.gbspecies();
1126    GBL_call_env   callEnv(gb_data, base_env);
1127
1128    GBL_env      env_tree_nuc(E.gbmain(), "tree_nuc");
1129    GBL_call_env callEnv_tree_nuc(gb_data, env_tree_nuc);
1130
1131    TEST_CI_ERROR_CONTAINS("", "taxonomy",        "syntax: taxonomy([tree_name,]count)");
1132    TEST_CI_ERROR_CONTAINS("", "taxonomy(1,2,3)", "syntax: taxonomy([tree_name,]count)");
1133    TEST_CI_WITH_ENV("", callEnv_tree_nuc, "taxonomy(1)", "group1");
1134
1135    // diff, filter + change
1136    TEST_CI("..acg--ta-cgt..." ","
1137            "..acg--ta-cgt...", WITH_SPLITTED("|diff(pairwise=1)"),
1138            "................");
1139    TEST_CI("..acg--ta-cgt..." ","
1140            "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,equal==)"),
1141            "==cgt=====acg===");
1142    TEST_CI("..acg--ta-cgt..." ","
1143            "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,differ=X)"),
1144            "..XXX.....XXX...");
1145    TEST_CI("", "sequence|diff(species=LcbFruct)|checksum", "645E3107");
1146
1147    TEST_CI("..XXX.....XXX..." ","
1148            "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,exclude=X)"),
1149            "..--ta-...");
1150    TEST_CI("..XXX.....XXX..." ","
1151            "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,include=X)"),
1152            "acgcgt");
1153    TEST_CI("", "sequence|filter(species=LcbFruct,include=.-)", "-----------T----T-------G----------C-----T----T...");
1154
1155    TEST_CI("...XXX....XXX..." ","
1156            "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=C,change=100)"),
1157            "..aCC--ta-CCC...");
1158    TEST_CI("...XXXXXXXXX...." ","
1159            "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=-,change=100)"),
1160            "..a---------t...");
1161
1162    // test environment forwarding
1163    TEST_CI("x", ":*=*,*(acc)",                                 "x,X76328");          // test DB-item is forwarded to direct SRT-command
1164    TEST_CI("x", "srt(\"*=*,*(acc)\")",                         "x,X76328");          // test DB-item is forwarded to ACI-command 'srt'
1165    TEST_CI("x", ":*=*,*(acc|dd;\",\";readdb(name))",           "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside SRT-command
1166    TEST_CI("x", "srt(\"*=*,*(acc|dd;\"\\,\";readdb(name))\")", "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside ACI-command 'srt'
1167    TEST_CI("x", "command(\"dd;\\\",\\\";readdb(name)\")",      "x,LcbReu40");        // test DB-item is forwarded to ACI-subexpression inside ACI-command 'command'
1168
1169    // test treename is forwarded to sub-expressions
1170    TEST_CI_WITH_ENV("x", callEnv_tree_nuc, ":*=*,*(acc|dd;\\\",\\\";taxonomy(1))",                   "x,X76328,group1");
1171    TEST_CI_WITH_ENV("",  callEnv_tree_nuc, "taxonomy(5)|srt(*=*\\,*(acc|dd;\\\",\\\";taxonomy(1)))", "lower-red/group1,X76328,group1");
1172    TEST_CI_WITH_ENV("",  callEnv_tree_nuc, "taxonomy(5)|command(\"dd;\\\",\\\";taxonomy(1)\")",      "lower-red/group1,group1");
1173
1174    // test database root is forwarded to sub-expressions (used by commands 'ali_name', 'sequence_type', ...)
1175    TEST_CI("x", ":*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)",         "x,X76328,ali_16s,rna"); 
1176    TEST_CI("x", "srt(\"*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)\")", "x,X76328,ali_16s,rna");
1177    TEST_CI("x", "command(\"dd;\\\",\\\";ali_name;\\\",\\\";sequence_type\")",        "x,ali_16s,rna");
1178
1179    // exec
1180    TEST_CI("c,b,c,b,a,a", WITH_SPLITTED("|exec(\"(sort|uniq)\")|split|dropempty"),              "a,b,c");
1181    TEST_CI("a,aba,cac",   WITH_SPLITTED("|exec(\"perl\",-pe,s/([bc])/$1$1/g)|split|dropempty"), "a,abba,ccacc");
1182
1183    // error cases
1184    TEST_CI_ERROR_CONTAINS("", "nocmd",            "Unknown command 'nocmd'");
1185    TEST_CI_ERROR_CONTAINS("", "|nocmd",           "Unknown command 'nocmd'");
1186    TEST_CI_ERROR_CONTAINS("", "caps(x)",          "syntax: caps (no parameters)");
1187    TEST_CI_ERROR_CONTAINS("", "trace",            "syntax: trace(0|1)");
1188    TEST_CI_ERROR_CONTAINS("", "count",            "syntax: count(\"characters to count\")");
1189    TEST_CI_ERROR_CONTAINS("", "count(a,b)",       "syntax: count(\"characters to count\")");
1190    TEST_CI_ERROR_CONTAINS("", "len(a,b)",         "syntax: len[(\"characters not to count\")]");
1191    TEST_CI_ERROR_CONTAINS("", "plus(a,b,c)",      "syntax: plus[(Expr1[,Expr2])]");
1192    TEST_CI_ERROR_CONTAINS("", "count(a,b",        "Reason: Missing ')'");
1193    TEST_CI_ERROR_CONTAINS("", "count(a,\"b)",     "unbalanced '\"' in 'count(a,\"b)'");
1194    TEST_CI_ERROR_CONTAINS("", "count(a,\"b)\"",   "Reason: Missing ')'");
1195    TEST_CI_ERROR_CONTAINS("", "dd;dd|count",      "syntax: count(\"characters to count\")");
1196    TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x)",   "Invalid parameter syntax for '\"a\"x'");
1197    TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x\")", "unbalanced '\"' in '|count(\"a\"x\")'");
1198    TEST_CI_ERROR_CONTAINS("", "|count(\"a)",      "unbalanced '\"' in '|count(\"a)'");
1199
1200    TEST_CI_ERROR_CONTAINS__BROKEN("", "|\"xx\"bla", "bla", "xx"); // @@@ should report some error referring to unseparated + unknown command 'bla'
1201
1202    TEST_CI_ERROR_CONTAINS("", "translate(a)",       "syntax: translate(old,new[,other])");
1203    TEST_CI_ERROR_CONTAINS("", "translate(a,b,c,d)", "syntax: translate(old,new[,other])");
1204    TEST_CI_ERROR_CONTAINS("", "translate(a,b,xx)",  "has to be one character");
1205    TEST_CI_ERROR_CONTAINS("", "translate(a,b,)",    "has to be one character");
1206
1207    TEST_CI_ERROR_CONTAINS(NULp, "whatever", "ARB ERROR: Can't read this DB entry as string"); // here gb_data is the species container
1208
1209    TEST_CI("hello",   ":??""=(?-?)",   "(h-e)(l-l)o");
1210    TEST_CI("hello",   ":??""=(?-?)?",  "(h-e)?(l-l)?o");
1211    TEST_CI("hello",   ":??""=(?-?0)?", "(h-e0)?(l-l0)?o");
1212    TEST_CI("hello",   ":??""=(?-?3)?", "(h-?)e(l-?)lo");
1213
1214    // show linefeed is handled identical for encoded and escaped linefeeds:
1215    TEST_CI("abc",   ":?=?\\n", "a\nb\nc\n");
1216    TEST_CI("abc",   ":?=?\n",  "a\nb\nc\n");
1217
1218    // same for string-terminator:
1219    TEST_CI("abc",   ":?=?.\\0 ignored:b=d", "a.b.c.");
1220    TEST_CI("abc",   ":?=?.\0  ignored:b=d", "a.b.c.");
1221
1222    TEST_CI("",   ":*=X*Y*(full_name|len)", "XY21");
1223    TEST_CI("",   ":*=*(full_name\\:reuteri=xxx)", "Lactobacillus xxx");
1224    TEST_CI("",   ":*=*(abc\\:a=A)", ""); // non-existing field -> empty input -> empty output
1225    TEST_CI("hello world",   ":* =*(\\:*=hi)-", "hi-world"); // srt subexpressions also work w/o key
1226    TEST_CI_ERROR_CONTAINS("",   ":*=*(full_name\\:reuteri)", "no '=' found"); // test handling of errors from invalid srt-subexpression
1227
1228    TEST_CI("", ":*=*(acc#have no acc)", "X76328");
1229    TEST_CI("", ":*=*(abc#have no abc)", "have no abc");
1230    TEST_CI("", ":*=*(#no field)",       "no field");
1231
1232    TEST_CI_ERROR_CONTAINS("", ":*=*(unbalanced",     "Unbalanced parenthesis in '(unbalanced'");
1233    TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)ced",   "Unbalanced parenthesis in '(unba(lan)ced'");
1234    TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)c)ed)", "Invalid char '(' in key 'unba(lan)c'");      // invalid key name
1235    TEST_CI               ("", ":*=*(unbalanc)ed)",   "ed)");
1236}
1237
1238__ATTR__REDUCED_OPTIMIZE void TEST_GB_command_interpreter_3() {
1239    ACI_test_env E;
1240    GBL_env      base_env(E.gbmain(), NULp);
1241
1242    {
1243        // execute ACI on 'full_name' (=GB_STRING) in this section ------------------------------
1244        GBDATA * const gb_data = GB_entry(E.gbspecies(), "full_name");
1245        GBL_call_env   callEnv(gb_data, base_env);
1246
1247        TEST_CI(NULp, "",                            "Lactobacillus reuteri");     // noop
1248        TEST_CI(NULp, "|len",                        "21");
1249        TEST_CI(NULp, ":tobac=",                     "Lacillus reuteri");
1250        TEST_CI(NULp, "/ba.*us/B/",                  "LactoB reuteri");
1251        TEST_CI(NULp, ":::*=hello:::hell=heaven:::", "heaveno");                   // test superfluous ':'s
1252        TEST_CI(NULp, ":* *=;*2,*1;",                ";reuteri,Lactobacillus;");   // tests multiple successful matches of '*'
1253        TEST_CI(NULp, ":* ??*=;?2,?1,*2,*1;",        ";e,r,uteri,Lactobacillus;"); // tests multiple successful matches of '*' and '?' (also tests working multi-wildcards "??" and "?*")
1254        TEST_CI(NULp, ":Lacto*eutei=*1",             "Lactobacillus reuteri");     // test match failing after '*' (=> skips replace)
1255        TEST_CI(NULp, ":Lact?bac?lls=?1?2",          "Lactobacillus reuteri");     // test match failing after 2nd '?' (=> skips replace)
1256        TEST_CI(NULp, ":*reuteri?=?1",               "Lactobacillus reuteri");     // test match failing on '?' behind EOS (=> skips replace)
1257
1258        // tests for (unwanted) multi-wildcards:
1259        TEST_CI__BROKEN(NULp, ":Lacto*?lus=(*1,?1)", "(baci,l)", "Lactobacillus reuteri"); // @@@ diffcult to achieve (alternative: forbid "*?" and report error)
1260        TEST_CI__BROKEN("Lactobaci\4lus reuteri", ":Lacto*?lus=(*1,?1)", "<want error instead>", "(baci,?) reuteri"); // @@@ pathological case forcing a match for above situation (ASCII 4 is code for '?' wildcard)
1261        TEST_CI_ERROR_CONTAINS__BROKEN(NULp, ":Lacto**lus=(*1,*2)", "invalid", "Lactobacillus reuteri"); // @@@ impossible: (forbid "**" and report error)
1262        TEST_CI_ERROR_CONTAINS__BROKEN("Lactobac\3lus reuteri", ":Lacto**lus=(*1,*2)", "invalid", "(bac,*) reuteri"); // @@@ pathological case forcing a match for above situation (ASCII 3 is code for '*' wildcard)
1263
1264        TEST_CI_ERROR_CONTAINS(NULp, ":*=*(|wot)",    "Unknown command 'wot'"); // provoke error in gbs_build_replace_string [coverage]
1265        TEST_CI_ERROR_CONTAINS("",   ":*=X*Y*(|wot)", "Unknown command 'wot'"); // dito (other caller)
1266
1267        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(full_name|len)",    "can't read key 'full_name' (DB item is no container)");
1268        TEST_CI               ("", ":*=X*Y*(../full_name|len)", "XY21"); // searches entry via parent-entry (from non-container)
1269
1270        TEST_CI(NULp, "|taxonomy(1)", "No default tree");
1271        TEST_CI_ERROR_CONTAINS(NULp, "|taxonomy(tree_nuc,2)", "Container has neither 'name' nor 'group_name' entry - can't detect container type");
1272    }
1273    {
1274        // execute ACI on 'ARB_color' (=GB_INT) in this section ------------------------------
1275        GBDATA * const gb_data = GB_entry(E.gbspecies(), "ARB_color");
1276        GBL_call_env   callEnv(gb_data, base_env);
1277
1278        TEST_CI(NULp, "", "1"); // noop
1279        TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna"); // test global database access works when specific database element is specified
1280    }
1281    {
1282        // execute ACI without database element in this section ------------------------------
1283        GBDATA * const gb_data = NULp;
1284        GBL_call_env   callEnv(gb_data, base_env);
1285
1286        TEST_CI_ERROR_CONTAINS(NULp, "", "no input streams found");
1287        TEST_CI("", ":*=\\tA*1Z\t", "\tAZ\t"); // special case (match empty input using '*'); test TAB conversion
1288
1289        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(|wot)",      "Unknown command 'wot'");
1290        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey|len)", "can't read key 'nokey' (called w/o database item)");
1291        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey)",     "can't read key 'nokey' (called w/o database item)");
1292
1293        // test global database access also works w/o specific database element
1294        TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna");
1295        TEST_CI("", "command(\"ali_name;\\\",\\\";sequence_type\")", "ali_16s,rna");
1296
1297        // empty+NULp commands:
1298        TEST_CI("in", NULp, "in");
1299        TEST_CI("in", "", "in");
1300        TEST_CI("in", ":", "in");
1301        TEST_CI("in", "::", "in");
1302
1303        // empty+NULp commands:
1304        TEST_CI("in", NULp, "in");
1305        TEST_CI("in", "", "in");
1306        TEST_CI("in", ":", "in");
1307        TEST_CI("in", "::", "in");
1308    }
1309
1310    // register custom ACI commands
1311    {
1312        const GBL_command_lookup_table& stdCmds = ACI_get_standard_commands();
1313
1314        GBL_command_definition custom_cmds[] = {
1315            { "custom", gbx_custom },              // new command 'custom'
1316            { "upper",  stdCmds.lookup("lower") }, // change meaning of lower ..
1317            { "lower",  stdCmds.lookup("upper") }, // .. and upper
1318
1319            {NULp, NULp}
1320        };
1321
1322        GBL_custom_command_lookup_table custom(custom_cmds, ARRAY_ELEMS(custom_cmds)-1, stdCmds, PERMIT_SUBSTITUTION);
1323
1324        GBDATA * const gb_data = E.gbspecies();
1325
1326        GBL_env      custom_env(E.gbmain(), NULp, custom);
1327        GBL_call_env customCallEnv(gb_data, custom_env);
1328        GBL_call_env callEnv(gb_data, base_env);
1329
1330        // lookup overwritten commands:
1331        TEST_EXPECT(custom.lookup("upper") == stdCmds.lookup("lower"));
1332        TEST_EXPECT(custom.lookup("lower") == stdCmds.lookup("upper"));
1333
1334        // test new commands:
1335        TEST_CI_WITH_ENV      ("abc", customCallEnv,  "dd;custom;dd", "abc4711abc");
1336        TEST_CI_ERROR_CONTAINS("abc", "dd;custom;dd", "Unknown command 'custom'"); // unknown in standard environment
1337
1338        // test overwritten commands:
1339        TEST_CI_WITH_ENV("abcDEF,", customCallEnv, "dd;lower;upper", "abcDEF,ABCDEF,abcdef,");
1340        TEST_CI         ("abcDEF,",                "dd;lower;upper", "abcDEF,abcdef,ABCDEF,");
1341    }
1342}
1343
1344void TEST_GB_command_interpreter_4() {
1345    ACI_test_env E;
1346    GBL_env      base_env(E.gbmain(), NULp);
1347
1348    // execute ACI on species container (=GB_DB) in this section ------------------------------
1349    GBDATA * const gb_data = E.gbspecies();
1350    GBL_call_env   callEnv(gb_data, base_env);
1351
1352    TEST_CI("LcbReu40", "findspec(\"readdb             (acc)\")", "X76328");
1353    TEST_CI("LcbFruct", "findspec(\"readdb             (acc)\")", "X76330");
1354    TEST_CI("",         "readdb(name)|findspec(\"readdb(acc)\")", "X76328");
1355
1356    TEST_CI("LcbReu40;lcbfruct", "split(\";\")|findspec(\"readdb(acc)\")|merge(\";\")", "X76328;X76330");     // usecase ("bring next-relatives info into name-independent format")
1357    TEST_CI("X76328;x76330",     "split(\";\")|findacc(\"readdb(name)\")|merge(\";\")", "LcbReu40;LcbFruct"); // perform opposite (tests 'findacc')
1358
1359    TEST_CI               ("",         "findspec(\"invalid\")", "");                                           // does not execute command for unnamed item
1360    TEST_CI_ERROR_CONTAINS("LcbReu40", "findspec(\"invalid\")", "Unknown command 'invalid'");
1361    TEST_CI_ERROR_CONTAINS("unknown",  "findspec(\"invalid\")", "No species with name 'unknown' found");
1362    TEST_CI_ERROR_CONTAINS("unknown",  "findacc(\"invalid\")",  "No species with acc 'unknown' found");
1363}
1364
1365#endif // UNIT_TESTS
1366
Note: See TracBrowser for help on using the repository browser.