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 *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 
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 #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 
515 static GBDATA *RCI_gb_main = NULp;
516 static const char *RCI_input = NULp;
517 static const char *RCI_cmd = NULp;
518 static GBDATA *RCI_gbd = NULp;
519 
520 inline 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 
546 static 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 
554 class ACI_test_env : virtual Noncopyable {
555  GB_shell shell;
556  GBDATA *gb_main;
557  LocallyModify<int> traceMode;
559  GBDATA *gb_species;
560 public:
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() {
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", "//\\/", "railing backslash"); // [Tt]railing (lib dependent?)
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  TEST_CI_ERROR_CONTAINS("a;b;c", "split(;)|merge(-)", "Invalid separator (cannot be empty");
834  TEST_CI ("a;b;c", "split(\";\")|merge(-)", "a-b-c");
835 
836  // head, tail + mid/mid0
837  TEST_CI ("1234567890", "head(3)", "123");
838  TEST_CI ("1234567890", "head(9)", "123456789");
839  TEST_CI_NOOP("1234567890", "head(10)");
840  TEST_CI_NOOP("1234567890", "head(20)");
841 
842  TEST_CI ("1234567890", "tail(4)", "7890");
843  TEST_CI ("1234567890", "tail(9)", "234567890");
844  TEST_CI_NOOP("1234567890", "tail(10)");
845  TEST_CI_NOOP("1234567890", "tail(20)");
846 
847  TEST_CI("1234567890", "tail(0)", "");
848  TEST_CI("1234567890", "head(0)", "");
849  TEST_CI("1234567890", "tail(-2)", "");
850  TEST_CI("1234567890", "head(-2)", "");
851 
852  TEST_CI("1234567890", "mid(3,5)", "345");
853  TEST_CI("1234567890", "mid(2,2)", "2");
854 
855  TEST_CI("1234567890", "mid0(3,5)", "456");
856 
857  TEST_CI("1234567890", "mid(9,20)", "90");
858  TEST_CI("1234567890", "mid(20,20)", "");
859 
860  TEST_CI("1234567890", "tail(3)", "890"); // example from ../HELP_SOURCE/oldhelp/commands.hlp@mid0
861  TEST_CI("1234567890", "mid(-2,0)", "890");
862  TEST_CI("1234567890", "mid0(-3,-1)", "890");
863 
864  // tab + pretab
865  TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(2)"), "x ,xx,xxx");
866  TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(3)"), "x ,xx ,xxx");
867  TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(4)"), "x ,xx ,xxx ");
868  TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(2)"), " x,xx,xxx");
869  TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(3)"), " x, xx,xxx");
870  TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(4)"), " x, xx, xxx");
871 
872  // crop
873  TEST_CI(" x x ", "crop(\" \")", "x x");
874  TEST_CI("\n \t x x \n \t", "crop(\"\t\n \")", "x x");
875 
876  // cut, drop, dropempty and dropzero
877  TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|cut(2,3,5)"), "two,three,five");
878  TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|drop(2,3,5)"), "one,four,six");
879 
880  TEST_CI_ERROR_CONTAINS("a", "drop(2)", "Illegal stream number '2' (allowed [1..1])");
881  TEST_CI_ERROR_CONTAINS("a", "drop(0)", "Illegal stream number '0' (allowed [1..1])");
882  TEST_CI_ERROR_CONTAINS("a", "drop", "syntax: drop(streamnumber[,streamnumber]+)");
883  TEST_CI_ERROR_CONTAINS("a", "cut(2)", "Illegal stream number '2' (allowed [1..1])");
884  TEST_CI_ERROR_CONTAINS("a", "cut(0)", "Illegal stream number '0' (allowed [1..1])");
885  TEST_CI_ERROR_CONTAINS("a", "cut", "syntax: cut(streamnumber[,streamnumber]+)");
886  TEST_CI_ERROR_CONTAINS("a", "cut()", "Invalid empty parameter list '()'");
887  TEST_CI_ERROR_CONTAINS("a", "cut(\"\")", "Illegal stream number '0' (allowed [1..1])"); // still strange (atoi("")->0)
888 
889  TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|dropempty|streams"), "6");
890  TEST_CI("one,two,,,five,six", WITH_SPLITTED("|dropempty|streams"), "4");
891  TEST_CI(",,,,,", WITH_SPLITTED("|dropempty"), "");
892  TEST_CI(",,,,,", WITH_SPLITTED("|dropempty|streams"), "0");
893 
894  TEST_CI("1,0,0,2,3,0", WITH_SPLITTED("|dropzero"), "1,2,3");
895  TEST_CI("0,0,0,0,0,0", WITH_SPLITTED("|dropzero"), "");
896  TEST_CI("0,0,0,0,0,0", WITH_SPLITTED("|dropzero|streams"), "0");
897 
898  // swap
899  TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap"), "1,2,3,four,six,five");
900  TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)"), "1,3,2,four,five,six");
901  TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)|swap(4,3)"), "1,3,four,2,five,six");
902  TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,3)"));
903  TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(2,3)"));
904  TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(3,1)|swap(2,1)|swap(1,3)"));
905 
906  TEST_CI_ERROR_CONTAINS("a", "swap", "need at least two input streams");
907  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(2,3)"), "Illegal stream number '3' (allowed [1..2])");
908  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(3,2)"), "Illegal stream number '3' (allowed [1..2])");
909  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1)"), "syntax: swap[(streamnumber,streamnumber)]");
910  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(1,2,3)"), "syntax: swap[(streamnumber,streamnumber)]");
911 
912  // toback + tofront
913  TEST_CI ("front,mid,back", WITH_SPLITTED("|toback(2)"), "front,back,mid");
914  TEST_CI ("front,mid,back", WITH_SPLITTED("|tofront(2)"), "mid,front,back");
915  TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|toback(3)"));
916  TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|tofront(1)"));
917  TEST_CI_NOOP("a", WITH_SPLITTED("|tofront(1)"));
918  TEST_CI_NOOP("a", WITH_SPLITTED("|toback(1)"));
919 
920  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront(3)"), "Illegal stream number '3' (allowed [1..2])");
921  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(3)"), "Illegal stream number '3' (allowed [1..2])");
922  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront"), "syntax: tofront(streamnumber)");
923  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(1,2)"), "syntax: toback(streamnumber)");
924  TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|merge(1,2)"), "syntax: merge[(\"separator\")]");
925 
926  // split
927  TEST_CI ("a\nb", "|split" ACI_MERGE, "a,b");
928  TEST_CI ("a-b", "|split(-)" ACI_MERGE, "a,b");
929  TEST_CI ("a-b", "|split(-,0)" ACI_MERGE, "a,b");
930  TEST_CI ("a-b", "|split(-,1)" ACI_MERGE, "a,-b");
931  TEST_CI ("a-b", "|split(-,2)" ACI_MERGE, "a-,b");
932  TEST_CI_ERROR_CONTAINS("a-b", "|split(-,3)" ACI_MERGE, "Illegal split mode '3' (valid: 0..2)");
933  TEST_CI_ERROR_CONTAINS("a\nb", "|split(1,2,3)" ACI_MERGE, "syntax: split[(\"separator\"[,mode])]");
934 
935 #define C0_9 "0123456789"
936 #define CA_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
937 #define Ca_z "abcdefghijklmnopqrstuvwxyz"
938 
939  // extract_words + extract_sequence
940  TEST_CI("1,2,3,four,five,six", "extract_words(\"" C0_9 "\",1)", "1 2 3");
941  TEST_CI("1,2,3,four,five,six", "extract_words(\"" Ca_z "\", 3)", "five four six");
942  TEST_CI("1,2,3,four,five,six", "extract_words(\"" CA_Z "\", 3)", ""); // extract words works case sensitive
943  TEST_CI("1,2,3,four,five,six", "extract_words(\"" Ca_z "\", 4)", "five four");
944  TEST_CI("1,2,3,four,five,six", "extract_words(\"" Ca_z "\", 5)", "");
945  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
946 
947  TEST_CI ("1,2,3,four,five,six", "extract_sequence(\"acgtu\", 1.0)", "");
948  TEST_CI ("1,2,3,four,five,six", "extract_sequence(\"acgtu\", 0.5)", "");
949  TEST_CI ("1,2,3,four,five,six", "extract_sequence(\"acgtu\", 0.0)", "four five six");
950  TEST_CI ("..acg--ta-cgt...", "extract_sequence(\"acgtu\", 1.0)", "");
951  TEST_CI_NOOP("..acg--ta-cgt...", "extract_sequence(\"acgtu-.\", 1.0)");
952  TEST_CI_NOOP("..acg--ta-ygt...", "extract_sequence(\"acgtu-.\", 0.7)");
953  TEST_CI ("70 ..acg--ta-cgt... 70", "extract_sequence(\"acgtu-.\", 1.0)", "..acg--ta-cgt...");
954 
955  // checksum + gcgchecksum
956  TEST_CI("", "sequence|checksum", "4C549A5F");
957  TEST_CI("", "sequence | gcgchecksum", "4308");
958 
959  // SRT
960  TEST_CI("The quick brown fox", "srt(\"quick=lazy:brown fox=dog\")", "The lazy dog"); // no need to escape spaces in quoted ACI parameter
961  TEST_CI("The quick brown fox", "srt(quick=lazy:brown\\ fox=dog)", "The lazy dog"); // spaces need to be escaped in unquoted ACI parameter
962  TEST_CI_ERROR_CONTAINS("x", "srt(x=y,z)", "SRT ERROR: no '=' found in command");
963  TEST_CI_ERROR_CONTAINS("x", "srt", "syntax: srt(expr[,expr]+)");
964 
965  // REG-replace and -match
966  TEST_CI("stars*to*stripes", "/\\*/--/", "stars--to--stripes");
967 
968  TEST_CI_ERROR_CONTAINS("xxx", "//--", "Regular expression format is '/expr/' or '/expr/i', not '//--'");
969  TEST_CI_ERROR_CONTAINS("xxx", "/*/bla/",
970 #if defined(DARWIN)
971  // @@@ RESULT_MODIFIED_OSX: this test depends on library version
972  // should either test for one-of-several results or just test for any error
973  "repetition-operator operand invalid"
974 #else // !DARWIN
975  "Invalid preceding regular expression"
976 #endif
977  );
978 
979  TEST_CI("sImILaRWIllBE,GonEEASIly", WITH_SPLITTED("|command(/[A-Z]//)"), "small,only");
980  TEST_CI("sthBIGinside,FATnotCAP", WITH_SPLITTED("|command(/([A-Z])+/)"), "BIG,FAT"); // does only do match
981 
982  // command-queue vs. command-pipe (vs. both as sub-commands)
983  TEST_CI("a,bb,ccc", WITH_SPLITTED("|\"[\";len;\"]\""), "[,1,2,3,]"); // queue
984  TEST_CI("a,bb,ccc", WITH_SPLITTED("|command(\"\"[\";len;\"]\"\")"), "[1],[2],[3]"); // queue as sub-command
985 
986  TEST_CI("a,bb,ccc", WITH_SPLITTED("|len|minus(1)"), "0,1,2"); // pipe
987  TEST_CI("a,bb,ccc", WITH_SPLITTED("|command(\"len|minus(1)\")"), "0,1,2"); // pipe as sub-command
988 
989  TEST_CI( "a,bb,ccc,dd", WITH_SPLITTED("|len|minus"), "-1,1"); // pipe
990  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
991 
992  // calculator
993  TEST_CI("", "echo(9.9,3.9) |plus;fplus" ACI_MERGE, "12,13.8");
994  TEST_CI("", "echo(9.1,3.9) |minus;fminus" ACI_MERGE, "6,5.2");
995  TEST_CI("", "echo(9,3.5) |mult;fmult" ACI_MERGE, "27,31.5");
996  TEST_CI("", "echo(9,0.1) |mult;fmult" ACI_MERGE, "0,0.9");
997  TEST_CI("", "echo(9,3) |div;fdiv" ACI_MERGE, "3,3");
998  TEST_CI("", "echo(10,3) |div;fdiv" ACI_MERGE, "3,3.33333");
999 
1000  TEST_CI("", "echo(9,3)|rest", "0");
1001  TEST_CI("", "echo(9,5)|rest", "4");
1002 
1003  TEST_CI("", "echo(9,3) |per_cent;fper_cent" ACI_MERGE, "300,300");
1004  TEST_CI("", "echo(3,9) |per_cent;fper_cent" ACI_MERGE, "33,33.3333");
1005  TEST_CI("", "echo(1,8) |per_cent;fper_cent" ACI_MERGE, "12,12.5");
1006  TEST_CI("", "echo(15,16)|per_cent;fper_cent" ACI_MERGE, "93,93.75");
1007  TEST_CI("", "echo(1,8) |fper_cent|round(0)", "13");
1008  TEST_CI("", "echo(15,16)|fper_cent|round(0);round(1)" ACI_MERGE, "94,93.8");
1009 
1010  TEST_CI("", "echo(1,2,3)|plus(1)" ACI_MERGE, "2,3,4");
1011  TEST_CI("", "echo(1,2,3)|minus(2)" ACI_MERGE, "-1,0,1");
1012  TEST_CI("", "echo(1,2,3)|mult(42)" ACI_MERGE, "42,84,126");
1013  TEST_CI("", "echo(1,2,3)|div(2)" ACI_MERGE, "0,1,1");
1014  TEST_CI("", "echo(1,2,3)|rest(2)" ACI_MERGE, "1,0,1");
1015  TEST_CI("", "echo(1,2,3)|per_cent(3)" ACI_MERGE, "33,66,100");
1016 
1017  // rounding
1018 #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
1019 
1020  TEST_CI("", ROUND_FLOATS(4), "0.3826,0.5085,12.58,77.2,700.099,0.0001,1.75e+06");
1021  TEST_CI("", ROUND_FLOATS(3), "0.383,0.508,12.58,77.2,700.099,0,1.75e+06");
1022  TEST_CI("", ROUND_FLOATS(2), "0.38,0.51,12.58,77.2,700.1,0,1.75e+06");
1023  TEST_CI("", ROUND_FLOATS(1), "0.4,0.5,12.6,77.2,700.1,0,1.75e+06");
1024  TEST_CI("", ROUND_FLOATS(0), "0,1,13,77,700,0,1.75e+06");
1025  TEST_CI("", ROUND_FLOATS(-1), "0,0,10,80,700,0,1.75e+06");
1026  TEST_CI("", ROUND_FLOATS(-2), "0,0,0,100,700,0,1.75e+06");
1027  TEST_CI("", ROUND_FLOATS(-3), "0,0,0,0,1000,0,1.75e+06");
1028  TEST_CI("", ROUND_FLOATS(-5), "0,0,0,0,0,0,1.8e+06");
1029  TEST_CI("", ROUND_FLOATS(-6), "0,0,0,0,0,0,2e+06");
1030 
1031 
1032  // compare (integers)
1033  TEST_CI("", "echo(9,3)|isBelow;isAbove;isEqual", "010");
1034  TEST_CI("", "echo(3,9)|isBelow;isAbove;isEqual", "100");
1035  TEST_CI("", "echo(5,5)|isBelow;isAbove;isEqual", "001");
1036 
1037  TEST_CI("", "echo(1,2,3)|isBelow(2)", "100");
1038  TEST_CI("", "echo(1,2,3)|isAbove(2)", "001");
1039  TEST_CI("", "echo(1,2,3)|isEqual(2)", "010");
1040 
1041  TEST_CI("", "echo(1,2,3,4,5)|inRange(2,4)", "01110");
1042  TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-2,-4)", "00000"); // empty range
1043  TEST_CI("", "echo(-1,-2,-3,-4,-5)|inRange(-4,-2)", "01110");
1044 
1045  // compare (floats)
1046  TEST_CI("", "echo(1.7,1.4) |isBelow;isAbove;isEqual", "010");
1047  TEST_CI("", "echo(-0.7,0.1) |isBelow;isAbove;isEqual", "100");
1048  TEST_CI("", "echo(5.10,5.1) |isBelow;isAbove;isEqual", "001");
1049  TEST_CI("", "echo(0.10,.11) |isBelow;isAbove;isEqual", "100");
1050  TEST_CI("", "echo(-7.1,-6.9)|isBelow;isAbove;isEqual", "100");
1051  TEST_CI("", "echo(1e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1052  TEST_CI("", "echo(2e+5,1e+6)|isBelow;isAbove;isEqual", "100");
1053  TEST_CI("", "echo(2e+5,1e-6)|isBelow;isAbove;isEqual", "010");
1054  TEST_CI("", "echo(2e-5,1e+6)|isBelow;isAbove;isEqual", "100");
1055 
1056  TEST_CI("", "echo(.1,.2,.3,.4,.5) |inRange(.2,.4)", "01110");
1057  TEST_CI("", "echo(.8,.9,1.0,1.1,1.2)|inRange(.9,1.1)", "01110");
1058  TEST_CI("", "echo(-.2,-.1,0.0,.1,.2)|inRange(-.1,.1)", "01110");
1059 
1060  // boolean operators
1061  TEST_CI("0", "Not", "1");
1062  TEST_CI("1", "Not", "0");
1063 
1064  TEST_CI("", "Not", "1");
1065  TEST_CI("text", "Not", "1");
1066 
1067  TEST_CI("", "echo(0,1)|Not", "10");
1068  TEST_CI("", "echo(0,0)|Or;And", "00");
1069  TEST_CI("", "echo(0,1)|Or;And", "10");
1070  TEST_CI("", "echo(1,0)|Or;And", "10");
1071  TEST_CI("", "echo(1,1)|Or;And", "11");
1072 
1073  TEST_CI("", "command(echo(1\\,0)|Or);command(echo(0\\,1)|Or)|And", "1");
1074 
1075  // readdb
1076  TEST_CI("", "readdb(name)", "LcbReu40");
1077  TEST_CI("", "readdb(acc)", "X76328");
1078  TEST_CI("", "readdb(acc,name)", "X76328LcbReu40");
1079 
1080  TEST_CI_ERROR_CONTAINS("", "readdb()", "Invalid empty parameter list '()'");
1081  TEST_CI_ERROR_CONTAINS("", "readdb", "syntax: readdb(fieldname[,fieldname]+)");
1082  TEST_CI ("", "readdb(\"\")", ""); // still weird (want field error?)
1083 
1084  // taxonomy
1085  TEST_CI("", "taxonomy(1)", "No default tree");
1086  TEST_CI("", "taxonomy(tree_nuc, 1)", "group1");
1087  TEST_CI("", "taxonomy(tree_nuc, 5)", "lower-red/group1");
1088 
1089  GBL_env env_tree_nuc(E.gbmain(), "tree_nuc");
1090  GBL_call_env callEnv_tree_nuc(gb_data, env_tree_nuc);
1091 
1092  TEST_CI_ERROR_CONTAINS("", "taxonomy", "syntax: taxonomy([tree_name,]count)");
1093  TEST_CI_ERROR_CONTAINS("", "taxonomy(1,2,3)", "syntax: taxonomy([tree_name,]count)");
1094  TEST_CI_WITH_ENV("", callEnv_tree_nuc, "taxonomy(1)", "group1");
1095 
1096  // diff, filter + change
1097  TEST_CI("..acg--ta-cgt..." ","
1098  "..acg--ta-cgt...", WITH_SPLITTED("|diff(pairwise=1)"),
1099  "................");
1100  TEST_CI("..acg--ta-cgt..." ","
1101  "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,equal==)"),
1102  "==cgt=====acg===");
1103  TEST_CI("..acg--ta-cgt..." ","
1104  "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,differ=X)"),
1105  "..XXX.....XXX...");
1106  TEST_CI("", "sequence|diff(species=LcbFruct)|checksum", "645E3107");
1107 
1108  TEST_CI("..XXX.....XXX..." ","
1109  "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,exclude=X)"),
1110  "..--ta-...");
1111  TEST_CI("..XXX.....XXX..." ","
1112  "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,include=X)"),
1113  "acgcgt");
1114  TEST_CI("", "sequence|filter(species=LcbFruct,include=.-)", "-----------T----T-------G----------C-----T----T...");
1115 
1116  TEST_CI("...XXX....XXX..." ","
1117  "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=C,change=100)"),
1118  "..aCC--ta-CCC...");
1119  TEST_CI("...XXXXXXXXX...." ","
1120  "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=-,change=100)"),
1121  "..a---------t...");
1122 
1123  // test environment forwarding
1124  TEST_CI("x", ":*=*,*(acc)", "x,X76328"); // test DB-item is forwarded to direct SRT-command
1125  TEST_CI("x", "srt(\"*=*,*(acc)\")", "x,X76328"); // test DB-item is forwarded to ACI-command 'srt'
1126  TEST_CI("x", ":*=*,*(acc|dd;\",\";readdb(name))", "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside SRT-command
1127  TEST_CI("x", "srt(\"*=*,*(acc|dd;\"\\,\";readdb(name))\")", "x,X76328,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside ACI-command 'srt'
1128  TEST_CI("x", "command(\"dd;\\\",\\\";readdb(name)\")", "x,LcbReu40"); // test DB-item is forwarded to ACI-subexpression inside ACI-command 'command'
1129 
1130  // test treename is forwarded to sub-expressions
1131  TEST_CI_WITH_ENV("x", callEnv_tree_nuc, ":*=*,*(acc|dd;\\\",\\\";taxonomy(1))", "x,X76328,group1");
1132  TEST_CI_WITH_ENV("", callEnv_tree_nuc, "taxonomy(5)|srt(*=*\\,*(acc|dd;\\\",\\\";taxonomy(1)))", "lower-red/group1,X76328,group1");
1133  TEST_CI_WITH_ENV("", callEnv_tree_nuc, "taxonomy(5)|command(\"dd;\\\",\\\";taxonomy(1)\")", "lower-red/group1,group1");
1134 
1135  // test database root is forwarded to sub-expressions (used by commands 'ali_name', 'sequence_type', ...)
1136  TEST_CI("x", ":*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)", "x,X76328,ali_16s,rna");
1137  TEST_CI("x", "srt(\"*=*,*(acc|dd;\\\",\\\";ali_name;\\\",\\\";sequence_type)\")", "x,X76328,ali_16s,rna");
1138  TEST_CI("x", "command(\"dd;\\\",\\\";ali_name;\\\",\\\";sequence_type\")", "x,ali_16s,rna");
1139 
1140  // exec
1141  TEST_CI("c,b,c,b,a,a", WITH_SPLITTED("|exec(\"(sort|uniq)\")|split|dropempty"), "a,b,c");
1142  TEST_CI("a,aba,cac", WITH_SPLITTED("|exec(\"perl\",-pe,s/([bc])/$1$1/g)|split|dropempty"), "a,abba,ccacc");
1143 
1144  // error cases
1145  TEST_CI_ERROR_CONTAINS("", "nocmd", "Unknown command 'nocmd'");
1146  TEST_CI_ERROR_CONTAINS("", "|nocmd", "Unknown command 'nocmd'");
1147  TEST_CI_ERROR_CONTAINS("", "caps(x)", "syntax: caps (no parameters)");
1148  TEST_CI_ERROR_CONTAINS("", "trace", "syntax: trace(0|1)");
1149  TEST_CI_ERROR_CONTAINS("", "count", "syntax: count(\"characters to count\")");
1150  TEST_CI_ERROR_CONTAINS("", "count(a,b)", "syntax: count(\"characters to count\")");
1151  TEST_CI_ERROR_CONTAINS("", "len(a,b)", "syntax: len[(\"characters not to count\")]");
1152  TEST_CI_ERROR_CONTAINS("", "plus(a,b,c)", "syntax: plus[(Expr1[,Expr2])]");
1153  TEST_CI_ERROR_CONTAINS("", "count(a,b", "Reason: Missing ')'");
1154  TEST_CI_ERROR_CONTAINS("", "count(a,\"b)", "unbalanced '\"' in 'count(a,\"b)'");
1155  TEST_CI_ERROR_CONTAINS("", "count(a,\"b)\"", "Reason: Missing ')'");
1156  TEST_CI_ERROR_CONTAINS("", "dd;dd|count", "syntax: count(\"characters to count\")");
1157  TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x)", "Invalid parameter syntax for '\"a\"x'");
1158  TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x\")", "unbalanced '\"' in '|count(\"a\"x\")'");
1159  TEST_CI_ERROR_CONTAINS("", "|count(\"a)", "unbalanced '\"' in '|count(\"a)'");
1160 
1161  TEST_CI_ERROR_CONTAINS__BROKEN("", "|\"xx\"bla", "bla", "xx"); // @@@ should report some error referring to unseparated + unknown command 'bla'
1162 
1163  TEST_CI_ERROR_CONTAINS("", "translate(a)", "syntax: translate(old,new[,other])");
1164  TEST_CI_ERROR_CONTAINS("", "translate(a,b,c,d)", "syntax: translate(old,new[,other])");
1165  TEST_CI_ERROR_CONTAINS("", "translate(a,b,xx)", "has to be one character");
1166  TEST_CI_ERROR_CONTAINS("", "translate(a,b,)", "has to be one character");
1167 
1168  TEST_CI_ERROR_CONTAINS(NULp, "whatever", "ARB ERROR: Can't read this DB entry as string"); // here gb_data is the species container
1169 
1170  TEST_CI("hello", ":??""=(?-?)", "(h-e)(l-l)o");
1171  TEST_CI("hello", ":??""=(?-?)?", "(h-e)?(l-l)?o");
1172  TEST_CI("hello", ":??""=(?-?0)?", "(h-e0)?(l-l0)?o");
1173  TEST_CI("hello", ":??""=(?-?3)?", "(h-?)e(l-?)lo");
1174 
1175  // show linefeed is handled identical for encoded and escaped linefeeds:
1176  TEST_CI("abc", ":?=?\\n", "a\nb\nc\n");
1177  TEST_CI("abc", ":?=?\n", "a\nb\nc\n");
1178 
1179  // same for string-terminator:
1180  TEST_CI("abc", ":?=?.\\0 ignored:b=d", "a.b.c.");
1181  TEST_CI("abc", ":?=?.\0 ignored:b=d", "a.b.c.");
1182 
1183  TEST_CI("", ":*=X*Y*(full_name|len)", "XY21");
1184  TEST_CI("", ":*=*(full_name\\:reuteri=xxx)", "Lactobacillus xxx");
1185  TEST_CI("", ":*=*(abc\\:a=A)", ""); // non-existing field -> empty input -> empty output
1186  TEST_CI("hello world", ":* =*(\\:*=hi)-", "hi-world"); // srt subexpressions also work w/o key
1187  TEST_CI_ERROR_CONTAINS("", ":*=*(full_name\\:reuteri)", "no '=' found"); // test handling of errors from invalid srt-subexpression
1188 
1189  TEST_CI("", ":*=*(acc#have no acc)", "X76328");
1190  TEST_CI("", ":*=*(abc#have no abc)", "have no abc");
1191  TEST_CI("", ":*=*(#no field)", "no field");
1192 
1193  TEST_CI_ERROR_CONTAINS("", ":*=*(unbalanced", "Unbalanced parenthesis in '(unbalanced'");
1194  TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)ced", "Unbalanced parenthesis in '(unba(lan)ced'");
1195  TEST_CI_ERROR_CONTAINS("", ":*=*(unba(lan)c)ed)", "Invalid char '(' in key 'unba(lan)c'"); // invalid key name
1196  TEST_CI ("", ":*=*(unbalanc)ed)", "ed)");
1197 }
1198 
1199 __ATTR__REDUCED_OPTIMIZE void TEST_GB_command_interpreter_3() {
1200  ACI_test_env E;
1201  GBL_env base_env(E.gbmain(), NULp);
1202 
1203  {
1204  // execute ACI on 'full_name' (=GB_STRING) in this section ------------------------------
1205  GBDATA * const gb_data = GB_entry(E.gbspecies(), "full_name");
1206  GBL_call_env callEnv(gb_data, base_env);
1207 
1208  TEST_CI(NULp, "", "Lactobacillus reuteri"); // noop
1209  TEST_CI(NULp, "|len", "21");
1210  TEST_CI(NULp, ":tobac=", "Lacillus reuteri");
1211  TEST_CI(NULp, "/ba.*us/B/", "LactoB reuteri");
1212  TEST_CI(NULp, ":::*=hello:::hell=heaven:::", "heaveno"); // test superfluous ':'s
1213  TEST_CI(NULp, ":* *=;*2,*1;", ";reuteri,Lactobacillus;"); // tests multiple successful matches of '*'
1214  TEST_CI(NULp, ":* ??*=;?2,?1,*2,*1;", ";e,r,uteri,Lactobacillus;"); // tests multiple successful matches of '*' and '?' (also tests working multi-wildcards "??" and "?*")
1215  TEST_CI(NULp, ":Lacto*eutei=*1", "Lactobacillus reuteri"); // test match failing after '*' (=> skips replace)
1216  TEST_CI(NULp, ":Lact?bac?lls=?1?2", "Lactobacillus reuteri"); // test match failing after 2nd '?' (=> skips replace)
1217  TEST_CI(NULp, ":*reuteri?=?1", "Lactobacillus reuteri"); // test match failing on '?' behind EOS (=> skips replace)
1218 
1219  // tests for (unwanted) multi-wildcards:
1220  TEST_CI__BROKEN(NULp, ":Lacto*?lus=(*1,?1)", "(baci,l)", "Lactobacillus reuteri"); // @@@ diffcult to achieve (alternative: forbid "*?" and report error)
1221  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)
1222  TEST_CI_ERROR_CONTAINS__BROKEN(NULp, ":Lacto**lus=(*1,*2)", "invalid", "Lactobacillus reuteri"); // @@@ impossible: (forbid "**" and report error)
1223  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)
1224 
1225  TEST_CI_ERROR_CONTAINS(NULp, ":*=*(|wot)", "Unknown command 'wot'"); // provoke error in gbs_build_replace_string [coverage]
1226  TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(|wot)", "Unknown command 'wot'"); // dito (other caller)
1227 
1228  TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(full_name|len)", "can't read key 'full_name' (DB item is no container)");
1229  TEST_CI ("", ":*=X*Y*(../full_name|len)", "XY21"); // searches entry via parent-entry (from non-container)
1230 
1231  TEST_CI(NULp, "|taxonomy(1)", "No default tree");
1232  TEST_CI_ERROR_CONTAINS(NULp, "|taxonomy(tree_nuc,2)", "Container has neither 'name' nor 'group_name' entry - can't detect container type");
1233  }
1234  {
1235  // execute ACI on 'ARB_color' (=GB_INT) in this section ------------------------------
1236  GBDATA * const gb_data = GB_entry(E.gbspecies(), "ARB_color");
1237  GBL_call_env callEnv(gb_data, base_env);
1238 
1239  TEST_CI(NULp, "", "1"); // noop
1240  TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna"); // test global database access works when specific database element is specified
1241  }
1242  {
1243  // execute ACI without database element in this section ------------------------------
1244  GBDATA * const gb_data = NULp;
1245  GBL_call_env callEnv(gb_data, base_env);
1246 
1247  TEST_CI_ERROR_CONTAINS(NULp, "", "no input streams found");
1248  TEST_CI("", ":*=\\tA*1Z\t", "\tAZ\t"); // special case (match empty input using '*'); test TAB conversion
1249 
1250  TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(|wot)", "Unknown command 'wot'");
1251  TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey|len)", "can't read key 'nokey' (called w/o database item)");
1252  TEST_CI_ERROR_CONTAINS("", ":*=X*Y*(nokey)", "can't read key 'nokey' (called w/o database item)");
1253 
1254  // test global database access also works w/o specific database element
1255  TEST_CI("", "ali_name;\",\";sequence_type", "ali_16s,rna");
1256  TEST_CI("", "command(\"ali_name;\\\",\\\";sequence_type\")", "ali_16s,rna");
1257 
1258  // empty+NULp commands:
1259  TEST_CI("in", NULp, "in");
1260  TEST_CI("in", "", "in");
1261  TEST_CI("in", ":", "in");
1262  TEST_CI("in", "::", "in");
1263 
1264  // empty+NULp commands:
1265  TEST_CI("in", NULp, "in");
1266  TEST_CI("in", "", "in");
1267  TEST_CI("in", ":", "in");
1268  TEST_CI("in", "::", "in");
1269  }
1270 
1271  // register custom ACI commands
1272  {
1274 
1275  GBL_command_definition custom_cmds[] = {
1276  { "custom", gbx_custom }, // new command 'custom'
1277  { "upper", stdCmds.lookup("lower") }, // change meaning of lower ..
1278  { "lower", stdCmds.lookup("upper") }, // .. and upper
1279 
1280  {NULp, NULp}
1281  };
1282 
1283  GBL_custom_command_lookup_table custom(custom_cmds, ARRAY_ELEMS(custom_cmds)-1, stdCmds, PERMIT_SUBSTITUTION);
1284 
1285  GBDATA * const gb_data = E.gbspecies();
1286 
1287  GBL_env custom_env(E.gbmain(), NULp, custom);
1288  GBL_call_env customCallEnv(gb_data, custom_env);
1289  GBL_call_env callEnv(gb_data, base_env);
1290 
1291  // lookup overwritten commands:
1292  TEST_EXPECT(custom.lookup("upper") == stdCmds.lookup("lower"));
1293  TEST_EXPECT(custom.lookup("lower") == stdCmds.lookup("upper"));
1294 
1295  // test new commands:
1296  TEST_CI_WITH_ENV ("abc", customCallEnv, "dd;custom;dd", "abc4711abc");
1297  TEST_CI_ERROR_CONTAINS("abc", "dd;custom;dd", "Unknown command 'custom'"); // unknown in standard environment
1298 
1299  // test overwritten commands:
1300  TEST_CI_WITH_ENV("abcDEF,", customCallEnv, "dd;lower;upper", "abcDEF,ABCDEF,abcdef,");
1301  TEST_CI ("abcDEF,", "dd;lower;upper", "abcDEF,abcdef,ABCDEF,");
1302  }
1303 }
1304 
1305 void TEST_GB_command_interpreter_4() {
1306  ACI_test_env E;
1307  GBL_env base_env(E.gbmain(), NULp);
1308 
1309  // execute ACI on species container (=GB_DB) in this section ------------------------------
1310  GBDATA * const gb_data = E.gbspecies();
1311  GBL_call_env callEnv(gb_data, base_env);
1312 
1313  TEST_CI("LcbReu40", "findspec(\"readdb (acc)\")", "X76328");
1314  TEST_CI("LcbFruct", "findspec(\"readdb (acc)\")", "X76330");
1315  TEST_CI("", "readdb(name)|findspec(\"readdb(acc)\")", "X76328");
1316 
1317  TEST_CI("LcbReu40;lcbfruct", "split(\";\")|findspec(\"readdb(acc)\")|merge(\";\")", "X76328;X76330"); // usecase ("bring next-relatives info into name-independent format")
1318  TEST_CI("X76328;x76330", "split(\";\")|findacc(\"readdb(name)\")|merge(\";\")", "LcbReu40;LcbFruct"); // perform opposite (tests 'findacc')
1319 
1320  TEST_CI ("", "findspec(\"invalid\")", ""); // does not execute command for unnamed item
1321  TEST_CI_ERROR_CONTAINS("LcbReu40", "findspec(\"invalid\")", "Unknown command 'invalid'");
1322  TEST_CI_ERROR_CONTAINS("unknown", "findspec(\"invalid\")", "No species with name 'unknown' found");
1323  TEST_CI_ERROR_CONTAINS("unknown", "findacc(\"invalid\")", "No species with acc 'unknown' found");
1324 }
1325 
1326 #endif // UNIT_TESTS
1327 
GB_ERROR GB_get_error()
Definition: arb_msg.cxx:344
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:913
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:1030
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:204
bool GB_have_error()
Definition: arb_msg.cxx:349
void nput(char c, size_t count)
Definition: arb_strbuf.h:143
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:156
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:259
GBS_strstruct * GBS_stropen(long init_size)
Definition: arb_strbuf.cxx:39
#define TEST_EXPECT(cond)
Definition: test_unit.h:1312
GB_TYPES GB_read_type(GBDATA *gbd)
Definition: arbdb.cxx:1617
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
void GBS_strcat(GBS_strstruct *strstr, const char *ptr)
Definition: arb_strbuf.cxx:108
static void error(const char *msg)
Definition: mkptypes.cxx:96
#define TRACE_ACI(text)
Definition: gb_aci_impl.h:197
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:204
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:264
#define gb_assert(cond)
Definition: arbdbt.h:11
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:32
char * ARB_strndup(const char *start, int len)
Definition: arb_string.h:83
void modify_trace_indent(int diff)
Definition: gb_aci_impl.h:149
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
char * GBS_strclose(GBS_strstruct *strstr)
Definition: arb_strbuf.cxx:69
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:1107
int param_count() const
Definition: gb_aci.h:244
#define NULp
Definition: cxxforward.h:97
void swap(GBL_streams &other)
Definition: gb_aci.h:45
GBDATA * GBT_find_species(GBDATA *gb_main, const char *name)
Definition: aditem.cxx:136
#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:70
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:874
GBDATA * gb_main
Definition: adname.cxx:33
GBL_COMMAND function
Definition: gb_aci.h:53
const GBL_command_lookup_table & ACI_get_standard_commands()
Definition: adlang1.cxx:2724
GBDATA * GB_entry(GBDATA *father, const char *key)
Definition: adquery.cxx:334
size_t get_position() const
Definition: arb_strbuf.h:65
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:195
void GB_close(GBDATA *gbd)
Definition: arbdb.cxx:625
void put(char c)
Definition: arb_strbuf.h:138
#define UNCOVERED()
Definition: arb_assert.h:380
GB_write_int const char s
Definition: AW_awar.cxx:156