source: tags/ms_ra2q1/ARBDB/gb_aci.cxx

Last change on this file was 17140, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 61.0 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_ref()) {
375            GB_export_error("ACI: no input streams found");
376            return NULp;
377        }
378
379        if (GB_read_type(callEnv.get_ref()) == GB_STRING) {
380            str = GB_read_char_pntr(callEnv.get_ref());
381        }
382        else {
383            char *asstr = GB_read_as_string(callEnv.get_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#define TEST_CI_ERROR_CONTAINS(input,cmd,errorpart_expected)                                                            \
505    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_command_interpreter_in_env(input, cmd, callEnv), errorpart_expected)
506
507#define TEST_CI_ERROR_CONTAINS__BROKEN(input,cmd,errorpart_expected,unexpected_result) do{                              \
508        char         *result;                                                                                           \
509        TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS__BROKEN(result = GB_command_interpreter_in_env(input, cmd, callEnv), errorpart_expected); \
510        TEST_EXPECT_EQUAL(result, unexpected_result);                                                                   \
511        free(result);                                                                                                   \
512    }while(0)
513
514
515static GBDATA     *RCI_gb_main = NULp;
516static const char *RCI_input   = NULp;
517static const char *RCI_cmd     = NULp;
518static GBDATA     *RCI_gbd     = NULp;
519
520inline void run_ci() {
521    GBL_env      env(RCI_gb_main, NULp);
522    GBL_call_env callEnv(RCI_gbd, env);
523    GB_command_interpreter_in_env(RCI_input, RCI_cmd, callEnv);
524}
525
526#define TEST_CI_SEGFAULTS(input,cmd) do{        \
527        RCI_gb_main = gb_main;                  \
528        RCI_input   = input;                    \
529        RCI_cmd     = cmd;                      \
530        RCI_gbd     = gb_data;                  \
531        TEST_EXPECT_SEGFAULT(run_ci);           \
532    }while(0)
533
534#define TEST_CI_SEGFAULTS__UNWANTED(input,cmd) do{        \
535        RCI_gb_main = gb_main;                            \
536        RCI_input   = input;                              \
537        RCI_cmd     = cmd;                                \
538        RCI_gbd     = gb_data;                            \
539        TEST_EXPECT_SEGFAULT__UNWANTED(run_ci);           \
540    }while(0)
541
542#define ACI_SPLIT          "|split(\",\",0)"
543#define ACI_MERGE          "|merge(\",\")"
544#define WITH_SPLITTED(aci) ACI_SPLIT aci ACI_MERGE
545
546static GB_ERROR gbx_custom(GBL_command_arguments *args) {
547    EXPECT_NO_PARAM(args);
548    for (int i=0; i<args->input.size(); ++i) {
549        args->output.insert(strdup("4711"));
550    }
551    return NULp;
552}
553
554class ACI_test_env : virtual Noncopyable {
555    GB_shell            shell;
556    GBDATA             *gb_main;
557    LocallyModify<int>  traceMode;
558    GB_transaction      ta;
559    GBDATA             *gb_species;
560public:
561    ACI_test_env() :
562        gb_main(GB_open("TEST_aci.arb", "rw")),
563        traceMode(traceACI, 0), // set to 1 to trace all ACI tests
564        ta(gb_main)
565    {
566        gb_assert(gb_main);
567        gb_species = GBT_find_species(gb_main, "LcbReu40"); // ../UNIT_TESTER/run/TEST_aci.arb@LcbReu40
568    }
569    ~ACI_test_env() {
570        TEST_EXPECT_NO_ERROR(ta.close(NULp));
571        GB_close(gb_main);
572    }
573
574    GBDATA *gbmain() const { return gb_main; }
575    GBDATA *gbspecies() const { return gb_species; }
576};
577
578__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_1() {
579    ACI_test_env E;
580    GBL_env      base_env(E.gbmain(), NULp);
581
582    // execute ACI on species container (=GB_DB) in this section ------------------------------
583    GBDATA * const gb_data = E.gbspecies();
584    GBL_call_env   callEnv(gb_data, base_env);
585
586    TEST_CI_NOOP("bla", "");
587
588    TEST_CI("bla", ":a=u", "blu"); // simple SRT
589
590    // GBS_REGREPLACE_TESTS:
591    TEST_CI("bla",    "/a/u/",      "blu");  // simple regExp replace
592
593    TEST_CI("test",   "/_[0-9]+//", "test"); // simple regExp replace (failing, ie. no match -> no replace)
594
595    TEST_CI("blabla", "/l.*b/",     "lab");  // simple regExp match
596    TEST_CI("blabla", "/b.b/",      "");     // simple regExp match (failing)
597
598    TEST_CI("tx_01_2", "/_[0-9]+//",  "tx");    // simple regExp replace (replace all occurrences)
599    TEST_CI("tx_01_2", "/_[0-9]+$//", "tx_01"); // simple regExp replace (replace one occurrence)
600
601    TEST_CI_ERROR_CONTAINS("xx_____",    "/_*//",   "regular expression '_*' matched an empty string"); // caused a deadlock until [16326]
602    TEST_CI("xx_____",   "/_//",                  "xx");      // working removal of multiple '_'
603    TEST_CI("xx_____yy", "/(_+)([^_]|$)/-=-\\2/", "xx-=-yy"); // replace multiple consecutive '_'
604    TEST_CI("xx_____",   "/(_+)([^_]|$)/-=-\\2/", "xx-=-");   // replace multiple consecutive '_'
605
606    TEST_CI("xx/yy/zz",   "/\\//-/", "xx-yy-zz");   // search expression with an escaped slash
607    TEST_CI("xx-yy-zz",   "/-/\\//", "xx/yy/zz");   // reverse (escaped slash in replace expression)
608
609    TEST_CI_ERROR_CONTAINS("xx",    "\\///",   "Unknown command");
610    TEST_CI               ("x/x",   "/\\//",   "/");
611    TEST_CI_ERROR_CONTAINS("xx",    "//\\/",   "Trailing backslash");
612
613    // escape / quote
614    TEST_CI_INVERSE("ac", "|quote",        "|unquote",          "\"ac\"");
615    TEST_CI_INVERSE("ac", "|escape",       "|unescape",         "ac");
616    TEST_CI_INVERSE("ac", "|escape|quote", "|unquote|unescape", "\"ac\"");
617    TEST_CI_INVERSE("ac", "|quote|escape", "|unescape|unquote", "\\\"ac\\\"");
618
619    TEST_CI_INVERSE("a\"b\\c", "|quote",        "|unquote",          "\"a\"b\\c\"");
620    TEST_CI_INVERSE("a\"b\\c", "|escape",       "|unescape",         "a\\\"b\\\\c");
621    TEST_CI_INVERSE("a\"b\\c", "|escape|quote", "|unquote|unescape", "\"a\\\"b\\\\c\"");
622    TEST_CI_INVERSE("a\"b\\c", "|quote|escape", "|unescape|unquote", "\\\"a\\\"b\\\\c\\\"");
623
624    TEST_CI_NOOP("ac", "|unquote");
625    TEST_CI_NOOP("\"ac", "|unquote");
626    TEST_CI_NOOP("ac\"", "|unquote");
627
628    TEST_CI               ("blabla", "|coUNT(ab)",         "4");                            // simple ACI
629    TEST_CI               ("l",      "|\"b\";dd;\"a\"|dd", "bla");                          // ACI with muliple streams
630    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)
631    TEST_CI               ("bla",    "|count(\"\")",       "0");                            // explicitly empty parameter (still strange, counts 0-byte before string-terminator; always 0)
632    TEST_CI               ("b a",    "|count(\" \")",      "1");                            // space in quotes
633    TEST_CI               ("b\\a",   "|count(\\a)",        "2");                            // count '\\' and 'a' (ok)
634    TEST_CI__BROKEN       ("b\\a",   "|count(\"\\a\")",    "1", "2");                       // should only count 'a' (which is escaped in param)
635    TEST_CI               ("b\\a",   "|count(\"\a\")",     "0");                            // does not contain '\a'
636    TEST_CI               ("b\a",    "|count(\"\a\")",     "1");                            // counts '\a'
637
638    // escaping (behavior is unexpected or weird and not documented very well)
639    TEST_CI("b\\a\a", "|count(\\a)",         "2"); // counts '\\' and 'a' (but not '\a')
640    TEST_CI("b\\a",   "|contains(\"\\\\\")", "0"); // searches for 2 backslashes (not in input)
641    TEST_CI("b\\a",   "|contains(\"\")",     "0"); // search for empty string never succeeds
642    TEST_CI("b\\a",   "|contains(\\)",       "2"); // finds backslash at position 1
643    TEST_CI("b\\\\a", "|contains(\"\\\\\")", "2"); // finds two backslashes at position 2
644
645    TEST_CI_ERROR_CONTAINS("b\\a",   "|contains(\"\\\")",   "ARB ERROR: unbalanced '\"' in '|contains(\"\\\")'"); // FIX: raises error (should search for 1 backslash)
646
647    // test binary ops
648    {
649        // LocallyModify<int> traceHere(traceACI, 1);
650        TEST_CI("", "\"5\";\"7\"|minus",                        "-2");
651        TEST_CI("", "\"5\"|minus(\"7\")",                       "-2");
652        // TEST_CI("", "minus(5,7)",               "-2"); // @@@ this fails (stating command '5' fails). fix how?
653        TEST_CI("", "minus(\"\"5\"\",\"\"7\"\")",               "-2"); // @@@ syntax needed here 'minus(""5"", ""7"")' should be documented more in-depth
654        TEST_CI("", "minus(\"\"5\";\"2\"|mult\",\"\"7\"\")",    "3"); // (5*2)-7
655        TEST_CI("", "minus(\"\"5\";\"2\\,\"|mult\",\"\"7\"\")", "3"); // comma has to be escaped
656
657        TEST_CI_ERROR_CONTAINS("", "minus(\"\"5\";\"2,\"|mult\",\"\"7\"\")", "Invalid parameter syntax for '\"\"5\";\"2'");
658    }
659
660    TEST_CI_NOOP("ab,bcb,abac", WITH_SPLITTED(""));
661    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|len"),                       "2,3,4");
662    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|count(a)"),                  "1,0,2");
663    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(len,count(a))"),       "1,3,2");
664    TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(\"\"5\"\",count(a))"), "4,5,3");
665
666    // test other recursive uses of GB_command_interpreter
667    TEST_CI("one",   "|dd;\"two\";dd|command(\"dd;\"_\";dd;\"-\"\")",                          "one_one-two_two-one_one-");
668    TEST_CI("",      "|sequence|command(\"/^([\\\\.-]*)[A-Z].*/\\\\1/\")|len",                 "9"); // count gaps at start of sequence
669    TEST_CI("one",   "|dd;dd|eval(\"\"up\";\"per\"|merge\")",                                  "ONEONE");
670    TEST_CI("1,2,3", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "one,two,three");
671    TEST_CI_ERROR_CONTAINS("1,4", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "Illegal param number '4' (allowed [0..3])");
672
673    // test define and do
674    TEST_CI("ignored", "define(d4, \"dd;dd;dd;dd\")",        "");
675    TEST_CI("ignored", "define(d16, \"do(d4)|do(d4)\")",     "");
676    TEST_CI("ignored", "define(d64, \"do(d4)|do(d16)\")",    "");
677    TEST_CI("ignored", "define(d4096, \"do(d64)|do(d64)\")", "");
678
679    TEST_CI("x",  "do(d4)",           "xxxx");
680    TEST_CI("xy", "do(d4)",           "xyxyxyxy");
681    TEST_CI("x",  "do(d16)",          "xxxxxxxxxxxxxxxx");
682    TEST_CI("x",  "do(d64)|len",      "64");
683    TEST_CI("xy", "do(d4)|len",       "8");
684    TEST_CI("xy", "do(d4)|len(\"\")", "8");
685    TEST_CI("xy", "do(d4)|len(x)",    "4");
686    TEST_CI("x",  "do(d4096)|len",    "4096");
687
688    // create 4096 streams (disable trace; logs to much):
689    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");
690
691    // other commands
692
693    // streams
694    TEST_CI("x", "dd;dd|streams",             "2");
695    TEST_CI("x", "dd;dd|dd;dd|streams",       "4");
696    TEST_CI("x", "dd;dd|dd;dd|dd;dd|streams", "8");
697    TEST_CI("x", "do(d4)|streams",            "1"); // stream is merged when do() returns
698
699    TEST_CI("", "ali_name", "ali_16s");  // ask for default-alignment name
700    TEST_CI("", "sequence_type", "rna"); // ask for sequence_type of default-alignment
701
702    // format
703    TEST_CI("acgt", "format", "          acgt");
704    TEST_CI("acgt", "format(firsttab=1)", " acgt");
705    TEST_CI("acgt", "format(firsttab=1, width=2)",
706            " ac\n"
707            "          gt");
708    TEST_CI("acgt", "format(firsttab=1,tab=1,width=2)",
709            " ac\n"
710            " gt");
711    TEST_CI("acgt", "format(firsttab=0,tab=0,width=2)",
712            "ac\n"
713            "gt");
714    TEST_CI("acgt", "format(firsttab=0,tab=0,width=1)",
715            "a\n"
716            "c\n"
717            "g\n"
718            "t");
719
720    TEST_CI_ERROR_CONTAINS("acgt", "format(gap=0)",   "Unknown Parameter 'gap=0' in command 'format'");
721    TEST_CI_ERROR_CONTAINS("acgt", "format(numleft)", "Unknown Parameter 'numleft' in command 'format'");
722
723    // format_sequence
724    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(numright=5, numleft)", "You may only specify 'numleft' OR 'numright',  not both");
725
726    TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numleft=1)",
727            "1    acgt\n"
728            "5    acgt\n"
729            "9    acgt\n"
730            "13   acg");
731
732    TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numright=9)", // test EMBL sequence formatting
733            "     acgt         4\n"
734            "     acgt         8\n"
735            "     acgt        12\n"
736            "     acg         15");
737
738    TEST_CI("acgtacgtacgtac", "format_sequence(firsttab=5,tab=5,width=4,gap=2,numright=-1)", // autodetect width for 'numright'
739            "     ac gt  4\n"
740            "     ac gt  8\n"
741            "     ac gt 12\n"
742            "     ac    14");
743
744    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,width=2,gap=1)",
745            "a c\n"
746            "g t");
747    TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=1)", "a c g t");
748    TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=2)", "ac gt");
749    TEST_CI("acgtacgt", "format_sequence(firsttab=0,width=10,gap=4)",      "acgt acgt");
750    TEST_CI("acgtacgt", "format_sequence(firsttab=1,width=10,gap=4)",      " acgt acgt");
751
752    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=0)",   "acgt");
753    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=-1)",  "acgt"); // no big alloc
754    TEST_CI("acgt", "format_sequence(firsttab=0,tab=-1,gap=-1)", "acgt"); // no big alloc
755    TEST_CI("acgt", "format(firsttab=0,tab=0,width=-1)",         "acgt"); // no big alloc for(!)format
756
757    TEST_CI("acgt", "format(firsttab=-1,tab=0)",           "acgt"); // did a 4Gb-alloc!
758    TEST_CI("acgt", "format(firsttab=-1,tab=-1)",          "acgt"); // did a 4Gb-alloc!
759    TEST_CI("acgt", "format(firsttab=-1,tab=-1,width=-1)", "acgt"); // did a 4Gb-alloc!
760
761    TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,gap=0,width=-1)",    "acgt"); // did a 4Gb-alloc!
762    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=0,gap=-1)",           "acgt"); // did a 4Gb-alloc!
763    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=-1,gap=-1)",          "acgt"); // did a 4Gb-alloc!
764    TEST_CI("acgt", "format_sequence(firsttab=-1,tab=-1,gap=-1,width=-1)", "acgt"); // did a 4Gb-alloc!
765
766    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(nl=c)",     "Unknown Parameter 'nl=c' in command 'format_sequence'");
767    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(forcenl=)", "Unknown Parameter 'forcenl=' in command 'format_sequence'");
768
769    TEST_CI_ERROR_CONTAINS("acgt", "format(width=0)",          "Illegal zero width");
770    TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(width=0)", "Illegal zero width");
771
772    // remove + keep
773    TEST_CI_NOOP("acgtacgt",         "remove(-.)");
774    TEST_CI     ("..acg--ta-cgt...", "remove(-.)", "acgtacgt");
775    TEST_CI     ("..acg--ta-cgt...", "remove(acgt)", "..---...");
776
777    TEST_CI_NOOP("acgtacgt",         "keep(acgt)");
778    TEST_CI     ("..acg--ta-cgt...", "keep(-.)", "..---...");
779    TEST_CI     ("..acg--ta-cgt...", "keep(acgt)", "acgtacgt");
780
781    // compare + icompare
782    TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|compare"),  "-1,0,1,1,1,-1");
783    TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|icompare"), "-1,0,1,-1,0,1");
784
785    TEST_CI("x,y,z", WITH_SPLITTED("|compare(\"y\")"), "-1,0,1");
786
787    // equals + iequals
788    TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|equals"),  "0,1,0");
789    TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|iequals"), "0,1,1");
790
791    // contains + icontains
792    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|contains(\"bc\")"),   "2,1,0");
793    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"bc\")"),  "2,1,1");
794    TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"d\")"),   "0,3,3");
795
796    // partof + ipartof
797    TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|partof(\"abcdefg\")"),   "1,0,4,0");
798    TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|ipartof(\"abcdefg\")"),  "1,2,4,0");
799
800    TEST_CI(", ,  ,x", WITH_SPLITTED("|isempty"),             "1,0,0,0");
801    TEST_CI(", ,  ,x", WITH_SPLITTED("|crop(\" \")|isempty"), "1,1,1,0");
802
803    // translate
804    TEST_CI("abcdefgh", "translate(abc,cba)",     "cbadefgh");
805    TEST_CI("abcdefgh", "translate(cba,abc)",     "cbadefgh");
806    TEST_CI("abcdefgh", "translate(hcba,abch,-)", "hcb----a");
807    TEST_CI("abcdefgh", "translate(aceg,aceg,-)", "a-c-e-g-");
808    TEST_CI("abbaabba", "translate(ab,ba,-)",     "baabbaab");
809    TEST_CI("abbaabba", "translate(a,x,-)",       "x--xx--x");
810    TEST_CI("abbaabba", "translate(,,-)",         "--------");
811
812    // echo
813    TEST_CI("", "echo", "");
814    TEST_CI("", "echo(x,y,z)", "xyz");
815    TEST_CI("", "echo(x;y,z)", "xyz"); // check ';' as param-separator
816    TEST_CI("", "echo(x;y;z)", "xyz");
817    TEST_CI("", "echo(x,y,z)|streams", "3");
818
819    // upper, lower + caps
820    TEST_CI("the QUICK brOwn Fox", "lower", "the quick brown fox");
821    TEST_CI("the QUICK brOwn Fox", "upper", "THE QUICK BROWN FOX");
822    TEST_CI("the QUICK brOwn FoX", "caps",  "The Quick Brown Fox");
823}
824
825__ATTR__REDUCED_OPTIMIZE__NO_GCSE void TEST_GB_command_interpreter_2() {
826    ACI_test_env E;
827    GBL_env      base_env(E.gbmain(), NULp);
828
829    // execute ACI on species container (=GB_DB) in this section ------------------------------
830    GBDATA * const gb_data = E.gbspecies();
831    GBL_call_env   callEnv(gb_data, base_env);
832
833    // head, tail + mid/mid0
834    TEST_CI     ("1234567890", "head(3)", "123");
835    TEST_CI     ("1234567890", "head(9)", "123456789");
836    TEST_CI_NOOP("1234567890", "head(10)");
837    TEST_CI_NOOP("1234567890", "head(20)");
838
839    TEST_CI     ("1234567890", "tail(4)", "7890");
840    TEST_CI     ("1234567890", "tail(9)", "234567890");
841    TEST_CI_NOOP("1234567890", "tail(10)");
842    TEST_CI_NOOP("1234567890", "tail(20)");
843
844    TEST_CI("1234567890", "tail(0)", "");
845    TEST_CI("1234567890", "head(0)", "");
846    TEST_CI("1234567890", "tail(-2)", "");
847    TEST_CI("1234567890", "head(-2)", "");
848
849    TEST_CI("1234567890", "mid(3,5)", "345");
850    TEST_CI("1234567890", "mid(2,2)", "2");
851
852    TEST_CI("1234567890", "mid0(3,5)", "456");
853
854    TEST_CI("1234567890", "mid(9,20)", "90");
855    TEST_CI("1234567890", "mid(20,20)", "");
856
857    TEST_CI("1234567890", "tail(3)",     "890"); // example from ../HELP_SOURCE/oldhelp/commands.hlp@mid0
858    TEST_CI("1234567890", "mid(-2,0)",   "890");
859    TEST_CI("1234567890", "mid0(-3,-1)", "890");
860
861    // tab + pretab
862    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(2)"),    "x ,xx,xxx");
863    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(3)"),    "x  ,xx ,xxx");
864    TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(4)"),    "x   ,xx  ,xxx ");
865    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(2)"), " x,xx,xxx");
866    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(3)"), "  x, xx,xxx");
867    TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(4)"), "   x,  xx, xxx");
868
869    // crop
870    TEST_CI("   x  x  ",         "crop(\" \")",     "x  x");
871    TEST_CI("\n \t  x  x \n \t", "crop(\"\t\n \")", "x  x");
872
873    // cut, drop, dropempty and dropzero
874    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|cut(2,3,5)"),        "two,three,five");
875    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|drop(2,3,5)"),       "one,four,six");
876
877    TEST_CI_ERROR_CONTAINS("a", "drop(2)",   "Illegal stream number '2' (allowed [1..1])");
878    TEST_CI_ERROR_CONTAINS("a", "drop(0)",   "Illegal stream number '0' (allowed [1..1])");
879    TEST_CI_ERROR_CONTAINS("a", "drop",      "syntax: drop(streamnumber[,streamnumber]+)");
880    TEST_CI_ERROR_CONTAINS("a", "cut(2)",    "Illegal stream number '2' (allowed [1..1])");
881    TEST_CI_ERROR_CONTAINS("a", "cut(0)",    "Illegal stream number '0' (allowed [1..1])");
882    TEST_CI_ERROR_CONTAINS("a", "cut",       "syntax: cut(streamnumber[,streamnumber]+)");
883    TEST_CI_ERROR_CONTAINS("a", "cut()",     "Invalid empty parameter list '()'");
884    TEST_CI_ERROR_CONTAINS("a", "cut(\"\")", "Illegal stream number '0' (allowed [1..1])"); // still strange (atoi("")->0)
885
886    TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|dropempty|streams"), "6");
887    TEST_CI("one,two,,,five,six",          WITH_SPLITTED("|dropempty|streams"), "4");
888    TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty"),         "");
889    TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty|streams"), "0");
890
891    TEST_CI("1,0,0,2,3,0",                 WITH_SPLITTED("|dropzero"),          "1,2,3");
892    TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero"),          "");
893    TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero|streams"),  "0");
894
895    // swap
896    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap"),                "1,2,3,four,six,five");
897    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)"),           "1,3,2,four,five,six");
898    TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)|swap(4,3)"), "1,3,four,2,five,six");
899    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,3)"));
900    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(2,3)"));
901    TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(3,1)|swap(2,1)|swap(1,3)"));
902
903    TEST_CI_ERROR_CONTAINS("a",   "swap",                        "need at least two input streams");
904    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(2,3)"),   "Illegal stream number '3' (allowed [1..2])");
905    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(3,2)"),   "Illegal stream number '3' (allowed [1..2])");
906    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1)"),     "syntax: swap[(streamnumber,streamnumber)]");
907    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1,2,3)"), "syntax: swap[(streamnumber,streamnumber)]");
908
909    // toback + tofront
910    TEST_CI     ("front,mid,back", WITH_SPLITTED("|toback(2)"),  "front,back,mid");
911    TEST_CI     ("front,mid,back", WITH_SPLITTED("|tofront(2)"), "mid,front,back");
912    TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|toback(3)"));
913    TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|tofront(1)"));
914    TEST_CI_NOOP("a",              WITH_SPLITTED("|tofront(1)"));
915    TEST_CI_NOOP("a",              WITH_SPLITTED("|toback(1)"));
916
917    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront(3)"),  "Illegal stream number '3' (allowed [1..2])");
918    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(3)"),   "Illegal stream number '3' (allowed [1..2])");
919    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront"),     "syntax: tofront(streamnumber)");
920    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(1,2)"), "syntax: toback(streamnumber)");
921    TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|merge(1,2)"),  "syntax: merge[(\"separator\")]");
922
923    // split
924    TEST_CI               ("a\nb", "|split"        ACI_MERGE, "a,b");
925    TEST_CI               ("a-b",  "|split(-)"     ACI_MERGE, "a,b");
926    TEST_CI               ("a-b",  "|split(-,0)"   ACI_MERGE, "a,b");
927    TEST_CI               ("a-b",  "|split(-,1)"   ACI_MERGE, "a,-b");
928    TEST_CI               ("a-b",  "|split(-,2)"   ACI_MERGE, "a-,b");
929    TEST_CI_ERROR_CONTAINS("a-b",  "|split(-,3)"   ACI_MERGE, "Illegal split mode '3' (valid: 0..2)");
930    TEST_CI_ERROR_CONTAINS("a\nb", "|split(1,2,3)" ACI_MERGE, "syntax: split[(\"separator\"[,mode])]");
931
932#define C0_9 "0123456789"
933#define CA_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
934#define Ca_z "abcdefghijklmnopqrstuvwxyz"
935
936    // extract_words + extract_sequence
937    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" C0_9 "\",1)",            "1 2 3");
938    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 3)",           "five four six");
939    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" CA_Z "\", 3)",           "");                 // extract words works case sensitive
940    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 4)",           "five four");
941    TEST_CI("1,2,3,four,five,six",     "extract_words(\"" Ca_z "\", 5)",           "");
942    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
943
944    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 1.0)",   "");
945    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.5)",   "");
946    TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.0)",   "four five six");
947    TEST_CI     ("..acg--ta-cgt...",       "extract_sequence(\"acgtu\", 1.0)",   "");
948    TEST_CI_NOOP("..acg--ta-cgt...",       "extract_sequence(\"acgtu-.\", 1.0)");
949    TEST_CI_NOOP("..acg--ta-ygt...",       "extract_sequence(\"acgtu-.\", 0.7)");
950    TEST_CI     ("70 ..acg--ta-cgt... 70", "extract_sequence(\"acgtu-.\", 1.0)", "..acg--ta-cgt...");
951
952    // checksum + gcgchecksum
953    TEST_CI("", "sequence|checksum",      "4C549A5F");
954    TEST_CI("", "sequence | gcgchecksum", "4308");
955
956    // SRT
957    TEST_CI("The quick brown fox", "srt(\"quick=lazy:brown fox=dog\")", "The lazy dog"); // no need to escape spaces in quoted ACI parameter
958    TEST_CI("The quick brown fox", "srt(quick=lazy:brown\\ fox=dog)",   "The lazy dog"); // spaces need to be escaped in unquoted ACI parameter
959    TEST_CI_ERROR_CONTAINS("x", "srt(x=y,z)", "SRT ERROR: no '=' found in command");
960    TEST_CI_ERROR_CONTAINS("x", "srt",        "syntax: srt(expr[,expr]+)");
961
962    // REG-replace and -match
963    TEST_CI("stars*to*stripes", "/\\*/--/", "stars--to--stripes");
964
965    TEST_CI_ERROR_CONTAINS("xxx", "//--",    "Regular expression format is '/expr/' or '/expr/i', not '//--'");
966    TEST_CI_ERROR_CONTAINS("xxx", "/*/bla/", "Invalid preceding regular expression");
967
968    TEST_CI("sImILaRWIllBE,GonEEASIly", WITH_SPLITTED("|command(/[A-Z]//)"),   "small,only");
969    TEST_CI("sthBIGinside,FATnotCAP",   WITH_SPLITTED("|command(/([A-Z])+/)"), "BIG,FAT"); // does only do match
970
971    // command-queue vs. command-pipe (vs. both as sub-commands)
972    TEST_CI("a,bb,ccc",           WITH_SPLITTED("|\"[\";len;\"]\""),    "[,1,2,3,]");   // queue
973    TEST_CI("a,bb,ccc", WITH_SPLITTED("|command(\"\"[\";len;\"]\"\")"), "[1],[2],[3]"); // queue as sub-command
974
975    TEST_CI("a,bb,ccc",              WITH_SPLITTED("|len|minus(1)"),    "0,1,2"); // pipe
976    TEST_CI("a,bb,ccc",    WITH_SPLITTED("|command(\"len|minus(1)\")"), "0,1,2"); // pipe as sub-command
977
978    TEST_CI(               "a,bb,ccc,dd",           WITH_SPLITTED("|len|minus"), "-1,1"); // pipe
979    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
980
981    // calculator
982    TEST_CI("", "echo(9.9,3.9) |plus;fplus"   ACI_MERGE, "12,13.8");
983    TEST_CI("", "echo(9.1,3.9) |minus;fminus" ACI_MERGE, "6,5.2");
984    TEST_CI("", "echo(9,3.5)   |mult;fmult"   ACI_MERGE, "27,31.5");
985    TEST_CI("", "echo(9,0.1)   |mult;fmult"   ACI_MERGE, "0,0.9");
986    TEST_CI("", "echo(9,3)     |div;fdiv"     ACI_MERGE, "3,3");
987    TEST_CI("", "echo(10,3)    |div;fdiv"     ACI_MERGE, "3,3.33333");
988
989    TEST_CI("", "echo(9,3)|rest", "0");
990    TEST_CI("", "echo(9,5)|rest", "4");
991
992    TEST_CI("", "echo(9,3)  |per_cent;fper_cent" ACI_MERGE, "300,300");
993    TEST_CI("", "echo(3,9)  |per_cent;fper_cent" ACI_MERGE, "33,33.3333");
994    TEST_CI("", "echo(1,8)  |per_cent;fper_cent" ACI_MERGE, "12,12.5");
995    TEST_CI("", "echo(15,16)|per_cent;fper_cent" ACI_MERGE, "93,93.75");
996    TEST_CI("", "echo(1,8)  |fper_cent|round(0)", "13");
997    TEST_CI("", "echo(15,16)|fper_cent|round(0);round(1)" ACI_MERGE, "94,93.8");
998
999    TEST_CI("", "echo(1,2,3)|plus(1)"     ACI_MERGE, "2,3,4");
1000    TEST_CI("", "echo(1,2,3)|minus(2)"    ACI_MERGE, "-1,0,1");
1001    TEST_CI("", "echo(1,2,3)|mult(42)"    ACI_MERGE, "42,84,126");
1002    TEST_CI("", "echo(1,2,3)|div(2)"      ACI_MERGE, "0,1,1");
1003    TEST_CI("", "echo(1,2,3)|rest(2)"     ACI_MERGE, "1,0,1");
1004    TEST_CI("", "echo(1,2,3)|per_cent(3)" ACI_MERGE, "33,66,100");
1005
1006    // rounding
1007#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
1008
1009    TEST_CI("", ROUND_FLOATS(4),  "0.3826,0.5085,12.58,77.2,700.099,0.0001,1.75e+06");
1010    TEST_CI("", ROUND_FLOATS(3),  "0.383,0.508,12.58,77.2,700.099,0,1.75e+06");
1011    TEST_CI("", ROUND_FLOATS(2),  "0.38,0.51,12.58,77.2,700.1,0,1.75e+06");
1012    TEST_CI("", ROUND_FLOATS(1),  "0.4,0.5,12.6,77.2,700.1,0,1.75e+06");
1013    TEST_CI("", ROUND_FLOATS(0),  "0,1,13,77,700,0,1.75e+06");
1014    TEST_CI("", ROUND_FLOATS(-1), "0,0,10,80,700,0,1.75e+06");
1015    TEST_CI("", ROUND_FLOATS(-2), "0,0,0,100,700,0,1.75e+06");
1016    TEST_CI("", ROUND_FLOATS(-3), "0,0,0,0,1000,0,1.75e+06");
1017    TEST_CI("", ROUND_FLOATS(-5), "0,0,0,0,0,0,1.8e+06");
1018    TEST_CI("", ROUND_FLOATS(-6), "0,0,0,0,0,0,2e+06");
1019
1020
1021    // compare (integers)
1022    TEST_CI("", "echo(9,3)|isBelow;isAbove;isEqual", "010");
1023    TEST_CI("", "echo(3,9)|isBelow;isAbove;isEqual", "100");
1024    TEST_CI("", "echo(5,5)|isBelow;isAbove;isEqual", "001");
1025
1026    TEST_CI("", "echo(1,2,3)|isBelow(2)", "100");
1027    TEST_CI("", "echo(1,2,3)|isAbove(2)", "001");
1028    TEST_CI("", "echo(1,2,3)|isEqual(2)", "010");
1029
1030    TEST_CI("", "echo(1,2,3,4,5)|inRange(2,4)",        "01110");
1031    TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-2,-4)", "00000"); // empty range
1032    TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-4,-2)", "01110");
1033
1034    // compare (floats)
1035    TEST_CI("", "echo(1.7,1.4)  |isBelow;isAbove;isEqual", "010");
1036    TEST_CI("", "echo(-0.7,0.1) |isBelow;isAbove;isEqual", "100");
1037    TEST_CI("", "echo(5.10,5.1) |isBelow;isAbove;isEqual", "001");
1038    TEST_CI("", "echo(0.10,.11) |isBelow;isAbove;isEqual", "100");
1039    TEST_CI("", "echo(-7.1,-6.9)|isBelow;isAbove;isEqual", "100");
1040    TEST_CI("", "echo(1e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1041    TEST_CI("", "echo(2e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1042    TEST_CI("", "echo(2e+5,1e-6)|isBelow;isAbove;isEqual", "010");
1043    TEST_CI("", "echo(2e-5,1e+6)|isBelow;isAbove;isEqual", "100");
1044
1045    TEST_CI("", "echo(.1,.2,.3,.4,.5)   |inRange(.2,.4)",  "01110"); 
1046    TEST_CI("", "echo(.8,.9,1.0,1.1,1.2)|inRange(.9,1.1)", "01110");
1047    TEST_CI("", "echo(-.2,-.1,0.0,.1,.2)|inRange(-.1,.1)", "01110");
1048
1049    // boolean operators
1050    TEST_CI("0", "Not", "1");
1051    TEST_CI("1", "Not", "0");
1052
1053    TEST_CI("",     "Not", "1");
1054    TEST_CI("text", "Not", "1");
1055
1056    TEST_CI("", "echo(0,1)|Not", "10");
1057    TEST_CI("", "echo(0,0)|Or;And",  "00");
1058    TEST_CI("", "echo(0,1)|Or;And",  "10");
1059    TEST_CI("", "echo(1,0)|Or;And",  "10");
1060    TEST_CI("", "echo(1,1)|Or;And",  "11");
1061
1062    TEST_CI("", "command(echo(1\\,0)|Or);command(echo(0\\,1)|Or)|And",  "1");
1063
1064    // readdb
1065    TEST_CI("", "readdb(name)",     "LcbReu40");
1066    TEST_CI("", "readdb(acc)",      "X76328");
1067    TEST_CI("", "readdb(acc,name)", "X76328LcbReu40");
1068
1069    TEST_CI_ERROR_CONTAINS("", "readdb()",     "Invalid empty parameter list '()'");
1070    TEST_CI_ERROR_CONTAINS("", "readdb",       "syntax: readdb(fieldname[,fieldname]+)");
1071    TEST_CI               ("", "readdb(\"\")", ""); // still weird (want field error?)
1072
1073    // taxonomy
1074    TEST_CI("", "taxonomy(1)",           "No default tree");
1075    TEST_CI("", "taxonomy(tree_nuc, 1)", "group1");
1076    TEST_CI("", "taxonomy(tree_nuc, 5)", "lower-red/group1");
1077
1078    GBL_env      env_tree_nuc(E.gbmain(), "tree_nuc");
1079    GBL_call_env callEnv_tree_nuc(gb_data, env_tree_nuc);
1080
1081    TEST_CI_ERROR_CONTAINS("", "taxonomy",        "syntax: taxonomy([tree_name,]count)");
1082    TEST_CI_ERROR_CONTAINS("", "taxonomy(1,2,3)", "syntax: taxonomy([tree_name,]count)");
1083    TEST_CI_WITH_ENV("", callEnv_tree_nuc, "taxonomy(1)", "group1");
1084
1085    // diff, filter + change
1086    TEST_CI("..acg--ta-cgt..." ","
1087            "..acg--ta-cgt...", WITH_SPLITTED("|diff(pairwise=1)"),
1088            "................");
1089    TEST_CI("..acg--ta-cgt..." ","
1090            "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,equal==)"),
1091            "==cgt=====acg===");
1092    TEST_CI("..acg--ta-cgt..." ","
1093            "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,differ=X)"),
1094            "..XXX.....XXX...");
1095    TEST_CI("", "sequence|diff(species=LcbFruct)|checksum", "645E3107");
1096
1097    TEST_CI("..XXX.....XXX..." ","
1098            "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,exclude=X)"),
1099            "..--ta-...");
1100    TEST_CI("..XXX.....XXX..." ","
1101            "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,include=X)"),
1102            "acgcgt");
1103    TEST_CI("", "sequence|filter(species=LcbFruct,include=.-)", "-----------T----T-------G----------C-----T----T...");
1104
1105    TEST_CI("...XXX....XXX..." ","
1106            "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=C,change=100)"),
1107            "..aCC--ta-CCC...");
1108    TEST_CI("...XXXXXXXXX...." ","
1109            "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=-,change=100)"),
1110            "..a---------t...");
1111
1112    // test environment forwarding
1113    TEST_CI("x", ":*=*,*(acc)",                                 "x,X76328");          // test DB-item is forwarded to direct SRT-command
1114    TEST_CI("x", "srt(\"*=*,*(acc)\")",                         "x,X76328");          // test DB-item is forwarded to ACI-command 'srt'
1115    TEST_CI("x", ":*=*,*(acc|dd;\",\";readdb(name))",           "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside SRT-command
1116    TEST_CI("x", "srt(\"*=*,*(acc|dd;\"\\,\";readdb(name))\")", "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside ACI-command 'srt'
1117    TEST_CI("x", "command(\"dd;\\\",\\\";readdb(name)\")",      "x,LcbReu40");        // test DB-item is forwarded to ACI-subexpression inside ACI-command 'command'
1118
1119    // test treename is forwarded to sub-expressions
1120    TEST_CI_WITH_ENV("x", callEnv_tree_nuc, ":*=*,*(acc|dd;\\\",\\\";taxonomy(1))",                   "x,X76328,group1");
1121    TEST_CI_WITH_ENV("",  callEnv_tree_nuc, "taxonomy(5)|srt(*=*\\,*(acc|dd;\\\",\\\";taxonomy(1)))", "lower-red/group1,X76328,group1");
1122    TEST_CI_WITH_ENV("",  callEnv_tree_nuc, "taxonomy(5)|command(\"dd;\\\",\\\";taxonomy(1)\")",      "lower-red/group1,group1");
1123
1124    // test database root is forwarded to sub-expressions (used by commands 'ali_name', 'sequence_type', ...)
1125    TEST_CI("x", ":*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)",         "x,X76328,ali_16s,rna"); 
1126    TEST_CI("x", "srt(\"*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)\")", "x,X76328,ali_16s,rna");
1127    TEST_CI("x", "command(\"dd;\\\",\\\";ali_name;\\\",\\\";sequence_type\")",        "x,ali_16s,rna");
1128
1129    // exec
1130    TEST_CI("c,b,c,b,a,a", WITH_SPLITTED("|exec(\"(sort|uniq)\")|split|dropempty"),              "a,b,c");
1131    TEST_CI("a,aba,cac",   WITH_SPLITTED("|exec(\"perl\",-pe,s/([bc])/$1$1/g)|split|dropempty"), "a,abba,ccacc");
1132
1133    // error cases
1134    TEST_CI_ERROR_CONTAINS("", "nocmd",            "Unknown command 'nocmd'");
1135    TEST_CI_ERROR_CONTAINS("", "|nocmd",           "Unknown command 'nocmd'");
1136    TEST_CI_ERROR_CONTAINS("", "caps(x)",          "syntax: caps (no parameters)");
1137    TEST_CI_ERROR_CONTAINS("", "trace",            "syntax: trace(0|1)");
1138    TEST_CI_ERROR_CONTAINS("", "count",            "syntax: count(\"characters to count\")");
1139    TEST_CI_ERROR_CONTAINS("", "count(a,b)",       "syntax: count(\"characters to count\")");
1140    TEST_CI_ERROR_CONTAINS("", "len(a,b)",         "syntax: len[(\"characters not to count\")]");
1141    TEST_CI_ERROR_CONTAINS("", "plus(a,b,c)",      "syntax: plus[(Expr1[,Expr2])]");
1142    TEST_CI_ERROR_CONTAINS("", "count(a,b",        "Reason: Missing ')'");
1143    TEST_CI_ERROR_CONTAINS("", "count(a,\"b)",     "unbalanced '\"' in 'count(a,\"b)'");
1144    TEST_CI_ERROR_CONTAINS("", "count(a,\"b)\"",   "Reason: Missing ')'");
1145    TEST_CI_ERROR_CONTAINS("", "dd;dd|count",      "syntax: count(\"characters to count\")");
1146    TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x)",   "Invalid parameter syntax for '\"a\"x'");
1147    TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x\")", "unbalanced '\"' in '|count(\"a\"x\")'");
1148    TEST_CI_ERROR_CONTAINS("", "|count(\"a)",      "unbalanced '\"' in '|count(\"a)'");
1149
1150    TEST_CI_ERROR_CONTAINS__BROKEN("", "|\"xx\"bla", "bla", "xx"); // @@@ should report some error referring to unseparated + unknown command 'bla'
1151
1152    TEST_CI_ERROR_CONTAINS("", "translate(a)",       "syntax: translate(old,new[,other])");
1153    TEST_CI_ERROR_CONTAINS("", "translate(a,b,c,d)", "syntax: translate(old,new[,other])");
1154    TEST_CI_ERROR_CONTAINS("", "translate(a,b,xx)",  "has to be one character");
1155    TEST_CI_ERROR_CONTAINS("", "translate(a,b,)",    "has to be one character");
1156
1157    TEST_CI_ERROR_CONTAINS(NULp, "whatever", "ARB ERROR: Can't read this DB entry as string"); // here gb_data is the species container
1158
1159    TEST_CI("hello",   ":??""=(?-?)",   "(h-e)(l-l)o");
1160    TEST_CI("hello",   ":??""=(?-?)?",  "(h-e)?(l-l)?o");
1161    TEST_CI("hello",   ":??""=(?-?0)?", "(h-e0)?(l-l0)?o");
1162    TEST_CI("hello",   ":??""=(?-?3)?", "(h-?)e(l-?)lo");
1163
1164    // show linefeed is handled identical for encoded and escaped linefeeds:
1165    TEST_CI("abc",   ":?=?\\n", "a\nb\nc\n");
1166    TEST_CI("abc",   ":?=?\n",  "a\nb\nc\n");
1167
1168    // same for string-terminator:
1169    TEST_CI("abc",   ":?=?.\\0 ignored:b=d", "a.b.c.");
1170    TEST_CI("abc",   ":?=?.\0  ignored:b=d", "a.b.c.");
1171
1172    TEST_CI("",   ":*=X*Y*(full_name|len)", "XY21");
1173    TEST_CI("",   ":*=*(full_name\\:reuteri=xxx)", "Lactobacillus xxx");
1174    TEST_CI("",   ":*=*(abc\\:a=A)", ""); // non-existing field -> empty input -> empty output
1175    TEST_CI("hello world",   ":* =*(\\:*=hi)-", "hi-world"); // srt subexpressions also work w/o key
1176    TEST_CI_ERROR_CONTAINS("",   ":*=*(full_name\\:reuteri)", "no '=' found"); // test handling of errors from invalid srt-subexpression
1177
1178    TEST_CI("", ":*=*(acc#have no acc)", "X76328");
1179    TEST_CI("", ":*=*(abc#have no abc)", "have no abc");
1180    TEST_CI("", ":*=*(#no field)",       "no field");
1181
1182    TEST_CI_ERROR_CONTAINS("", ":*=*(unbalanced",     "Unbalanced parenthesis in '(unbalanced'");
1183    TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)ced",   "Unbalanced parenthesis in '(unba(lan)ced'");
1184    TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)c)ed)", "Invalid char '(' in key 'unba(lan)c'");      // invalid key name
1185    TEST_CI               ("", ":*=*(unbalanc)ed)",   "ed)");
1186}
1187
1188__ATTR__REDUCED_OPTIMIZE void TEST_GB_command_interpreter_3() {
1189    ACI_test_env E;
1190    GBL_env      base_env(E.gbmain(), NULp);
1191
1192    {
1193        // execute ACI on 'full_name' (=GB_STRING) in this section ------------------------------
1194        GBDATA * const gb_data = GB_entry(E.gbspecies(), "full_name");
1195        GBL_call_env   callEnv(gb_data, base_env);
1196
1197        TEST_CI(NULp, "",                            "Lactobacillus reuteri");     // noop
1198        TEST_CI(NULp, "|len",                        "21");
1199        TEST_CI(NULp, ":tobac=",                     "Lacillus reuteri");
1200        TEST_CI(NULp, "/ba.*us/B/",                  "LactoB reuteri");
1201        TEST_CI(NULp, ":::*=hello:::hell=heaven:::", "heaveno");                   // test superfluous ':'s
1202        TEST_CI(NULp, ":* *=;*2,*1;",                ";reuteri,Lactobacillus;");   // tests multiple successful matches of '*'
1203        TEST_CI(NULp, ":* ??*=;?2,?1,*2,*1;",        ";e,r,uteri,Lactobacillus;"); // tests multiple successful matches of '*' and '?' (also tests working multi-wildcards "??" and "?*")
1204        TEST_CI(NULp, ":Lacto*eutei=*1",             "Lactobacillus reuteri");     // test match failing after '*' (=> skips replace)
1205        TEST_CI(NULp, ":Lact?bac?lls=?1?2",          "Lactobacillus reuteri");     // test match failing after 2nd '?' (=> skips replace)
1206        TEST_CI(NULp, ":*reuteri?=?1",               "Lactobacillus reuteri");     // test match failing on '?' behind EOS (=> skips replace)
1207
1208        // tests for (unwanted) multi-wildcards:
1209        TEST_CI__BROKEN(NULp, ":Lacto*?lus=(*1,?1)", "(baci,l)", "Lactobacillus reuteri"); // @@@ diffcult to achieve (alternative: forbid "*?" and report error)
1210        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)
1211        TEST_CI_ERROR_CONTAINS__BROKEN(NULp, ":Lacto**lus=(*1,*2)", "invalid", "Lactobacillus reuteri"); // @@@ impossible: (forbid "**" and report error)
1212        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)
1213
1214        TEST_CI_ERROR_CONTAINS(NULp, ":*=*(|wot)",    "Unknown command 'wot'"); // provoke error in gbs_build_replace_string [coverage]
1215        TEST_CI_ERROR_CONTAINS("",   ":*=X*Y*(|wot)", "Unknown command 'wot'"); // dito (other caller)
1216
1217        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(full_name|len)",    "can't read key 'full_name' (DB item is no container)");
1218        TEST_CI               ("", ":*=X*Y*(../full_name|len)", "XY21"); // searches entry via parent-entry (from non-container)
1219
1220        TEST_CI(NULp, "|taxonomy(1)", "No default tree");
1221        TEST_CI_ERROR_CONTAINS(NULp, "|taxonomy(tree_nuc,2)", "Container has neither 'name' nor 'group_name' entry - can't detect container type");
1222    }
1223    {
1224        // execute ACI on 'ARB_color' (=GB_INT) in this section ------------------------------
1225        GBDATA * const gb_data = GB_entry(E.gbspecies(), "ARB_color");
1226        GBL_call_env   callEnv(gb_data, base_env);
1227
1228        TEST_CI(NULp, "", "1"); // noop
1229        TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna"); // test global database access works when specific database element is specified
1230    }
1231    {
1232        // execute ACI without database element in this section ------------------------------
1233        GBDATA * const gb_data = NULp;
1234        GBL_call_env   callEnv(gb_data, base_env);
1235
1236        TEST_CI_ERROR_CONTAINS(NULp, "", "no input streams found");
1237        TEST_CI("", ":*=\\tA*1Z\t", "\tAZ\t"); // special case (match empty input using '*'); test TAB conversion
1238
1239        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(|wot)",      "Unknown command 'wot'");
1240        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey|len)", "can't read key 'nokey' (called w/o database item)");
1241        TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey)",     "can't read key 'nokey' (called w/o database item)");
1242
1243        // test global database access also works w/o specific database element
1244        TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna");
1245        TEST_CI("", "command(\"ali_name;\\\",\\\";sequence_type\")", "ali_16s,rna");
1246
1247        // empty+NULp commands:
1248        TEST_CI("in", NULp, "in");
1249        TEST_CI("in", "", "in");
1250        TEST_CI("in", ":", "in");
1251        TEST_CI("in", "::", "in");
1252
1253        // empty+NULp commands:
1254        TEST_CI("in", NULp, "in");
1255        TEST_CI("in", "", "in");
1256        TEST_CI("in", ":", "in");
1257        TEST_CI("in", "::", "in");
1258    }
1259
1260    // register custom ACI commands
1261    {
1262        const GBL_command_lookup_table& stdCmds = ACI_get_standard_commands();
1263
1264        GBL_command_definition custom_cmds[] = {
1265            { "custom", gbx_custom },              // new command 'custom'
1266            { "upper",  stdCmds.lookup("lower") }, // change meaning of lower ..
1267            { "lower",  stdCmds.lookup("upper") }, // .. and upper
1268
1269            {NULp, NULp}
1270        };
1271
1272        GBL_custom_command_lookup_table custom(custom_cmds, ARRAY_ELEMS(custom_cmds)-1, stdCmds, PERMIT_SUBSTITUTION);
1273
1274        GBDATA * const gb_data = E.gbspecies();
1275
1276        GBL_env      custom_env(E.gbmain(), NULp, custom);
1277        GBL_call_env customCallEnv(gb_data, custom_env);
1278        GBL_call_env callEnv(gb_data, base_env);
1279
1280        // lookup overwritten commands:
1281        TEST_EXPECT(custom.lookup("upper") == stdCmds.lookup("lower"));
1282        TEST_EXPECT(custom.lookup("lower") == stdCmds.lookup("upper"));
1283
1284        // test new commands:
1285        TEST_CI_WITH_ENV      ("abc", customCallEnv,  "dd;custom;dd", "abc4711abc");
1286        TEST_CI_ERROR_CONTAINS("abc", "dd;custom;dd", "Unknown command 'custom'"); // unknown in standard environment
1287
1288        // test overwritten commands:
1289        TEST_CI_WITH_ENV("abcDEF,", customCallEnv, "dd;lower;upper", "abcDEF,ABCDEF,abcdef,");
1290        TEST_CI         ("abcDEF,",                "dd;lower;upper", "abcDEF,abcdef,ABCDEF,");
1291    }
1292}
1293
1294TEST_PUBLISH(TEST_GB_command_interpreter_3);
1295
1296#endif // UNIT_TESTS
1297
Note: See TracBrowser for help on using the repository browser.