source: tags/ms_r18q1/ARBDB/adlang1.cxx

Last change on this file was 17110, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 90.6 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adlang1.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "gb_aci_impl.h"
12#include "gb_key.h"
13
14#include "TreeNode.h"
15
16#include <aw_awar_defs.hxx>
17
18#include <adGene.h>
19#include <ad_cb.h>
20
21#include <arb_defs.h>
22#include <arb_strbuf.h>
23#include <arb_file.h>
24#include <arb_strarray.h>
25#include <arb_sort.h>
26
27#include <cctype>
28#include <cmath>
29#include <algorithm>
30
31// hook for 'export_sequence'
32
33static gb_export_sequence_cb get_export_sequence = NULp;
34
35NOT4PERL void GB_set_export_sequence_hook(gb_export_sequence_cb escb) {
36    gb_assert(!get_export_sequence || !escb); // avoid unwanted overwrite
37    get_export_sequence = escb;
38}
39
40using namespace GBL_IMPL;
41
42namespace GBL_IMPL {
43    // global ACI/SRT debug switch
44    int traceACI    = 0;
45    int traceIndent = 0;
46
47    void print_trace(const char *text) {
48        FILE *DUMPOUT = stdout;
49
50        gb_assert(traceACI>0);
51        if (traceIndent>0) {
52            ConstStrArray line;
53            GBT_split_string(line, text, "\n", true);
54            for (unsigned L = 0; L<line.size(); ++L) {
55                for (int i = 0; i<traceIndent; ++i) {
56                    fputc(' ', DUMPOUT);
57                }
58                fputs(line[L], DUMPOUT);
59                fputc('\n', DUMPOUT);
60            }
61        }
62        else {
63            fputs(text, DUMPOUT);
64        }
65        fflush(DUMPOUT);
66    }
67
68    GB_ERROR trace_params(const GBL_streams& param, gbl_param *ppara, const char *com) {
69        GB_ERROR error = NULp;
70        int      i;
71
72        int argc = param.size();
73        for (i=0; i<argc; i++) {
74            gbl_param  *para;
75            const char *argument = param.get(i);
76
77            for (para = ppara; para && !error; para = para->next) {
78                if (para->param_name) { // NULp means param is inactive (see PARAM_IF)
79                    int len = strlen(para->param_name);
80
81                    if (strncmp(argument, para->param_name, len) == 0) {
82                        const char *value = argument+len; // set to start of value
83
84                        if (para->type == GB_BIT) {
85                            // GB_BIT is special cause param_name does NOT contain trailing '='
86
87                            if (!value[0]) { // only 'name' -> handle like 'name=1'
88                                ;
89                            }
90                            else if (value[0] == '=') {
91                                value++;
92                            }
93                        }
94
95                        switch (para->type) {
96                            case GB_STRING:
97                                *(const char **)para->varaddr  = value;
98                                break;
99
100                            case GB_INT:
101                                STATIC_ASSERT(sizeof(int) == sizeof(nat)); // assumed by GBL_PARAM_UINT
102                                *(int *)para->varaddr = atoi(value);
103                                break;
104
105                            case GB_BIT:
106                                // 'param=' is same as 'param' or 'param=1' (historical reason, don't change)
107                                *(int *)para->varaddr = (value[0] ? atoi(value) : 1);
108                                break;
109
110                            case GB_BYTE:
111                                *(char *)para->varaddr = *value; // this may use the terminal zero-byte (e.g. for p1 in 'p0=0,p1=,p2=2' )
112                                if (value[0] && value[1]) { // found at least 2 chars
113                                    GB_warningf("Only one character expected in value '%s' of param '%s' - rest is ignored", value, para->param_name);
114                                }
115                                break;
116
117                            default:
118                                gb_assert(0);
119                                error = GBS_global_string("Parameter '%s': Unknown type %i (internal error)", para->param_name, para->type);
120                                break;
121                        }
122                        break; // accept parameter
123                    }
124                }
125            }
126
127            if (!error && !para) { // no parameter found for argument
128                int             pcount = 0;
129                gbl_param     **params;
130                int             k;
131                char           *res;
132                GBS_strstruct  *str    = GBS_stropen(1000);
133                GB_ERROR        err;
134
135
136                for (para = ppara; para; para = para->next) pcount++;
137                ARB_calloc(params, pcount);
138                for (k = 0, para = ppara; para; para = para->next) params[k++] = para;
139
140
141                for (pcount--; pcount>=0; pcount--) {
142                    para = params[pcount];
143                    if (para->param_name) {
144                        GBS_strcat(str, "  ");
145                        GBS_strcat(str, para->param_name);
146                        switch (para->type) {
147                            case GB_STRING: GBS_strcat(str, "STRING"); break;
148                            case GB_INT:    GBS_strcat(str, "INT");    break;
149                            case GB_FLOAT:  GBS_strcat(str, "FLOAT");  break;
150                            case GB_BYTE:   GBS_strcat(str, "CHAR");   break;
151                            case GB_BIT:    GBS_strcat(str, "    ");   break;
152                            default:        gb_assert (0); GBS_strcat(str, "????"); break;
153                        }
154                        GBS_strcat(str, "\t\t;");
155                        GBS_strcat(str, para->help_text);
156                        GBS_strcat(str, "\n");
157                    }
158                }
159                freenull(params);
160                res = GBS_strclose(str);
161                err = GB_export_errorf("Unknown Parameter '%s' in command '%s'\n  PARAMETERS:\n%s", argument, com, res);
162                free(res);
163                return err;
164            }
165        }
166
167        return error;
168    }
169};
170
171
172
173// -------------------------
174//      String functions
175
176static int gbl_stricmp(const char *s1, const char *s2) {
177    // case insensitive strcmp
178    int i;
179    for (i = 0; ; ++i) {
180        char c1 = tolower(s1[i]);
181        char c2 = tolower(s2[i]);
182
183        if (c1 == c2) {
184            if (!c1) break; // equal strings
185        }
186        else {
187            if (c1<c2) return -1;
188            return 1;
189        }
190    }
191    return 0;
192}
193static int gbl_strincmp(const char *s1, const char *s2, int size2) {
194    // case insensitive strcmp
195    int i;
196    for (i = 0; i<size2; ++i) {
197        char c1 = tolower(s1[i]);
198        char c2 = tolower(s2[i]);
199
200        if (c1 == c2) {
201            if (!c1) break; // equal strings
202        }
203        else {
204            if (c1<c2) return -1;
205            return 1;
206        }
207    }
208    return 0;
209}
210static const char *gbl_stristr(const char *haystack, const char *needle) {
211    // case insensitive strstr
212    const char *hp          = haystack;
213    char        c1          = toupper(needle[0]);
214    char        c2          = tolower(c1);
215    int         needle_size = strlen(needle);
216
217    if (c1 == c2) {
218        hp = strchr(hp, c1);
219        while (hp) {
220            if (gbl_strincmp(hp, needle, needle_size) == 0) return hp;
221            hp = strchr(hp+1, c1);
222        }
223    }
224    else {
225        while (hp) {
226            const char *h1 = strchr(hp, c1);
227            const char *h2 = strchr(hp, c2);
228
229            if (h1 && h2) {
230                if (h1<h2) {
231                    if (gbl_strincmp(h1, needle, needle_size) == 0) return h1;
232                    hp = h1+1;
233                }
234                else {
235                    gb_assert(h1>h2);
236                    if (gbl_strincmp(h2, needle, needle_size) == 0) return h2;
237                    hp = h2+1;
238                }
239            }
240            else {
241                if (h1) { hp = h1; }
242                else if (h2) { hp = h2; c1 = c2; }
243                else { hp = NULp; }
244
245                while (hp) {
246                    if (gbl_strincmp(hp, needle, needle_size) == 0) return hp;
247                    hp = strchr(hp+1, c1);
248                }
249            }
250        }
251    }
252    return NULp;
253}
254
255inline int approve_pos(int pos, int len) { return pos<0 ? (-pos<len ? len+pos : 0) : pos; }
256
257static GB_ERROR gbl_mid_streams(const GBL_streams& arg_input, GBL_streams& arg_out, int start, int end) {
258    // used as well to copy all streams (e.g. by 'dd')
259    for (int i=0; i<arg_input.size(); i++) {
260        const char *p   = arg_input.get(i);
261        int         len = strlen(p);
262
263        int s = approve_pos(start, len);
264        int e = approve_pos(end, len);
265
266        char *res;
267        if (s >= len || e<s) {
268            res = ARB_strdup("");
269        }
270        else {
271            gb_assert(s >= 0);
272            res = ARB_strpartdup(p+s, p+e);
273        }
274        arg_out.insert(res);
275    }
276    return NULp;
277}
278
279static GB_ERROR gbl_trace(GBL_command_arguments *args) {
280    int tmp_trace;
281
282    EXPECT_PARAMS(args, 1, "0|1");
283
284    tmp_trace = atoi(args->get_param(0));
285    if (tmp_trace<0 || tmp_trace>1) return GBS_global_string("Illegal value %i to trace", tmp_trace);
286
287    if (tmp_trace != traceACI) {
288        traceACI = 1;
289        print_trace(GBS_global_string("%sctivated ACI trace\n", tmp_trace ? "A" : "De-a"));
290        traceACI = tmp_trace;
291    }
292
293    return gbl_mid_streams(args->input, args->output, 0, -1); // copy all streams
294}
295
296/* ---------------------------------------------------------------------------------------
297 * Binary operators work on pairs of values.
298 * Three different operational modes are implemented for all binary operators:
299 *
300 * 1. inputstreams|operator
301 *
302 *    The number of inputstreams has to be even and the operator will be
303 *    applied to pairs of them.
304 *
305 *    Example : a;b;c;d;e;f | plus
306 *    Result  : a+b;c+d;e+f
307 *
308 * 2. inputstreams|operator(x)
309 *
310 *    The number of inputstreams has to be at least 1.
311 *    The operator is applied to each inputstream.
312 *
313 *    Example : a;b;c | plus(d)
314 *    Result  : a+d;b+d;c+d
315 *
316 * 3. operator(x, y)
317 *
318 *    @@@ this decription does not match behavior!
319 *    @@@ check description in helpfile as well
320 *
321 *    Inputstreams will be ignored and the operator is applied
322 *    to the arguments
323 *
324 *    Example : a;b | plus(c,d)
325 *    Result  : c+d
326 */
327
328template <typename T>
329GB_ERROR gbl_apply_binary_operator(GBL_command_arguments *args, char *(*op)(const char *, const char *, T), T client_data) {
330    GB_ERROR error = NULp;
331    switch (args->param_count()) {
332        case 0:
333            gb_assert(args->set_params_checked());
334            if (args->input.size() == 0) error = "Expect at least two input streams if called with 0 parameters";
335            else if (args->input.size()%2) error = "Expect an even number of input streams if called with 0 parameters";
336            else {
337                int inputpairs = args->input.size()/2;
338                int i;
339                for (i = 0; i<inputpairs; ++i) {
340                    PASS_2_OUT(args, op(args->input.get(i*2), args->input.get(i*2+1), client_data));
341                }
342            }
343            break;
344
345        case 1:
346            gb_assert(args->set_params_checked());
347            if (args->input.size() == 0) error = "Expect at least one input stream if called with 1 parameter";
348            else {
349                int         i;
350                const char *argument = args->get_param(0);
351                for (i = 0; i<args->input.size(); ++i) {
352                    PASS_2_OUT(args, op(args->input.get(i), argument, client_data));
353                }
354            }
355            break;
356
357        case 2:
358            gb_assert(args->set_params_checked());
359            for (int i = 0; i<args->input.size(); ++i) {
360                char *result1       = args->get_callEnv().interpret_subcommand(args->input.get(i), args->get_param(0)); // @@@ EVALUATED_PARAM (#768)
361                if (!result1) error = GB_await_error();
362                else {
363                    char *result2       = args->get_callEnv().interpret_subcommand(args->input.get(i), args->get_param(1)); // @@@ EVALUATED_PARAM (#768)
364                    if (!result2) error = GB_await_error();
365                    else {
366                        PASS_2_OUT(args, op(result1, result2, client_data));
367                        free(result2);
368                    }
369                    free(result1);
370                }
371            }
372            break;
373
374        default:
375            error = check_optional_parameters(args, 0, NULp, 2, "Expr1[,Expr2]", true, false);
376            break;
377    }
378
379    return error;
380}
381
382// --------------------------------
383//      escape/unescape strings
384
385static char *unEscapeString(const char *escapedString) {
386    // replaces all \x by x
387    char *result = nulldup(escapedString);
388    char *to     = result;
389    char *from   = result;
390
391    while (1) {
392        char c = *from++;
393        if (!c) break;
394
395        if (c=='\\') {
396            *to++ = *from++;
397        }
398        else {
399            *to++ = c;
400        }
401    }
402    *to = 0;
403    return result;
404}
405static char *escapeString(const char *unescapedString) {
406    // replaces all '\' and '"' by '\\' and '\"'
407    int         len    = strlen(unescapedString);
408    char       *result = ARB_alloc<char>(2*len+1);
409    char       *to     = result;
410    const char *from   = unescapedString;
411
412    while (1) {
413        char c = *from++;
414        if (!c) break;
415
416        if (c=='\\' || c == '\"') {
417            *to++ = '\\';
418            *to++ = c;
419        }
420        else {
421            *to++ = c;
422        }
423    }
424    *to = 0;
425    return result;
426}
427
428// ---------------------------------
429//      the commands themselves:
430
431static GB_ERROR gbl_quote(GBL_command_arguments *args) {
432    EXPECT_NO_PARAM(args);
433
434    for (int i=0; i<args->input.size(); i++) {
435        FORMAT_2_OUT(args, "\"%s\"", args->input.get(i));
436    }
437    return NULp;
438}
439static GB_ERROR gbl_unquote(GBL_command_arguments *args) {
440    EXPECT_NO_PARAM(args);
441
442    for (int i=0; i<args->input.size(); i++) {
443        const char *str = args->input.get(i);
444        int         len = strlen(str);
445
446        if (str[0] == '\"' && str[len-1] == '\"') {
447            PASS_2_OUT(args, ARB_strpartdup(str+1, str+len-2));
448        }
449        else {
450            IN_2_OUT(args, i);
451        }
452    }
453    return NULp;
454}
455
456static GB_ERROR gbl_escape(GBL_command_arguments *args) {
457    EXPECT_NO_PARAM(args);
458
459    for (int i=0; i<args->input.size(); i++) {
460        char *escaped = escapeString(args->input.get(i));
461        PASS_2_OUT(args, escaped);
462    }
463    return NULp;
464}
465static GB_ERROR gbl_unescape(GBL_command_arguments *args) {
466    EXPECT_NO_PARAM(args);
467
468    for (int i=0; i<args->input.size(); i++) {
469        char *unescaped = unEscapeString(args->input.get(i));
470        PASS_2_OUT(args, unescaped);
471    }
472    return NULp;
473}
474
475static GB_ERROR gbl_command(GBL_command_arguments *args) {
476    EXPECT_PARAMS(args, 1, "\"ACI command\"");
477
478    GB_ERROR  error   = NULp;
479    char     *command = unEscapeString(args->get_param(0));
480
481    if (traceACI) {
482        print_trace(GBS_global_string("executing command '%s'\n", command));
483    }
484
485    for (int i=0; i<args->input.size() && !error; i++) {
486        char *result = args->get_callEnv().interpret_subcommand(args->input.get(i), command);
487        if (!result) error = GB_await_error();
488        else PASS_2_OUT(args, result);
489    }
490    free(command);
491    return error;
492}
493
494static GB_ERROR gbl_eval(GBL_command_arguments *args) {
495    EXPECT_PARAMS(args, 1, "\"expression evaluating to ACI command\"");
496
497    GB_ERROR  error   = NULp;
498    char     *to_eval = unEscapeString(args->get_param(0));
499    TRACE_ACI(GBS_global_string("evaluating '%s'\n", to_eval));
500
501    char *command = args->get_callEnv().interpret_subcommand("", to_eval); // evaluate independent
502    if (!command) error = GB_await_error();
503    else {
504        TRACE_ACI(GBS_global_string("executing  '%s'\n", command));
505
506        for (int i=0; i<args->input.size() && !error; i++) {
507            char *result       = args->get_callEnv().interpret_subcommand(args->input.get(i), command);
508            if (!result) error = GB_await_error();
509            else  PASS_2_OUT(args, result);
510        }
511        free(command);
512    }
513    free(to_eval);
514    return error;
515}
516
517class DefinedCommands : virtual Noncopyable {
518    GB_HASH *cmds;
519public:
520    DefinedCommands() { cmds = GBS_create_dynaval_hash(100, GB_MIND_CASE, GBS_dynaval_free); }
521    ~DefinedCommands() { GBS_free_hash(cmds); }
522
523    void set(const char *name, char* cmd) { GBS_dynaval_free(GBS_write_hash(cmds, name, (long)cmd)); } // takes ownership of 'cmd'!
524    const char *get(const char *name) const { return (const char *)GBS_read_hash(cmds, name); }
525};
526
527static DefinedCommands defined_commands;
528
529static GB_ERROR gbl_define(GBL_command_arguments *args) {
530    COMMAND_DROPS_INPUT_STREAMS(args);
531    EXPECT_PARAMS(args, 2, "name, \"ACI command\"");
532
533    const char *name = args->get_param(0);
534    char       *cmd  = unEscapeString(args->get_param(1));
535
536    defined_commands.set(name, cmd);
537    TRACE_ACI(GBS_global_string("defining command '%s'='%s'\n", name, cmd));
538    return NULp;
539}
540
541static GB_ERROR gbl_do(GBL_command_arguments *args) {
542    EXPECT_PARAMS(args, 1, "definedCommandName");
543
544    GB_ERROR    error = NULp;
545    const char *name  = args->get_param(0);
546    const char *cmd   = defined_commands.get(name);
547
548    if (!cmd) {
549        error = GBS_global_string("Can't do undefined command '%s' - use define(%s, ...) first", name, name);
550    }
551    else {
552        TRACE_ACI(GBS_global_string("executing defined command '%s'='%s' on %i streams\n", name, cmd, args->input.size()));
553
554        for (int i=0; i<args->input.size() && !error; i++) {
555            char *result       = args->get_callEnv().interpret_subcommand(args->input.get(i), cmd);
556            if (!result) error = GB_await_error();
557            else  PASS_2_OUT(args, result);
558        }
559    }
560    return error;
561}
562
563static GB_ERROR gbl_streams(GBL_command_arguments *args) {
564    EXPECT_NO_PARAM(args);
565
566    FORMAT_2_OUT(args, "%i", args->input.size());
567    return NULp;
568}
569
570static GB_ERROR apply_to_origin(GBL_command_arguments *args, bool organism) {
571    EXPECT_PARAMS(args, 1, "\"ACI command\"");
572
573    GB_ERROR error = NULp;
574    if (!GEN_is_pseudo_gene_species(args->get_ref())) {
575        error = GBS_global_string("'%s' applies to gene-species only", args->get_cmdName());
576    }
577    else {
578        GBDATA *gb_origin = NULp;
579        if (organism) {
580            gb_origin = GEN_find_origin_organism(args->get_ref(), NULp);
581        }
582        else {
583            gb_origin = GEN_find_origin_gene(args->get_ref(), NULp);
584        }
585
586        if (!error && !gb_origin) error = GB_await_error();
587
588        if (!error) {
589            char         *command = unEscapeString(args->get_param(0));
590            GBL_call_env  callEnv(gb_origin, args->get_env()); // refer to gb_origin for subcommands
591
592            for (int i=0; i<args->input.size() && !error; i++) {
593                char *result       = callEnv.interpret_subcommand(args->input.get(i), command);
594                if (!result) error = GB_await_error();
595                else         PASS_2_OUT(args, result);
596            }
597
598            free(command);
599        }
600    }
601
602    return error;
603}
604
605static GB_ERROR gbl_origin_gene(GBL_command_arguments *args) { return apply_to_origin(args, false); }
606static GB_ERROR gbl_origin_organism(GBL_command_arguments *args) { return apply_to_origin(args, true); }
607
608class Tab {
609    bool tab[256];
610public:
611    Tab(bool take, const char *invert) {
612        bool init = !take;
613        for (int i = 0; i<256; ++i) tab[i] = init;
614        for (int i = 0; invert[i]; ++i) tab[safeCharIndex(invert[i])] = take;
615    }
616    bool operator[](int i) const { return tab[i]; }
617};
618
619inline GB_ERROR count_by_tab(GBL_command_arguments *args, const Tab& tab) {
620    for (int i=0; i<args->input.size(); ++i) {
621        long        sum = 0;            // count frequencies
622        const char *p   = args->input.get(i);
623
624        while (*p) sum += tab[safeCharIndex(*(p++))];
625        FORMAT_2_OUT(args, "%li", sum);
626    }
627    return NULp;
628}
629inline GB_ERROR remove_by_tab(GBL_command_arguments *args, const Tab& tab) {
630    for (int i=0; i<args->input.size(); ++i) {
631        GBS_strstruct *strstruct = GBS_stropen(1000);
632        for (const char *p = args->input.get(i); *p; p++) {
633            if (!tab[(unsigned int)*p]) {
634                GBS_chrcat(strstruct, *p);
635            }
636        }
637        PASS_2_OUT(args, GBS_strclose(strstruct));
638    }
639    return NULp;
640}
641
642static GB_ERROR gbl_count(GBL_command_arguments *args) {
643    EXPECT_PARAMS(args, 1, "\"characters to count\"");
644    return count_by_tab(args, Tab(true, args->get_param(0)));
645}
646static GB_ERROR gbl_len(GBL_command_arguments *args) {
647    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "\"characters not to count\"");
648    const char *exclude = args->get_optional_param(0, "");
649    return count_by_tab(args, Tab(false, exclude));
650}
651static GB_ERROR gbl_remove(GBL_command_arguments *args) {
652    EXPECT_PARAMS(args, 1, "\"characters to remove\"");
653    return remove_by_tab(args, Tab(true, args->get_param(0)));
654}
655static GB_ERROR gbl_keep(GBL_command_arguments *args) {
656    EXPECT_PARAMS(args, 1, "\"characters to keep\"");
657    return remove_by_tab(args, Tab(false, args->get_param(0)));
658}
659
660
661static char *binop_compare(const char *arg1, const char *arg2, bool case_sensitive) {
662    int result;
663
664    if (case_sensitive) result = strcmp(arg1, arg2);
665    else result                = gbl_stricmp(arg1, arg2);
666
667    return GBS_global_string_copy("%i", result<0 ? -1 : (result>0 ? 1 : 0));
668}
669static char *binop_equals(const char *arg1, const char *arg2, bool case_sensitive) {
670    int result;
671
672    if (case_sensitive) result = strcmp(arg1, arg2);
673    else result                = gbl_stricmp(arg1, arg2);
674
675    return GBS_global_string_copy("%i", result == 0 ? 1 : 0);
676}
677static char *binop_contains(const char *arg1, const char *arg2, bool case_sensitive) {
678    const char *found = NULp;
679
680    if (!arg2[0]) return strdup("0"); // do not report matches of empty string
681
682    if (case_sensitive) found = strstr(arg1, arg2);
683    else found                = gbl_stristr(arg1, arg2);
684
685    return GBS_global_string_copy("%ti", found ? (found-arg1)+1 : 0);
686}
687static char *binop_partof(const char *arg1, const char *arg2, bool case_sensitive) {
688    return binop_contains(arg2, arg1, case_sensitive);
689}
690
691static GB_ERROR gbl_compare  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_compare,  true);  }
692static GB_ERROR gbl_icompare (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_compare,  false); }
693static GB_ERROR gbl_equals   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_equals,   true);  }
694static GB_ERROR gbl_iequals  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_equals,   false); }
695static GB_ERROR gbl_contains (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_contains, true);  }
696static GB_ERROR gbl_icontains(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_contains, false); }
697static GB_ERROR gbl_partof   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_partof,   true);  }
698static GB_ERROR gbl_ipartof  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_partof,   false); }
699
700static GB_ERROR gbl_isEmpty(GBL_command_arguments *args) {
701    EXPECT_NO_PARAM(args);
702    for (int i=0; i<args->input.size(); i++) {
703        const char *str = args->input.get(i);
704        FORMAT_2_OUT(args, "%i", str[0] == 0);
705    }
706    return NULp;
707}
708static GB_ERROR gbl_inRange(GBL_command_arguments *args) {
709    EXPECT_PARAMS(args, 2, "low,high");
710
711    double low  = strtod(args->get_param(0), NULp);
712    double high = strtod(args->get_param(1), NULp);
713
714    for (int i=0; i<args->input.size(); i++) {
715        double val     = strtod(args->input.get(i), NULp);
716        bool   inRange = low<=val && val<=high;
717        FORMAT_2_OUT(args, "%i", inRange);
718    }
719    return NULp;
720}
721
722
723static GB_ERROR gbl_translate(GBL_command_arguments *args) {
724    EXPECT_OPTIONAL_PARAMS(args, 2, "old,new", 1, "other");
725
726    const char *other = args->get_optional_param(2, NULp);
727    if (other && (other[0] == 0 || other[1] != 0)) {
728        return "third parameter of translate has to be one character (i.e. \"-\")";
729    }
730    const char replace_other = other ? other[0] : 0;
731
732    // build translation table :
733    unsigned char tab[256];
734    {
735        const unsigned char *o = (const unsigned char *)args->get_param(0);
736        const unsigned char *n = (const unsigned char *)args->get_param(1);
737        char        used[256];
738
739        if (strlen((const char *)o) != strlen((const char *)n)) {
740            return "arguments 1 and 2 of translate should be strings with identical length";
741        }
742
743        for (int i = 0; i<256; ++i) { // IRRELEVANT_LOOP
744            tab[i]  = replace_other ? replace_other : i; // replace unused or identity translation
745            used[i] = 0;
746        }
747
748        for (int i = 0; o[i]; ++i) {
749            if (used[o[i]]) return GBS_global_string("character '%c' used twice in argument 1 of translate", o[i]);
750            used[o[i]] = 1;
751            tab[o[i]]  = n[i]; // real translation
752        }
753    }
754
755    for (int i=0; i<args->input.size(); i++) {
756        GBS_strstruct *strstruct = GBS_stropen(1000);
757        for (const char *p = args->input.get(i); *p; p++) {
758            GBS_chrcat(strstruct, tab[(unsigned char)*p]);
759        }
760        PASS_2_OUT(args, GBS_strclose(strstruct));
761    }
762    return NULp;
763}
764
765
766static GB_ERROR gbl_echo(GBL_command_arguments *args) {
767    ACCEPT_ANY_PARAMS(args);
768    COMMAND_DROPS_INPUT_STREAMS(args);
769    for (int i=0; i<args->param_count(); i++) PARAM_2_OUT(args, i);
770    return NULp;
771}
772
773static GB_ERROR gbl_dd(GBL_command_arguments *args) {
774    EXPECT_NO_PARAM(args);
775    return gbl_mid_streams(args->input, args->output, 0, -1); // copy all streams
776}
777
778enum Case { UPPER, LOWER, CAPS };
779
780static GB_ERROR convert_case(GBL_command_arguments *args, Case convTo) {
781    EXPECT_NO_PARAM(args);
782
783    for (int i=0; i<args->input.size(); i++) {
784        char *p              = ARB_strdup(args->input.get(i));
785        bool  last_was_alnum = false;
786
787        for (char *pp = p; pp[0]; ++pp) {
788            switch (convTo) {
789                case LOWER:  pp[0] = tolower(pp[0]); break;
790                case UPPER:  pp[0] = toupper(pp[0]); break;
791                case CAPS: {
792                    bool alnum = isalnum(pp[0]);
793                    if (alnum) pp[0] = (last_was_alnum ? tolower : toupper)(pp[0]);
794                    last_was_alnum = alnum;
795                    break;
796                }
797                default: gb_assert(0); break;
798            }
799        }
800
801        PASS_2_OUT(args, p);
802    }
803
804    return NULp;
805}
806
807static GB_ERROR gbl_caps (GBL_command_arguments *args) { return convert_case(args, CAPS); }
808static GB_ERROR gbl_upper(GBL_command_arguments *args) { return convert_case(args, UPPER); }
809static GB_ERROR gbl_lower(GBL_command_arguments *args) { return convert_case(args, LOWER); }
810
811static GB_ERROR gbl_head(GBL_command_arguments *args) {
812    EXPECT_PARAMS(args, 1, "length_of_head");
813    int start = atoi(args->get_param(0));
814    if (start <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
815    return gbl_mid_streams(args->input, args->output, 0, start-1);
816}
817static GB_ERROR gbl_tail(GBL_command_arguments *args) {
818    EXPECT_PARAMS(args, 1, "length_of_tail");
819    int end = atoi(args->get_param(0));
820    if (end <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
821    return gbl_mid_streams(args->input, args->output, -end, -1);
822}
823
824inline GB_ERROR mid(GBL_command_arguments *args, int start_index) {
825    EXPECT_PARAMS(args, 2, "start,end");
826    return gbl_mid_streams(args->input, args->output, atoi(args->get_param(0))-start_index, atoi(args->get_param(1))-start_index);
827}
828static GB_ERROR gbl_mid0(GBL_command_arguments *args) { return mid(args, 0); }
829static GB_ERROR gbl_mid (GBL_command_arguments *args) { return mid(args, 1); }
830
831static GB_ERROR tab(GBL_command_arguments *args, bool pretab) {
832    EXPECT_PARAMS(args, 1, "tabstop");
833
834    int tab = atoi(args->get_param(0));
835    for (int i=0; i<args->input.size(); i++) {
836        int len = strlen(args->input.get(i));
837        if (len >= tab) IN_2_OUT(args, i);
838        else {
839            char *p = ARB_alloc<char>(tab+1);
840            if (pretab) {
841                int spaces = tab-len;
842                for (int j = 0; j<spaces; ++j) p[j] = ' ';
843                strcpy(p+spaces, args->input.get(i));
844            }
845            else {
846                strcpy(p, args->input.get(i));
847                for (int j=len; j<tab; j++) p[j] = ' ';
848                p[tab] = 0;
849            }
850            PASS_2_OUT(args, p);
851        }
852    }
853    return NULp;
854}
855static GB_ERROR gbl_tab   (GBL_command_arguments *args) { return tab(args, false); }
856static GB_ERROR gbl_pretab(GBL_command_arguments *args) { return tab(args, true); }
857
858static GB_ERROR gbl_crop(GBL_command_arguments *args) {
859    EXPECT_PARAMS(args, 1, "\"chars_to_crop\"");
860
861    const char *chars_to_crop = args->get_param(0);
862    for (int i=0; i<args->input.size(); i++) {
863        const char *s = args->input.get(i);
864        while (s[0] && strchr(chars_to_crop, s[0])) s++; // crop at beg of line
865
866        int   len = strlen(s);
867        char *p   = ARB_alloc<char>(len+1);
868        strcpy(p, s);
869
870        {
871            char *pe = p+len-1;
872
873            while (pe >= p && strchr(chars_to_crop, pe[0])) { // crop at end of line
874                --pe;
875            }
876            gb_assert(pe >= (p-1));
877            pe[1] = 0;
878        }
879        PASS_2_OUT(args, p);
880    }
881    return NULp;
882}
883
884
885
886static GB_ERROR gbl_cut(GBL_command_arguments *args) {
887    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
888
889    for (int i=0; i<args->param_count(); i++) {
890        int stream = atoi(args->get_param(i));
891        EXPECT_LEGAL_STREAM_INDEX(args, stream);
892        IN_2_OUT(args, bio2info(stream));
893    }
894    return NULp;
895}
896static GB_ERROR gbl_drop(GBL_command_arguments *args) {
897    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
898
899    GB_ERROR  error   = NULp;
900    bool     *dropped = ARB_alloc<bool>(args->input.size());
901
902    for (int i=0; i<args->input.size(); ++i) dropped[i] = false;
903
904    for (int i=0; i<args->param_count() && !error; ++i) {
905        int stream = atoi(args->get_param(i));
906        error = check_valid_stream_index(args, stream);
907        if (!error) dropped[bio2info(stream)] = true;
908    }
909
910    if (!error) {
911        for (int i=0; i<args->input.size(); ++i) {
912            if (!dropped[i]) IN_2_OUT(args, i);
913        }
914    }
915    free(dropped);
916
917    return error;
918}
919
920static GB_ERROR gbl_dropempty(GBL_command_arguments *args) {
921    EXPECT_NO_PARAM(args);
922
923    for (int i=0; i<args->input.size(); ++i) {
924        if (args->input.get(i)[0]) { // if non-empty
925            IN_2_OUT(args, i);
926        }
927    }
928    return NULp;
929}
930
931static GB_ERROR gbl_dropzero(GBL_command_arguments *args) {
932    EXPECT_NO_PARAM(args);
933
934    for (int i=0; i<args->input.size(); ++i) {
935        if (atoi(args->input.get(i))) { // if non-zero
936            IN_2_OUT(args, i);
937        }
938    }
939    return NULp;
940}
941
942static GB_ERROR gbl_swap(GBL_command_arguments *args) {
943    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 2, "streamnumber,streamnumber");
944
945    if (args->input.size()<2) return "need at least two input streams";
946
947    int swap1;
948    int swap2;
949    if (args->param_count() == 0) {
950        swap1 = args->input.size()-1;
951        swap2 = args->input.size()-2;
952    }
953    else {
954        gb_assert(args->param_count() == 2);
955
956        swap1 = atoi(args->get_param(0));
957        swap2 = atoi(args->get_param(1));
958
959        EXPECT_LEGAL_STREAM_INDEX(args, swap1);
960        EXPECT_LEGAL_STREAM_INDEX(args, swap2);
961
962        swap1 = bio2info(swap1);
963        swap2 = bio2info(swap2);
964    }
965
966    for (int i = 0; i<args->input.size(); ++i) {
967        int j = i == swap1 ? swap2 : (i == swap2 ? swap1 : i);
968        IN_2_OUT(args, j);
969    }
970
971    return NULp;
972}
973
974static GB_ERROR backfront_stream(GBL_command_arguments *args, int toback) {
975    EXPECT_PARAMS(args, 1, "streamnumber");
976    if (args->input.size()<1) return "need at least one input stream";
977
978    int stream_to_move = atoi(args->get_param(0));
979    EXPECT_LEGAL_STREAM_INDEX(args, stream_to_move);
980    stream_to_move = bio2info(stream_to_move);
981
982    if (!toback) IN_2_OUT(args, stream_to_move);
983    for (int i = 0; i<args->input.size(); ++i) {
984        if (i != stream_to_move) IN_2_OUT(args, i);
985    }
986    if (toback) IN_2_OUT(args, stream_to_move);
987
988    return NULp;
989}
990static GB_ERROR gbl_toback (GBL_command_arguments *args) { return backfront_stream(args, 1); }
991static GB_ERROR gbl_tofront(GBL_command_arguments *args) { return backfront_stream(args, 0); }
992
993static GB_ERROR gbl_merge(GBL_command_arguments *args) {
994    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "\"separator\"");
995    const char *separator = args->get_optional_param(0, NULp);
996
997    if (args->input.size()) {
998        GBS_strstruct *str = GBS_stropen(1000);
999        GBS_strcat(str, args->input.get(0));
1000
1001        for (int i = 1; i<args->input.size(); ++i) {
1002            if (separator) GBS_strcat(str, separator);
1003            GBS_strcat(str, args->input.get(i));
1004        }
1005
1006        PASS_2_OUT(args, GBS_strclose(str));
1007    }
1008    return NULp;
1009}
1010
1011static GB_ERROR gbl_split(GBL_command_arguments *args) {
1012    EXPECT_OPTIONAL_PARAMS_CUSTOM(args, 0, NULp, 2, "\"separator\"[,mode]", true, false);
1013
1014    const char *separator = args->get_optional_param(0, "\n");
1015    int split_mode        = atoi(args->get_optional_param(1, "0")); // 0: remove separator, 1: split before separator, 2: split behind separator
1016
1017    if (split_mode<0 || split_mode>2) return GBS_global_string("Illegal split mode '%i' (valid: 0..2)", split_mode);
1018
1019    {
1020        size_t sepLen = strlen(separator);
1021
1022        for (int i = 0; i<args->input.size(); ++i) {
1023            const char *in   = args->input.get(i);
1024            const char *from = in; // search from here
1025
1026            while (in) {
1027                const char *splitAt = strstr(from, separator);
1028                if (splitAt) {
1029                    size_t  len;
1030                    char   *copy;
1031
1032                    if (split_mode == 2) splitAt += sepLen; // split behind separator
1033
1034                    len  = splitAt-in;
1035                    copy = ARB_strndup(in, len);
1036
1037                    PASS_2_OUT(args, copy);
1038
1039                    in   = splitAt + (split_mode == 0 ? sepLen : 0);
1040                    from = in+(split_mode == 1 ? sepLen : 0);
1041                }
1042                else {
1043                    COPY_2_OUT(args, in); // last part
1044                    in = NULp;
1045                }
1046            }
1047        }
1048    }
1049
1050    return NULp;
1051}
1052
1053// ----------------------------------
1054//      Extended string functions
1055
1056static char *do_extract_words(const char *source, const char *chars, float minlen, bool sort_output) {
1057    /* extract all words in a text that:
1058     * if minlen < 1.0 -> contain more than minlen*len_of_text characters that also exists in chars
1059     * if minlen > 1.0 -> contain more than minlen characters that also exists in chars
1060     */
1061
1062    char           *s         = ARB_strdup(source);
1063    char          **ps        = ARB_calloc<char*>((strlen(source)>>1) + 1);
1064    GBS_strstruct  *strstruct = GBS_stropen(1000);
1065    char           *f         = s;
1066    int             count     = 0;
1067    char           *p; // @@@ fix locals
1068    char           *h;
1069    int             cnt;
1070    int             len;
1071    int             iminlen   = (int) (minlen+.5);
1072
1073    while ((p = strtok(f, " \t,;:|"))) {
1074        f = NULp;
1075        cnt = 0;
1076        len = strlen(p);
1077        for (h=p; *h; h++) {
1078            if (strchr(chars, *h)) cnt++;
1079        }
1080
1081        if (minlen == 1.0) {
1082            if (cnt != len) continue;
1083        }
1084        else if (minlen > 1.0) {
1085            if (cnt < iminlen) continue;
1086        }
1087        else {
1088            if (len < 3 || cnt < minlen*len) continue;
1089        }
1090        ps[count] = p;
1091        count ++;
1092    }
1093    if (sort_output) {
1094        GB_sort((void **)ps, 0, count, GB_string_comparator, NULp);
1095    }
1096    for (cnt = 0; cnt<count; cnt++) {
1097        if (cnt) {
1098            GBS_chrcat(strstruct, ' ');
1099        }
1100        GBS_strcat(strstruct, ps[cnt]);
1101    }
1102
1103    free(ps);
1104    free(s);
1105    return GBS_strclose(strstruct);
1106}
1107
1108static GB_ERROR gbl_extract_words(GBL_command_arguments *args) {
1109    EXPECT_PARAMS(args, 2, "\"chars\", minchars");
1110
1111    float len = atof(args->get_param(1));
1112    for (int i=0; i<args->input.size(); i++) {
1113        char *res = do_extract_words(args->input.get(i), args->get_param(0), len, 1);
1114        gb_assert(res);
1115        PASS_2_OUT(args, res);
1116    }
1117    return NULp;
1118}
1119
1120static GB_ERROR gbl_extract_sequence(GBL_command_arguments *args) {
1121    EXPECT_PARAMS(args, 2, "\"chars\",minFrequency");
1122
1123    const char *chars   = args->get_param(0);
1124    float       minFreq = atof(args->get_param(1));
1125
1126    if (minFreq <0.0 || minFreq > 1.0) return GBS_global_string("Illegal minFrequency=%f (allowed: ]0.0 .. 1.0[)", minFreq);
1127
1128    for (int i=0; i<args->input.size(); i++) {
1129        char *res = do_extract_words(args->input.get(i), chars, minFreq, 0);
1130        gb_assert(res);
1131        PASS_2_OUT(args, res);
1132    }
1133    return NULp;
1134}
1135
1136static GB_ERROR gbl_checksum(GBL_command_arguments *args) {
1137    GBL_BEGIN_PARAMS;
1138    GBL_PARAM_STRING(exclude, "exclude=", "", "Remove given characters before calculating");
1139    GBL_PARAM_BIT   (upper,   "toupper",  0,  "Convert all characters to uppercase before calculating");
1140    GBL_TRACE_PARAMS(args);
1141    GBL_END_PARAMS;
1142
1143    for (int i=0; i<args->input.size(); i++) {
1144        long id = GBS_checksum(args->input.get(i), upper, exclude);
1145        FORMAT_2_OUT(args, "%lX", id);
1146    }
1147    return NULp;
1148}
1149
1150static GB_ERROR gbl_gcgchecksum(GBL_command_arguments *args) {
1151    EXPECT_NO_PARAM(args);
1152
1153    for (int i=0; i<args->input.size(); i++) {
1154        long id = GBS_gcgchecksum(args->input.get(i));
1155        FORMAT_2_OUT(args, "%li", id);
1156    }
1157    return NULp;
1158}
1159
1160// ------------
1161//      SRT
1162
1163static GB_ERROR gbl_srt(GBL_command_arguments *args) {
1164    EXPECT_PARAMS_PASSED(args, "expr[,expr]+");
1165
1166    GB_ERROR error = NULp;
1167    for (int i=0; i<args->input.size() && !error; i++) {
1168        char *modsource = NULp;
1169
1170        for (int j=0; j<args->param_count() && !error; j++) {
1171            char *hs = GBS_string_eval_in_env(modsource ? modsource : args->input.get(i), args->get_param(j), args->get_callEnv());
1172
1173            if (hs) freeset(modsource, hs);
1174            else {
1175                error = GB_await_error();
1176                free(modsource);
1177            }
1178        }
1179
1180        if (!error) {
1181            if (modsource) PASS_2_OUT(args, modsource);
1182            else           IN_2_OUT(args, i);
1183        }
1184    }
1185    return error;
1186}
1187
1188// -----------------------------
1189//      Calculator Functions
1190
1191struct binop_pair {
1192    int    (*INT)   (int, int);
1193    double (*DOUBLE)(double, double);
1194    binop_pair(int (*INT_)(int, int), double (*DOUBLE_)(double, double)) : INT(INT_), DOUBLE(DOUBLE_) {}
1195};
1196
1197static char *apply_numeric_binop(const char *arg1, const char *arg2, int (*num_bin_op)(int,int)) {
1198    int v1     = atoi(arg1);
1199    int v2     = atoi(arg2);
1200    int result = num_bin_op(v1, v2);
1201
1202    return GBS_global_string_copy("%i", result);
1203}
1204
1205static char *apply_double_binop(const char *arg1, const char *arg2, double (*num_bin_op)(double,double)) {
1206    double v1     = strtod(arg1, NULp);
1207    double v2     = strtod(arg2, NULp);
1208    double result = num_bin_op(v1, v2);
1209
1210    return GBS_global_string_copy("%g", result);
1211}
1212
1213static char *apply_auto_numeric_binop(const char *arg1, const char *arg2, binop_pair multiop) {
1214    // argument type detection (int vs double)
1215    int    i1 = atoi(arg1);
1216    int    i2 = atoi(arg2);
1217    double d1 = strtod(arg1, NULp);
1218    double d2 = strtod(arg2, NULp);
1219
1220    if (double(i1) == d1 || double(i2) == d2) {
1221        int result = multiop.INT(i1, i2);
1222        return GBS_global_string_copy("%i", result);
1223    }
1224    else {
1225        double result = multiop.DOUBLE(d1, d2);
1226        return GBS_global_string_copy("%g", result);
1227    }
1228}
1229
1230
1231
1232template <typename T> static T binop_plus    (T v1, T v2) { return v1+v2; }
1233template <typename T> static T binop_minus   (T v1, T v2) { return v1-v2; }
1234template <typename T> static T binop_mult    (T v1, T v2) { return v1*v2; }
1235template <typename T> static T binop_div     (T v1, T v2) { return v2 ? v1/v2 : 0; }
1236template <typename T> static T binop_per_cent(T v1, T v2) { return v2 ? (v1*100)/v2 : 0; }
1237
1238static int binop_rest(int i1, int i2) { return i2 ? i1%i2 : 0; }
1239
1240
1241static GB_ERROR gbl_plus     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_plus<int>);        }
1242static GB_ERROR gbl_fplus    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_plus<double>);     }
1243static GB_ERROR gbl_minus    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_minus<int>);       }
1244static GB_ERROR gbl_fminus   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_minus<double>);    }
1245static GB_ERROR gbl_mult     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_mult<int>);        }
1246static GB_ERROR gbl_fmult    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_mult<double>);     }
1247static GB_ERROR gbl_div      (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_div<int>);         }
1248static GB_ERROR gbl_fdiv     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_div<double>);      }
1249static GB_ERROR gbl_rest     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_rest);             }
1250static GB_ERROR gbl_per_cent (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_per_cent<int>);    }
1251static GB_ERROR gbl_fper_cent(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_per_cent<double>); }
1252
1253template <typename T> static T binop_isAbove(T i1, T i2) { return i1>i2; }
1254template <typename T> static T binop_isBelow(T i1, T i2) { return i1<i2; }
1255template <typename T> static T binop_isEqual(T i1, T i2) { return i1 == i2; }
1256
1257static GB_ERROR gbl_isAbove(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_auto_numeric_binop, binop_pair(binop_isAbove<int>, binop_isAbove<double>)); }
1258static GB_ERROR gbl_isBelow(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_auto_numeric_binop, binop_pair(binop_isBelow<int>, binop_isBelow<double>)); }
1259static GB_ERROR gbl_isEqual(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_auto_numeric_binop, binop_pair(binop_isEqual<int>, binop_isEqual<double>)); }
1260
1261inline double float_shift_factor(int digits) {
1262    if (digits<0) {
1263        return 1.0/float_shift_factor(-digits);
1264    }
1265    int factor = 1;
1266    while (digits>0) { // LOOP_VECTORIZED=* // @@@ vectorized only 2x with 4.9.3; 3x with 5.x
1267        factor *= 10;
1268        --digits;
1269    }
1270    return factor;
1271}
1272
1273static GB_ERROR gbl_round(GBL_command_arguments *args) {
1274    EXPECT_PARAMS(args, 1, "digits");
1275    int digits = atoi(args->get_param(0));
1276
1277    double factor = float_shift_factor(digits);
1278    for (int i=0; i<args->input.size(); ++i) {
1279        double val = strtod(args->input.get(i), NULp);
1280        val = round(val*factor)/factor;
1281        FORMAT_2_OUT(args, "%g", val);
1282    }
1283    return NULp;
1284}
1285
1286
1287
1288// boolean operators
1289
1290static GB_ERROR gbl_not(GBL_command_arguments *args) {
1291    EXPECT_NO_PARAM(args);
1292
1293    for (int i=0; i<args->input.size(); ++i) {
1294        const char *s   = args->input.get(i);
1295        int         val = atoi(s);
1296        FORMAT_2_OUT(args, "%i", !val);
1297    }
1298    return NULp;
1299}
1300
1301static GB_ERROR gbl_and(GBL_command_arguments *args) {
1302    EXPECT_NO_PARAM(args);
1303    bool conjunction = true;
1304    for (int i=0; conjunction && i<args->input.size(); ++i) {
1305        const char *s = args->input.get(i);
1306        conjunction = conjunction && atoi(s);
1307    }
1308    FORMAT_2_OUT(args, "%i", conjunction);
1309    return NULp;
1310}
1311static GB_ERROR gbl_or(GBL_command_arguments *args) {
1312    EXPECT_NO_PARAM(args);
1313    bool disjunction = false;
1314    for (int i=0; !disjunction && i<args->input.size(); ++i) {
1315        const char *s = args->input.get(i);
1316        disjunction = disjunction || atoi(s);
1317    }
1318    FORMAT_2_OUT(args, "%i", disjunction);
1319    return NULp;
1320}
1321
1322static GB_ERROR gbl_select(GBL_command_arguments *args) {
1323    ACCEPT_ANY_PARAMS(args);
1324
1325    GB_ERROR error = NULp;
1326    for (int i=0; i<args->input.size() && !error; i++) {
1327        int paraidx = atoi(args->input.get(i));
1328        error       = check_valid_param_index(args, paraidx);
1329        if (!error) {
1330            char *result = args->get_callEnv().interpret_subcommand("", args->get_param(paraidx)); // @@@ EVALUATED_PARAM (#768)
1331            if (!result) error = GB_await_error();
1332            else PASS_2_OUT(args, result);
1333        }
1334    }
1335    return error;
1336}
1337
1338static GB_ERROR gbl_readdb(GBL_command_arguments *args) {
1339    EXPECT_PARAMS_PASSED(args, "fieldname[,fieldname]+");
1340    COMMAND_DROPS_INPUT_STREAMS(args);
1341
1342    GBS_strstruct *strstr = GBS_stropen(1024);
1343    for (int i=0; i<args->param_count(); i++) {
1344        char *val = GBT_read_as_string(args->get_ref(), args->get_param(i));
1345        if (val) {
1346            GBS_strcat(strstr, val);
1347            free(val);
1348        }
1349    }
1350    PASS_2_OUT(args, GBS_strclose(strstr));
1351    return NULp;
1352}
1353
1354
1355enum GBT_ITEM_TYPE {
1356    GBT_ITEM_UNKNOWN,
1357    GBT_ITEM_SPECIES,
1358    GBT_ITEM_GENE
1359};
1360
1361static GBT_ITEM_TYPE identify_gb_item(GBDATA *gb_item) {
1362    /* returns: GBT_ITEM_UNKNOWN    -> unknown database_item
1363     *          GBT_ITEM_SPECIES    -> /species_data/species
1364     *          GBT_ITEM_GENE       -> /species_data/species/gene_data/gene */
1365
1366    GBT_ITEM_TYPE res = GBT_ITEM_UNKNOWN;
1367    if (gb_item) {
1368        GBDATA *gb_father = GB_get_father(gb_item);
1369        if (gb_father) {
1370            const char *key = GB_KEY(gb_item);
1371
1372            if (strcmp(key, "species")                    == 0 &&
1373                strcmp(GB_KEY(gb_father), "species_data") == 0) {
1374                res = GBT_ITEM_SPECIES;
1375            }
1376            else if (strcmp(key, "gene")                   == 0 &&
1377                strcmp(GB_KEY(gb_father), "gene_data")     == 0 &&
1378                identify_gb_item(GB_get_father(gb_father)) == GBT_ITEM_SPECIES) {
1379                res = GBT_ITEM_GENE;
1380            }
1381        }
1382    }
1383    return res;
1384}
1385
1386// --------------------------------------------------------------------------------
1387// taxonomy caching
1388
1389#if defined(DEBUG)
1390// #define DUMP_TAXONOMY_CACHING
1391#endif
1392
1393
1394#define GROUP_COUNT_CHARS 6                         // characters in taxonomy-key reserved for group-counter (hex number)
1395#define BITS_PER_HEXCHAR  4
1396#define MAX_GROUPS        (1 << (GROUP_COUNT_CHARS*BITS_PER_HEXCHAR)) // resulting number of groups
1397
1398struct cached_taxonomy {
1399    char    *tree_name;         // tree for which taxonomy is cached here
1400    int      groups;            // number of named groups in tree (at time of caching)
1401    GB_HASH *taxonomy; /* keys: "!species", ">XXXXgroup" and "<root>".
1402                        * Species and groups contain their first parent (i.e. '>XXXXgroup' or '<root>').
1403                        * Species not in hash are not members of tree.
1404                        * The 'XXXX' in groupname is simply a counter to avoid multiple groups with same name.
1405                        * The group-db-entries are stored in hash as pointers ('>>%p') and
1406                        * point to their own group entry ('>XXXXgroup')
1407                        *
1408                        * Note: the number of 'X's in 'XXXX' above is defined by GROUP_COUNT_CHARS!
1409                        */
1410};
1411
1412static void free_cached_taxonomy(cached_taxonomy *ct) {
1413    free(ct->tree_name);
1414    GBS_free_hash(ct->taxonomy);
1415    free(ct);
1416}
1417
1418static void build_taxonomy_rek(TreeNode *node, GB_HASH *tax_hash, const char *parent_group, int *group_counter) {
1419    if (node->is_leaf()) {
1420        GBDATA *gb_species = node->gb_node;
1421        if (gb_species) { // not zombie
1422            GBS_write_hash(tax_hash, GBS_global_string("!%s", GBT_read_name(gb_species)), (long)ARB_strdup(parent_group));
1423        }
1424    }
1425    else {
1426        if (node->has_group_info()) { // node with name
1427            char       *hash_entry;
1428            const char *hash_binary_entry;
1429            (*group_counter)++;
1430
1431            gb_assert((*group_counter)<MAX_GROUPS); // overflow - increase GROUP_COUNT_CHARS
1432
1433            TreeNode *keelTarget = node->keelTarget();
1434
1435            hash_entry = GBS_global_string_copy(">%0*x%s%s",
1436                                                GROUP_COUNT_CHARS, *group_counter,
1437                                                keelTarget ? "!" : "",
1438                                                node->name);
1439            GBS_write_hash(tax_hash, hash_entry, (long)ARB_strdup(parent_group));
1440
1441            hash_binary_entry = GBS_global_string(">>%p", node->gb_node);
1442            GBS_write_hash(tax_hash, hash_binary_entry, (long)ARB_strdup(hash_entry));
1443
1444            if (keelTarget) { // keeled group (projected to son)
1445                if (keelTarget->is_leftson()) {
1446                    build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry,   group_counter); // pass down hash_entry only to keelTarget
1447                    build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1448                }
1449                else {
1450                    build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1451                    build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry,   group_counter);
1452                }
1453            }
1454            else { // normal group
1455                build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry, group_counter); // pass down hash_entry to both sons
1456                build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry, group_counter);
1457            }
1458
1459            free(hash_entry);
1460        }
1461        else {
1462            build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1463            build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1464        }
1465    }
1466}
1467
1468static GB_HASH *cached_taxonomies = NULp;
1469
1470static bool is_cached_taxonomy(const char */*key*/, long val, void *cl_ct) {
1471    cached_taxonomy *ct1 = (cached_taxonomy *)val;
1472    cached_taxonomy *ct2 = (cached_taxonomy *)cl_ct;
1473
1474    return ct1 == ct2;
1475}
1476
1477static const char *tree_of_cached_taxonomy(cached_taxonomy *ct) {
1478    /* search the hash to find the correct cached taxonomy.
1479     * searching for tree name does not work, because the tree possibly already was deleted
1480     */
1481    const char *tree = GBS_hash_next_element_that(cached_taxonomies, NULp, is_cached_taxonomy, ct);
1482#ifdef DUMP_TAXONOMY_CACHING
1483    if (tree) printf("tree_of_cached_taxonomy: tree='%s' ct->tree_name='%s'\n", tree, ct->tree_name);
1484#endif // DUMP_TAXONOMY_CACHING
1485    return tree;
1486}
1487
1488static void flush_taxonomy_cb(GBDATA *gbd, cached_taxonomy *ct) {
1489    /* this cb is bound all tree db members below "/tree_data/tree_xxx" which
1490     * may have an effect on the displayed taxonomy
1491     * it invalidates cached taxonomies for that tree (when changed or deleted)
1492     */
1493
1494    GB_ERROR    error = NULp;
1495    const char *found = tree_of_cached_taxonomy(ct);
1496
1497    if (found) {
1498#ifdef DUMP_TAXONOMY_CACHING
1499        fprintf(stderr, "Deleting cached taxonomy ct=%p (tree='%s')\n", ct, found);
1500#endif // DUMP_TAXONOMY_CACHING
1501        GBS_write_hash(cached_taxonomies, found, 0); // delete cached taxonomy from hash
1502        free_cached_taxonomy(ct);
1503    }
1504#ifdef DUMP_TAXONOMY_CACHING
1505    else {
1506        fprintf(stderr, "No tree found for cached_taxonomies ct=%p (already deleted?)\n", ct);
1507    }
1508#endif // DUMP_TAXONOMY_CACHING
1509
1510    if (!GB_inside_callback(gbd, GB_CB_DELETE)) {
1511        GB_remove_all_callbacks_to(gbd, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1512    }
1513
1514    if (found && !error) {
1515        GBDATA *gb_main = GB_get_gb_main_during_cb();
1516        if (gb_main) {
1517            GBDATA *gb_tree_refresh = GB_search(gb_main, AWAR_TREE_REFRESH, GB_INT);
1518            if (!gb_tree_refresh) {
1519                error = GBS_global_string("%s (while trying to force refresh)", GB_await_error());
1520            }
1521            else {
1522                GB_touch(gb_tree_refresh); // Note : force tree update
1523            }
1524        }
1525    }
1526
1527    if (error) {
1528        fprintf(stderr, "Error in flush_taxonomy_cb: %s\n", error);
1529    }
1530}
1531
1532static void flush_taxonomy_if_new_group_cb(GBDATA *gb_tree, cached_taxonomy *ct) {
1533    // detects the creation of new groups and call flush_taxonomy_cb() manually
1534#ifdef DUMP_TAXONOMY_CACHING
1535    fputs("flush_taxonomy_if_new_group_cb() has been called\n", stderr);
1536#endif // DUMP_TAXONOMY_CACHING
1537
1538    const char *tree_name = tree_of_cached_taxonomy(ct);
1539    if (tree_name) {
1540        int     groups = 0;
1541        GBDATA *gb_group_node;
1542
1543        for (gb_group_node = GB_entry(gb_tree, "node");
1544             gb_group_node;
1545             gb_group_node = GB_nextEntry(gb_group_node))
1546        {
1547            if (GB_entry(gb_group_node, "group_name")) {
1548                groups++; // count named groups only
1549            }
1550        }
1551
1552#ifdef DUMP_TAXONOMY_CACHING
1553        fprintf(stderr, "cached_groups=%i  counted_groups=%i\n", ct->groups, groups);
1554#endif // DUMP_TAXONOMY_CACHING
1555        if (groups != ct->groups) {
1556#ifdef DUMP_TAXONOMY_CACHING
1557            fprintf(stderr, "Number of groups changed -> invoking flush_taxonomy_cb() manually\n");
1558#endif // DUMP_TAXONOMY_CACHING
1559            flush_taxonomy_cb(gb_tree, ct);
1560        }
1561    }
1562#ifdef DUMP_TAXONOMY_CACHING
1563    else {
1564        fprintf(stderr, "cached taxonomy no longer valid.\n");
1565    }
1566#endif // DUMP_TAXONOMY_CACHING
1567}
1568
1569static cached_taxonomy *get_cached_taxonomy(GBDATA *gb_main, const char *tree_name, GB_ERROR *error) {
1570    long cached;
1571    *error = NULp;
1572    if (!cached_taxonomies) {
1573        cached_taxonomies = GBS_create_hash(20, GB_IGNORE_CASE);
1574    }
1575    cached = GBS_read_hash(cached_taxonomies, tree_name);
1576    if (!cached) {
1577        TreeNode *tree    = GBT_read_tree(gb_main, tree_name, new SimpleRoot);
1578        if (!tree) *error = GB_await_error();
1579        else     *error   = GBT_link_tree(tree, gb_main, false, NULp, NULp);
1580
1581        if (!*error) {
1582            GBDATA *gb_tree = GBT_find_tree(gb_main, tree_name);
1583            if (!gb_tree) {
1584                *error = GBS_global_string("Can't find tree '%s'", tree_name);
1585            }
1586            else {
1587                cached_taxonomy *ct            = ARB_alloc<cached_taxonomy>(1);
1588                long             nodes         = GBT_count_leafs(tree);
1589                int              group_counter = 0;
1590
1591                ct->tree_name = ARB_strdup(tree_name);
1592                ct->taxonomy  = GBS_create_dynaval_hash(int(nodes), GB_IGNORE_CASE, GBS_dynaval_free);
1593                ct->groups    = 0; // counted below
1594
1595                build_taxonomy_rek(tree, ct->taxonomy, "<root>", &group_counter);
1596                cached = (long)ct;
1597                GBS_write_hash(cached_taxonomies, tree_name, (long)ct);
1598
1599                GB_remove_all_callbacks_to(gb_tree, GB_CB_SON_CREATED, CASTSIG(GB_CB, flush_taxonomy_if_new_group_cb));
1600                GB_add_callback(gb_tree, GB_CB_SON_CREATED, makeDatabaseCallback(flush_taxonomy_if_new_group_cb, ct));
1601
1602                {
1603                    GBDATA *gb_tree_entry = GB_entry(gb_tree, "tree");
1604                    GBDATA *gb_group_node;
1605
1606                    if (gb_tree_entry) {
1607                        GB_remove_all_callbacks_to(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1608                        GB_add_callback(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1609                    }
1610
1611                    // add callbacks for all node/group_name subentries
1612                    for (gb_group_node = GB_entry(gb_tree, "node");
1613                         gb_group_node;
1614                         gb_group_node = GB_nextEntry(gb_group_node))
1615                    {
1616                        GBDATA *gb_group_name = GB_entry(gb_group_node, "group_name");
1617                        if (gb_group_name) { // group with id = 0 has no name
1618                            GB_remove_all_callbacks_to(gb_group_name, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1619                            GB_add_callback(gb_group_name, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1620                            ct->groups++;
1621                        }
1622                    }
1623                }
1624#ifdef DUMP_TAXONOMY_CACHING
1625                fprintf(stderr, "Created taxonomy hash for '%s' (ct=%p)\n", tree_name, ct);
1626#endif // DUMP_TAXONOMY_CACHING
1627            }
1628        }
1629
1630        destroy(tree);
1631    }
1632
1633    if (!*error) {
1634        cached_taxonomy *ct = (cached_taxonomy*)cached;
1635        gb_assert(ct);
1636        return ct;
1637    }
1638
1639    return NULp;
1640}
1641
1642static char *get_taxonomy_string(GB_HASH *tax_hash, const char *group_key, int depth, GB_ERROR *error) {
1643    long  found;
1644    char *result = NULp;
1645
1646    gb_assert(depth>0);
1647    gb_assert(!(group_key[0] == '>' && group_key[1] == '>')); // internal group-pointers not allowed here!
1648
1649    found = GBS_read_hash(tax_hash, group_key);
1650    if (found) {
1651        const char *parent_group_key            = (const char *)found;
1652        if (strcmp(parent_group_key, "<root>") == 0) { // root reached
1653            result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1654        }
1655        else {
1656            if (depth>1) {
1657                char *parent_name = get_taxonomy_string(tax_hash, parent_group_key, depth-1, error);
1658                if (parent_name) {
1659                    result = GBS_global_string_copy("%s/%s", parent_name, group_key+(GROUP_COUNT_CHARS+1));
1660                    free(parent_name);
1661                }
1662                else {
1663                    *error = GBS_global_string("In get_taxonomy_string(%s): %s", group_key, *error);
1664                    result = NULp;
1665                }
1666            }
1667            else {
1668                result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1669            }
1670        }
1671    }
1672    else {
1673        *error = GBS_global_string("Not in tax_hash: '%s'", group_key);
1674    }
1675    return result;
1676}
1677
1678static const char *get_taxonomy(GBDATA *gb_species_or_group, const char *tree_name, bool is_current_tree, int depth, GB_ERROR *error) {
1679    GBDATA          *gb_main = GB_get_root(gb_species_or_group);
1680    cached_taxonomy *tax     = get_cached_taxonomy(gb_main, tree_name, error);
1681    const char      *result  = NULp;
1682
1683    if (tax) {
1684        GBDATA *gb_name       = GB_entry(gb_species_or_group, "name");
1685        GBDATA *gb_group_name = GB_entry(gb_species_or_group, "group_name");
1686
1687        if (gb_name && !gb_group_name) { // it's a species
1688            char *name = GB_read_string(gb_name);
1689            if (name) {
1690                GB_HASH *tax_hash = tax->taxonomy;
1691                long     found    = GBS_read_hash(tax_hash, GBS_global_string("!%s", name));
1692
1693                if (found) {
1694                    const char *parent_group = (const char *)found;
1695
1696                    if (strcmp(parent_group, "<root>") == 0) {
1697                        result = ""; // not member of any group
1698                    }
1699                    else {
1700                        static char *parent = NULp;
1701
1702                        freeset(parent, get_taxonomy_string(tax_hash, parent_group, depth, error));
1703                        result = parent;
1704                    }
1705                }
1706                else {
1707                    result = GBS_global_string("Species '%s' not in '%s'", name, tree_name);
1708                }
1709                free(name);
1710            }
1711            else {
1712                *error = GBS_global_string("Species without 'name' entry!");
1713            }
1714        }
1715        else if (gb_group_name && !gb_name) { // it's a group
1716            char *group_name = GB_read_string(gb_group_name);
1717            if (group_name) {
1718                if (is_current_tree) {
1719                    GB_HASH *tax_hash = tax->taxonomy;
1720                    long     found    = GBS_read_hash(tax_hash, GBS_global_string(">>%p", gb_species_or_group));
1721
1722                    if (found) {
1723                        static char *full_group = NULp;
1724                        const char  *group_id   = (const char *)found;
1725
1726                        freeset(full_group, get_taxonomy_string(tax_hash, group_id, depth, error));
1727                        result = full_group;
1728                    }
1729                    else {
1730                        result = GBS_global_string("Group '%s' not in '%s'", group_name, tree_name);
1731                    }
1732                }
1733                else {
1734                    *error = "It's not possible to specify the tree name in taxonomy() for groups";
1735                }
1736                free(group_name);
1737            }
1738            else {
1739                *error = "Group without 'group_name' entry";
1740            }
1741        }
1742        else if (gb_group_name) {
1743            *error = "Container has 'name' and 'group_name' entry - can't detect container type";
1744        }
1745        else {
1746            *error = "Container has neither 'name' nor 'group_name' entry - can't detect container type";
1747        }
1748    }
1749
1750    return result;
1751}
1752
1753static GB_ERROR gbl_taxonomy(GBL_command_arguments *args) {
1754    GB_ERROR error = check_optional_parameters(args, 1, "count", 1, "tree_name", false, true);
1755    if (!error) {
1756        COMMAND_DROPS_INPUT_STREAMS(args);
1757
1758        char *tree_name       = NULp;
1759        bool  is_current_tree = false;
1760        int   depth           = -1;
1761        char *result          = NULp;
1762
1763        if (args->param_count() == 1) {   // only 'depth'
1764            if (!args->get_treename()) {
1765                result = ARB_strdup("No default tree");
1766            }
1767            else {
1768                tree_name = ARB_strdup(args->get_treename());
1769                depth = atoi(args->get_param(0));
1770                is_current_tree = true;
1771            }
1772        }
1773        else { // 'tree_name', 'depth'
1774            tree_name = ARB_strdup(args->get_param(0));
1775            depth     = atoi(args->get_param(1));
1776        }
1777
1778        if (!result) {
1779            if (depth<1) {
1780                error = GBS_global_string("Illegal depth '%i' (allowed 1..n)", depth);
1781            }
1782            if (!error) {
1783                const char *taxonomy_string = get_taxonomy(args->get_ref(), tree_name, is_current_tree, depth, &error);
1784                if (taxonomy_string) result = ARB_strdup(taxonomy_string);
1785            }
1786        }
1787
1788        gb_assert(contradicted(result, error));
1789        if (result) PASS_2_OUT(args, result);
1790        free(tree_name);
1791    }
1792    return error;
1793}
1794
1795static GB_ERROR gbl_sequence(GBL_command_arguments *args) {
1796    COMMAND_DROPS_INPUT_STREAMS(args);
1797
1798    GB_ERROR error = check_no_parameter(args);
1799    if (!error) {
1800        switch (identify_gb_item(args->get_ref())) {
1801            case GBT_ITEM_UNKNOWN: {
1802                error = "'sequence' used for unknown item";
1803                break;
1804            }
1805            case GBT_ITEM_SPECIES: {
1806                char *use = GBT_get_default_alignment(args->get_gb_main());
1807
1808                if (!use) {
1809                    error = GB_have_error() ? GB_await_error() : "no default alignment defined";
1810                }
1811                else {
1812                    GBDATA *gb_seq = GBT_find_sequence(args->get_ref(), use);
1813
1814                    if (gb_seq) PASS_2_OUT(args, GB_read_string(gb_seq));
1815                    else        COPY_2_OUT(args, ""); // if current alignment does not exist -> return empty string
1816
1817                    free(use);
1818                }
1819                break;
1820            }
1821            case GBT_ITEM_GENE: {
1822                char *seq = GBT_read_gene_sequence(args->get_ref(), true, 0);
1823
1824                if (!seq) error = GB_await_error();
1825                else PASS_2_OUT(args, seq);
1826
1827                break;
1828            }
1829        }
1830    }
1831    return error;
1832}
1833
1834static GB_ERROR gbl_export_sequence(GBL_command_arguments *args) {
1835    COMMAND_DROPS_INPUT_STREAMS(args);
1836
1837    GB_ERROR error = check_no_parameter(args);
1838    if (!error) {
1839        switch (identify_gb_item(args->get_ref())) {
1840            case GBT_ITEM_UNKNOWN: {
1841                error = "'export_sequence' used for unknown item";
1842                break;
1843            }
1844            case GBT_ITEM_SPECIES: {
1845                if (!get_export_sequence) {
1846                    error = "No export-sequence-hook defined (can't use 'export_sequence' here)";
1847                }
1848                else {
1849                    size_t      len;
1850                    const char *seq = get_export_sequence(args->get_ref(), &len, &error);
1851
1852                    gb_assert(error || seq);
1853
1854                    if (seq) PASS_2_OUT(args, ARB_strduplen(seq, len));
1855                }
1856                break;
1857            }
1858            case GBT_ITEM_GENE: {
1859                error = "'export_sequence' cannot be used for gene";
1860                break;
1861            }
1862        }
1863    }
1864    return error;
1865}
1866
1867static GB_ERROR gbl_ali_name(GBL_command_arguments *args) {
1868    COMMAND_DROPS_INPUT_STREAMS(args);
1869
1870    GB_ERROR error = check_no_parameter(args);
1871    if (!error) {
1872        GBDATA *gb_main = args->get_gb_main();
1873        char   *use     = GBT_get_default_alignment(gb_main);
1874        PASS_2_OUT(args, use);
1875    }
1876    return error;
1877}
1878
1879static GB_ERROR gbl_sequence_type(GBL_command_arguments *args) {
1880    COMMAND_DROPS_INPUT_STREAMS(args);
1881
1882    GB_ERROR error = check_no_parameter(args);
1883    if (!error) {
1884        GBDATA *gb_main = args->get_gb_main();
1885        char   *use     = GBT_get_default_alignment(gb_main);
1886        PASS_2_OUT(args, GBT_get_alignment_type_string(gb_main, use));
1887        free(use);
1888    }
1889
1890    return error;
1891}
1892
1893static GB_ERROR format(GBL_command_arguments *args, bool simple_format) {
1894    // simple_format: true = "format", false="format_sequence"
1895
1896    GB_ERROR error = NULp;
1897    int      ic;
1898
1899    GBL_BEGIN_PARAMS;
1900    GBL_PARAM_INT(firsttab, "firsttab=", 10, "Indent first line");
1901    GBL_PARAM_INT(tab,      "tab=",      10, "Indent not first line");
1902    GBL_PARAM_INT(width,    "width=",    50, "Sequence width (bases only)");
1903
1904    // "format_sequence"-only
1905    GBL_PARAM_BIT (numleft,  PARAM_IF(!simple_format, "numleft"),  0,  "Numbers left of sequence");
1906    GBL_PARAM_INT (numright, PARAM_IF(!simple_format, "numright="), 0, "Numbers right of sequence (specifies width; -1 -> auto-width)");
1907    GBL_PARAM_UINT(gap,      PARAM_IF(!simple_format, "gap="),     10, "Insert ' ' every n sequence characters");
1908
1909    // "format"-only
1910    GBL_PARAM_STRING(nl,      PARAM_IF(simple_format, "nl="),      " ",  "Break line at characters 'str' if wrapping needed");
1911    GBL_PARAM_STRING(forcenl, PARAM_IF(simple_format, "forcenl="), "\n", "Always break line at characters 'str'");
1912
1913    GBL_TRACE_PARAMS(args);
1914    GBL_END_PARAMS;
1915
1916    if (tab      < 0) tab = 0;
1917    if (firsttab < 0) firsttab = 0;
1918
1919    if (width == 0)               return "Illegal zero width";
1920    if (numleft && numright != 0) return "You may only specify 'numleft' OR 'numright',  not both.";
1921
1922    if (gap<1) gap = UINT_MAX;
1923
1924    for (ic = 0; ic<args->input.size(); ++ic) {
1925        const char *src           = args->input.get(ic);
1926        size_t      data_size     = strlen(src);
1927        size_t      needed_size;
1928        size_t      line_size;
1929        int         numright_used = numright;
1930
1931        if (numright_used<0) {
1932            numright_used = calc_digits(data_size);
1933        }
1934
1935        {
1936            size_t lines;
1937
1938            if (simple_format) {
1939                lines     = data_size/2 + 1; // worst case
1940                line_size = tab + (width>0 ? width : data_size) + 1;
1941            }
1942            else {
1943                size_t gapsPerLine = (width-1)/gap;
1944                lines              = data_size/width+1;
1945                line_size          = tab + width + gapsPerLine + 1;
1946
1947                if (numright_used) {
1948                    // add space for numright
1949                    line_size += numright_used+1; // plus space
1950                }
1951            }
1952
1953            needed_size = lines*line_size + firsttab + 1 + 10;
1954        }
1955
1956        char *result = ARB_alloc<char>(needed_size);
1957        if (!result) {
1958            error = GBS_global_string("Out of memory (tried to alloc %zu bytes)", needed_size);
1959        }
1960        else {
1961            char   *dst       = result;
1962            size_t  rest_data = data_size;
1963
1964            if (simple_format) {
1965                /* format string w/o gaps or numleft
1966                 * does word-wrapping at chars in nl
1967                 */
1968
1969                // build wrap table
1970                unsigned char isWrapChar[256];
1971                memset(isWrapChar, 0, sizeof(isWrapChar));
1972                for (int i = 0; nl[i]; ++i) isWrapChar[(unsigned char)nl[i]] = 1;
1973                for (int i = 0; forcenl[i]; ++i) isWrapChar[(unsigned char)forcenl[i]] = 2;
1974
1975                if (firsttab>0) {
1976                    memset(dst, ' ', firsttab);
1977                    dst += firsttab;
1978                }
1979
1980                while (width>0 && rest_data>unsigned(width)) {
1981                    int take;
1982                    int move;
1983                    int took;
1984
1985                    for (take = width; take > 0; --take) {
1986                        if (isWrapChar[(unsigned char)src[take]]) break;
1987                    }
1988                    if (take <= 0) { // no wrap character found -> hard wrap at width
1989                        take  = move = width;
1990                    }
1991                    else { // soft wrap at last found wrap character
1992                        move = take+1;
1993                    }
1994
1995                    for (took = 0; took<take; took++) {
1996                        char c = src[took];
1997                        if (isWrapChar[(unsigned char)c] == 2) { // forced newline
1998                            take = took;
1999                            move = take+1;
2000                            break;
2001                        }
2002                        dst[took] = c;
2003                    }
2004
2005                    dst       += take;
2006                    src       += move;
2007                    rest_data -= move;
2008
2009                    if (rest_data>0) {
2010                        *dst++ = '\n';
2011                        if (tab>0) {
2012                            memset(dst, ' ', tab);
2013                            dst += tab;
2014                        }
2015                    }
2016                }
2017
2018                if (rest_data>0) {
2019                    size_t j, k;
2020                    for (j = 0, k = 0; j<rest_data; ++j) {
2021                        char c = src[j];
2022
2023                        if (isWrapChar[(unsigned char)c] == 2) {
2024                            dst[k++] = '\n';
2025                            if (tab>0) {
2026                                memset(dst+k, ' ', tab);
2027                                k += tab;
2028                            }
2029                        }
2030                        else {
2031                            dst[k++] = c;
2032                        }
2033                    }
2034                    src       += j;
2035                    dst       += k;
2036                    rest_data  = 0;
2037                }
2038            }
2039            else {
2040                // "format_sequence" with gaps and numleft
2041                char       *format        = NULp;
2042                const char *src_start     = src;
2043                const char *dst_linestart = dst;
2044
2045                if (numleft) {
2046                    /* Warning: Be very careful, when you change format strings here!
2047                     * currently all format strings result in '%u' or '%-##u' (where # are digits)
2048                     */
2049                    if (firsttab>0) {
2050                        char *firstFormat = GBS_global_string_copy("%%-%iu ", firsttab-1);
2051                        dst += sprintf(dst, firstFormat, (unsigned)1);
2052                        free(firstFormat);
2053                    }
2054                    else {
2055                        dst += sprintf(dst, "%u ", (unsigned)1);
2056                    }
2057                    format = tab>0 ? GBS_global_string_copy("%%-%iu ", tab-1) : ARB_strdup("%u ");
2058                }
2059                else if (firsttab>0) {
2060                    memset(dst, ' ', firsttab);
2061                    dst += firsttab;
2062                }
2063
2064                while (rest_data>0) {
2065                    size_t take = (width>0 && rest_data>unsigned(width)) ? width : rest_data;
2066
2067                    rest_data -= take;
2068
2069                    while (take>gap) {
2070                        memcpy(dst, src, gap);
2071                        dst  += gap;
2072                        src  += gap;
2073                        *dst++ = ' ';
2074                        take -= gap;
2075                    }
2076
2077                    memcpy(dst, src, take);
2078                    dst += take;
2079                    src += take;
2080
2081                    if (numright_used) {
2082                        if (rest_data) *dst++ = ' ';
2083                        else {
2084                            // fill in missing spaces for proper alignment of numright
2085                            size_t currSize = dst-dst_linestart;
2086                            size_t wantSize = line_size-numright_used-1;
2087                            if (currSize<wantSize) {
2088                                size_t spaces  = wantSize-currSize;
2089                                memset(dst, ' ', spaces);
2090                                dst           += spaces;
2091                            }
2092                        }
2093                        unsigned int num  = (src-src_start);
2094                        dst              += sprintf(dst, "%*u", numright_used, num);
2095                    }
2096
2097                    if (rest_data>0) {
2098                        *dst++ = '\n';
2099                        dst_linestart = dst;
2100                        if (numleft) {
2101                            unsigned int num  = (src-src_start)+1; // this goes to the '%u' (see comment above)
2102                            dst              += sprintf(dst, format, num);
2103                        }
2104                        else if (tab>0) {
2105                            memset(dst, ' ', tab);
2106                            dst += tab;
2107                        }
2108                    }
2109                }
2110
2111                free(format);
2112            }
2113
2114            *dst++ = 0;         // close str
2115
2116#if defined(DEBUG)
2117            { // check for array overflow
2118                size_t used_size = dst-result;
2119                gb_assert(used_size <= needed_size);
2120                ARB_realloc(result, used_size);
2121            }
2122#endif // DEBUG
2123        }
2124
2125        if (!error) PASS_2_OUT(args, result);
2126        else free(result);
2127    }
2128    return error;
2129}
2130
2131static GB_ERROR gbl_format         (GBL_command_arguments *args) { return format(args, true); }
2132static GB_ERROR gbl_format_sequence(GBL_command_arguments *args) { return format(args, false); }
2133
2134
2135static char *gbl_read_seq_sai_or_species(GBDATA *gb_main, const char *species, const char *sai, const char *ali, size_t *seqLen) {
2136    /* Reads the alignment 'ali'  of 'species' or 'sai'.
2137     * If 'ali' is NULp, use default alignment.
2138     * Returns NULp in case of error (which is exported then)
2139     */
2140
2141    char     *seq   = NULp;
2142    GB_ERROR  error = NULp;
2143
2144    int sources = !!species + !!sai;
2145    if (sources != 1) {
2146        error = "Either parameters 'species' or 'SAI' must be specified";
2147    }
2148    else {
2149        GBDATA     *gb_item = NULp;
2150        const char *what    = NULp;
2151        const char *name    = NULp;
2152
2153        if (species) {
2154            gb_item = GBT_find_species(gb_main, species);
2155            what    = "species";
2156            name    = species;
2157        }
2158        else {
2159            gb_item = GBT_find_SAI(gb_main, sai);
2160            what    = "SAI";
2161            name    = sai;
2162        }
2163
2164        if (!gb_item) error = GBS_global_string("Can't find %s '%s'", what, name);
2165        else {
2166            char *freeMe = NULp;
2167
2168            if (!ali) {
2169                ali = freeMe = GBT_get_default_alignment(gb_main);
2170                if (!ali) error = "can't detect default alignment";
2171            }
2172
2173            if (ali) {
2174                GBDATA *gb_ali = GB_entry(gb_item, ali);
2175
2176                if (gb_ali) {
2177                    GBDATA *gb_seq;
2178
2179                    for (gb_seq = GB_child(gb_ali); gb_seq; gb_seq = GB_nextChild(gb_seq)) {
2180                        long type = GB_read_type(gb_seq);
2181                        if (type == GB_BITS) {
2182                            seq     = GB_read_bits(gb_seq, '-', '+');
2183                            if (seqLen) *seqLen = GB_read_bits_count(gb_seq);
2184                            break;
2185                        }
2186                        if (type == GB_STRING) {
2187                            seq     = GB_read_string(gb_seq);
2188                            if (seqLen) *seqLen = GB_read_string_count(gb_seq);
2189                            break;
2190                        }
2191                    }
2192                }
2193
2194                if (!seq) error = GBS_global_string("%s '%s' has no (usable) data in alignment '%s'", what, name, ali);
2195            }
2196            free(freeMe);
2197        }
2198    }
2199
2200    if (error) {
2201        gb_assert(!seq);
2202        GB_export_error(error);
2203    }
2204
2205    return seq;
2206}
2207
2208struct common_filter_params {
2209    const char *align;
2210    const char *sai;
2211    const char *species;
2212    int         first;
2213    int         pairwise;
2214};
2215
2216#define GBL_COMMON_FILTER_PARAMS                                                                                        \
2217    common_filter_params common_param;                                                                                  \
2218    GBL_STRUCT_PARAM_STRING(common_param, align,    "align=",    NULp, "alignment to use (defaults to default alignment)"); \
2219    GBL_STRUCT_PARAM_STRING(common_param, sai,      "SAI=",      NULp, "Use default sequence of given SAI as a filter"); \
2220    GBL_STRUCT_PARAM_STRING(common_param, species,  "species=",  NULp, "Use default sequence of given species as a filter"); \
2221    GBL_STRUCT_PARAM_BIT   (common_param, first,    "first=",    0,    "Use 1st stream as filter for other streams");   \
2222    GBL_STRUCT_PARAM_BIT   (common_param, pairwise, "pairwise=", 0,    "Use 1st stream as filter for 2nd, 3rd for 4th, ...")
2223
2224typedef char* (*filter_fun)(const char *seq, const char *filter, size_t flen, void *param);
2225/* Note:
2226 * filter_fun has to return a heap copy of the filter-result.
2227 * if 'flen' != 0, it contains the length of 'filter'
2228 * 'param' may be any client data
2229 */
2230
2231static GB_ERROR apply_filters(GBL_command_arguments *args, common_filter_params *common, filter_fun filter_one, void *param) {
2232    GB_ERROR error = NULp;
2233
2234    if (args->input.size()==0) error = "No input stream";
2235    else {
2236        int methodCount = !!common->sai + !!common->species + !!common->pairwise + !!common->first;
2237
2238        if (methodCount != 1) error = "Need exactly one of the parameters 'SAI', 'species', 'pairwise' or 'first'";
2239        else {
2240            if (common->pairwise) {
2241                if (args->input.size() % 2) error = "Using 'pairwise' requires an even number of input streams";
2242                else {
2243                    int i;
2244                    for (i = 1; i<args->input.size(); i += 2) {
2245                        PASS_2_OUT(args, filter_one(args->input.get(i), args->input.get(i-1), 0, param));
2246                    }
2247                }
2248            }
2249            else {
2250                int     i      = 0;
2251                char   *filter = NULp;
2252                size_t  flen   = 0;
2253
2254                if (common->first) {
2255                    if (args->input.size()<2) error = "Using 'first' needs at least 2 input streams";
2256                    else {
2257                        const char *in = args->input.get(i++);
2258                        gb_assert(in);
2259
2260                        flen   = strlen(in);
2261                        filter = ARB_strduplen(in, flen);
2262                    }
2263                }
2264                else {
2265                    filter = gbl_read_seq_sai_or_species(args->get_gb_main(), common->species, common->sai, common->align, &flen);
2266                    if (!filter) error = GB_await_error();
2267                }
2268
2269                gb_assert(filter || error);
2270                if (filter) {
2271                    for (; i<args->input.size(); ++i) {
2272                        PASS_2_OUT(args, filter_one(args->input.get(i), filter, flen, param));
2273                    }
2274                }
2275                free(filter);
2276            }
2277        }
2278    }
2279    return error;
2280}
2281
2282// -------------------------
2283//      calculate diff
2284
2285struct diff_params {
2286    char equalC;
2287    char diffC;
2288};
2289static char *calc_diff(const char *seq, const char *filter, size_t /*flen*/, void *paramP) {
2290    // filters 'seq' through 'filter'
2291    // - replace all equal     positions by 'equal_char' (if != 0)
2292    // - replace all differing positions by 'diff_char'  (if != 0)
2293
2294    diff_params *param      = (diff_params*)paramP;
2295    char         equal_char = param->equalC;
2296    char         diff_char  = param->diffC;
2297
2298    char *result = ARB_strdup(seq);
2299    int   p;
2300
2301    for (p = 0; result[p] && filter[p]; ++p) {
2302        if (result[p] == filter[p]) {
2303            if (equal_char) result[p] = equal_char;
2304        }
2305        else {
2306            if (diff_char) result[p] = diff_char;
2307        }
2308    }
2309
2310    // if 'seq' is longer than 'filter' and diff_char is given
2311    // -> fill rest of 'result' with 'diff_char'
2312    if (diff_char) {
2313        for (; result[p]; ++p) {
2314            result[p] = diff_char;
2315        }
2316    }
2317
2318    return result;
2319}
2320static GB_ERROR gbl_diff(GBL_command_arguments *args) {
2321    GBL_BEGIN_PARAMS;
2322    GBL_COMMON_FILTER_PARAMS;
2323
2324    diff_params param;
2325    GBL_STRUCT_PARAM_CHAR(param, equalC,   "equal=",    '.', "symbol for equal characters");
2326    GBL_STRUCT_PARAM_CHAR(param, diffC,    "differ=",   0,   "symbol for diff characters (default: use char from input stream)");
2327
2328    GBL_TRACE_PARAMS(args);
2329    GBL_END_PARAMS;
2330
2331    return apply_filters(args, &common_param, calc_diff, &param);
2332}
2333
2334// -------------------------
2335//      standard filter
2336
2337enum filter_function { FP_FILTER, FP_MODIFY };
2338
2339struct filter_params { // used by gbl_filter and gbl_change_gc
2340    filter_function function;
2341
2342    const char *include;
2343    const char *exclude;
2344
2345    // FP_MODIFY only:
2346    int         change_pc;
2347    const char *change_to;
2348};
2349
2350static char *filter_seq(const char *seq, const char *filter, size_t flen, void *paramP) {
2351    filter_params *param = (filter_params*)paramP;
2352
2353    size_t slen     = strlen(seq);
2354    if (!flen) flen = strlen(filter);
2355    size_t mlen     = slen<flen ? slen : flen;
2356
2357    GBS_strstruct *out = GBS_stropen(mlen+1); // +1 to avoid invalid, zero-length buffer
2358
2359    const char *charset;
2360    int         include;
2361
2362    if (param->include) {
2363        charset = param->include;
2364        include = 1;
2365    }
2366    else {
2367        gb_assert(param->exclude);
2368        charset = param->exclude;
2369        include = 0;
2370    }
2371
2372    size_t pos  = 0;
2373    size_t rest = slen;
2374    size_t ctl  = 0;
2375    if (param->function == FP_MODIFY) ctl  = strlen(param->change_to);
2376
2377    int inset = 1; // 1 -> check chars in charset, 0 -> check chars NOT in charset
2378    while (rest) {
2379        size_t count;
2380        if (pos >= flen) {      // behind filter
2381            // trigger last loop
2382            count = rest;
2383            inset = 0; // if 'include' -> 'applies' will get false, otherwise true
2384                       // (meaning is: behind filter nothing can match 'include' or 'exclude')
2385        }
2386        else {
2387            count = (inset ? strspn : strcspn)(filter+pos, charset); // count how many chars are 'inset'
2388        }
2389        if (count) {
2390            int applies = !!include == !!inset; // true -> 'filter' matches 'include' or doesn't match 'exclude'
2391            if (count>rest) count = rest;
2392
2393            switch (param->function) {
2394                case FP_FILTER:
2395                    if (applies) GBS_strncat(out, seq+pos, count);
2396                    break;
2397
2398                case FP_MODIFY:
2399                    if (applies) { // then modify
2400                        size_t i;
2401                        for (i = 0; i<count; i++) {
2402                            char c = seq[pos+i];
2403                            if (isalpha(c) && GB_random(100)<param->change_pc) c = param->change_to[GB_random(ctl)];
2404                            GBS_chrcat(out, c);
2405                        }
2406                    }
2407                    else { // otherwise simply copy
2408                        GBS_strncat(out, seq+pos, count);
2409                    }
2410                    break;
2411            }
2412
2413            pos  += count;
2414            rest -= count;
2415        }
2416        inset = 1-inset; // toggle
2417    }
2418    return GBS_strclose(out);
2419}
2420
2421static GB_ERROR gbl_filter(GBL_command_arguments *args) {
2422    GBL_BEGIN_PARAMS;
2423    GBL_COMMON_FILTER_PARAMS;
2424
2425    filter_params param;
2426    GBL_STRUCT_PARAM_STRING(param, exclude, "exclude=", NULp, "Exclude colums");
2427    GBL_STRUCT_PARAM_STRING(param, include, "include=", NULp, "Include colums");
2428    param.function = FP_FILTER;
2429
2430    GBL_TRACE_PARAMS(args);
2431    GBL_END_PARAMS;
2432
2433    GB_ERROR error  = NULp;
2434    int      inOrEx = !!param.include + !!param.exclude;
2435
2436    if (inOrEx != 1)    error = "Need exactly one parameter of: 'include', 'exclude'";
2437    else error                = apply_filters(args, &common_param, filter_seq, &param);
2438
2439    return error;
2440}
2441
2442static GB_ERROR gbl_change_gc(GBL_command_arguments *args) {
2443    GBL_BEGIN_PARAMS;
2444    GBL_COMMON_FILTER_PARAMS;
2445
2446    filter_params param;
2447    GBL_STRUCT_PARAM_STRING(param, exclude,   "exclude=", NULp, "Exclude colums");
2448    GBL_STRUCT_PARAM_STRING(param, include,   "include=", NULp, "Include colums");
2449    GBL_STRUCT_PARAM_INT   (param, change_pc, "change=",  0,    "percentage of changed columns (default: silently change nothing)");
2450    GBL_STRUCT_PARAM_STRING(param, change_to, "to=",      "GC", "change to one of this");
2451    param.function = FP_MODIFY;
2452
2453    GBL_TRACE_PARAMS(args);
2454    GBL_END_PARAMS;
2455
2456    GB_ERROR error  = NULp;
2457    int      inOrEx = !!param.include + !!param.exclude;
2458
2459    if (inOrEx != 1) error = "Need exactly one parameter of: 'include', 'exclude'";
2460    else {
2461        error = apply_filters(args, &common_param, filter_seq, &param);
2462    }
2463
2464    return error;
2465}
2466
2467static GB_ERROR gbl_exec(GBL_command_arguments *args) {
2468    EXPECT_PARAMS_PASSED(args, "command[,arguments]+");
2469
2470    // write inputstreams to temp file:
2471    GB_ERROR error = NULp;
2472    char *inputname;
2473    int i;
2474    {
2475        char *filename = GB_unique_filename("arb_exec_input", "tmp");
2476        FILE *out      = GB_fopen_tempfile(filename, "wt", &inputname);
2477
2478        if (!out) error = GB_await_error();
2479        else {
2480            for (i=0; i<args->input.size(); i++) {
2481                fprintf(out, "%s\n", args->input.get(i));
2482            }
2483            fclose(out);
2484        }
2485        free(filename);
2486    }
2487
2488    if (!error) {
2489        // build shell command to execute
2490        char *sys;
2491        {
2492            GBS_strstruct *str = GBS_stropen(1000);
2493
2494            GBS_strcat(str, args->get_param(0));
2495            for (i=1; i<args->param_count(); i++) {
2496                GBS_strcat(str, " \'");
2497                GBS_strcat(str, args->get_param(i)); // @@@ use GBK_singlequote here?
2498                GBS_chrcat(str, '\'');
2499            }
2500            GBS_strcat(str, " <");
2501            GBS_strcat(str, inputname);
2502
2503            sys = GBS_strclose(str);
2504        }
2505
2506        char *result = NULp;
2507        {
2508            FILE *in = popen(sys, "r");
2509            if (in) {
2510                GBS_strstruct *str = GBS_stropen(4096);
2511
2512                while ((i=getc(in)) != EOF) { GBS_chrcat(str, i); }
2513                result = GBS_strclose(str);
2514                pclose(in);
2515            }
2516            else {
2517                error = GBS_global_string("Cannot execute shell command '%s'", sys);
2518            }
2519        }
2520
2521        if (!error) {
2522            gb_assert(result);
2523            PASS_2_OUT(args, result);
2524        }
2525
2526        free(sys);
2527    }
2528
2529    gb_assert(GB_is_privatefile(inputname, false));
2530    GB_unlink_or_warn(inputname, &error);
2531    free(inputname);
2532
2533    return error;
2534}
2535
2536
2537static GBL_command_definition gbl_command_table[] = {
2538    { "ali_name",        gbl_ali_name },
2539    { "caps",            gbl_caps },
2540    { "change",          gbl_change_gc },
2541    { "checksum",        gbl_checksum },
2542    { "command",         gbl_command },
2543    { "compare",         gbl_compare },
2544    { "icompare",        gbl_icompare },
2545    { "contains",        gbl_contains },
2546    { "icontains",       gbl_icontains },
2547    { "count",           gbl_count },
2548    { "crop",            gbl_crop },
2549    { "cut",             gbl_cut },
2550    { "dd",              gbl_dd },
2551    { "define",          gbl_define },
2552    { "diff",            gbl_diff },
2553    { "div",             gbl_div },
2554    { "fdiv",            gbl_fdiv },
2555    { "do",              gbl_do },
2556    { "drop",            gbl_drop },
2557    { "dropempty",       gbl_dropempty },
2558    { "dropzero",        gbl_dropzero },
2559    { "echo",            gbl_echo },
2560    { "equals",          gbl_equals },
2561    { "iequals",         gbl_iequals },
2562    { "escape",          gbl_escape },
2563    { "unescape",        gbl_unescape },
2564    { "eval",            gbl_eval },
2565    { "exec",            gbl_exec },
2566    { "export_sequence", gbl_export_sequence },
2567    { "extract_sequence", gbl_extract_sequence },
2568    { "extract_words",   gbl_extract_words },
2569    { "filter",          gbl_filter },
2570    { "format",          gbl_format },
2571    { "format_sequence", gbl_format_sequence },
2572    { "gcgchecksum",     gbl_gcgchecksum },
2573    { "head",            gbl_head },
2574    { "inRange",         gbl_inRange },
2575    { "isAbove",         gbl_isAbove },
2576    { "isBelow",         gbl_isBelow },
2577    { "isEqual",         gbl_isEqual },
2578    { "isEmpty",         gbl_isEmpty },
2579    { "keep",            gbl_keep },
2580    { "left",            gbl_head },
2581    { "len",             gbl_len },
2582    { "lower",           gbl_lower },
2583    { "merge",           gbl_merge },
2584    { "mid",             gbl_mid },
2585    { "mid0",            gbl_mid0 },
2586    { "minus",           gbl_minus },
2587    { "fminus",          gbl_fminus },
2588    { "mult",            gbl_mult },
2589    { "fmult",           gbl_fmult },
2590    { "and",             gbl_and },
2591    { "or",              gbl_or },
2592    { "not",             gbl_not },
2593    { "origin_gene",     gbl_origin_gene },
2594    { "origin_organism", gbl_origin_organism },
2595    { "partof",          gbl_partof },
2596    { "ipartof",         gbl_ipartof },
2597    { "per_cent",        gbl_per_cent },
2598    { "fper_cent",       gbl_fper_cent },
2599    { "plus",            gbl_plus },
2600    { "fplus",           gbl_fplus },
2601    { "pretab",          gbl_pretab },
2602    { "quote",           gbl_quote },
2603    { "unquote",         gbl_unquote },
2604    { "readdb",          gbl_readdb },
2605    { "remove",          gbl_remove },
2606    { "rest",            gbl_rest },
2607    { "right",           gbl_tail },
2608    { "round",           gbl_round },
2609    { "select",          gbl_select },
2610    { "sequence",        gbl_sequence },
2611    { "sequence_type",   gbl_sequence_type },
2612    { "split",           gbl_split },
2613    { "srt",             gbl_srt },
2614    { "streams",         gbl_streams },
2615    { "swap",            gbl_swap },
2616    { "tab",             gbl_tab },
2617    { "tail",            gbl_tail },
2618    { "taxonomy",        gbl_taxonomy },
2619    { "toback",          gbl_toback },
2620    { "tofront",         gbl_tofront },
2621    { "trace",           gbl_trace },
2622    { "translate",       gbl_translate },
2623    { "upper",           gbl_upper },
2624
2625    { NULp, NULp }
2626};
2627
2628const GBL_command_lookup_table& ACI_get_standard_commands() {
2629    static GBL_command_lookup_table clt(gbl_command_table, ARRAY_ELEMS(gbl_command_table)-1);
2630    return clt;
2631}
2632
Note: See TracBrowser for help on using the repository browser.