ARB
gb_aci.cxx
Go to the documentation of this file.
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 
16 using namespace GBL_IMPL;
17 
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 
33 namespace 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 
112 static 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 
132 static 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 
160 static 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;
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  {
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 
349  int count = size();
350  if (!count) return ARB_strdup("");
351  if (count == 1) return ARB_strdup(get(0));
352 
353  GBS_strstruct buf(1000);
354  for (int i=0; i<count; i++) {
355  const char *s = get(i);
356  if (s) buf.cat(s);
357  }
358  return buf.release_memfriendly();
359 }
360 
361 NOT4PERL 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  }
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 
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 
453 char *GB_command_interpreter(const char *str, const char *commands, GBDATA *gb_main) {
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 
461 void 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 
516 static GBDATA *RCI_gb_main = NULp;
517 static const char *RCI_input = NULp;
518 static const char *RCI_cmd = NULp;
519 static GBDATA *RCI_gbd = NULp;
520 
521 inline 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 
547 static 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 
555 class ACI_test_env : virtual Noncopyable {
556  GB_shell shell;
557  GBDATA *gb_main;
558  LocallyModify<int> traceMode;
560  GBDATA *gb_species;
561 public:
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() {
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/source/aci.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  {
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 
1344 void 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 
GB_ERROR GB_get_error()
Definition: arb_msg.cxx:333
const GBL_streams & get_param_streams() const
Definition: gb_aci.h:243
int traceACI
Definition: adlang1.cxx:44
string result
GBDATA * GB_open(const char *path, const char *opent)
Definition: ad_load.cxx:1363
static GB_ERROR tab(GBL_command_arguments *args, bool pretab)
Definition: adlang1.cxx:914
GBL_streams & output
Definition: gb_aci.h:212
GBDATA * get_item_ref() const
Definition: gb_aci.h:160
static void dumpStreams(const char *name, const GBL_streams &streams)
Definition: gb_aci.cxx:112
GBL_COMMAND lookup_command(const char *identifier) const
Definition: gb_aci.h:138
char * GBS_string_eval_in_env(const char *insource, const char *icommand, const GBL_call_env &callEnv)
Definition: admatch.cxx:493
CONSTEXPR_INLINE unsigned char safeCharIndex(char c)
Definition: dupstr.h:73
const char * identifier
Definition: gb_aci.h:52
const char * get(int idx) const
Definition: gb_aci.h:37
bool is_defined() const
Definition: gb_aci.h:55
char * ARB_strdup(const char *str)
Definition: arb_string.h:27
const char * get_cmdName() const
Definition: gb_aci.h:241
char * GB_read_as_string(GBDATA *gbd)
Definition: arbdb.cxx:1060
NOT4PERL char * GB_command_interpreter_in_env(const char *str, const char *commands, const GBL_call_env &callEnv)
Definition: gb_aci.cxx:361
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
bool GB_have_error()
Definition: arb_msg.cxx:338
void nput(char c, size_t count)
Definition: arb_strbuf.h:180
char * GBS_regreplace(const char *str, const char *regReplExpr, GB_ERROR *error)
Definition: arb_match.cxx:175
void cat(const char *from)
Definition: arb_strbuf.h:199
char * ARB_strpartdup(const char *start, const char *end)
Definition: arb_string.h:51
#define ARRAY_ELEMS(array)
Definition: arb_defs.h:19
char buffer[MESSAGE_BUFFERSIZE]
Definition: seq_search.cxx:34
const char * GBS_regmatch(const char *str, const char *regExpr, size_t *matchlen, GB_ERROR *error)
Definition: arb_match.cxx:138
#define NOT4PERL
Definition: arbdb_base.h:23
int traceIndent
Definition: adlang1.cxx:45
bool empty() const
Definition: gb_aci.h:40
GB_ERROR GB_export_error(const char *error)
Definition: arb_msg.cxx:257
#define TEST_EXPECT(cond)
Definition: test_unit.h:1328
GB_TYPES GB_read_type(GBDATA *gbd)
Definition: arbdb.cxx:1643
GB_ERROR(* GBL_COMMAND)(GBL_command_arguments *args)
Definition: gb_aci.h:49
void print_trace(const char *text)
Definition: adlang1.cxx:47
const GroupXfer_callenv & custom_env(GBL_command_arguments *args)
const GBL_env & get_env() const
Definition: gb_aci.h:161
const char * get_param(int idx) const
Definition: gb_aci.h:245
#define SmartMallocPtr(type)
Definition: smartptr.h:45
static void error(const char *msg)
Definition: mkptypes.cxx:96
#define TRACE_ACI(text)
Definition: gb_aci_impl.h:195
char * str
Definition: defines.h:20
static const char * shortenLongString(const char *str, size_t wanted_len)
Definition: gb_aci.cxx:132
#define __ATTR__REDUCED_OPTIMIZE
Definition: test_unit.h:83
void insert(char *copy)
Definition: gb_aci.h:35
static const char * search_matching_dquote(const char *str)
Definition: gb_aci.cxx:34
#define EXPECT_NO_PARAM(args)
Definition: gb_aci_impl.h:202
static char * apply_ACI(const char *str, const char *commands, const GBL_call_env &callEnv)
Definition: gb_aci.cxx:160
GB_ERROR GB_export_errorf(const char *templat,...)
Definition: arb_msg.cxx:262
#define gb_assert(cond)
Definition: arbdbt.h:11
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:35
char * ARB_strndup(const char *start, int len)
Definition: arb_string.h:83
void modify_trace_indent(int diff)
Definition: gb_aci_impl.h:147
int size() const
Definition: gb_aci.h:39
static const char * search_next_separator(const char *source, const char *seps)
Definition: gb_aci.cxx:70
virtual GBL_COMMAND lookup(const char *identifier) const
Definition: gb_aci.h:77
char * GB_command_interpreter(const char *str, const char *commands, GBDATA *gb_main)
Definition: gb_aci.cxx:453
GBL_command_lookup_table(const GBL_command_definition *table, unsigned size)
Definition: gb_aci.cxx:18
const char * search_matching_parenthesis(const char *source)
Definition: gb_aci.cxx:49
static ARB_init_perl_interface init
Definition: ARB_ext.c:101
void erase()
Definition: gb_aci.h:42
#define TEST_EXPECT_NO_ERROR(call)
Definition: test_unit.h:1118
int param_count() const
Definition: gb_aci.h:244
#define NULp
Definition: cxxforward.h:116
void swap(GBL_streams &other)
Definition: gb_aci.h:45
GBDATA * GBT_find_species(GBDATA *gb_main, const char *name)
Definition: aditem.cxx:139
#define MAX_PRINT_LEN
#define __ATTR__REDUCED_OPTIMIZE__NO_GCSE
Definition: test_unit.h:88
static char * command
Definition: arb_a2ps.c:319
const char * get_data() const
Definition: arb_strbuf.h:120
GBL_streams & input
Definition: gb_aci.h:211
GB_transaction ta(gb_var)
char * concatenated() const
Definition: gb_aci.cxx:348
GB_CSTR GB_read_char_pntr(GBDATA *gbd)
Definition: arbdb.cxx:904
GBDATA * gb_main
Definition: adname.cxx:32
GBL_COMMAND function
Definition: gb_aci.h:53
const GBL_command_lookup_table & ACI_get_standard_commands()
Definition: adlang1.cxx:2749
char * release_memfriendly()
Definition: arb_strbuf.h:133
GBDATA * GB_entry(GBDATA *father, const char *key)
Definition: adquery.cxx:334
size_t get_position() const
Definition: arb_strbuf.h:112
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194
void GB_close(GBDATA *gbd)
Definition: arbdb.cxx:655
void put(char c)
Definition: arb_strbuf.h:174
#define UNCOVERED()
Definition: arb_assert.h:380
GB_write_int const char s
Definition: AW_awar.cxx:154